diff options
| author | 2023-03-01 21:34:26 +0100 | |
|---|---|---|
| committer | 2023-03-01 21:34:26 +0100 | |
| commit | 5fd5d1cdf8e5354788dc40729c4565ef377d3bba (patch) | |
| tree | 0921efc7dc13a3050e03482147a791f85515f1f2 /renderer/src | |
| parent | 3f6e28fa9b1b8d911f765c9efb5249a9e0c942d5 (diff) | |
| download | iced-5fd5d1cdf8e5354788dc40729c4565ef377d3bba.tar.gz iced-5fd5d1cdf8e5354788dc40729c4565ef377d3bba.tar.bz2 iced-5fd5d1cdf8e5354788dc40729c4565ef377d3bba.zip | |
Implement `Canvas` support for `iced_tiny_skia`
Diffstat (limited to '')
| -rw-r--r-- | renderer/src/backend.rs | 4 | ||||
| -rw-r--r-- | renderer/src/lib.rs | 14 | ||||
| -rw-r--r-- | renderer/src/widget.rs | 12 | ||||
| -rw-r--r-- | renderer/src/widget/canvas.rs | 177 | ||||
| -rw-r--r-- | renderer/src/widget/canvas/cache.rs | 85 | ||||
| -rw-r--r-- | renderer/src/widget/qr_code.rs (renamed from graphics/src/widget/qr_code.rs) | 90 | 
6 files changed, 332 insertions, 50 deletions
| diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index b0a409dc..6c0b4e5c 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -1,4 +1,4 @@ -use crate::{Font, Point, Size}; +use crate::{Font, Geometry, Point, Size};  use iced_graphics::backend;  use iced_graphics::text; @@ -12,6 +12,8 @@ pub enum Backend {  }  impl iced_graphics::Backend for Backend { +    type Geometry = Geometry; +      fn trim_measurements(&mut self) {          match self {              Self::Wgpu(backend) => backend.trim_measurements(), diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index f9bfc373..d9c85e82 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -4,11 +4,14 @@ pub mod window;  mod backend;  mod settings; +pub use iced_graphics::primitive; +  pub use backend::Backend; +pub use primitive::Primitive;  pub use settings::Settings;  pub use iced_graphics::{ -    Antialiasing, Color, Error, Font, Point, Size, Viewport, +    Antialiasing, Color, Error, Font, Point, Rectangle, Size, Vector, Viewport,  };  /// The default graphics renderer for [`iced`]. @@ -16,3 +19,12 @@ pub use iced_graphics::{  /// [`iced`]: https://github.com/iced-rs/iced  pub type Renderer<Theme = iced_native::Theme> =      iced_graphics::Renderer<Backend, Theme>; + +#[derive(Debug, Clone)] +pub struct Geometry(pub(crate) Primitive); + +impl From<Geometry> for Primitive { +    fn from(geometry: Geometry) -> Self { +        geometry.0 +    } +} diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs index 417cc06f..6c0c2a83 100644 --- a/renderer/src/widget.rs +++ b/renderer/src/widget.rs @@ -1,5 +1,11 @@ -#[cfg(feature = "qr_code")] -pub use iced_graphics::widget::qr_code; +#[cfg(feature = "canvas")] +pub mod canvas;  #[cfg(feature = "canvas")] -pub use iced_graphics::widget::canvas; +pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +pub use qr_code::QRCode; diff --git a/renderer/src/widget/canvas.rs b/renderer/src/widget/canvas.rs new file mode 100644 index 00000000..f40a1097 --- /dev/null +++ b/renderer/src/widget/canvas.rs @@ -0,0 +1,177 @@ +mod cache; + +pub use cache::Cache; + +pub use iced_native::widget::canvas::event::{self, Event}; +pub use iced_native::widget::canvas::fill::{self, Fill}; +pub use iced_native::widget::canvas::gradient::{self, Gradient}; +pub use iced_native::widget::canvas::path::{self, Path}; +pub use iced_native::widget::canvas::stroke::{self, Stroke}; +pub use iced_native::widget::canvas::{ +    Canvas, Cursor, LineCap, LineDash, LineJoin, Program, Renderer, Style, Text, +}; + +use crate::{Backend, Point, Rectangle, Size, Vector}; + +pub use crate::Geometry; + +pub enum Frame { +    Wgpu(iced_wgpu::widget::canvas::Frame), +    TinySkia(iced_tiny_skia::canvas::Frame), +} + +macro_rules! delegate { +    ($frame:expr, $name:ident => $body:expr) => { +        match $frame { +            Self::Wgpu($name) => $body, +            Self::TinySkia($name) => $body, +        } +    }; +} + +impl Frame { +    pub fn new<Theme>(renderer: &crate::Renderer<Theme>, size: Size) -> Self { +        match renderer.backend() { +            Backend::Wgpu(_) => { +                Frame::Wgpu(iced_wgpu::widget::canvas::Frame::new(size)) +            } +            Backend::TinySkia(_) => { +                Frame::TinySkia(iced_tiny_skia::canvas::Frame::new(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)); +    } + +    /// 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)); +    } + +    /// 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)); +    } + +    /// 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. +    /// +    /// [`Canvas`]: crate::widget::Canvas +    pub fn fill_text(&mut self, text: impl Into<Text>) { +        delegate!(self, frame => frame.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(&mut self, f: impl FnOnce(&mut Frame)) { +        delegate!(self, frame => frame.push_transform()); + +        f(self); + +        delegate!(self, frame => frame.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(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { +        let mut frame = match self { +            Self::Wgpu(_) => { +                Self::Wgpu(iced_wgpu::widget::canvas::Frame::new(region.size())) +            } +            Self::TinySkia(_) => Self::TinySkia( +                iced_tiny_skia::canvas::Frame::new(region.size()), +            ), +        }; + +        f(&mut frame); + +        let translation = Vector::new(region.x, region.y); + +        match (self, frame) { +            (Self::Wgpu(target), Self::Wgpu(frame)) => { +                target.clip(frame, translation); +            } +            (Self::TinySkia(target), Self::TinySkia(frame)) => { +                target.clip(frame, translation); +            } +            _ => unreachable!(), +        }; +    } + +    /// 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: f32) { +        delegate!(self, frame => frame.rotate(angle)); +    } + +    /// Applies a scaling to the current transform of the [`Frame`]. +    #[inline] +    pub fn scale(&mut self, scale: f32) { +        delegate!(self, frame => frame.scale(scale)); +    } + +    pub fn into_geometry(self) -> Geometry { +        Geometry(delegate!(self, frame => frame.into_primitive())) +    } +} diff --git a/renderer/src/widget/canvas/cache.rs b/renderer/src/widget/canvas/cache.rs new file mode 100644 index 00000000..7d6b4811 --- /dev/null +++ b/renderer/src/widget/canvas/cache.rs @@ -0,0 +1,85 @@ +use crate::widget::canvas::{Frame, Geometry}; +use crate::{Primitive, Renderer, Size}; + +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: Arc<Primitive>, +    }, +} + +impl Cache { +    /// Creates a new empty [`Cache`]. +    pub fn new() -> Self { +        Cache { +            state: Default::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<Theme>( +        &self, +        renderer: &Renderer<Theme>, +        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 { +                return Geometry(Primitive::Cache { +                    content: primitive.clone(), +                }); +            } +        } + +        let mut frame = Frame::new(renderer, bounds); +        draw_fn(&mut frame); + +        let primitive = { +            let geometry = frame.into_geometry(); + +            Arc::new(geometry.0) +        }; + +        *self.state.borrow_mut() = State::Filled { +            bounds, +            primitive: primitive.clone(), +        }; + +        Geometry(Primitive::Cache { content: primitive }) +    } +} diff --git a/graphics/src/widget/qr_code.rs b/renderer/src/widget/qr_code.rs index 12ce5b1f..aae4ec88 100644 --- a/graphics/src/widget/qr_code.rs +++ b/renderer/src/widget/qr_code.rs @@ -1,7 +1,8 @@  //! Encode and display information in a QR code. -use crate::renderer::{self, Renderer};  use crate::widget::canvas; -use crate::Backend; +use crate::Renderer; + +use iced_graphics::renderer;  use iced_native::layout;  use iced_native::widget::Tree; @@ -48,10 +49,7 @@ impl<'a> QRCode<'a> {      }  } -impl<'a, Message, B, T> Widget<Message, Renderer<B, T>> for QRCode<'a> -where -    B: Backend, -{ +impl<'a, Message, Theme> Widget<Message, Renderer<Theme>> for QRCode<'a> {      fn width(&self) -> Length {          Length::Shrink      } @@ -62,7 +60,7 @@ where      fn layout(          &self, -        _renderer: &Renderer<B, T>, +        _renderer: &Renderer<Theme>,          _limits: &layout::Limits,      ) -> layout::Node {          let side_length = (self.state.width + 2 * QUIET_ZONE) as f32 @@ -74,8 +72,8 @@ where      fn draw(          &self,          _state: &Tree, -        renderer: &mut Renderer<B, T>, -        _theme: &T, +        renderer: &mut Renderer<Theme>, +        _theme: &Theme,          _style: &renderer::Style,          layout: Layout<'_>,          _cursor_position: Point, @@ -87,50 +85,52 @@ where          let side_length = self.state.width + 2 * QUIET_ZONE;          // Reuse cache if possible -        let geometry = self.state.cache.draw(bounds.size(), |frame| { -            // Scale units to cell size -            frame.scale(f32::from(self.cell_size)); - -            // Draw background -            frame.fill_rectangle( -                Point::ORIGIN, -                Size::new(side_length as f32, side_length as f32), -                self.light, -            ); - -            // Avoid drawing on the quiet zone -            frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32)); - -            // Draw contents -            self.state -                .contents -                .iter() -                .enumerate() -                .filter(|(_, value)| **value == qrcode::Color::Dark) -                .for_each(|(index, _)| { -                    let row = index / self.state.width; -                    let column = index % self.state.width; - -                    frame.fill_rectangle( -                        Point::new(column as f32, row as f32), -                        Size::UNIT, -                        self.dark, -                    ); -                }); -        }); +        let geometry = +            self.state.cache.draw(renderer, bounds.size(), |frame| { +                // Scale units to cell size +                frame.scale(f32::from(self.cell_size)); + +                // Draw background +                frame.fill_rectangle( +                    Point::ORIGIN, +                    Size::new(side_length as f32, side_length as f32), +                    self.light, +                ); + +                // Avoid drawing on the quiet zone +                frame.translate(Vector::new( +                    QUIET_ZONE as f32, +                    QUIET_ZONE as f32, +                )); + +                // Draw contents +                self.state +                    .contents +                    .iter() +                    .enumerate() +                    .filter(|(_, value)| **value == qrcode::Color::Dark) +                    .for_each(|(index, _)| { +                        let row = index / self.state.width; +                        let column = index % self.state.width; + +                        frame.fill_rectangle( +                            Point::new(column as f32, row as f32), +                            Size::UNIT, +                            self.dark, +                        ); +                    }); +            });          let translation = Vector::new(bounds.x, bounds.y);          renderer.with_translation(translation, |renderer| { -            renderer.draw_primitive(geometry.into_primitive()); +            renderer.draw_primitive(geometry.0);          });      }  } -impl<'a, Message, B, T> From<QRCode<'a>> -    for Element<'a, Message, Renderer<B, T>> -where -    B: Backend, +impl<'a, Message, Theme> From<QRCode<'a>> +    for Element<'a, Message, Renderer<Theme>>  {      fn from(qr_code: QRCode<'a>) -> Self {          Self::new(qr_code) | 
