diff options
author | 2024-03-21 22:27:17 +0100 | |
---|---|---|
committer | 2024-03-21 22:27:17 +0100 | |
commit | 3645d34d6a1ba1247238e830e9eefd52d9e5b986 (patch) | |
tree | 2d38961161df0a85c1667474b2b696aab86b7160 /graphics | |
parent | 7e4ae8450e1f28c15717ca5ca9748981af9c9541 (diff) | |
download | iced-3645d34d6a1ba1247238e830e9eefd52d9e5b986.tar.gz iced-3645d34d6a1ba1247238e830e9eefd52d9e5b986.tar.bz2 iced-3645d34d6a1ba1247238e830e9eefd52d9e5b986.zip |
Implement composable, type-safe renderer fallback
Diffstat (limited to 'graphics')
-rw-r--r-- | graphics/src/backend.rs | 3 | ||||
-rw-r--r-- | graphics/src/geometry.rs | 272 | ||||
-rw-r--r-- | graphics/src/lib.rs | 4 | ||||
-rw-r--r-- | graphics/src/mesh.rs | 6 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 106 |
5 files changed, 341 insertions, 50 deletions
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 10eb337f..e394c956 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -2,6 +2,7 @@ use crate::core::image; use crate::core::svg; use crate::core::Size; +use crate::Mesh; use std::borrow::Cow; @@ -10,7 +11,7 @@ use std::borrow::Cow; /// [`Renderer`]: crate::Renderer pub trait Backend { /// The custom kind of primitives this [`Backend`] supports. - type Primitive; + type Primitive: TryFrom<Mesh, Error = &'static str>; } /// A graphics backend that supports text rendering. diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index d7d6a0aa..cd4c9267 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -14,11 +14,277 @@ 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) +} + /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: crate::core::Renderer { /// The kind of geometry this renderer can draw. - type Geometry; + type Geometry: Geometry; + + /// The kind of [`Frame`] this renderer supports. + 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(), + } + } - /// Draws the given layers of [`Self::Geometry`]. - fn draw(&mut self, layers: Vec<Self::Geometry>); + fn cache(self) -> Arc<Self> { + Arc::new(self) + } } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index aa9d00e8..6d0862ad 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,8 +9,8 @@ )] #![forbid(rust_2018_idioms)] #![deny( - missing_debug_implementations, - missing_docs, + // missing_debug_implementations, + // missing_docs, unsafe_code, unused_results, rustdoc::broken_intra_doc_links diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index 041986cf..5be3ee5b 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -1,6 +1,6 @@ //! Draw triangles! use crate::color; -use crate::core::{Rectangle, Size}; +use crate::core::{self, Rectangle, Size}; use crate::gradient; use crate::Damage; @@ -74,3 +74,7 @@ pub struct GradientVertex2D { /// The packed vertex data of the gradient. pub gradient: gradient::Packed, } + +pub trait Renderer: core::Renderer { + fn draw_mesh(&mut self, mesh: Mesh); +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index e7154385..3b21aa11 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -8,8 +8,9 @@ use crate::core::text::Text; use crate::core::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; +use crate::mesh; use crate::text; -use crate::Primitive; +use crate::{Mesh, Primitive}; use std::borrow::Cow; @@ -20,6 +21,7 @@ pub struct Renderer<B: Backend> { default_font: Font, default_text_size: Pixels, primitives: Vec<Primitive<B::Primitive>>, + stack: Vec<Vec<Primitive<B::Primitive>>>, } impl<B: Backend> Renderer<B> { @@ -34,6 +36,7 @@ impl<B: Backend> Renderer<B> { default_font, default_text_size, primitives: Vec::new(), + stack: Vec::new(), } } @@ -56,59 +59,45 @@ impl<B: Backend> Renderer<B> { f(&mut self.backend, &self.primitives) } - /// Starts recording a new layer. - pub fn start_layer(&mut self) -> Vec<Primitive<B::Primitive>> { - std::mem::take(&mut self.primitives) - } - - /// Ends the recording of a layer. - pub fn end_layer( - &mut self, - primitives: Vec<Primitive<B::Primitive>>, - bounds: Rectangle, - ) { - let layer = std::mem::replace(&mut self.primitives, primitives); - - self.primitives.push(Primitive::group(layer).clip(bounds)); - } - - /// 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( + #[cfg(feature = "geometry")] + pub fn draw_geometry<Geometry>( &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)); + layers: impl IntoIterator<Item = Geometry>, + ) where + Geometry: Into<Primitive<B::Primitive>>, + { + for layer in layers { + self.draw_primitive(layer.into()); + } } } impl<B: Backend> iced_core::Renderer for Renderer<B> { - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { - let current = self.start_layer(); + fn start_layer(&mut self) { + self.stack.push(std::mem::take(&mut self.primitives)); + } - f(self); + fn end_layer(&mut self, bounds: Rectangle) { + let layer = std::mem::replace( + &mut self.primitives, + self.stack.pop().expect("a layer should be recording"), + ); - self.end_layer(current, bounds); + self.primitives.push(Primitive::group(layer).clip(bounds)); } - fn with_transformation( - &mut self, - transformation: Transformation, - f: impl FnOnce(&mut Self), - ) { - let current = self.start_transformation(); + fn start_transformation(&mut self) { + self.stack.push(std::mem::take(&mut self.primitives)); + } - f(self); + 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.end_transformation(current, transformation); + self.primitives + .push(Primitive::group(layer).transform(transformation)); } 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); + } +} |