summaryrefslogtreecommitdiffstats
path: root/renderer/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--renderer/src/backend.rs4
-rw-r--r--renderer/src/lib.rs14
-rw-r--r--renderer/src/widget.rs12
-rw-r--r--renderer/src/widget/canvas.rs177
-rw-r--r--renderer/src/widget/canvas/cache.rs85
-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)