diff options
-rw-r--r-- | examples/arc/src/main.rs | 2 | ||||
-rw-r--r-- | examples/bezier_tool/src/main.rs | 10 | ||||
-rw-r--r-- | examples/clock/src/main.rs | 4 | ||||
-rw-r--r-- | examples/color_palette/src/main.rs | 2 | ||||
-rw-r--r-- | examples/game_of_life/src/main.rs | 8 | ||||
-rw-r--r-- | examples/layout/src/main.rs | 6 | ||||
-rw-r--r-- | examples/loading_spinners/src/circular.rs | 2 | ||||
-rw-r--r-- | examples/multitouch/src/main.rs | 2 | ||||
-rw-r--r-- | examples/sierpinski_triangle/src/main.rs | 2 | ||||
-rw-r--r-- | examples/solar_system/src/main.rs | 1 | ||||
-rw-r--r-- | examples/vectorial_text/src/main.rs | 2 | ||||
-rw-r--r-- | graphics/src/geometry.rs | 254 | ||||
-rw-r--r-- | graphics/src/geometry/cache.rs | 123 | ||||
-rw-r--r-- | graphics/src/geometry/frame.rs | 208 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 3 | ||||
-rw-r--r-- | renderer/src/fallback.rs | 20 | ||||
-rw-r--r-- | renderer/src/geometry.rs | 236 | ||||
-rw-r--r-- | renderer/src/geometry/cache.rs | 137 | ||||
-rw-r--r-- | tiny_skia/src/geometry.rs | 8 | ||||
-rw-r--r-- | wgpu/src/geometry.rs | 11 | ||||
-rw-r--r-- | widget/src/canvas.rs | 14 | ||||
-rw-r--r-- | widget/src/qr_code.rs | 2 |
22 files changed, 378 insertions, 679 deletions
diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index 0aba82a9..4576404f 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -2,7 +2,7 @@ use std::{f32::consts::PI, time::Instant}; use iced::mouse; use iced::widget::canvas::{ - self, stroke, Cache, Canvas, Frame, Geometry, Path, Stroke, + self, stroke, Cache, Canvas, Geometry, Path, Stroke, }; use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme}; diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 31d1e29c..289c919b 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -52,9 +52,7 @@ impl Example { mod bezier { use iced::mouse; use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{ - self, frame, Canvas, Frame, Geometry, Path, Stroke, - }; + use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; use iced::{Element, Length, Point, Rectangle, Renderer, Theme}; #[derive(Default)] @@ -184,7 +182,7 @@ mod bezier { } impl Curve { - fn draw_all(curves: &[Curve], frame: &mut impl Frame) { + fn draw_all(curves: &[Curve], frame: &mut Frame) { let curves = Path::new(|p| { for curve in curves { p.move_to(curve.from); @@ -209,7 +207,7 @@ mod bezier { bounds: Rectangle, cursor: mouse::Cursor, ) -> Geometry { - let mut frame = frame(renderer, bounds.size()); + let mut frame = Frame::new(renderer, bounds.size()); if let Some(cursor_position) = cursor.position_in(bounds) { match *self { @@ -229,7 +227,7 @@ mod bezier { }; } - frame.into() + frame.into_geometry() } } } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 468443bc..897f8f1b 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,8 +1,6 @@ use iced::alignment; use iced::mouse; -use iced::widget::canvas::{ - stroke, Cache, Frame, Geometry, LineCap, Path, Stroke, -}; +use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke}; use iced::widget::{canvas, container}; use iced::{ Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription, diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 81ad6e41..d9325edb 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -156,7 +156,7 @@ impl Theme { .into() } - fn draw(&self, frame: &mut impl Frame, text_color: Color) { + fn draw(&self, frame: &mut Frame, text_color: Color) { let pad = 20.0; let box_size = Size { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index a3d385f3..0716b2a4 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -193,9 +193,7 @@ mod grid { use iced::touch; use iced::widget::canvas; use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{ - frame, Cache, Canvas, Frame, Geometry, Path, Text, - }; + use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text}; use iced::{ Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; @@ -548,7 +546,7 @@ mod grid { }); let overlay = { - let mut frame = frame(renderer, bounds.size()); + let mut frame = Frame::new(renderer, bounds.size()); let hovered_cell = cursor.position_in(bounds).map(|position| { Cell::at(self.project(position, frame.size())) @@ -601,7 +599,7 @@ mod grid { ..text }); - frame.into() + frame.into_geometry() }; if self.scaling >= 0.2 && self.show_lines { diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 35d2d3ba..713e2b70 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -297,9 +297,7 @@ fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<canvas::Geometry> { - use canvas::Frame; - - let mut frame = canvas::frame(renderer, bounds.size()); + let mut frame = canvas::Frame::new(renderer, bounds.size()); let palette = theme.extended_palette(); @@ -309,7 +307,7 @@ fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> { palette.background.strong.color, ); - vec![frame.into()] + vec![frame.into_geometry()] } } diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 306988af..cdc6b7ac 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -6,7 +6,7 @@ use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; use iced::event; use iced::mouse; use iced::time::Instant; -use iced::widget::canvas::{self, Frame}; +use iced::widget::canvas; use iced::window::{self, RedrawRequest}; use iced::{ Background, Color, Element, Event, Length, Radians, Rectangle, Renderer, diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index 6d9039fa..2453c7f5 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -88,8 +88,6 @@ impl canvas::Program<Message> for Multitouch { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<Geometry> { - use canvas::Frame; - let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| { if self.fingers.len() < 2 { return; diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 409bc718..9cd6237f 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -112,8 +112,6 @@ impl canvas::Program<Message> for SierpinskiGraph { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<Geometry> { - use canvas::Frame; - let geom = self.cache.draw(renderer, bounds.size(), |frame| { frame.stroke( &canvas::Path::rectangle(Point::ORIGIN, frame.size()), diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index e8f94ed0..deb211d8 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -131,7 +131,6 @@ impl<Message> canvas::Program<Message> for State { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<Geometry> { - use canvas::Frame; use std::f32::consts::PI; let background = diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs index 9b605d23..a7391e23 100644 --- a/examples/vectorial_text/src/main.rs +++ b/examples/vectorial_text/src/main.rs @@ -129,8 +129,6 @@ impl<Message> canvas::Program<Message> for State { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<canvas::Geometry> { - use canvas::Frame; - let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.palette(); let center = bounds.center(); diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index cd4c9267..2b18243e 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -1,12 +1,16 @@ //! Build and draw geometry. pub mod fill; +pub mod frame; pub mod path; pub mod stroke; +mod cache; mod style; mod text; +pub use cache::Cache; pub use fill::Fill; +pub use frame::Frame; pub use path::Path; pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use style::Style; @@ -14,18 +18,7 @@ pub use text::Text; 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) -} +use crate::core::Size; /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: crate::core::Renderer { @@ -33,7 +26,7 @@ pub trait Renderer: crate::core::Renderer { type Geometry: Geometry; /// The kind of [`Frame`] this renderer supports. - type Frame: Frame<Geometry = Self::Geometry>; + type Frame: frame::Backend<Geometry = Self::Geometry>; fn new_frame(&self, size: Size) -> Self::Frame; @@ -43,127 +36,11 @@ pub trait Renderer: crate::core::Renderer { pub trait Backend { /// The kind of [`Frame`] this backend supports. - type Frame: Frame; + type Frame: frame::Backend; 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; @@ -171,120 +48,3 @@ pub trait Geometry: Sized { 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) - } -} diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs new file mode 100644 index 00000000..490e69e2 --- /dev/null +++ b/graphics/src/geometry/cache.rs @@ -0,0 +1,123 @@ +use crate::core::Size; +use crate::geometry::{self, Frame, Geometry}; +use crate::Primitive; + +use std::cell::RefCell; +use std::sync::Arc; + +/// 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: geometry::Renderer, +{ + state: RefCell<State<Renderer::Geometry>>, +} + +impl<Renderer> Cache<Renderer> +where + Renderer: geometry::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 Frame<Renderer>), + ) -> 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::new(renderer, bounds); + draw_fn(&mut frame); + + let geometry = frame.into_geometry().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: geometry::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: geometry::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) + } +} diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs new file mode 100644 index 00000000..e88c43b0 --- /dev/null +++ b/graphics/src/geometry/frame.rs @@ -0,0 +1,208 @@ +use crate::core::{Point, Radians, Rectangle, Size, Vector}; +use crate::geometry::{self, Geometry}; +use crate::geometry::{Fill, Path, Stroke, Text}; + +pub struct Frame<Renderer> +where + Renderer: geometry::Renderer, +{ + raw: Renderer::Frame, +} + +impl<Renderer> Frame<Renderer> +where + Renderer: geometry::Renderer, +{ + pub fn new(renderer: &Renderer, size: Size) -> Self { + Self { + raw: renderer.new_frame(size), + } + } + + /// Returns the width of the [`Frame`]. + pub fn width(&self) -> f32 { + self.raw.width() + } + + /// Returns the height of the [`Frame`]. + pub fn height(&self) -> f32 { + self.raw.height() + } + + /// Returns the dimensions of the [`Frame`]. + pub fn size(&self) -> Size { + self.raw.size() + } + + /// Returns the coordinate of the center of the [`Frame`]. + pub fn center(&self) -> Point { + self.raw.center() + } + + /// 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>) { + self.raw.fill(path, 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. + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into<Fill>, + ) { + self.raw.fill_rectangle(top_left, size, fill); + } + + /// 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>>) { + self.raw.stroke(path, stroke); + } + + /// 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>) { + self.raw.fill_text(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] + pub 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. + pub fn push_transform(&mut self) { + self.raw.push_transform(); + } + + /// Pops a transform from the transform stack and sets it as the current transform. + pub fn pop_transform(&mut self) { + self.raw.pop_transform(); + } + + /// 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 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 + pub fn draft(&mut self, size: Size) -> Self { + Self { + raw: self.raw.draft(size), + } + } + + /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. + pub fn paste(&mut self, frame: Self, at: Point) { + self.raw.paste(frame.raw, at); + } + + /// Applies a translation to the current transform of the [`Frame`]. + pub fn translate(&mut self, translation: Vector) { + self.raw.translate(translation); + } + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + pub fn rotate(&mut self, angle: impl Into<Radians>) { + self.raw.rotate(angle); + } + + /// Applies a uniform scaling to the current transform of the [`Frame`]. + pub fn scale(&mut self, scale: impl Into<f32>) { + self.raw.scale(scale); + } + + /// Applies a non-uniform scaling to the current transform of the [`Frame`]. + pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { + self.raw.scale_nonuniform(scale); + } + + pub fn into_geometry(self) -> Renderer::Geometry { + self.raw.into_geometry() + } +} + +/// The internal implementation of a [`Frame`]. +/// +/// Analogous to [`Frame`]. See [`Frame`] for the documentation +/// of each method. +#[allow(missing_docs)] +pub trait Backend: Sized { + type Geometry: Geometry; + + fn width(&self) -> f32; + fn height(&self) -> f32; + fn size(&self) -> Size; + fn center(&self) -> Point; + + fn push_transform(&mut self); + fn pop_transform(&mut self); + + fn translate(&mut self, translation: Vector); + fn rotate(&mut self, angle: impl Into<Radians>); + fn scale(&mut self, scale: impl Into<f32>); + fn scale_nonuniform(&mut self, scale: impl Into<Vector>); + + fn draft(&mut self, size: Size) -> Self; + fn paste(&mut self, frame: Self, at: Point); + + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>); + + fn fill(&mut self, path: &Path, fill: impl Into<Fill>); + fn fill_text(&mut self, text: impl Into<Text>); + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into<Fill>, + ); + + fn into_geometry(self) -> Self::Geometry; +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 3b21aa11..2fcb55aa 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -257,7 +257,8 @@ impl<B: Backend> mesh::Renderer for Renderer<B> { impl<B> crate::geometry::Renderer for Renderer<B> where B: Backend + crate::geometry::Backend, - B::Frame: crate::geometry::Frame<Geometry = Primitive<B::Primitive>>, + B::Frame: + crate::geometry::frame::Backend<Geometry = Primitive<B::Primitive>>, { type Frame = B::Frame; type Geometry = Primitive<B::Primitive>; diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index a4c725c0..659f253d 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -459,10 +459,10 @@ mod geometry { Right(R), } - impl<L, R> geometry::Frame for Frame<L, R> + impl<L, R> geometry::frame::Backend for Frame<L, R> where - L: geometry::Frame, - R: geometry::Frame, + L: geometry::frame::Backend, + R: geometry::frame::Backend, { type Geometry = Geometry<L::Geometry, R::Geometry>; @@ -545,17 +545,11 @@ mod geometry { 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()), + fn into_geometry(self) -> Self::Geometry { + match self { + Frame::Left(frame) => Geometry::Left(frame.into_geometry()), + Frame::Right(frame) => Geometry::Right(frame.into_geometry()), } } } diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs deleted file mode 100644 index a16cecd5..00000000 --- a/renderer/src/geometry.rs +++ /dev/null @@ -1,236 +0,0 @@ -mod cache; - -pub use cache::Cache; - -use crate::core::{Point, Radians, Rectangle, Size, Transformation, Vector}; -use crate::graphics::geometry::{Fill, Path, Stroke, Text}; -use crate::Renderer; - -macro_rules! delegate { - ($frame:expr, $name:ident, $body:expr) => { - match $frame { - Self::TinySkia($name) => $body, - #[cfg(feature = "wgpu")] - Self::Wgpu($name) => $body, - #[cfg(feature = "custom")] - Self::Custom($name) => $body, - } - }; -} - -pub enum Geometry { - TinySkia(iced_tiny_skia::Primitive), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::Primitive), - #[cfg(feature = "custom")] - Custom(Box<dyn crate::custom::Geometry>), -} - -impl Geometry { - pub fn transform(self, transformation: Transformation) -> Self { - match self { - Self::TinySkia(primitive) => { - Self::TinySkia(primitive.transform(transformation)) - } - #[cfg(feature = "wgpu")] - Self::Wgpu(primitive) => { - Self::Wgpu(primitive.transform(transformation)) - } - #[cfg(feature = "custom")] - Self::Custom(geometry) => { - Self::Custom(geometry.transform(transformation)) - } - } - } -} - -pub enum Frame { - TinySkia(iced_tiny_skia::geometry::Frame), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::geometry::Frame), - #[cfg(feature = "custom")] - Custom(Box<dyn crate::custom::Frame>), -} - -impl Frame { - pub fn new(renderer: &Renderer, size: Size) -> Self { - match renderer { - Renderer::TinySkia(_) => { - Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size)) - } - #[cfg(feature = "wgpu")] - Renderer::Wgpu(_) => { - Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) - } - #[cfg(feature = "custom")] - Renderer::Custom(renderer) => { - Frame::Custom(renderer.new_frame(size)) - } - } - } - - /// Returns the width of the [`Frame`]. - #[inline] - pub fn width(&self) -> f32 { - delegate!(self, frame, frame.width()) - } - - /// Returns the height of the [`Frame`]. - #[inline] - pub fn height(&self) -> f32 { - delegate!(self, frame, frame.height()) - } - - /// Returns the dimensions of the [`Frame`]. - #[inline] - pub fn size(&self) -> Size { - delegate!(self, frame, frame.size()) - } - - /// Returns the coordinate of the center of the [`Frame`]. - #[inline] - pub fn center(&self) -> Point { - delegate!(self, frame, frame.center()) - } - - /// 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>) { - delegate!(self, frame, frame.fill(path, fill.into())); - } - - /// 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>, - ) { - delegate!( - self, - frame, - frame.fill_rectangle(top_left, size, fill.into()) - ); - } - - /// 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>>) { - delegate!(self, frame, frame.stroke(path, stroke.into())); - } - - /// 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>) { - delegate!(self, frame, frame.fill_text(text.into())); - } - - /// 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 { - delegate!(self, frame, frame.push_transform()); - - let result = f(self); - - delegate!(self, frame, frame.pop_transform()); - - result - } - - /// 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 = match self { - Self::TinySkia(_) => Self::TinySkia( - iced_tiny_skia::geometry::Frame::new(region.size()), - ), - #[cfg(feature = "wgpu")] - Self::Wgpu(_) => { - Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) - } - #[cfg(feature = "custom")] - Self::Custom(frame) => Self::Custom(frame.new(region.size())), - }; - - let result = f(&mut frame); - - let origin = Point::new(region.x, region.y); - - match (self, frame) { - (Self::TinySkia(target), Self::TinySkia(frame)) => { - target.clip(frame, origin); - } - #[cfg(feature = "wgpu")] - (Self::Wgpu(target), Self::Wgpu(frame)) => { - target.clip(frame, origin); - } - #[cfg(feature = "custom")] - (Self::Custom(target), Self::Custom(frame)) => { - target.clip(frame, origin); - } - #[allow(unreachable_patterns)] - _ => unreachable!(), - }; - - result - } - - /// Applies a translation to the current transform of the [`Frame`]. - #[inline] - pub fn translate(&mut self, translation: Vector) { - delegate!(self, frame, frame.translate(translation)); - } - - /// Applies a rotation in radians to the current transform of the [`Frame`]. - #[inline] - pub fn rotate(&mut self, angle: impl Into<Radians>) { - delegate!(self, frame, frame.rotate(angle.into())); - } - - /// Applies a uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale(&mut self, scale: impl Into<f32>) { - delegate!(self, frame, frame.scale(scale.into())); - } - - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { - delegate!(self, frame, frame.scale_nonuniform(scale.into())); - } - - pub fn into_geometry(self) -> Geometry { - match self { - Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()), - #[cfg(feature = "wgpu")] - Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()), - #[cfg(feature = "custom")] - Self::Custom(frame) => Geometry::Custom(frame.into_geometry()), - } - } -} diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs deleted file mode 100644 index 20f73f22..00000000 --- a/renderer/src/geometry/cache.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::core::Size; -use crate::geometry::{Frame, Geometry}; -use crate::Renderer; - -use std::cell::RefCell; -use std::sync::Arc; - -/// 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. -#[derive(Debug, Default)] -pub struct Cache { - state: RefCell<State>, -} - -#[derive(Debug, Default)] -enum State { - #[default] - Empty, - Filled { - bounds: Size, - primitive: Internal, - }, -} - -#[derive(Debug, Clone)] -enum Internal { - TinySkia(Arc<iced_tiny_skia::Primitive>), - #[cfg(feature = "wgpu")] - Wgpu(Arc<iced_wgpu::Primitive>), - #[cfg(feature = "custom")] - Custom(Arc<dyn crate::custom::Geometry>), -} - -impl Cache { - /// Creates a new empty [`Cache`]. - pub fn new() -> Self { - Cache { - state: RefCell::default(), - } - } - - /// 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 Frame), - ) -> Geometry { - use std::ops::Deref; - - if let State::Filled { - bounds: cached_bounds, - primitive, - } = self.state.borrow().deref() - { - if *cached_bounds == bounds { - match primitive { - Internal::TinySkia(primitive) => { - return Geometry::TinySkia( - iced_tiny_skia::Primitive::Cache { - content: primitive.clone(), - }, - ); - } - #[cfg(feature = "wgpu")] - Internal::Wgpu(primitive) => { - return Geometry::Wgpu(iced_wgpu::Primitive::Cache { - content: primitive.clone(), - }); - } - #[cfg(feature = "custom")] - Internal::Custom(geometry) => { - return Geometry::Custom(geometry.clone().load()) - } - } - } - } - - let mut frame = Frame::new(renderer, bounds); - draw_fn(&mut frame); - - let primitive = { - let geometry = frame.into_geometry(); - - match geometry { - Geometry::TinySkia(primitive) => { - Internal::TinySkia(Arc::new(primitive)) - } - #[cfg(feature = "wgpu")] - Geometry::Wgpu(primitive) => { - Internal::Wgpu(Arc::new(primitive)) - } - #[cfg(feature = "custom")] - Geometry::Custom(geometry) => { - Internal::Custom(geometry.cache()) - } - } - }; - - *self.state.borrow_mut() = State::Filled { - bounds, - primitive: primitive.clone(), - }; - - match primitive { - Internal::TinySkia(primitive) => { - Geometry::TinySkia(iced_tiny_skia::Primitive::Cache { - content: primitive, - }) - } - #[cfg(feature = "wgpu")] - Internal::Wgpu(primitive) => { - Geometry::Wgpu(iced_wgpu::Primitive::Cache { - content: primitive, - }) - } - #[cfg(feature = "custom")] - Internal::Custom(geometry) => Geometry::Custom(geometry.load()), - } - } -} diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 6b1888d0..76482e12 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -35,7 +35,7 @@ impl Frame { } } -impl geometry::Frame for Frame { +impl geometry::frame::Backend for Frame { type Geometry = Primitive; fn width(&self) -> f32 { @@ -228,11 +228,9 @@ impl geometry::Frame for Frame { self.transform = self.transform.pre_scale(scale.x, scale.y); } -} -impl From<Frame> for Primitive { - fn from(frame: Frame) -> Self { - frame.into_primitive() + fn into_geometry(self) -> Self::Geometry { + self.into_primitive() } } diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 4f6b67b1..ba56c59d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -80,7 +80,7 @@ impl Frame { } } -impl geometry::Frame for Frame { +impl geometry::frame::Backend for Frame { type Geometry = Primitive; /// Creates a new empty [`Frame`] with the given dimensions. @@ -339,11 +339,10 @@ impl geometry::Frame for Frame { ], }); } -} -impl From<Frame> for Primitive { - fn from(frame: Frame) -> Self { - Self::Group { - primitives: frame.into_primitives(), + + fn into_geometry(self) -> Self::Geometry { + Primitive::Group { + primitives: self.into_primitives(), } } } diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 81067491..7a21895a 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -6,7 +6,10 @@ mod program; pub use event::Event; pub use program::Program; -pub use crate::graphics::geometry::*; +pub use crate::graphics::geometry::{ + fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin, + Path, Stroke, Style, Text, +}; use crate::core; use crate::core::layout::{self, Layout}; @@ -30,13 +33,16 @@ pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>; pub type Geometry<Renderer = crate::Renderer> = <Renderer as geometry::Renderer>::Geometry; +/// The frame supported by a renderer. +pub type Frame<Renderer = crate::Renderer> = geometry::Frame<Renderer>; + /// A widget capable of drawing 2D graphics. /// /// ## Drawing a simple circle /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Geometry, Path, Program}; +/// # use iced_widget::canvas::{self, Canvas, Fill, Frame, Geometry, Path, Program}; /// # use iced_widget::core::{Color, Rectangle}; /// # use iced_widget::core::mouse; /// # use iced_widget::{Renderer, Theme}; @@ -53,7 +59,7 @@ pub type Geometry<Renderer = crate::Renderer> = /// /// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry> { /// // We prepare a new `Frame` -/// let mut frame = frame(renderer, bounds.size()); +/// let mut frame = Frame::new(renderer, bounds.size()); /// /// // We create a `Path` representing a simple circle /// let circle = Path::circle(frame.center(), self.radius); @@ -62,7 +68,7 @@ pub type Geometry<Renderer = crate::Renderer> = /// frame.fill(&circle, Color::BLACK); /// /// // Finally, we produce the geometry -/// vec![frame.into()] +/// vec![frame.into_geometry()] /// } /// } /// diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index bc46aaaa..84898dc0 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -91,8 +91,6 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer> _cursor: mouse::Cursor, _viewport: &Rectangle, ) { - use canvas::Frame; - let state = tree.state.downcast_ref::<State>(); let bounds = layout.bounds(); |