Implement composable, type-safe renderer fallback
This commit is contained in:
parent
7e4ae8450e
commit
3645d34d6a
35 changed files with 1474 additions and 1210 deletions
|
|
@ -11,17 +11,41 @@ use crate::{
|
||||||
|
|
||||||
/// A component that can be used by widgets to draw themselves on a screen.
|
/// A component that can be used by widgets to draw themselves on a screen.
|
||||||
pub trait Renderer {
|
pub trait Renderer {
|
||||||
|
/// Starts recording a new layer.
|
||||||
|
fn start_layer(&mut self);
|
||||||
|
|
||||||
|
/// Ends recording a new layer.
|
||||||
|
///
|
||||||
|
/// The new layer will clip its contents to the provided `bounds`.
|
||||||
|
fn end_layer(&mut self, bounds: Rectangle);
|
||||||
|
|
||||||
/// Draws the primitives recorded in the given closure in a new layer.
|
/// Draws the primitives recorded in the given closure in a new layer.
|
||||||
///
|
///
|
||||||
/// The layer will clip its contents to the provided `bounds`.
|
/// The layer will clip its contents to the provided `bounds`.
|
||||||
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self));
|
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
|
||||||
|
self.start_layer();
|
||||||
|
f(self);
|
||||||
|
self.end_layer(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts recording with a new [`Transformation`].
|
||||||
|
fn start_transformation(&mut self);
|
||||||
|
|
||||||
|
/// Ends recording a new layer.
|
||||||
|
///
|
||||||
|
/// The new layer will clip its contents to the provided `bounds`.
|
||||||
|
fn end_transformation(&mut self, transformation: Transformation);
|
||||||
|
|
||||||
/// Applies a [`Transformation`] to the primitives recorded in the given closure.
|
/// Applies a [`Transformation`] to the primitives recorded in the given closure.
|
||||||
fn with_transformation(
|
fn with_transformation(
|
||||||
&mut self,
|
&mut self,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
f: impl FnOnce(&mut Self),
|
f: impl FnOnce(&mut Self),
|
||||||
);
|
) {
|
||||||
|
self.start_transformation();
|
||||||
|
f(self);
|
||||||
|
self.end_transformation(transformation);
|
||||||
|
}
|
||||||
|
|
||||||
/// Applies a translation to the primitives recorded in the given closure.
|
/// Applies a translation to the primitives recorded in the given closure.
|
||||||
fn with_translation(
|
fn with_translation(
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,13 @@ impl Null {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer for Null {
|
impl Renderer for Null {
|
||||||
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
|
fn start_layer(&mut self) {}
|
||||||
|
|
||||||
fn with_transformation(
|
fn end_layer(&mut self, _bounds: Rectangle) {}
|
||||||
&mut self,
|
|
||||||
_transformation: Transformation,
|
fn start_transformation(&mut self) {}
|
||||||
_f: impl FnOnce(&mut Self),
|
|
||||||
) {
|
fn end_transformation(&mut self, _transformation: Transformation) {}
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&mut self) {}
|
fn clear(&mut self) {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
use std::{f32::consts::PI, time::Instant};
|
use std::{f32::consts::PI, time::Instant};
|
||||||
|
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::widget::canvas::{
|
use iced::widget::canvas::{self, stroke, Cache, Canvas, Frame, Path, Stroke};
|
||||||
self, stroke, Cache, Canvas, Geometry, Path, Stroke,
|
|
||||||
};
|
|
||||||
use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme};
|
use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
|
|
@ -57,11 +55,11 @@ impl<Message> canvas::Program<Message> for Arc {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_state: &Self::State,
|
_state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<Geometry> {
|
) {
|
||||||
let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
|
let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
|
||||||
let palette = theme.palette();
|
let palette = theme.palette();
|
||||||
|
|
||||||
|
|
@ -104,6 +102,6 @@ impl<Message> canvas::Program<Message> for Arc {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![geometry]
|
renderer.draw_geometry([geometry]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ impl Example {
|
||||||
mod bezier {
|
mod bezier {
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::widget::canvas::event::{self, Event};
|
use iced::widget::canvas::event::{self, Event};
|
||||||
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
|
use iced::widget::canvas::{self, frame, Canvas, Frame, Path, Stroke};
|
||||||
use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
|
use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -138,30 +138,25 @@ mod bezier {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
_theme: &Theme,
|
_theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> Vec<Geometry> {
|
) {
|
||||||
let content = self.state.cache.draw(
|
let content =
|
||||||
renderer,
|
self.state.cache.draw(renderer, bounds.size(), |frame| {
|
||||||
bounds.size(),
|
|
||||||
|frame: &mut Frame| {
|
|
||||||
Curve::draw_all(self.curves, frame);
|
Curve::draw_all(self.curves, frame);
|
||||||
|
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&Path::rectangle(Point::ORIGIN, frame.size()),
|
&Path::rectangle(Point::ORIGIN, frame.size()),
|
||||||
Stroke::default().with_width(2.0),
|
Stroke::default().with_width(2.0),
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
renderer.draw_geometry([content]);
|
||||||
|
|
||||||
if let Some(pending) = state {
|
if let Some(pending) = state {
|
||||||
let pending_curve = pending.draw(renderer, bounds, cursor);
|
pending.draw(renderer, bounds, cursor);
|
||||||
|
|
||||||
vec![content, pending_curve]
|
|
||||||
} else {
|
|
||||||
vec![content]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +182,7 @@ mod bezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Curve {
|
impl Curve {
|
||||||
fn draw_all(curves: &[Curve], frame: &mut Frame) {
|
fn draw_all(curves: &[Curve], frame: &mut impl Frame) {
|
||||||
let curves = Path::new(|p| {
|
let curves = Path::new(|p| {
|
||||||
for curve in curves {
|
for curve in curves {
|
||||||
p.move_to(curve.from);
|
p.move_to(curve.from);
|
||||||
|
|
@ -208,11 +203,11 @@ mod bezier {
|
||||||
impl Pending {
|
impl Pending {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> Geometry {
|
) {
|
||||||
let mut frame = Frame::new(renderer, bounds.size());
|
let mut frame = frame(renderer, bounds.size());
|
||||||
|
|
||||||
if let Some(cursor_position) = cursor.position_in(bounds) {
|
if let Some(cursor_position) = cursor.position_in(bounds) {
|
||||||
match *self {
|
match *self {
|
||||||
|
|
@ -232,7 +227,7 @@ mod bezier {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.into_geometry()
|
renderer.draw_geometry([frame]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use iced::alignment;
|
use iced::alignment;
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke};
|
use iced::widget::canvas::{stroke, Cache, Frame, LineCap, Path, Stroke};
|
||||||
use iced::widget::{canvas, container};
|
use iced::widget::{canvas, container};
|
||||||
use iced::{
|
use iced::{
|
||||||
Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription,
|
Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription,
|
||||||
|
|
@ -82,11 +82,11 @@ impl<Message> canvas::Program<Message> for Clock {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_state: &Self::State,
|
_state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<Geometry> {
|
) {
|
||||||
let clock = self.clock.draw(renderer, bounds.size(), |frame| {
|
let clock = self.clock.draw(renderer, bounds.size(), |frame| {
|
||||||
let palette = theme.extended_palette();
|
let palette = theme.extended_palette();
|
||||||
|
|
||||||
|
|
@ -163,7 +163,7 @@ impl<Message> canvas::Program<Message> for Clock {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![clock]
|
renderer.draw_geometry([clock]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use iced::alignment::{self, Alignment};
|
use iced::alignment::{self, Alignment};
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
|
use iced::widget::canvas::{self, Canvas, Frame, Path};
|
||||||
use iced::widget::{column, row, text, Slider};
|
use iced::widget::{column, row, text, Slider};
|
||||||
use iced::{
|
use iced::{
|
||||||
Color, Element, Font, Length, Pixels, Point, Rectangle, Renderer, Size,
|
Color, Element, Font, Length, Pixels, Point, Rectangle, Renderer, Size,
|
||||||
|
|
@ -156,7 +156,7 @@ impl Theme {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, frame: &mut Frame, text_color: Color) {
|
fn draw(&self, frame: &mut impl Frame, text_color: Color) {
|
||||||
let pad = 20.0;
|
let pad = 20.0;
|
||||||
|
|
||||||
let box_size = Size {
|
let box_size = Size {
|
||||||
|
|
@ -252,18 +252,18 @@ impl<Message> canvas::Program<Message> for Theme {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_state: &Self::State,
|
_state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &iced::Theme,
|
theme: &iced::Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<Geometry> {
|
) {
|
||||||
let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
|
let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
|
||||||
let palette = theme.extended_palette();
|
let palette = theme.extended_palette();
|
||||||
|
|
||||||
self.draw(frame, palette.background.base.text);
|
self.draw(frame, palette.background.base.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![theme]
|
renderer.draw_geometry([theme]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ mod grid {
|
||||||
use iced::touch;
|
use iced::touch;
|
||||||
use iced::widget::canvas;
|
use iced::widget::canvas;
|
||||||
use iced::widget::canvas::event::{self, Event};
|
use iced::widget::canvas::event::{self, Event};
|
||||||
use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
|
use iced::widget::canvas::{frame, Cache, Canvas, Frame, Path, Text};
|
||||||
use iced::{
|
use iced::{
|
||||||
Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
|
Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
|
||||||
};
|
};
|
||||||
|
|
@ -516,11 +516,11 @@ mod grid {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_interaction: &Interaction,
|
_interaction: &Interaction,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
_theme: &Theme,
|
_theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> Vec<Geometry> {
|
) {
|
||||||
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
|
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
|
||||||
|
|
||||||
let life = self.life_cache.draw(renderer, bounds.size(), |frame| {
|
let life = self.life_cache.draw(renderer, bounds.size(), |frame| {
|
||||||
|
|
@ -546,7 +546,7 @@ mod grid {
|
||||||
});
|
});
|
||||||
|
|
||||||
let overlay = {
|
let overlay = {
|
||||||
let mut frame = Frame::new(renderer, bounds.size());
|
let mut frame = frame(renderer, bounds.size());
|
||||||
|
|
||||||
let hovered_cell = cursor.position_in(bounds).map(|position| {
|
let hovered_cell = cursor.position_in(bounds).map(|position| {
|
||||||
Cell::at(self.project(position, frame.size()))
|
Cell::at(self.project(position, frame.size()))
|
||||||
|
|
@ -599,12 +599,10 @@ mod grid {
|
||||||
..text
|
..text
|
||||||
});
|
});
|
||||||
|
|
||||||
frame.into_geometry()
|
frame.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.scaling < 0.2 || !self.show_lines {
|
if self.scaling >= 0.2 && self.show_lines {
|
||||||
vec![life, overlay]
|
|
||||||
} else {
|
|
||||||
let grid =
|
let grid =
|
||||||
self.grid_cache.draw(renderer, bounds.size(), |frame| {
|
self.grid_cache.draw(renderer, bounds.size(), |frame| {
|
||||||
frame.translate(center);
|
frame.translate(center);
|
||||||
|
|
@ -640,7 +638,9 @@ mod grid {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![life, grid, overlay]
|
renderer.draw_geometry([life, grid, overlay]);
|
||||||
|
} else {
|
||||||
|
renderer.draw_geometry([life, overlay]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@ mod rainbow {
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
|
use iced::advanced::graphics::mesh::{
|
||||||
|
self, Mesh, Renderer as _, SolidVertex2D,
|
||||||
|
};
|
||||||
use iced::advanced::Renderer as _;
|
use iced::advanced::Renderer as _;
|
||||||
|
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
|
||||||
|
|
@ -292,12 +292,14 @@ fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_state: &Self::State,
|
_state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<canvas::Geometry> {
|
) {
|
||||||
let mut frame = canvas::Frame::new(renderer, bounds.size());
|
use canvas::Frame;
|
||||||
|
|
||||||
|
let mut frame = canvas::frame(renderer, bounds.size());
|
||||||
|
|
||||||
let palette = theme.extended_palette();
|
let palette = theme.extended_palette();
|
||||||
|
|
||||||
|
|
@ -307,7 +309,7 @@ fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
|
||||||
palette.background.strong.color,
|
palette.background.strong.color,
|
||||||
);
|
);
|
||||||
|
|
||||||
vec![frame.into_geometry()]
|
renderer.draw_geometry([frame]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
|
||||||
use iced::event;
|
use iced::event;
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::time::Instant;
|
use iced::time::Instant;
|
||||||
use iced::widget::canvas;
|
use iced::widget::canvas::{self, Frame};
|
||||||
use iced::window::{self, RedrawRequest};
|
use iced::window::{self, RedrawRequest};
|
||||||
use iced::{
|
use iced::{
|
||||||
Background, Color, Element, Event, Length, Radians, Rectangle, Renderer,
|
Background, Color, Element, Event, Length, Radians, Rectangle, Renderer,
|
||||||
|
|
@ -356,9 +356,7 @@ where
|
||||||
renderer.with_translation(
|
renderer.with_translation(
|
||||||
Vector::new(bounds.x, bounds.y),
|
Vector::new(bounds.x, bounds.y),
|
||||||
|renderer| {
|
|renderer| {
|
||||||
use iced::advanced::graphics::geometry::Renderer as _;
|
renderer.draw_geometry([geometry]);
|
||||||
|
|
||||||
renderer.draw(vec![geometry]);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use iced::mouse;
|
||||||
use iced::touch;
|
use iced::touch;
|
||||||
use iced::widget::canvas::event;
|
use iced::widget::canvas::event;
|
||||||
use iced::widget::canvas::stroke::{self, Stroke};
|
use iced::widget::canvas::stroke::{self, Stroke};
|
||||||
use iced::widget::canvas::{self, Canvas, Geometry};
|
use iced::widget::canvas::{self, Canvas};
|
||||||
use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme};
|
use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -83,11 +83,13 @@ impl canvas::Program<Message> for Multitouch {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_state: &Self::State,
|
_state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
_theme: &Theme,
|
_theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<Geometry> {
|
) {
|
||||||
|
use canvas::Frame;
|
||||||
|
|
||||||
let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| {
|
let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| {
|
||||||
if self.fingers.len() < 2 {
|
if self.fingers.len() < 2 {
|
||||||
return;
|
return;
|
||||||
|
|
@ -154,6 +156,6 @@ impl canvas::Program<Message> for Multitouch {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![fingerweb]
|
renderer.draw_geometry([fingerweb]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,11 +107,13 @@ impl canvas::Program<Message> for SierpinskiGraph {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_state: &Self::State,
|
_state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
_theme: &Theme,
|
_theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<canvas::Geometry> {
|
) {
|
||||||
|
use canvas::Frame;
|
||||||
|
|
||||||
let geom = self.cache.draw(renderer, bounds.size(), |frame| {
|
let geom = self.cache.draw(renderer, bounds.size(), |frame| {
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&canvas::Path::rectangle(Point::ORIGIN, frame.size()),
|
&canvas::Path::rectangle(Point::ORIGIN, frame.size()),
|
||||||
|
|
@ -139,7 +141,7 @@ impl canvas::Program<Message> for SierpinskiGraph {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![geom]
|
renderer.draw_geometry([geom]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,11 +126,12 @@ impl<Message> canvas::Program<Message> for State {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_state: &Self::State,
|
_state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
_theme: &Theme,
|
_theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<canvas::Geometry> {
|
) {
|
||||||
|
use canvas::Frame;
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
let background =
|
let background =
|
||||||
|
|
@ -197,7 +198,7 @@ impl<Message> canvas::Program<Message> for State {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![background, system]
|
renderer.draw_geometry([background, system]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,11 +124,13 @@ impl<Message> canvas::Program<Message> for State {
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_state: &Self::State,
|
_state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<canvas::Geometry> {
|
) {
|
||||||
|
use canvas::Frame;
|
||||||
|
|
||||||
let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
|
let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
|
||||||
let palette = theme.palette();
|
let palette = theme.palette();
|
||||||
let center = bounds.center();
|
let center = bounds.center();
|
||||||
|
|
@ -153,7 +155,7 @@ impl<Message> canvas::Program<Message> for State {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![geometry]
|
renderer.draw_geometry([geometry]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
use crate::core::image;
|
use crate::core::image;
|
||||||
use crate::core::svg;
|
use crate::core::svg;
|
||||||
use crate::core::Size;
|
use crate::core::Size;
|
||||||
|
use crate::Mesh;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -10,7 +11,7 @@ use std::borrow::Cow;
|
||||||
/// [`Renderer`]: crate::Renderer
|
/// [`Renderer`]: crate::Renderer
|
||||||
pub trait Backend {
|
pub trait Backend {
|
||||||
/// The custom kind of primitives this [`Backend`] supports.
|
/// The custom kind of primitives this [`Backend`] supports.
|
||||||
type Primitive;
|
type Primitive: TryFrom<Mesh, Error = &'static str>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A graphics backend that supports text rendering.
|
/// A graphics backend that supports text rendering.
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,277 @@ pub use text::Text;
|
||||||
|
|
||||||
pub use crate::gradient::{self, Gradient};
|
pub use crate::gradient::{self, Gradient};
|
||||||
|
|
||||||
|
use crate::core::{Point, Radians, Rectangle, Size, Vector};
|
||||||
|
use crate::Primitive;
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub fn frame<Renderer>(renderer: &Renderer, size: Size) -> Renderer::Frame
|
||||||
|
where
|
||||||
|
Renderer: self::Renderer,
|
||||||
|
{
|
||||||
|
renderer.new_frame(size)
|
||||||
|
}
|
||||||
|
|
||||||
/// A renderer capable of drawing some [`Self::Geometry`].
|
/// A renderer capable of drawing some [`Self::Geometry`].
|
||||||
pub trait Renderer: crate::core::Renderer {
|
pub trait Renderer: crate::core::Renderer {
|
||||||
/// The kind of geometry this renderer can draw.
|
/// The kind of geometry this renderer can draw.
|
||||||
type Geometry;
|
type Geometry: Geometry;
|
||||||
|
|
||||||
/// Draws the given layers of [`Self::Geometry`].
|
/// The kind of [`Frame`] this renderer supports.
|
||||||
fn draw(&mut self, layers: Vec<Self::Geometry>);
|
type Frame: Frame<Geometry = Self::Geometry>;
|
||||||
|
|
||||||
|
fn new_frame(&self, size: Size) -> Self::Frame;
|
||||||
|
|
||||||
|
/// Draws the given [`Self::Geometry`].
|
||||||
|
fn draw_geometry(&mut self, geometry: Self::Geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Backend {
|
||||||
|
/// The kind of [`Frame`] this backend supports.
|
||||||
|
type Frame: Frame;
|
||||||
|
|
||||||
|
fn new_frame(&self, size: Size) -> Self::Frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Frame: Sized + Into<Self::Geometry> {
|
||||||
|
/// The kind of geometry this frame can draw.
|
||||||
|
type Geometry: Geometry;
|
||||||
|
|
||||||
|
/// Returns the width of the [`Frame`].
|
||||||
|
fn width(&self) -> f32;
|
||||||
|
|
||||||
|
/// Returns the height of the [`Frame`].
|
||||||
|
fn height(&self) -> f32;
|
||||||
|
|
||||||
|
/// Returns the dimensions of the [`Frame`].
|
||||||
|
fn size(&self) -> Size;
|
||||||
|
|
||||||
|
/// Returns the coordinate of the center of the [`Frame`].
|
||||||
|
fn center(&self) -> Point;
|
||||||
|
|
||||||
|
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
|
||||||
|
/// provided style.
|
||||||
|
fn fill(&mut self, path: &Path, fill: impl Into<Fill>);
|
||||||
|
|
||||||
|
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
|
||||||
|
/// its `Size` on the [`Frame`] by filling it with the provided style.
|
||||||
|
fn fill_rectangle(
|
||||||
|
&mut self,
|
||||||
|
top_left: Point,
|
||||||
|
size: Size,
|
||||||
|
fill: impl Into<Fill>,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Draws the stroke of the given [`Path`] on the [`Frame`] with the
|
||||||
|
/// provided style.
|
||||||
|
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
|
||||||
|
|
||||||
|
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
|
||||||
|
/// them with the given color.
|
||||||
|
///
|
||||||
|
/// __Warning:__ Text currently does not work well with rotations and scale
|
||||||
|
/// transforms! The position will be correctly transformed, but the
|
||||||
|
/// resulting glyphs will not be rotated or scaled properly.
|
||||||
|
///
|
||||||
|
/// Additionally, all text will be rendered on top of all the layers of
|
||||||
|
/// a `Canvas`. Therefore, it is currently only meant to be used for
|
||||||
|
/// overlays, which is the most common use case.
|
||||||
|
///
|
||||||
|
/// Support for vectorial text is planned, and should address all these
|
||||||
|
/// limitations.
|
||||||
|
fn fill_text(&mut self, text: impl Into<Text>);
|
||||||
|
|
||||||
|
/// Stores the current transform of the [`Frame`] and executes the given
|
||||||
|
/// drawing operations, restoring the transform afterwards.
|
||||||
|
///
|
||||||
|
/// This method is useful to compose transforms and perform drawing
|
||||||
|
/// operations in different coordinate systems.
|
||||||
|
#[inline]
|
||||||
|
fn with_save<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||||
|
self.push_transform();
|
||||||
|
|
||||||
|
let result = f(self);
|
||||||
|
|
||||||
|
self.pop_transform();
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes the current transform in the transform stack.
|
||||||
|
fn push_transform(&mut self);
|
||||||
|
|
||||||
|
/// Pops a transform from the transform stack and sets it as the current transform.
|
||||||
|
fn pop_transform(&mut self);
|
||||||
|
|
||||||
|
/// Executes the given drawing operations within a [`Rectangle`] region,
|
||||||
|
/// clipping any geometry that overflows its bounds. Any transformations
|
||||||
|
/// performed are local to the provided closure.
|
||||||
|
///
|
||||||
|
/// This method is useful to perform drawing operations that need to be
|
||||||
|
/// clipped.
|
||||||
|
#[inline]
|
||||||
|
fn with_clip<R>(
|
||||||
|
&mut self,
|
||||||
|
region: Rectangle,
|
||||||
|
f: impl FnOnce(&mut Self) -> R,
|
||||||
|
) -> R {
|
||||||
|
let mut frame = self.draft(region.size());
|
||||||
|
|
||||||
|
let result = f(&mut frame);
|
||||||
|
|
||||||
|
let origin = Point::new(region.x, region.y);
|
||||||
|
|
||||||
|
self.paste(frame, origin);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Frame`] with the given [`Size`].
|
||||||
|
///
|
||||||
|
/// Draw its contents back to this [`Frame`] with [`paste`].
|
||||||
|
///
|
||||||
|
/// [`paste`]: Self::paste
|
||||||
|
fn draft(&mut self, size: Size) -> Self;
|
||||||
|
|
||||||
|
/// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
|
||||||
|
fn paste(&mut self, frame: Self, at: Point);
|
||||||
|
|
||||||
|
/// Applies a translation to the current transform of the [`Frame`].
|
||||||
|
fn translate(&mut self, translation: Vector);
|
||||||
|
|
||||||
|
/// Applies a rotation in radians to the current transform of the [`Frame`].
|
||||||
|
fn rotate(&mut self, angle: impl Into<Radians>);
|
||||||
|
|
||||||
|
/// Applies a uniform scaling to the current transform of the [`Frame`].
|
||||||
|
fn scale(&mut self, scale: impl Into<f32>);
|
||||||
|
|
||||||
|
/// Applies a non-uniform scaling to the current transform of the [`Frame`].
|
||||||
|
fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Geometry: Sized {
|
||||||
|
type Cache;
|
||||||
|
|
||||||
|
fn load(cache: &Self::Cache) -> Self;
|
||||||
|
|
||||||
|
fn cache(self) -> Self::Cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
|
||||||
|
///
|
||||||
|
/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
|
||||||
|
/// change or it is explicitly cleared.
|
||||||
|
pub struct Cache<Renderer>
|
||||||
|
where
|
||||||
|
Renderer: self::Renderer,
|
||||||
|
{
|
||||||
|
state: RefCell<State<Renderer::Geometry>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Renderer> Cache<Renderer>
|
||||||
|
where
|
||||||
|
Renderer: self::Renderer,
|
||||||
|
{
|
||||||
|
/// Creates a new empty [`Cache`].
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Cache {
|
||||||
|
state: RefCell::new(State::Empty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the [`Cache`], forcing a redraw the next time it is used.
|
||||||
|
pub fn clear(&self) {
|
||||||
|
*self.state.borrow_mut() = State::Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws [`Geometry`] using the provided closure and stores it in the
|
||||||
|
/// [`Cache`].
|
||||||
|
///
|
||||||
|
/// The closure will only be called when
|
||||||
|
/// - the bounds have changed since the previous draw call.
|
||||||
|
/// - the [`Cache`] is empty or has been explicitly cleared.
|
||||||
|
///
|
||||||
|
/// Otherwise, the previously stored [`Geometry`] will be returned. The
|
||||||
|
/// [`Cache`] is not cleared in this case. In other words, it will keep
|
||||||
|
/// returning the stored [`Geometry`] if needed.
|
||||||
|
pub fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &Renderer,
|
||||||
|
bounds: Size,
|
||||||
|
draw_fn: impl FnOnce(&mut Renderer::Frame),
|
||||||
|
) -> Renderer::Geometry {
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
if let State::Filled {
|
||||||
|
bounds: cached_bounds,
|
||||||
|
geometry,
|
||||||
|
} = self.state.borrow().deref()
|
||||||
|
{
|
||||||
|
if *cached_bounds == bounds {
|
||||||
|
return Geometry::load(geometry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut frame = frame(renderer, bounds);
|
||||||
|
draw_fn(&mut frame);
|
||||||
|
|
||||||
|
let geometry = frame.into().cache();
|
||||||
|
let result = Geometry::load(&geometry);
|
||||||
|
|
||||||
|
*self.state.borrow_mut() = State::Filled { bounds, geometry };
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Renderer> std::fmt::Debug for Cache<Renderer>
|
||||||
|
where
|
||||||
|
Renderer: self::Renderer,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let state = self.state.borrow();
|
||||||
|
|
||||||
|
match *state {
|
||||||
|
State::Empty => write!(f, "Cache::Empty"),
|
||||||
|
State::Filled { bounds, .. } => {
|
||||||
|
write!(f, "Cache::Filled {{ bounds: {bounds:?} }}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Renderer> Default for Cache<Renderer>
|
||||||
|
where
|
||||||
|
Renderer: self::Renderer,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State<Geometry>
|
||||||
|
where
|
||||||
|
Geometry: self::Geometry,
|
||||||
|
{
|
||||||
|
Empty,
|
||||||
|
Filled {
|
||||||
|
bounds: Size,
|
||||||
|
geometry: Geometry::Cache,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Geometry for Primitive<T> {
|
||||||
|
type Cache = Arc<Self>;
|
||||||
|
|
||||||
|
fn load(cache: &Arc<Self>) -> Self {
|
||||||
|
Self::Cache {
|
||||||
|
content: cache.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache(self) -> Arc<Self> {
|
||||||
|
Arc::new(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@
|
||||||
)]
|
)]
|
||||||
#![forbid(rust_2018_idioms)]
|
#![forbid(rust_2018_idioms)]
|
||||||
#![deny(
|
#![deny(
|
||||||
missing_debug_implementations,
|
// missing_debug_implementations,
|
||||||
missing_docs,
|
// missing_docs,
|
||||||
unsafe_code,
|
unsafe_code,
|
||||||
unused_results,
|
unused_results,
|
||||||
rustdoc::broken_intra_doc_links
|
rustdoc::broken_intra_doc_links
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Draw triangles!
|
//! Draw triangles!
|
||||||
use crate::color;
|
use crate::color;
|
||||||
use crate::core::{Rectangle, Size};
|
use crate::core::{self, Rectangle, Size};
|
||||||
use crate::gradient;
|
use crate::gradient;
|
||||||
use crate::Damage;
|
use crate::Damage;
|
||||||
|
|
||||||
|
|
@ -74,3 +74,7 @@ pub struct GradientVertex2D {
|
||||||
/// The packed vertex data of the gradient.
|
/// The packed vertex data of the gradient.
|
||||||
pub gradient: gradient::Packed,
|
pub gradient: gradient::Packed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Renderer: core::Renderer {
|
||||||
|
fn draw_mesh(&mut self, mesh: Mesh);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@ use crate::core::text::Text;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||||
};
|
};
|
||||||
|
use crate::mesh;
|
||||||
use crate::text;
|
use crate::text;
|
||||||
use crate::Primitive;
|
use crate::{Mesh, Primitive};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -20,6 +21,7 @@ pub struct Renderer<B: Backend> {
|
||||||
default_font: Font,
|
default_font: Font,
|
||||||
default_text_size: Pixels,
|
default_text_size: Pixels,
|
||||||
primitives: Vec<Primitive<B::Primitive>>,
|
primitives: Vec<Primitive<B::Primitive>>,
|
||||||
|
stack: Vec<Vec<Primitive<B::Primitive>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Backend> Renderer<B> {
|
impl<B: Backend> Renderer<B> {
|
||||||
|
|
@ -34,6 +36,7 @@ impl<B: Backend> Renderer<B> {
|
||||||
default_font,
|
default_font,
|
||||||
default_text_size,
|
default_text_size,
|
||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
|
stack: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,59 +59,45 @@ impl<B: Backend> Renderer<B> {
|
||||||
f(&mut self.backend, &self.primitives)
|
f(&mut self.backend, &self.primitives)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts recording a new layer.
|
#[cfg(feature = "geometry")]
|
||||||
pub fn start_layer(&mut self) -> Vec<Primitive<B::Primitive>> {
|
pub fn draw_geometry<Geometry>(
|
||||||
std::mem::take(&mut self.primitives)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ends the recording of a layer.
|
|
||||||
pub fn end_layer(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
primitives: Vec<Primitive<B::Primitive>>,
|
layers: impl IntoIterator<Item = Geometry>,
|
||||||
bounds: Rectangle,
|
) where
|
||||||
) {
|
Geometry: Into<Primitive<B::Primitive>>,
|
||||||
let layer = std::mem::replace(&mut self.primitives, primitives);
|
{
|
||||||
|
for layer in layers {
|
||||||
self.primitives.push(Primitive::group(layer).clip(bounds));
|
self.draw_primitive(layer.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts recording a translation.
|
|
||||||
pub fn start_transformation(&mut self) -> Vec<Primitive<B::Primitive>> {
|
|
||||||
std::mem::take(&mut self.primitives)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ends the recording of a translation.
|
|
||||||
pub fn end_transformation(
|
|
||||||
&mut self,
|
|
||||||
primitives: Vec<Primitive<B::Primitive>>,
|
|
||||||
transformation: Transformation,
|
|
||||||
) {
|
|
||||||
let layer = std::mem::replace(&mut self.primitives, primitives);
|
|
||||||
|
|
||||||
self.primitives
|
|
||||||
.push(Primitive::group(layer).transform(transformation));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Backend> iced_core::Renderer for Renderer<B> {
|
impl<B: Backend> iced_core::Renderer for Renderer<B> {
|
||||||
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
|
fn start_layer(&mut self) {
|
||||||
let current = self.start_layer();
|
self.stack.push(std::mem::take(&mut self.primitives));
|
||||||
|
|
||||||
f(self);
|
|
||||||
|
|
||||||
self.end_layer(current, bounds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_transformation(
|
fn end_layer(&mut self, bounds: Rectangle) {
|
||||||
&mut self,
|
let layer = std::mem::replace(
|
||||||
transformation: Transformation,
|
&mut self.primitives,
|
||||||
f: impl FnOnce(&mut Self),
|
self.stack.pop().expect("a layer should be recording"),
|
||||||
) {
|
);
|
||||||
let current = self.start_transformation();
|
|
||||||
|
|
||||||
f(self);
|
self.primitives.push(Primitive::group(layer).clip(bounds));
|
||||||
|
}
|
||||||
|
|
||||||
self.end_transformation(current, transformation);
|
fn start_transformation(&mut self) {
|
||||||
|
self.stack.push(std::mem::take(&mut self.primitives));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_transformation(&mut self, transformation: Transformation) {
|
||||||
|
let layer = std::mem::replace(
|
||||||
|
&mut self.primitives,
|
||||||
|
self.stack.pop().expect("a layer should be recording"),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.primitives
|
||||||
|
.push(Primitive::group(layer).transform(transformation));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_quad(
|
fn fill_quad(
|
||||||
|
|
@ -250,3 +239,34 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<B: Backend> mesh::Renderer for Renderer<B> {
|
||||||
|
fn draw_mesh(&mut self, mesh: Mesh) {
|
||||||
|
match B::Primitive::try_from(mesh) {
|
||||||
|
Ok(primitive) => {
|
||||||
|
self.draw_primitive(Primitive::Custom(primitive));
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!("mesh primitive could not be drawn: {error:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "geometry")]
|
||||||
|
impl<B> crate::geometry::Renderer for Renderer<B>
|
||||||
|
where
|
||||||
|
B: Backend + crate::geometry::Backend,
|
||||||
|
B::Frame: crate::geometry::Frame<Geometry = Primitive<B::Primitive>>,
|
||||||
|
{
|
||||||
|
type Frame = B::Frame;
|
||||||
|
type Geometry = Primitive<B::Primitive>;
|
||||||
|
|
||||||
|
fn new_frame(&self, size: Size) -> Self::Frame {
|
||||||
|
self.backend.new_frame(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_geometry(&mut self, geometry: Self::Geometry) {
|
||||||
|
self.draw_primitive(geometry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,300 +1 @@
|
||||||
use crate::core::Color;
|
|
||||||
use crate::graphics::compositor::{Information, SurfaceError, Window};
|
|
||||||
use crate::graphics::{Error, Viewport};
|
|
||||||
use crate::{Renderer, Settings};
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::future::Future;
|
|
||||||
|
|
||||||
pub enum Compositor {
|
|
||||||
TinySkia(iced_tiny_skia::window::Compositor),
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Wgpu(iced_wgpu::window::Compositor),
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Custom(Box<dyn crate::custom::Compositor>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Surface {
|
|
||||||
TinySkia(iced_tiny_skia::window::Surface),
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Wgpu(iced_wgpu::window::Surface<'static>),
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Custom(Box<dyn crate::custom::Surface>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::graphics::Compositor for Compositor {
|
|
||||||
type Settings = Settings;
|
|
||||||
type Renderer = Renderer;
|
|
||||||
type Surface = Surface;
|
|
||||||
|
|
||||||
fn new<W: Window + Clone>(
|
|
||||||
settings: Self::Settings,
|
|
||||||
compatible_window: W,
|
|
||||||
) -> impl Future<Output = Result<Self, Error>> {
|
|
||||||
let candidates =
|
|
||||||
Candidate::list_from_env().unwrap_or(Candidate::default_list());
|
|
||||||
|
|
||||||
async move {
|
|
||||||
let mut error = Error::GraphicsAdapterNotFound;
|
|
||||||
|
|
||||||
for candidate in candidates {
|
|
||||||
match candidate.build(settings, compatible_window.clone()).await
|
|
||||||
{
|
|
||||||
Ok(compositor) => return Ok(compositor),
|
|
||||||
Err(new_error) => {
|
|
||||||
error = new_error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_renderer(&self) -> Self::Renderer {
|
|
||||||
match self {
|
|
||||||
Compositor::TinySkia(compositor) => {
|
|
||||||
Renderer::TinySkia(compositor.create_renderer())
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Compositor::Wgpu(compositor) => {
|
|
||||||
Renderer::Wgpu(compositor.create_renderer())
|
|
||||||
}
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Compositor::Custom(compositor) => {
|
|
||||||
Renderer::Custom(compositor.create_renderer())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_surface<W: Window + Clone>(
|
|
||||||
&mut self,
|
|
||||||
window: W,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
) -> Surface {
|
|
||||||
match self {
|
|
||||||
Self::TinySkia(compositor) => Surface::TinySkia(
|
|
||||||
compositor.create_surface(window, width, height),
|
|
||||||
),
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu(compositor) => {
|
|
||||||
Surface::Wgpu(compositor.create_surface(window, width, height))
|
|
||||||
}
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Self::Custom(compositor) => Surface::Custom(
|
|
||||||
compositor.create_surface(Box::new(window), width, height),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_surface(
|
|
||||||
&mut self,
|
|
||||||
surface: &mut Surface,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
) {
|
|
||||||
match (self, surface) {
|
|
||||||
(Self::TinySkia(compositor), Surface::TinySkia(surface)) => {
|
|
||||||
compositor.configure_surface(surface, width, height);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
(Self::Wgpu(compositor), Surface::Wgpu(surface)) => {
|
|
||||||
compositor.configure_surface(surface, width, height);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
(Self::Custom(compositor), Surface::Custom(surface)) => {
|
|
||||||
compositor.configure_surface(surface.as_mut(), width, height);
|
|
||||||
}
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
_ => panic!(
|
|
||||||
"The provided surface is not compatible with the compositor."
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch_information(&self) -> Information {
|
|
||||||
match self {
|
|
||||||
Self::TinySkia(compositor) => compositor.fetch_information(),
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu(compositor) => compositor.fetch_information(),
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Self::Custom(compositor) => compositor.fetch_information(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn present<T: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
renderer: &mut Self::Renderer,
|
|
||||||
surface: &mut Self::Surface,
|
|
||||||
viewport: &Viewport,
|
|
||||||
background_color: Color,
|
|
||||||
overlay: &[T],
|
|
||||||
) -> Result<(), SurfaceError> {
|
|
||||||
match (self, renderer, surface) {
|
|
||||||
(
|
|
||||||
Self::TinySkia(_compositor),
|
|
||||||
crate::Renderer::TinySkia(renderer),
|
|
||||||
Surface::TinySkia(surface),
|
|
||||||
) => renderer.with_primitives(|backend, primitives| {
|
|
||||||
iced_tiny_skia::window::compositor::present(
|
|
||||||
backend,
|
|
||||||
surface,
|
|
||||||
primitives,
|
|
||||||
viewport,
|
|
||||||
background_color,
|
|
||||||
overlay,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
(
|
|
||||||
Self::Wgpu(compositor),
|
|
||||||
crate::Renderer::Wgpu(renderer),
|
|
||||||
Surface::Wgpu(surface),
|
|
||||||
) => renderer.with_primitives(|backend, primitives| {
|
|
||||||
iced_wgpu::window::compositor::present(
|
|
||||||
compositor,
|
|
||||||
backend,
|
|
||||||
surface,
|
|
||||||
primitives,
|
|
||||||
viewport,
|
|
||||||
background_color,
|
|
||||||
overlay,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
(
|
|
||||||
Self::Custom(compositor),
|
|
||||||
crate::Renderer::Custom(renderer),
|
|
||||||
Surface::Custom(surface),
|
|
||||||
) => renderer.present(
|
|
||||||
surface.as_mut(),
|
|
||||||
viewport,
|
|
||||||
background_color,
|
|
||||||
compositor.as_mut(),
|
|
||||||
),
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
_ => panic!(
|
|
||||||
"The provided renderer or surface are not compatible \
|
|
||||||
with the compositor."
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn screenshot<T: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
renderer: &mut Self::Renderer,
|
|
||||||
surface: &mut Self::Surface,
|
|
||||||
viewport: &Viewport,
|
|
||||||
background_color: Color,
|
|
||||||
overlay: &[T],
|
|
||||||
) -> Vec<u8> {
|
|
||||||
match (self, renderer, surface) {
|
|
||||||
(
|
|
||||||
Self::TinySkia(_compositor),
|
|
||||||
Renderer::TinySkia(renderer),
|
|
||||||
Surface::TinySkia(surface),
|
|
||||||
) => renderer.with_primitives(|backend, primitives| {
|
|
||||||
iced_tiny_skia::window::compositor::screenshot(
|
|
||||||
surface,
|
|
||||||
backend,
|
|
||||||
primitives,
|
|
||||||
viewport,
|
|
||||||
background_color,
|
|
||||||
overlay,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
(
|
|
||||||
Self::Wgpu(compositor),
|
|
||||||
Renderer::Wgpu(renderer),
|
|
||||||
Surface::Wgpu(_),
|
|
||||||
) => renderer.with_primitives(|backend, primitives| {
|
|
||||||
iced_wgpu::window::compositor::screenshot(
|
|
||||||
compositor,
|
|
||||||
backend,
|
|
||||||
primitives,
|
|
||||||
viewport,
|
|
||||||
background_color,
|
|
||||||
overlay,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
_ => panic!(
|
|
||||||
"The provided renderer or backend are not compatible \
|
|
||||||
with the compositor."
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Candidate {
|
|
||||||
Wgpu,
|
|
||||||
TinySkia,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Candidate {
|
|
||||||
fn default_list() -> Vec<Self> {
|
|
||||||
vec![
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu,
|
|
||||||
Self::TinySkia,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_from_env() -> Option<Vec<Self>> {
|
|
||||||
let backends = env::var("ICED_BACKEND").ok()?;
|
|
||||||
|
|
||||||
Some(
|
|
||||||
backends
|
|
||||||
.split(',')
|
|
||||||
.map(str::trim)
|
|
||||||
.map(|backend| match backend {
|
|
||||||
"wgpu" => Self::Wgpu,
|
|
||||||
"tiny-skia" => Self::TinySkia,
|
|
||||||
_ => panic!("unknown backend value: \"{backend}\""),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn build<W: Window>(
|
|
||||||
self,
|
|
||||||
settings: Settings,
|
|
||||||
_compatible_window: W,
|
|
||||||
) -> Result<Compositor, Error> {
|
|
||||||
match self {
|
|
||||||
Self::TinySkia => {
|
|
||||||
let compositor = iced_tiny_skia::window::compositor::new(
|
|
||||||
iced_tiny_skia::Settings {
|
|
||||||
default_font: settings.default_font,
|
|
||||||
default_text_size: settings.default_text_size,
|
|
||||||
},
|
|
||||||
_compatible_window,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Compositor::TinySkia(compositor))
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu => {
|
|
||||||
let compositor = iced_wgpu::window::compositor::new(
|
|
||||||
iced_wgpu::Settings {
|
|
||||||
default_font: settings.default_font,
|
|
||||||
default_text_size: settings.default_text_size,
|
|
||||||
antialiasing: settings.antialiasing,
|
|
||||||
..iced_wgpu::Settings::from_env()
|
|
||||||
},
|
|
||||||
_compatible_window,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Compositor::Wgpu(compositor))
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "wgpu"))]
|
|
||||||
Self::Wgpu => {
|
|
||||||
panic!("`wgpu` feature was not enabled in `iced_renderer`")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -94,8 +94,6 @@ pub trait Renderer {
|
||||||
|
|
||||||
#[cfg(feature = "geometry")]
|
#[cfg(feature = "geometry")]
|
||||||
pub trait Frame: std::any::Any {
|
pub trait Frame: std::any::Any {
|
||||||
fn new(&self, size: Size) -> Box<dyn Frame>;
|
|
||||||
|
|
||||||
fn width(&self) -> f32;
|
fn width(&self) -> f32;
|
||||||
|
|
||||||
fn height(&self) -> f32;
|
fn height(&self) -> f32;
|
||||||
|
|
@ -108,7 +106,7 @@ pub trait Frame: std::any::Any {
|
||||||
|
|
||||||
fn fill_rectangle(&mut self, top_left: Point, size: Size, fill: Fill);
|
fn fill_rectangle(&mut self, top_left: Point, size: Size, fill: Fill);
|
||||||
|
|
||||||
fn stroke<'a>(&mut self, path: &Path, stroke: Stroke<'a>);
|
fn stroke(&mut self, path: &Path, stroke: Stroke<'_>);
|
||||||
|
|
||||||
fn fill_text(&mut self, text: geometry::Text);
|
fn fill_text(&mut self, text: geometry::Text);
|
||||||
|
|
||||||
|
|
|
||||||
562
renderer/src/fallback.rs
Normal file
562
renderer/src/fallback.rs
Normal file
|
|
@ -0,0 +1,562 @@
|
||||||
|
use crate::core::image;
|
||||||
|
use crate::core::renderer;
|
||||||
|
use crate::core::svg;
|
||||||
|
use crate::core::{
|
||||||
|
self, Background, Color, Point, Rectangle, Size, Transformation,
|
||||||
|
};
|
||||||
|
use crate::graphics;
|
||||||
|
use crate::graphics::compositor;
|
||||||
|
use crate::graphics::mesh;
|
||||||
|
|
||||||
|
pub enum Renderer<L, R>
|
||||||
|
where
|
||||||
|
L: core::Renderer,
|
||||||
|
R: core::Renderer,
|
||||||
|
{
|
||||||
|
Left(L),
|
||||||
|
Right(R),
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! delegate {
|
||||||
|
($renderer:expr, $name:ident, $body:expr) => {
|
||||||
|
match $renderer {
|
||||||
|
Self::Left($name) => $body,
|
||||||
|
Self::Right($name) => $body,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> Renderer<L, R>
|
||||||
|
where
|
||||||
|
L: core::Renderer,
|
||||||
|
R: core::Renderer,
|
||||||
|
{
|
||||||
|
#[cfg(feature = "geometry")]
|
||||||
|
pub fn draw_geometry<Geometry>(
|
||||||
|
&mut self,
|
||||||
|
layers: impl IntoIterator<Item = Geometry>,
|
||||||
|
) where
|
||||||
|
L: graphics::geometry::Renderer,
|
||||||
|
R: graphics::geometry::Renderer,
|
||||||
|
|
||||||
|
Geometry: Into<geometry::Geometry<L::Geometry, R::Geometry>>,
|
||||||
|
{
|
||||||
|
use graphics::geometry::Renderer;
|
||||||
|
|
||||||
|
for layer in layers {
|
||||||
|
<Self as Renderer>::draw_geometry(self, layer.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> core::Renderer for Renderer<L, R>
|
||||||
|
where
|
||||||
|
L: core::Renderer,
|
||||||
|
R: core::Renderer,
|
||||||
|
{
|
||||||
|
fn fill_quad(
|
||||||
|
&mut self,
|
||||||
|
quad: renderer::Quad,
|
||||||
|
background: impl Into<Background>,
|
||||||
|
) {
|
||||||
|
delegate!(self, renderer, renderer.fill_quad(quad, background.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
delegate!(self, renderer, renderer.clear());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_layer(&mut self) {
|
||||||
|
delegate!(self, renderer, renderer.start_layer());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_layer(&mut self, bounds: Rectangle) {
|
||||||
|
delegate!(self, renderer, renderer.end_layer(bounds));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_transformation(&mut self) {
|
||||||
|
delegate!(self, renderer, renderer.start_transformation());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_transformation(&mut self, transformation: Transformation) {
|
||||||
|
delegate!(self, renderer, renderer.end_transformation(transformation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> core::text::Renderer for Renderer<L, R>
|
||||||
|
where
|
||||||
|
L: core::text::Renderer,
|
||||||
|
R: core::text::Renderer<
|
||||||
|
Font = L::Font,
|
||||||
|
Paragraph = L::Paragraph,
|
||||||
|
Editor = L::Editor,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
type Font = L::Font;
|
||||||
|
type Paragraph = L::Paragraph;
|
||||||
|
type Editor = L::Editor;
|
||||||
|
|
||||||
|
const ICON_FONT: Self::Font = L::ICON_FONT;
|
||||||
|
const CHECKMARK_ICON: char = L::CHECKMARK_ICON;
|
||||||
|
const ARROW_DOWN_ICON: char = L::ARROW_DOWN_ICON;
|
||||||
|
|
||||||
|
fn default_font(&self) -> Self::Font {
|
||||||
|
delegate!(self, renderer, renderer.default_font())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_size(&self) -> core::Pixels {
|
||||||
|
delegate!(self, renderer, renderer.default_size())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_font(&mut self, font: std::borrow::Cow<'static, [u8]>) {
|
||||||
|
delegate!(self, renderer, renderer.load_font(font));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_paragraph(
|
||||||
|
&mut self,
|
||||||
|
text: &Self::Paragraph,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
delegate!(
|
||||||
|
self,
|
||||||
|
renderer,
|
||||||
|
renderer.fill_paragraph(text, position, color, clip_bounds)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_editor(
|
||||||
|
&mut self,
|
||||||
|
editor: &Self::Editor,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
delegate!(
|
||||||
|
self,
|
||||||
|
renderer,
|
||||||
|
renderer.fill_editor(editor, position, color, clip_bounds)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_text(
|
||||||
|
&mut self,
|
||||||
|
text: core::Text<'_, Self::Font>,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
delegate!(
|
||||||
|
self,
|
||||||
|
renderer,
|
||||||
|
renderer.fill_text(text, position, color, clip_bounds)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> image::Renderer for Renderer<L, R>
|
||||||
|
where
|
||||||
|
L: image::Renderer,
|
||||||
|
R: image::Renderer<Handle = L::Handle>,
|
||||||
|
{
|
||||||
|
type Handle = L::Handle;
|
||||||
|
|
||||||
|
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
|
||||||
|
delegate!(self, renderer, renderer.measure_image(handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_image(
|
||||||
|
&mut self,
|
||||||
|
handle: Self::Handle,
|
||||||
|
filter_method: image::FilterMethod,
|
||||||
|
bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
delegate!(
|
||||||
|
self,
|
||||||
|
renderer,
|
||||||
|
renderer.draw_image(handle, filter_method, bounds)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> svg::Renderer for Renderer<L, R>
|
||||||
|
where
|
||||||
|
L: svg::Renderer,
|
||||||
|
R: svg::Renderer,
|
||||||
|
{
|
||||||
|
fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> {
|
||||||
|
delegate!(self, renderer, renderer.measure_svg(handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_svg(
|
||||||
|
&mut self,
|
||||||
|
handle: svg::Handle,
|
||||||
|
color: Option<Color>,
|
||||||
|
bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
delegate!(self, renderer, renderer.draw_svg(handle, color, bounds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> mesh::Renderer for Renderer<L, R>
|
||||||
|
where
|
||||||
|
L: mesh::Renderer,
|
||||||
|
R: mesh::Renderer,
|
||||||
|
{
|
||||||
|
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
|
||||||
|
delegate!(self, renderer, renderer.draw_mesh(mesh));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Compositor<L, R>
|
||||||
|
where
|
||||||
|
L: graphics::Compositor,
|
||||||
|
R: graphics::Compositor,
|
||||||
|
{
|
||||||
|
Left(L),
|
||||||
|
Right(R),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Surface<L, R> {
|
||||||
|
Left(L),
|
||||||
|
Right(R),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> graphics::Compositor for Compositor<L, R>
|
||||||
|
where
|
||||||
|
L: graphics::Compositor,
|
||||||
|
R: graphics::Compositor,
|
||||||
|
L::Settings: From<crate::Settings>,
|
||||||
|
R::Settings: From<crate::Settings>,
|
||||||
|
{
|
||||||
|
type Settings = crate::Settings;
|
||||||
|
type Renderer = Renderer<L::Renderer, R::Renderer>;
|
||||||
|
type Surface = Surface<L::Surface, R::Surface>;
|
||||||
|
|
||||||
|
async fn new<W: compositor::Window + Clone>(
|
||||||
|
settings: Self::Settings,
|
||||||
|
compatible_window: W,
|
||||||
|
) -> Result<Self, graphics::Error> {
|
||||||
|
if let Ok(left) = L::new(settings.into(), compatible_window.clone())
|
||||||
|
.await
|
||||||
|
.map(Self::Left)
|
||||||
|
{
|
||||||
|
return Ok(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
R::new(settings.into(), compatible_window)
|
||||||
|
.await
|
||||||
|
.map(Self::Right)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_renderer(&self) -> Self::Renderer {
|
||||||
|
match self {
|
||||||
|
Self::Left(compositor) => {
|
||||||
|
Renderer::Left(compositor.create_renderer())
|
||||||
|
}
|
||||||
|
Self::Right(compositor) => {
|
||||||
|
Renderer::Right(compositor.create_renderer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_surface<W: compositor::Window + Clone>(
|
||||||
|
&mut self,
|
||||||
|
window: W,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Self::Surface {
|
||||||
|
match self {
|
||||||
|
Self::Left(compositor) => {
|
||||||
|
Surface::Left(compositor.create_surface(window, width, height))
|
||||||
|
}
|
||||||
|
Self::Right(compositor) => {
|
||||||
|
Surface::Right(compositor.create_surface(window, width, height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_surface(
|
||||||
|
&mut self,
|
||||||
|
surface: &mut Self::Surface,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) {
|
||||||
|
match (self, surface) {
|
||||||
|
(Self::Left(compositor), Surface::Left(surface)) => {
|
||||||
|
compositor.configure_surface(surface, width, height);
|
||||||
|
}
|
||||||
|
(Self::Right(compositor), Surface::Right(surface)) => {
|
||||||
|
compositor.configure_surface(surface, width, height);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_information(&self) -> compositor::Information {
|
||||||
|
delegate!(self, compositor, compositor.fetch_information())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn present<T: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
renderer: &mut Self::Renderer,
|
||||||
|
surface: &mut Self::Surface,
|
||||||
|
viewport: &graphics::Viewport,
|
||||||
|
background_color: Color,
|
||||||
|
overlay: &[T],
|
||||||
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
|
match (self, renderer, surface) {
|
||||||
|
(
|
||||||
|
Self::Left(compositor),
|
||||||
|
Renderer::Left(renderer),
|
||||||
|
Surface::Left(surface),
|
||||||
|
) => compositor.present(
|
||||||
|
renderer,
|
||||||
|
surface,
|
||||||
|
viewport,
|
||||||
|
background_color,
|
||||||
|
overlay,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Self::Right(compositor),
|
||||||
|
Renderer::Right(renderer),
|
||||||
|
Surface::Right(surface),
|
||||||
|
) => compositor.present(
|
||||||
|
renderer,
|
||||||
|
surface,
|
||||||
|
viewport,
|
||||||
|
background_color,
|
||||||
|
overlay,
|
||||||
|
),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn screenshot<T: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
renderer: &mut Self::Renderer,
|
||||||
|
surface: &mut Self::Surface,
|
||||||
|
viewport: &graphics::Viewport,
|
||||||
|
background_color: Color,
|
||||||
|
overlay: &[T],
|
||||||
|
) -> Vec<u8> {
|
||||||
|
match (self, renderer, surface) {
|
||||||
|
(
|
||||||
|
Self::Left(compositor),
|
||||||
|
Renderer::Left(renderer),
|
||||||
|
Surface::Left(surface),
|
||||||
|
) => compositor.screenshot(
|
||||||
|
renderer,
|
||||||
|
surface,
|
||||||
|
viewport,
|
||||||
|
background_color,
|
||||||
|
overlay,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Self::Right(compositor),
|
||||||
|
Renderer::Right(renderer),
|
||||||
|
Surface::Right(surface),
|
||||||
|
) => compositor.screenshot(
|
||||||
|
renderer,
|
||||||
|
surface,
|
||||||
|
viewport,
|
||||||
|
background_color,
|
||||||
|
overlay,
|
||||||
|
),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
impl<L, R> iced_wgpu::primitive::pipeline::Renderer for Renderer<L, R>
|
||||||
|
where
|
||||||
|
L: iced_wgpu::primitive::pipeline::Renderer,
|
||||||
|
R: core::Renderer,
|
||||||
|
{
|
||||||
|
fn draw_pipeline_primitive(
|
||||||
|
&mut self,
|
||||||
|
bounds: Rectangle,
|
||||||
|
primitive: impl iced_wgpu::primitive::pipeline::Primitive,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
Self::Left(renderer) => {
|
||||||
|
renderer.draw_pipeline_primitive(bounds, primitive);
|
||||||
|
}
|
||||||
|
Self::Right(_) => {
|
||||||
|
log::warn!(
|
||||||
|
"Custom shader primitive is not supported with this renderer."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "geometry")]
|
||||||
|
mod geometry {
|
||||||
|
use super::Renderer;
|
||||||
|
use crate::core::{Point, Radians, Size, Vector};
|
||||||
|
use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
|
||||||
|
|
||||||
|
impl<L, R> geometry::Renderer for Renderer<L, R>
|
||||||
|
where
|
||||||
|
L: geometry::Renderer,
|
||||||
|
R: geometry::Renderer,
|
||||||
|
{
|
||||||
|
type Geometry = Geometry<L::Geometry, R::Geometry>;
|
||||||
|
type Frame = Frame<L::Frame, R::Frame>;
|
||||||
|
|
||||||
|
fn new_frame(&self, size: iced_graphics::core::Size) -> Self::Frame {
|
||||||
|
match self {
|
||||||
|
Self::Left(renderer) => Frame::Left(renderer.new_frame(size)),
|
||||||
|
Self::Right(renderer) => Frame::Right(renderer.new_frame(size)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_geometry(&mut self, geometry: Self::Geometry) {
|
||||||
|
match (self, geometry) {
|
||||||
|
(Self::Left(renderer), Geometry::Left(geometry)) => {
|
||||||
|
renderer.draw_geometry(geometry);
|
||||||
|
}
|
||||||
|
(Self::Right(renderer), Geometry::Right(geometry)) => {
|
||||||
|
renderer.draw_geometry(geometry);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Geometry<L, R> {
|
||||||
|
Left(L),
|
||||||
|
Right(R),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> geometry::Geometry for Geometry<L, R>
|
||||||
|
where
|
||||||
|
L: geometry::Geometry,
|
||||||
|
R: geometry::Geometry,
|
||||||
|
{
|
||||||
|
type Cache = Geometry<L::Cache, R::Cache>;
|
||||||
|
|
||||||
|
fn load(cache: &Self::Cache) -> Self {
|
||||||
|
match cache {
|
||||||
|
Geometry::Left(cache) => Self::Left(L::load(cache)),
|
||||||
|
Geometry::Right(cache) => Self::Right(R::load(cache)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache(self) -> Self::Cache {
|
||||||
|
match self {
|
||||||
|
Self::Left(geometry) => Geometry::Left(geometry.cache()),
|
||||||
|
Self::Right(geometry) => Geometry::Right(geometry.cache()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Frame<L, R> {
|
||||||
|
Left(L),
|
||||||
|
Right(R),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> geometry::Frame for Frame<L, R>
|
||||||
|
where
|
||||||
|
L: geometry::Frame,
|
||||||
|
R: geometry::Frame,
|
||||||
|
{
|
||||||
|
type Geometry = Geometry<L::Geometry, R::Geometry>;
|
||||||
|
|
||||||
|
fn width(&self) -> f32 {
|
||||||
|
delegate!(self, frame, frame.width())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> f32 {
|
||||||
|
delegate!(self, frame, frame.height())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Size {
|
||||||
|
delegate!(self, frame, frame.size())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn center(&self) -> Point {
|
||||||
|
delegate!(self, frame, frame.center())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||||
|
delegate!(self, frame, frame.fill(path, fill));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_rectangle(
|
||||||
|
&mut self,
|
||||||
|
top_left: Point,
|
||||||
|
size: Size,
|
||||||
|
fill: impl Into<Fill>,
|
||||||
|
) {
|
||||||
|
delegate!(self, frame, frame.fill_rectangle(top_left, size, fill));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||||
|
delegate!(self, frame, frame.stroke(path, stroke));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_text(&mut self, text: impl Into<Text>) {
|
||||||
|
delegate!(self, frame, frame.fill_text(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_transform(&mut self) {
|
||||||
|
delegate!(self, frame, frame.push_transform());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_transform(&mut self) {
|
||||||
|
delegate!(self, frame, frame.pop_transform());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draft(&mut self, size: Size) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Left(frame) => Self::Left(frame.draft(size)),
|
||||||
|
Self::Right(frame) => Self::Right(frame.draft(size)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paste(&mut self, frame: Self, at: Point) {
|
||||||
|
match (self, frame) {
|
||||||
|
(Self::Left(target), Self::Left(source)) => {
|
||||||
|
target.paste(source, at);
|
||||||
|
}
|
||||||
|
(Self::Right(target), Self::Right(source)) => {
|
||||||
|
target.paste(source, at);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate(&mut self, translation: Vector) {
|
||||||
|
delegate!(self, frame, frame.translate(translation));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate(&mut self, angle: impl Into<Radians>) {
|
||||||
|
delegate!(self, frame, frame.rotate(angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale(&mut self, scale: impl Into<f32>) {
|
||||||
|
delegate!(self, frame, frame.scale(scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
||||||
|
delegate!(self, frame, frame.scale_nonuniform(scale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> From<Frame<L, R>> for Geometry<L::Geometry, R::Geometry>
|
||||||
|
where
|
||||||
|
L: geometry::Frame,
|
||||||
|
R: geometry::Frame,
|
||||||
|
{
|
||||||
|
fn from(frame: Frame<L, R>) -> Self {
|
||||||
|
match frame {
|
||||||
|
Frame::Left(frame) => Self::Left(frame.into()),
|
||||||
|
Frame::Right(frame) => Self::Right(frame.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,364 +4,42 @@
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
pub use iced_wgpu as wgpu;
|
pub use iced_wgpu as wgpu;
|
||||||
|
|
||||||
pub mod compositor;
|
pub mod fallback;
|
||||||
pub mod custom;
|
|
||||||
|
|
||||||
#[cfg(feature = "geometry")]
|
|
||||||
pub mod geometry;
|
|
||||||
|
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
pub use iced_graphics as graphics;
|
pub use iced_graphics as graphics;
|
||||||
pub use iced_graphics::core;
|
pub use iced_graphics::core;
|
||||||
|
|
||||||
pub use compositor::Compositor;
|
|
||||||
pub use settings::Settings;
|
|
||||||
|
|
||||||
#[cfg(feature = "geometry")]
|
#[cfg(feature = "geometry")]
|
||||||
pub use geometry::Geometry;
|
pub use iced_graphics::geometry;
|
||||||
|
|
||||||
use crate::core::renderer;
|
pub use settings::Settings;
|
||||||
use crate::core::text::{self, Text};
|
|
||||||
use crate::core::{
|
|
||||||
Background, Color, Font, Pixels, Point, Rectangle, Transformation,
|
|
||||||
};
|
|
||||||
use crate::graphics::text::Editor;
|
|
||||||
use crate::graphics::text::Paragraph;
|
|
||||||
use crate::graphics::Mesh;
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
/// The default graphics renderer for [`iced`].
|
/// The default graphics renderer for [`iced`].
|
||||||
///
|
///
|
||||||
/// [`iced`]: https://github.com/iced-rs/iced
|
/// [`iced`]: https://github.com/iced-rs/iced
|
||||||
pub enum Renderer {
|
#[cfg(not(feature = "wgpu"))]
|
||||||
TinySkia(iced_tiny_skia::Renderer),
|
pub type Renderer = iced_tiny_skia::Renderer;
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Wgpu(iced_wgpu::Renderer),
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Custom(Box<dyn custom::Renderer>),
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! delegate {
|
|
||||||
($renderer:expr, $name:ident, $body:expr) => {
|
|
||||||
match $renderer {
|
|
||||||
Self::TinySkia($name) => $body,
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu($name) => $body,
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Self::Custom($name) => $body,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Renderer {
|
|
||||||
pub fn draw_mesh(&mut self, mesh: Mesh) {
|
|
||||||
match self {
|
|
||||||
Self::TinySkia(_) => {
|
|
||||||
log::warn!("Unsupported mesh primitive: {mesh:?}");
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu(renderer) => {
|
|
||||||
renderer.draw_primitive(iced_wgpu::Primitive::Custom(
|
|
||||||
iced_wgpu::primitive::Custom::Mesh(mesh),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Self::Custom(renderer) => {
|
|
||||||
renderer.draw_mesh(mesh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::Renderer for Renderer {
|
|
||||||
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
|
|
||||||
match self {
|
|
||||||
Self::TinySkia(renderer) => {
|
|
||||||
let primitives = renderer.start_layer();
|
|
||||||
|
|
||||||
f(self);
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Self::TinySkia(renderer) => {
|
|
||||||
renderer.end_layer(primitives, bounds);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu(renderer) => {
|
|
||||||
let primitives = renderer.start_layer();
|
|
||||||
|
|
||||||
f(self);
|
|
||||||
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu(renderer) => {
|
|
||||||
renderer.end_layer(primitives, bounds);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Self::Custom(renderer) => {
|
|
||||||
renderer.start_layer();
|
|
||||||
|
|
||||||
f(self);
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Self::Custom(renderer) => {
|
|
||||||
renderer.end_layer(bounds);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_transformation(
|
|
||||||
&mut self,
|
|
||||||
transformation: Transformation,
|
|
||||||
f: impl FnOnce(&mut Self),
|
|
||||||
) {
|
|
||||||
match self {
|
|
||||||
Self::TinySkia(renderer) => {
|
|
||||||
let primitives = renderer.start_transformation();
|
|
||||||
|
|
||||||
f(self);
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Self::TinySkia(renderer) => {
|
|
||||||
renderer.end_transformation(primitives, transformation);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu(renderer) => {
|
|
||||||
let primitives = renderer.start_transformation();
|
|
||||||
|
|
||||||
f(self);
|
|
||||||
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu(renderer) => {
|
|
||||||
renderer.end_transformation(primitives, transformation);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Self::Custom(renderer) => {
|
|
||||||
renderer.start_transformation();
|
|
||||||
|
|
||||||
f(self);
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Self::Custom(renderer) => {
|
|
||||||
renderer.end_transformation(transformation);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_quad(
|
|
||||||
&mut self,
|
|
||||||
quad: renderer::Quad,
|
|
||||||
background: impl Into<Background>,
|
|
||||||
) {
|
|
||||||
delegate!(self, renderer, renderer.fill_quad(quad, background.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&mut self) {
|
|
||||||
delegate!(self, renderer, renderer.clear());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl text::Renderer for Renderer {
|
|
||||||
type Font = Font;
|
|
||||||
type Paragraph = Paragraph;
|
|
||||||
type Editor = Editor;
|
|
||||||
|
|
||||||
const ICON_FONT: Font = iced_tiny_skia::Renderer::ICON_FONT;
|
|
||||||
const CHECKMARK_ICON: char = iced_tiny_skia::Renderer::CHECKMARK_ICON;
|
|
||||||
const ARROW_DOWN_ICON: char = iced_tiny_skia::Renderer::ARROW_DOWN_ICON;
|
|
||||||
|
|
||||||
fn default_font(&self) -> Self::Font {
|
|
||||||
delegate!(self, renderer, renderer.default_font())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_size(&self) -> Pixels {
|
|
||||||
delegate!(self, renderer, renderer.default_size())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
|
||||||
delegate!(self, renderer, renderer.load_font(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_paragraph(
|
|
||||||
&mut self,
|
|
||||||
paragraph: &Self::Paragraph,
|
|
||||||
position: Point,
|
|
||||||
color: Color,
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
delegate!(
|
|
||||||
self,
|
|
||||||
renderer,
|
|
||||||
renderer.fill_paragraph(paragraph, position, color, clip_bounds)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_editor(
|
|
||||||
&mut self,
|
|
||||||
editor: &Self::Editor,
|
|
||||||
position: Point,
|
|
||||||
color: Color,
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
delegate!(
|
|
||||||
self,
|
|
||||||
renderer,
|
|
||||||
renderer.fill_editor(editor, position, color, clip_bounds)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_text(
|
|
||||||
&mut self,
|
|
||||||
text: Text<'_, Self::Font>,
|
|
||||||
position: Point,
|
|
||||||
color: Color,
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
delegate!(
|
|
||||||
self,
|
|
||||||
renderer,
|
|
||||||
renderer.fill_text(text, position, color, clip_bounds)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
|
||||||
impl crate::core::image::Renderer for Renderer {
|
|
||||||
type Handle = crate::core::image::Handle;
|
|
||||||
|
|
||||||
fn measure_image(
|
|
||||||
&self,
|
|
||||||
handle: &crate::core::image::Handle,
|
|
||||||
) -> core::Size<u32> {
|
|
||||||
delegate!(self, renderer, renderer.measure_image(handle))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_image(
|
|
||||||
&mut self,
|
|
||||||
handle: crate::core::image::Handle,
|
|
||||||
filter_method: crate::core::image::FilterMethod,
|
|
||||||
bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
delegate!(
|
|
||||||
self,
|
|
||||||
renderer,
|
|
||||||
renderer.draw_image(handle, filter_method, bounds)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
|
||||||
impl crate::core::svg::Renderer for Renderer {
|
|
||||||
fn measure_svg(
|
|
||||||
&self,
|
|
||||||
handle: &crate::core::svg::Handle,
|
|
||||||
) -> core::Size<u32> {
|
|
||||||
delegate!(self, renderer, renderer.measure_svg(handle))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_svg(
|
|
||||||
&mut self,
|
|
||||||
handle: crate::core::svg::Handle,
|
|
||||||
color: Option<crate::core::Color>,
|
|
||||||
bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
delegate!(self, renderer, renderer.draw_svg(handle, color, bounds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "geometry")]
|
|
||||||
impl crate::graphics::geometry::Renderer for Renderer {
|
|
||||||
type Geometry = crate::Geometry;
|
|
||||||
|
|
||||||
fn draw(&mut self, layers: Vec<Self::Geometry>) {
|
|
||||||
match self {
|
|
||||||
Self::TinySkia(renderer) => {
|
|
||||||
for layer in layers {
|
|
||||||
match layer {
|
|
||||||
crate::Geometry::TinySkia(primitive) => {
|
|
||||||
renderer.draw_primitive(primitive);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
crate::Geometry::Wgpu(_) => unreachable!(),
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
crate::Geometry::Custom(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
Self::Wgpu(renderer) => {
|
|
||||||
for layer in layers {
|
|
||||||
match layer {
|
|
||||||
crate::Geometry::Wgpu(primitive) => {
|
|
||||||
renderer.draw_primitive(primitive);
|
|
||||||
}
|
|
||||||
crate::Geometry::TinySkia(_) => unreachable!(),
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
crate::Geometry::Custom(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "custom")]
|
|
||||||
Self::Custom(renderer) => {
|
|
||||||
for layer in layers {
|
|
||||||
match layer {
|
|
||||||
crate::Geometry::Custom(geometry) => {
|
|
||||||
renderer.draw_geometry(geometry);
|
|
||||||
}
|
|
||||||
crate::Geometry::TinySkia(_) => unreachable!(),
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
crate::Geometry::Wgpu(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// The default graphics renderer for [`iced`].
|
||||||
|
///
|
||||||
|
/// [`iced`]: https://github.com/iced-rs/iced
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
impl iced_wgpu::primitive::pipeline::Renderer for Renderer {
|
pub type Renderer =
|
||||||
fn draw_pipeline_primitive(
|
fallback::Renderer<iced_wgpu::Renderer, iced_tiny_skia::Renderer>;
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
/// The default graphics compositor for [`iced`].
|
||||||
primitive: impl wgpu::primitive::pipeline::Primitive,
|
///
|
||||||
) {
|
/// [`iced`]: https://github.com/iced-rs/iced
|
||||||
match self {
|
#[cfg(not(feature = "wgpu"))]
|
||||||
Self::TinySkia(_renderer) => {
|
pub type Compositor = iced_tiny_skia::window::Compositor;
|
||||||
log::warn!(
|
|
||||||
"Custom shader primitive is unavailable with tiny-skia."
|
/// The default graphics renderer for [`iced`].
|
||||||
);
|
///
|
||||||
}
|
/// [`iced`]: https://github.com/iced-rs/iced
|
||||||
Self::Wgpu(renderer) => {
|
#[cfg(feature = "wgpu")]
|
||||||
renderer.draw_pipeline_primitive(bounds, primitive);
|
pub type Compositor = fallback::Compositor<
|
||||||
}
|
iced_wgpu::window::Compositor,
|
||||||
#[cfg(feature = "custom")]
|
iced_tiny_skia::window::Compositor,
|
||||||
Self::Custom(_renderer) => {
|
>;
|
||||||
log::warn!(
|
|
||||||
"Custom shader primitive is unavailable with custom renderer."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,24 @@ impl Default for Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Settings> for iced_tiny_skia::Settings {
|
||||||
|
fn from(settings: Settings) -> Self {
|
||||||
|
Self {
|
||||||
|
default_font: settings.default_font,
|
||||||
|
default_text_size: settings.default_text_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
impl From<Settings> for iced_wgpu::Settings {
|
||||||
|
fn from(settings: Settings) -> Self {
|
||||||
|
Self {
|
||||||
|
default_font: settings.default_font,
|
||||||
|
default_text_size: settings.default_text_size,
|
||||||
|
antialiasing: settings.antialiasing,
|
||||||
|
..iced_wgpu::Settings::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1018,3 +1018,12 @@ impl backend::Svg for Backend {
|
||||||
self.vector_pipeline.viewport_dimensions(handle)
|
self.vector_pipeline.viewport_dimensions(handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "geometry")]
|
||||||
|
impl crate::graphics::geometry::Backend for Backend {
|
||||||
|
type Frame = crate::geometry::Frame;
|
||||||
|
|
||||||
|
fn new_frame(&self, size: Size) -> Self::Frame {
|
||||||
|
crate::geometry::Frame::new(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::core::{
|
||||||
};
|
};
|
||||||
use crate::graphics::geometry::fill::{self, Fill};
|
use crate::graphics::geometry::fill::{self, Fill};
|
||||||
use crate::graphics::geometry::stroke::{self, Stroke};
|
use crate::graphics::geometry::stroke::{self, Stroke};
|
||||||
use crate::graphics::geometry::{Path, Style, Text};
|
use crate::graphics::geometry::{self, Path, Style, Text};
|
||||||
use crate::graphics::Gradient;
|
use crate::graphics::Gradient;
|
||||||
use crate::primitive::{self, Primitive};
|
use crate::primitive::{self, Primitive};
|
||||||
|
|
||||||
|
|
@ -25,23 +25,36 @@ impl Frame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn width(&self) -> f32 {
|
pub fn into_primitive(self) -> Primitive {
|
||||||
|
Primitive::Clip {
|
||||||
|
bounds: Rectangle::new(Point::ORIGIN, self.size),
|
||||||
|
content: Box::new(Primitive::Group {
|
||||||
|
primitives: self.primitives,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl geometry::Frame for Frame {
|
||||||
|
type Geometry = Primitive;
|
||||||
|
|
||||||
|
fn width(&self) -> f32 {
|
||||||
self.size.width
|
self.size.width
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn height(&self) -> f32 {
|
fn height(&self) -> f32 {
|
||||||
self.size.height
|
self.size.height
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> Size {
|
fn size(&self) -> Size {
|
||||||
self.size
|
self.size
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn center(&self) -> Point {
|
fn center(&self) -> Point {
|
||||||
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||||
let Some(path) =
|
let Some(path) =
|
||||||
convert_path(path).and_then(|path| path.transform(self.transform))
|
convert_path(path).and_then(|path| path.transform(self.transform))
|
||||||
else {
|
else {
|
||||||
|
|
@ -61,7 +74,7 @@ impl Frame {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill_rectangle(
|
fn fill_rectangle(
|
||||||
&mut self,
|
&mut self,
|
||||||
top_left: Point,
|
top_left: Point,
|
||||||
size: Size,
|
size: Size,
|
||||||
|
|
@ -89,7 +102,7 @@ impl Frame {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||||
let Some(path) =
|
let Some(path) =
|
||||||
convert_path(path).and_then(|path| path.transform(self.transform))
|
convert_path(path).and_then(|path| path.transform(self.transform))
|
||||||
else {
|
else {
|
||||||
|
|
@ -110,7 +123,7 @@ impl Frame {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill_text(&mut self, text: impl Into<Text>) {
|
fn fill_text(&mut self, text: impl Into<Text>) {
|
||||||
let text = text.into();
|
let text = text.into();
|
||||||
|
|
||||||
let (scale_x, scale_y) = self.transform.get_scale();
|
let (scale_x, scale_y) = self.transform.get_scale();
|
||||||
|
|
@ -174,51 +187,52 @@ impl Frame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_transform(&mut self) {
|
fn push_transform(&mut self) {
|
||||||
self.stack.push(self.transform);
|
self.stack.push(self.transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_transform(&mut self) {
|
fn pop_transform(&mut self) {
|
||||||
self.transform = self.stack.pop().expect("Pop transform");
|
self.transform = self.stack.pop().expect("Pop transform");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip(&mut self, frame: Self, at: Point) {
|
fn draft(&mut self, size: Size) -> Self {
|
||||||
|
Self::new(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paste(&mut self, frame: Self, at: Point) {
|
||||||
self.primitives.push(Primitive::Transform {
|
self.primitives.push(Primitive::Transform {
|
||||||
transformation: Transformation::translate(at.x, at.y),
|
transformation: Transformation::translate(at.x, at.y),
|
||||||
content: Box::new(frame.into_primitive()),
|
content: Box::new(frame.into_primitive()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn translate(&mut self, translation: Vector) {
|
fn translate(&mut self, translation: Vector) {
|
||||||
self.transform =
|
self.transform =
|
||||||
self.transform.pre_translate(translation.x, translation.y);
|
self.transform.pre_translate(translation.x, translation.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rotate(&mut self, angle: impl Into<Radians>) {
|
fn rotate(&mut self, angle: impl Into<Radians>) {
|
||||||
self.transform = self.transform.pre_concat(
|
self.transform = self.transform.pre_concat(
|
||||||
tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()),
|
tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scale(&mut self, scale: impl Into<f32>) {
|
fn scale(&mut self, scale: impl Into<f32>) {
|
||||||
let scale = scale.into();
|
let scale = scale.into();
|
||||||
|
|
||||||
self.scale_nonuniform(Vector { x: scale, y: scale });
|
self.scale_nonuniform(Vector { x: scale, y: scale });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
||||||
let scale = scale.into();
|
let scale = scale.into();
|
||||||
|
|
||||||
self.transform = self.transform.pre_scale(scale.x, scale.y);
|
self.transform = self.transform.pre_scale(scale.x, scale.y);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_primitive(self) -> Primitive {
|
impl From<Frame> for Primitive {
|
||||||
Primitive::Clip {
|
fn from(frame: Frame) -> Self {
|
||||||
bounds: Rectangle::new(Point::ORIGIN, self.size),
|
frame.into_primitive()
|
||||||
content: Box::new(Primitive::Group {
|
|
||||||
primitives: self.primitives,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::core::Rectangle;
|
use crate::core::Rectangle;
|
||||||
use crate::graphics::Damage;
|
use crate::graphics::{Damage, Mesh};
|
||||||
|
|
||||||
pub type Primitive = crate::graphics::Primitive<Custom>;
|
pub type Primitive = crate::graphics::Primitive<Custom>;
|
||||||
|
|
||||||
|
|
@ -42,3 +42,11 @@ impl Damage for Custom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Mesh> for Custom {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(_mesh: Mesh) -> Result<Self, Self::Error> {
|
||||||
|
Err("unsupported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -397,3 +397,12 @@ impl backend::Svg for Backend {
|
||||||
self.image_pipeline.viewport_dimensions(handle)
|
self.image_pipeline.viewport_dimensions(handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "geometry")]
|
||||||
|
impl crate::graphics::geometry::Backend for Backend {
|
||||||
|
type Frame = crate::geometry::Frame;
|
||||||
|
|
||||||
|
fn new_frame(&self, size: Size) -> Self::Frame {
|
||||||
|
crate::geometry::Frame::new(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::core::{
|
||||||
use crate::graphics::color;
|
use crate::graphics::color;
|
||||||
use crate::graphics::geometry::fill::{self, Fill};
|
use crate::graphics::geometry::fill::{self, Fill};
|
||||||
use crate::graphics::geometry::{
|
use crate::graphics::geometry::{
|
||||||
LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
|
self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
|
||||||
};
|
};
|
||||||
use crate::graphics::gradient::{self, Gradient};
|
use crate::graphics::gradient::{self, Gradient};
|
||||||
use crate::graphics::mesh::{self, Mesh};
|
use crate::graphics::mesh::{self, Mesh};
|
||||||
|
|
@ -14,6 +14,7 @@ use crate::primitive::{self, Primitive};
|
||||||
|
|
||||||
use lyon::geom::euclid;
|
use lyon::geom::euclid;
|
||||||
use lyon::tessellation;
|
use lyon::tessellation;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A frame for drawing some geometry.
|
/// A frame for drawing some geometry.
|
||||||
|
|
@ -27,6 +28,326 @@ pub struct Frame {
|
||||||
stroke_tessellator: tessellation::StrokeTessellator,
|
stroke_tessellator: tessellation::StrokeTessellator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
/// Creates a new [`Frame`] with the given [`Size`].
|
||||||
|
pub fn new(size: Size) -> Frame {
|
||||||
|
Frame {
|
||||||
|
size,
|
||||||
|
buffers: BufferStack::new(),
|
||||||
|
primitives: Vec::new(),
|
||||||
|
transforms: Transforms {
|
||||||
|
previous: Vec::new(),
|
||||||
|
current: Transform(lyon::math::Transform::identity()),
|
||||||
|
},
|
||||||
|
fill_tessellator: tessellation::FillTessellator::new(),
|
||||||
|
stroke_tessellator: tessellation::StrokeTessellator::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_primitives(mut self) -> Vec<Primitive> {
|
||||||
|
for buffer in self.buffers.stack {
|
||||||
|
match buffer {
|
||||||
|
Buffer::Solid(buffer) => {
|
||||||
|
if !buffer.indices.is_empty() {
|
||||||
|
self.primitives.push(Primitive::Custom(
|
||||||
|
primitive::Custom::Mesh(Mesh::Solid {
|
||||||
|
buffers: mesh::Indexed {
|
||||||
|
vertices: buffer.vertices,
|
||||||
|
indices: buffer.indices,
|
||||||
|
},
|
||||||
|
size: self.size,
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Buffer::Gradient(buffer) => {
|
||||||
|
if !buffer.indices.is_empty() {
|
||||||
|
self.primitives.push(Primitive::Custom(
|
||||||
|
primitive::Custom::Mesh(Mesh::Gradient {
|
||||||
|
buffers: mesh::Indexed {
|
||||||
|
vertices: buffer.vertices,
|
||||||
|
indices: buffer.indices,
|
||||||
|
},
|
||||||
|
size: self.size,
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.primitives
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl geometry::Frame for Frame {
|
||||||
|
type Geometry = Primitive;
|
||||||
|
|
||||||
|
/// Creates a new empty [`Frame`] with the given dimensions.
|
||||||
|
///
|
||||||
|
/// The default coordinate system of a [`Frame`] has its origin at the
|
||||||
|
/// top-left corner of its bounds.
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn width(&self) -> f32 {
|
||||||
|
self.size.width
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn height(&self) -> f32 {
|
||||||
|
self.size.height
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn size(&self) -> Size {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn center(&self) -> Point {
|
||||||
|
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||||
|
let Fill { style, rule } = fill.into();
|
||||||
|
|
||||||
|
let mut buffer = self
|
||||||
|
.buffers
|
||||||
|
.get_fill(&self.transforms.current.transform_style(style));
|
||||||
|
|
||||||
|
let options = tessellation::FillOptions::default()
|
||||||
|
.with_fill_rule(into_fill_rule(rule));
|
||||||
|
|
||||||
|
if self.transforms.current.is_identity() {
|
||||||
|
self.fill_tessellator.tessellate_path(
|
||||||
|
path.raw(),
|
||||||
|
&options,
|
||||||
|
buffer.as_mut(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let path = path.transform(&self.transforms.current.0);
|
||||||
|
|
||||||
|
self.fill_tessellator.tessellate_path(
|
||||||
|
path.raw(),
|
||||||
|
&options,
|
||||||
|
buffer.as_mut(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.expect("Tessellate path.");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_rectangle(
|
||||||
|
&mut self,
|
||||||
|
top_left: Point,
|
||||||
|
size: Size,
|
||||||
|
fill: impl Into<Fill>,
|
||||||
|
) {
|
||||||
|
let Fill { style, rule } = fill.into();
|
||||||
|
|
||||||
|
let mut buffer = self
|
||||||
|
.buffers
|
||||||
|
.get_fill(&self.transforms.current.transform_style(style));
|
||||||
|
|
||||||
|
let top_left = self
|
||||||
|
.transforms
|
||||||
|
.current
|
||||||
|
.0
|
||||||
|
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
|
||||||
|
|
||||||
|
let size =
|
||||||
|
self.transforms.current.0.transform_vector(
|
||||||
|
lyon::math::Vector::new(size.width, size.height),
|
||||||
|
);
|
||||||
|
|
||||||
|
let options = tessellation::FillOptions::default()
|
||||||
|
.with_fill_rule(into_fill_rule(rule));
|
||||||
|
|
||||||
|
self.fill_tessellator
|
||||||
|
.tessellate_rectangle(
|
||||||
|
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||||
|
&options,
|
||||||
|
buffer.as_mut(),
|
||||||
|
)
|
||||||
|
.expect("Fill rectangle");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||||
|
let stroke = stroke.into();
|
||||||
|
|
||||||
|
let mut buffer = self
|
||||||
|
.buffers
|
||||||
|
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||||
|
|
||||||
|
let mut options = tessellation::StrokeOptions::default();
|
||||||
|
options.line_width = stroke.width;
|
||||||
|
options.start_cap = into_line_cap(stroke.line_cap);
|
||||||
|
options.end_cap = into_line_cap(stroke.line_cap);
|
||||||
|
options.line_join = into_line_join(stroke.line_join);
|
||||||
|
|
||||||
|
let path = if stroke.line_dash.segments.is_empty() {
|
||||||
|
Cow::Borrowed(path)
|
||||||
|
} else {
|
||||||
|
Cow::Owned(dashed(path, stroke.line_dash))
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.transforms.current.is_identity() {
|
||||||
|
self.stroke_tessellator.tessellate_path(
|
||||||
|
path.raw(),
|
||||||
|
&options,
|
||||||
|
buffer.as_mut(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let path = path.transform(&self.transforms.current.0);
|
||||||
|
|
||||||
|
self.stroke_tessellator.tessellate_path(
|
||||||
|
path.raw(),
|
||||||
|
&options,
|
||||||
|
buffer.as_mut(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.expect("Stroke path");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_text(&mut self, text: impl Into<Text>) {
|
||||||
|
let text = text.into();
|
||||||
|
|
||||||
|
let (scale_x, scale_y) = self.transforms.current.scale();
|
||||||
|
|
||||||
|
if self.transforms.current.is_scale_translation()
|
||||||
|
&& scale_x == scale_y
|
||||||
|
&& scale_x > 0.0
|
||||||
|
&& scale_y > 0.0
|
||||||
|
{
|
||||||
|
let (position, size, line_height) =
|
||||||
|
if self.transforms.current.is_identity() {
|
||||||
|
(text.position, text.size, text.line_height)
|
||||||
|
} else {
|
||||||
|
let position =
|
||||||
|
self.transforms.current.transform_point(text.position);
|
||||||
|
|
||||||
|
let size = Pixels(text.size.0 * scale_y);
|
||||||
|
|
||||||
|
let line_height = match text.line_height {
|
||||||
|
LineHeight::Absolute(size) => {
|
||||||
|
LineHeight::Absolute(Pixels(size.0 * scale_y))
|
||||||
|
}
|
||||||
|
LineHeight::Relative(factor) => {
|
||||||
|
LineHeight::Relative(factor)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(position, size, line_height)
|
||||||
|
};
|
||||||
|
|
||||||
|
let bounds = Rectangle {
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
width: f32::INFINITY,
|
||||||
|
height: f32::INFINITY,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Honor layering!
|
||||||
|
self.primitives.push(Primitive::Text {
|
||||||
|
content: text.content,
|
||||||
|
bounds,
|
||||||
|
color: text.color,
|
||||||
|
size,
|
||||||
|
line_height,
|
||||||
|
font: text.font,
|
||||||
|
horizontal_alignment: text.horizontal_alignment,
|
||||||
|
vertical_alignment: text.vertical_alignment,
|
||||||
|
shaping: text.shaping,
|
||||||
|
clip_bounds: Rectangle::with_size(Size::INFINITY),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
text.draw_with(|path, color| self.fill(&path, color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn translate(&mut self, translation: Vector) {
|
||||||
|
self.transforms.current.0 =
|
||||||
|
self.transforms
|
||||||
|
.current
|
||||||
|
.0
|
||||||
|
.pre_translate(lyon::math::Vector::new(
|
||||||
|
translation.x,
|
||||||
|
translation.y,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn rotate(&mut self, angle: impl Into<Radians>) {
|
||||||
|
self.transforms.current.0 = self
|
||||||
|
.transforms
|
||||||
|
.current
|
||||||
|
.0
|
||||||
|
.pre_rotate(lyon::math::Angle::radians(angle.into().0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn scale(&mut self, scale: impl Into<f32>) {
|
||||||
|
let scale = scale.into();
|
||||||
|
|
||||||
|
self.scale_nonuniform(Vector { x: scale, y: scale });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
||||||
|
let scale = scale.into();
|
||||||
|
|
||||||
|
self.transforms.current.0 =
|
||||||
|
self.transforms.current.0.pre_scale(scale.x, scale.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_transform(&mut self) {
|
||||||
|
self.transforms.previous.push(self.transforms.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_transform(&mut self) {
|
||||||
|
self.transforms.current = self.transforms.previous.pop().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draft(&mut self, size: Size) -> Frame {
|
||||||
|
Frame::new(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paste(&mut self, frame: Frame, at: Point) {
|
||||||
|
let size = frame.size();
|
||||||
|
let primitives = frame.into_primitives();
|
||||||
|
let transformation = Transformation::translate(at.x, at.y);
|
||||||
|
|
||||||
|
let (text, meshes) = primitives
|
||||||
|
.into_iter()
|
||||||
|
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
|
||||||
|
|
||||||
|
self.primitives.push(Primitive::Group {
|
||||||
|
primitives: vec![
|
||||||
|
Primitive::Transform {
|
||||||
|
transformation,
|
||||||
|
content: Box::new(Primitive::Group { primitives: meshes }),
|
||||||
|
},
|
||||||
|
Primitive::Transform {
|
||||||
|
transformation,
|
||||||
|
content: Box::new(Primitive::Clip {
|
||||||
|
bounds: Rectangle::with_size(size),
|
||||||
|
content: Box::new(Primitive::Group {
|
||||||
|
primitives: text,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Frame> for Primitive {
|
||||||
|
fn from(frame: Frame) -> Self {
|
||||||
|
Self::Group {
|
||||||
|
primitives: frame.into_primitives(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum Buffer {
|
enum Buffer {
|
||||||
Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
|
Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
|
||||||
Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
|
Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
|
||||||
|
|
@ -165,386 +486,6 @@ impl Transform {
|
||||||
gradient
|
gradient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
|
||||||
/// Creates a new empty [`Frame`] with the given dimensions.
|
|
||||||
///
|
|
||||||
/// The default coordinate system of a [`Frame`] has its origin at the
|
|
||||||
/// top-left corner of its bounds.
|
|
||||||
pub fn new(size: Size) -> Frame {
|
|
||||||
Frame {
|
|
||||||
size,
|
|
||||||
buffers: BufferStack::new(),
|
|
||||||
primitives: Vec::new(),
|
|
||||||
transforms: Transforms {
|
|
||||||
previous: Vec::new(),
|
|
||||||
current: Transform(lyon::math::Transform::identity()),
|
|
||||||
},
|
|
||||||
fill_tessellator: tessellation::FillTessellator::new(),
|
|
||||||
stroke_tessellator: tessellation::StrokeTessellator::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the width of the [`Frame`].
|
|
||||||
#[inline]
|
|
||||||
pub fn width(&self) -> f32 {
|
|
||||||
self.size.width
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the height of the [`Frame`].
|
|
||||||
#[inline]
|
|
||||||
pub fn height(&self) -> f32 {
|
|
||||||
self.size.height
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the dimensions of the [`Frame`].
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> Size {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the coordinate of the center of the [`Frame`].
|
|
||||||
#[inline]
|
|
||||||
pub fn center(&self) -> Point {
|
|
||||||
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
|
|
||||||
/// provided style.
|
|
||||||
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
|
||||||
let Fill { style, rule } = fill.into();
|
|
||||||
|
|
||||||
let mut buffer = self
|
|
||||||
.buffers
|
|
||||||
.get_fill(&self.transforms.current.transform_style(style));
|
|
||||||
|
|
||||||
let options = tessellation::FillOptions::default()
|
|
||||||
.with_fill_rule(into_fill_rule(rule));
|
|
||||||
|
|
||||||
if self.transforms.current.is_identity() {
|
|
||||||
self.fill_tessellator.tessellate_path(
|
|
||||||
path.raw(),
|
|
||||||
&options,
|
|
||||||
buffer.as_mut(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let path = path.transform(&self.transforms.current.0);
|
|
||||||
|
|
||||||
self.fill_tessellator.tessellate_path(
|
|
||||||
path.raw(),
|
|
||||||
&options,
|
|
||||||
buffer.as_mut(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.expect("Tessellate path.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
|
|
||||||
/// its `Size` on the [`Frame`] by filling it with the provided style.
|
|
||||||
pub fn fill_rectangle(
|
|
||||||
&mut self,
|
|
||||||
top_left: Point,
|
|
||||||
size: Size,
|
|
||||||
fill: impl Into<Fill>,
|
|
||||||
) {
|
|
||||||
let Fill { style, rule } = fill.into();
|
|
||||||
|
|
||||||
let mut buffer = self
|
|
||||||
.buffers
|
|
||||||
.get_fill(&self.transforms.current.transform_style(style));
|
|
||||||
|
|
||||||
let top_left = self
|
|
||||||
.transforms
|
|
||||||
.current
|
|
||||||
.0
|
|
||||||
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
|
|
||||||
|
|
||||||
let size =
|
|
||||||
self.transforms.current.0.transform_vector(
|
|
||||||
lyon::math::Vector::new(size.width, size.height),
|
|
||||||
);
|
|
||||||
|
|
||||||
let options = tessellation::FillOptions::default()
|
|
||||||
.with_fill_rule(into_fill_rule(rule));
|
|
||||||
|
|
||||||
self.fill_tessellator
|
|
||||||
.tessellate_rectangle(
|
|
||||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
|
||||||
&options,
|
|
||||||
buffer.as_mut(),
|
|
||||||
)
|
|
||||||
.expect("Fill rectangle");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws the stroke of the given [`Path`] on the [`Frame`] with the
|
|
||||||
/// provided style.
|
|
||||||
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
|
||||||
let stroke = stroke.into();
|
|
||||||
|
|
||||||
let mut buffer = self
|
|
||||||
.buffers
|
|
||||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
|
||||||
|
|
||||||
let mut options = tessellation::StrokeOptions::default();
|
|
||||||
options.line_width = stroke.width;
|
|
||||||
options.start_cap = into_line_cap(stroke.line_cap);
|
|
||||||
options.end_cap = into_line_cap(stroke.line_cap);
|
|
||||||
options.line_join = into_line_join(stroke.line_join);
|
|
||||||
|
|
||||||
let path = if stroke.line_dash.segments.is_empty() {
|
|
||||||
Cow::Borrowed(path)
|
|
||||||
} else {
|
|
||||||
Cow::Owned(dashed(path, stroke.line_dash))
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.transforms.current.is_identity() {
|
|
||||||
self.stroke_tessellator.tessellate_path(
|
|
||||||
path.raw(),
|
|
||||||
&options,
|
|
||||||
buffer.as_mut(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let path = path.transform(&self.transforms.current.0);
|
|
||||||
|
|
||||||
self.stroke_tessellator.tessellate_path(
|
|
||||||
path.raw(),
|
|
||||||
&options,
|
|
||||||
buffer.as_mut(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.expect("Stroke path");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
|
|
||||||
/// them with the given color.
|
|
||||||
///
|
|
||||||
/// __Warning:__ Text currently does not work well with rotations and scale
|
|
||||||
/// transforms! The position will be correctly transformed, but the
|
|
||||||
/// resulting glyphs will not be rotated or scaled properly.
|
|
||||||
///
|
|
||||||
/// Additionally, all text will be rendered on top of all the layers of
|
|
||||||
/// a `Canvas`. Therefore, it is currently only meant to be used for
|
|
||||||
/// overlays, which is the most common use case.
|
|
||||||
///
|
|
||||||
/// Support for vectorial text is planned, and should address all these
|
|
||||||
/// limitations.
|
|
||||||
pub fn fill_text(&mut self, text: impl Into<Text>) {
|
|
||||||
let text = text.into();
|
|
||||||
|
|
||||||
let (scale_x, scale_y) = self.transforms.current.scale();
|
|
||||||
|
|
||||||
if self.transforms.current.is_scale_translation()
|
|
||||||
&& scale_x == scale_y
|
|
||||||
&& scale_x > 0.0
|
|
||||||
&& scale_y > 0.0
|
|
||||||
{
|
|
||||||
let (position, size, line_height) =
|
|
||||||
if self.transforms.current.is_identity() {
|
|
||||||
(text.position, text.size, text.line_height)
|
|
||||||
} else {
|
|
||||||
let position =
|
|
||||||
self.transforms.current.transform_point(text.position);
|
|
||||||
|
|
||||||
let size = Pixels(text.size.0 * scale_y);
|
|
||||||
|
|
||||||
let line_height = match text.line_height {
|
|
||||||
LineHeight::Absolute(size) => {
|
|
||||||
LineHeight::Absolute(Pixels(size.0 * scale_y))
|
|
||||||
}
|
|
||||||
LineHeight::Relative(factor) => {
|
|
||||||
LineHeight::Relative(factor)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(position, size, line_height)
|
|
||||||
};
|
|
||||||
|
|
||||||
let bounds = Rectangle {
|
|
||||||
x: position.x,
|
|
||||||
y: position.y,
|
|
||||||
width: f32::INFINITY,
|
|
||||||
height: f32::INFINITY,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Honor layering!
|
|
||||||
self.primitives.push(Primitive::Text {
|
|
||||||
content: text.content,
|
|
||||||
bounds,
|
|
||||||
color: text.color,
|
|
||||||
size,
|
|
||||||
line_height,
|
|
||||||
font: text.font,
|
|
||||||
horizontal_alignment: text.horizontal_alignment,
|
|
||||||
vertical_alignment: text.vertical_alignment,
|
|
||||||
shaping: text.shaping,
|
|
||||||
clip_bounds: Rectangle::with_size(Size::INFINITY),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
text.draw_with(|path, color| self.fill(&path, color));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores the current transform of the [`Frame`] and executes the given
|
|
||||||
/// drawing operations, restoring the transform afterwards.
|
|
||||||
///
|
|
||||||
/// This method is useful to compose transforms and perform drawing
|
|
||||||
/// operations in different coordinate systems.
|
|
||||||
#[inline]
|
|
||||||
pub fn with_save<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R {
|
|
||||||
self.push_transform();
|
|
||||||
|
|
||||||
let result = f(self);
|
|
||||||
|
|
||||||
self.pop_transform();
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes the current transform in the transform stack.
|
|
||||||
pub fn push_transform(&mut self) {
|
|
||||||
self.transforms.previous.push(self.transforms.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pops a transform from the transform stack and sets it as the current transform.
|
|
||||||
pub fn pop_transform(&mut self) {
|
|
||||||
self.transforms.current = self.transforms.previous.pop().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes the given drawing operations within a [`Rectangle`] region,
|
|
||||||
/// clipping any geometry that overflows its bounds. Any transformations
|
|
||||||
/// performed are local to the provided closure.
|
|
||||||
///
|
|
||||||
/// This method is useful to perform drawing operations that need to be
|
|
||||||
/// clipped.
|
|
||||||
#[inline]
|
|
||||||
pub fn with_clip<R>(
|
|
||||||
&mut self,
|
|
||||||
region: Rectangle,
|
|
||||||
f: impl FnOnce(&mut Frame) -> R,
|
|
||||||
) -> R {
|
|
||||||
let mut frame = Frame::new(region.size());
|
|
||||||
|
|
||||||
let result = f(&mut frame);
|
|
||||||
|
|
||||||
let origin = Point::new(region.x, region.y);
|
|
||||||
|
|
||||||
self.clip(frame, origin);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`].
|
|
||||||
pub fn clip(&mut self, frame: Frame, at: Point) {
|
|
||||||
let size = frame.size();
|
|
||||||
let primitives = frame.into_primitives();
|
|
||||||
let transformation = Transformation::translate(at.x, at.y);
|
|
||||||
|
|
||||||
let (text, meshes) = primitives
|
|
||||||
.into_iter()
|
|
||||||
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
|
|
||||||
|
|
||||||
self.primitives.push(Primitive::Group {
|
|
||||||
primitives: vec![
|
|
||||||
Primitive::Transform {
|
|
||||||
transformation,
|
|
||||||
content: Box::new(Primitive::Group { primitives: meshes }),
|
|
||||||
},
|
|
||||||
Primitive::Transform {
|
|
||||||
transformation,
|
|
||||||
content: Box::new(Primitive::Clip {
|
|
||||||
bounds: Rectangle::with_size(size),
|
|
||||||
content: Box::new(Primitive::Group {
|
|
||||||
primitives: text,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies a translation to the current transform of the [`Frame`].
|
|
||||||
#[inline]
|
|
||||||
pub fn translate(&mut self, translation: Vector) {
|
|
||||||
self.transforms.current.0 =
|
|
||||||
self.transforms
|
|
||||||
.current
|
|
||||||
.0
|
|
||||||
.pre_translate(lyon::math::Vector::new(
|
|
||||||
translation.x,
|
|
||||||
translation.y,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies a rotation in radians to the current transform of the [`Frame`].
|
|
||||||
#[inline]
|
|
||||||
pub fn rotate(&mut self, angle: impl Into<Radians>) {
|
|
||||||
self.transforms.current.0 = self
|
|
||||||
.transforms
|
|
||||||
.current
|
|
||||||
.0
|
|
||||||
.pre_rotate(lyon::math::Angle::radians(angle.into().0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies a uniform scaling to the current transform of the [`Frame`].
|
|
||||||
#[inline]
|
|
||||||
pub fn scale(&mut self, scale: impl Into<f32>) {
|
|
||||||
let scale = scale.into();
|
|
||||||
|
|
||||||
self.scale_nonuniform(Vector { x: scale, y: scale });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies a non-uniform scaling to the current transform of the [`Frame`].
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
|
||||||
let scale = scale.into();
|
|
||||||
|
|
||||||
self.transforms.current.0 =
|
|
||||||
self.transforms.current.0.pre_scale(scale.x, scale.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Produces the [`Primitive`] representing everything drawn on the [`Frame`].
|
|
||||||
pub fn into_primitive(self) -> Primitive {
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: self.into_primitives(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_primitives(mut self) -> Vec<Primitive> {
|
|
||||||
for buffer in self.buffers.stack {
|
|
||||||
match buffer {
|
|
||||||
Buffer::Solid(buffer) => {
|
|
||||||
if !buffer.indices.is_empty() {
|
|
||||||
self.primitives.push(Primitive::Custom(
|
|
||||||
primitive::Custom::Mesh(Mesh::Solid {
|
|
||||||
buffers: mesh::Indexed {
|
|
||||||
vertices: buffer.vertices,
|
|
||||||
indices: buffer.indices,
|
|
||||||
},
|
|
||||||
size: self.size,
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Buffer::Gradient(buffer) => {
|
|
||||||
if !buffer.indices.is_empty() {
|
|
||||||
self.primitives.push(Primitive::Custom(
|
|
||||||
primitive::Custom::Mesh(Mesh::Gradient {
|
|
||||||
buffers: mesh::Indexed {
|
|
||||||
vertices: buffer.vertices,
|
|
||||||
indices: buffer.indices,
|
|
||||||
},
|
|
||||||
size: self.size,
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.primitives
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GradientVertex2DBuilder {
|
struct GradientVertex2DBuilder {
|
||||||
gradient: gradient::Packed,
|
gradient: gradient::Packed,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,11 @@ impl Damage for Custom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Mesh> for Custom {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(mesh: Mesh) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Custom::Mesh(mesh))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ pub use event::Event;
|
||||||
pub use program::Program;
|
pub use program::Program;
|
||||||
|
|
||||||
pub use crate::graphics::geometry::*;
|
pub use crate::graphics::geometry::*;
|
||||||
pub use crate::renderer::geometry::*;
|
|
||||||
|
|
||||||
use crate::core;
|
use crate::core;
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
|
|
@ -21,13 +20,19 @@ use crate::graphics::geometry;
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
|
||||||
|
///
|
||||||
|
/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
|
||||||
|
/// change or it is explicitly cleared.
|
||||||
|
pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
|
||||||
|
|
||||||
/// A widget capable of drawing 2D graphics.
|
/// A widget capable of drawing 2D graphics.
|
||||||
///
|
///
|
||||||
/// ## Drawing a simple circle
|
/// ## Drawing a simple circle
|
||||||
/// If you want to get a quick overview, here's how we can draw a simple circle:
|
/// If you want to get a quick overview, here's how we can draw a simple circle:
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # use iced_widget::canvas::{self, Canvas, Fill, Frame, Geometry, Path, Program};
|
/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Path, Program};
|
||||||
/// # use iced_widget::core::{Color, Rectangle};
|
/// # use iced_widget::core::{Color, Rectangle};
|
||||||
/// # use iced_widget::core::mouse;
|
/// # use iced_widget::core::mouse;
|
||||||
/// # use iced_widget::{Renderer, Theme};
|
/// # use iced_widget::{Renderer, Theme};
|
||||||
|
|
@ -42,9 +47,9 @@ use std::marker::PhantomData;
|
||||||
/// impl Program<()> for Circle {
|
/// impl Program<()> for Circle {
|
||||||
/// type State = ();
|
/// type State = ();
|
||||||
///
|
///
|
||||||
/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry>{
|
/// fn draw(&self, _state: &(), renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) {
|
||||||
/// // We prepare a new `Frame`
|
/// // We prepare a new `Frame`
|
||||||
/// let mut frame = Frame::new(renderer, bounds.size());
|
/// let mut frame = frame(renderer, bounds.size());
|
||||||
///
|
///
|
||||||
/// // We create a `Path` representing a simple circle
|
/// // We create a `Path` representing a simple circle
|
||||||
/// let circle = Path::circle(frame.center(), self.radius);
|
/// let circle = Path::circle(frame.center(), self.radius);
|
||||||
|
|
@ -53,7 +58,7 @@ use std::marker::PhantomData;
|
||||||
/// frame.fill(&circle, Color::BLACK);
|
/// frame.fill(&circle, Color::BLACK);
|
||||||
///
|
///
|
||||||
/// // Finally, we produce the geometry
|
/// // Finally, we produce the geometry
|
||||||
/// vec![frame.into_geometry()]
|
/// renderer.draw_geometry([frame]);
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
|
@ -210,9 +215,7 @@ where
|
||||||
renderer.with_transformation(
|
renderer.with_transformation(
|
||||||
Transformation::translate(bounds.x, bounds.y),
|
Transformation::translate(bounds.x, bounds.y),
|
||||||
|renderer| {
|
|renderer| {
|
||||||
renderer.draw(
|
self.program.draw(state, renderer, theme, bounds, cursor);
|
||||||
self.program.draw(state, renderer, theme, bounds, cursor),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,22 +37,15 @@ where
|
||||||
(event::Status::Ignored, None)
|
(event::Status::Ignored, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws the state of the [`Program`], producing a bunch of [`Geometry`].
|
/// Draws the state of the [`Program`] with the given [`Renderer`].
|
||||||
///
|
|
||||||
/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
|
|
||||||
/// [`Cache`].
|
|
||||||
///
|
|
||||||
/// [`Geometry`]: crate::canvas::Geometry
|
|
||||||
/// [`Frame`]: crate::canvas::Frame
|
|
||||||
/// [`Cache`]: crate::canvas::Cache
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> Vec<Renderer::Geometry>;
|
);
|
||||||
|
|
||||||
/// Returns the current mouse interaction of the [`Program`].
|
/// Returns the current mouse interaction of the [`Program`].
|
||||||
///
|
///
|
||||||
|
|
@ -90,12 +83,12 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
renderer: &Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> Vec<Renderer::Geometry> {
|
) {
|
||||||
T::draw(self, state, renderer, theme, bounds, cursor)
|
T::draw(self, state, renderer, theme, bounds, cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ use crate::core::{
|
||||||
Color, Element, Layout, Length, Point, Rectangle, Size, Theme, Vector,
|
Color, Element, Layout, Length, Point, Rectangle, Size, Theme, Vector,
|
||||||
Widget,
|
Widget,
|
||||||
};
|
};
|
||||||
use crate::graphics::geometry::Renderer as _;
|
|
||||||
use crate::Renderer;
|
use crate::Renderer;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
@ -92,6 +91,8 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
|
use canvas::Frame;
|
||||||
|
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
@ -142,7 +143,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
|
||||||
renderer.with_translation(
|
renderer.with_translation(
|
||||||
bounds.position() - Point::ORIGIN,
|
bounds.position() - Point::ORIGIN,
|
||||||
|renderer| {
|
|renderer| {
|
||||||
renderer.draw(vec![geometry]);
|
renderer.draw_geometry(vec![geometry]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -161,11 +162,11 @@ where
|
||||||
/// The data of a [`QRCode`].
|
/// The data of a [`QRCode`].
|
||||||
///
|
///
|
||||||
/// It stores the contents that will be displayed.
|
/// It stores the contents that will be displayed.
|
||||||
#[derive(Debug)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
contents: Vec<qrcode::Color>,
|
contents: Vec<qrcode::Color>,
|
||||||
width: usize,
|
width: usize,
|
||||||
cache: canvas::Cache,
|
cache: canvas::Cache<Renderer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ pub fn default(theme: &Theme) -> Appearance {
|
||||||
/// settings.
|
/// settings.
|
||||||
pub async fn run<A, E, C>(
|
pub async fn run<A, E, C>(
|
||||||
settings: Settings<A::Flags>,
|
settings: Settings<A::Flags>,
|
||||||
compositor_settings: C::Settings,
|
compositor_settings: impl Into<C::Settings>,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
A: Application + 'static,
|
A: Application + 'static,
|
||||||
|
|
@ -219,7 +219,7 @@ where
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let compositor = C::new(compositor_settings, window.clone()).await?;
|
let compositor = C::new(compositor_settings.into(), window.clone()).await?;
|
||||||
let mut renderer = compositor.create_renderer();
|
let mut renderer = compositor.create_renderer();
|
||||||
|
|
||||||
for font in settings.fonts {
|
for font in settings.fonts {
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ where
|
||||||
/// settings.
|
/// settings.
|
||||||
pub fn run<A, E, C>(
|
pub fn run<A, E, C>(
|
||||||
settings: Settings<A::Flags>,
|
settings: Settings<A::Flags>,
|
||||||
compositor_settings: C::Settings,
|
compositor_settings: impl Into<C::Settings>,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
A: Application + 'static,
|
A: Application + 'static,
|
||||||
|
|
@ -186,8 +186,10 @@ where
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut compositor =
|
let mut compositor = executor::block_on(C::new(
|
||||||
executor::block_on(C::new(compositor_settings, main_window.clone()))?;
|
compositor_settings.into(),
|
||||||
|
main_window.clone(),
|
||||||
|
))?;
|
||||||
|
|
||||||
let mut window_manager = WindowManager::new();
|
let mut window_manager = WindowManager::new();
|
||||||
let _ = window_manager.insert(
|
let _ = window_manager.insert(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue