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 '')
| -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); +    } +} | 
