From 5100b5d0a1f654ec1254b7765ceadfb9091d6939 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 24 Feb 2023 23:24:48 +0100 Subject: Introduce `iced_renderer` subcrate featuring runtime renderer fallback --- renderer/Cargo.toml | 26 +++++++++++ renderer/src/backend.rs | 94 ++++++++++++++++++++++++++++++++++++++ renderer/src/lib.rs | 17 +++++++ renderer/src/settings.rs | 30 ++++++++++++ renderer/src/window.rs | 3 ++ renderer/src/window/compositor.rs | 96 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 266 insertions(+) create mode 100644 renderer/Cargo.toml create mode 100644 renderer/src/backend.rs create mode 100644 renderer/src/lib.rs create mode 100644 renderer/src/settings.rs create mode 100644 renderer/src/window.rs create mode 100644 renderer/src/window/compositor.rs (limited to 'renderer') diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml new file mode 100644 index 00000000..2a179f3a --- /dev/null +++ b/renderer/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "iced_renderer" +version = "0.1.0" +edition = "2021" + +[features] +image = ["iced_wgpu/image"] +svg = ["iced_wgpu/svg"] +tracing = ["iced_wgpu/tracing"] + +[dependencies] +raw-window-handle = "0.5" + +[dependencies.iced_native] +version = "0.9" +path = "../native" + +[dependencies.iced_graphics] +version = "0.7" +path = "../graphics" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +iced_wgpu = { version = "0.9", path = "../wgpu" } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +iced_wgpu = { version = "0.9", path = "../wgpu", features = ["webgl"] } diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs new file mode 100644 index 00000000..a9a09593 --- /dev/null +++ b/renderer/src/backend.rs @@ -0,0 +1,94 @@ +use crate::{Font, Point, Size}; + +use iced_graphics::backend; +use iced_graphics::text; + +use std::borrow::Cow; + +pub enum Backend { + Wgpu(iced_wgpu::Backend), +} + +impl iced_graphics::Backend for Backend {} + +impl backend::Text for Backend { + const ICON_FONT: Font = Font::Name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; + + fn default_font(&self) -> Font { + match self { + Self::Wgpu(backend) => backend.default_font(), + } + } + + fn default_size(&self) -> f32 { + match self { + Self::Wgpu(backend) => backend.default_size(), + } + } + + fn measure( + &self, + contents: &str, + size: f32, + font: Font, + bounds: Size, + ) -> (f32, f32) { + match self { + Self::Wgpu(backend) => { + backend.measure(contents, size, font, bounds) + } + } + } + + fn hit_test( + &self, + contents: &str, + size: f32, + font: Font, + bounds: Size, + position: Point, + nearest_only: bool, + ) -> Option { + match self { + Self::Wgpu(backend) => backend.hit_test( + contents, + size, + font, + bounds, + position, + nearest_only, + ), + } + } + + fn load_font(&mut self, font: Cow<'static, [u8]>) { + match self { + Self::Wgpu(backend) => { + backend.load_font(font); + } + } + } +} + +#[cfg(feature = "image")] +impl backend::Image for Backend { + fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { + match self { + Self::Wgpu(backend) => backend.dimensions(handle), + } + } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { + fn viewport_dimensions( + &self, + handle: &iced_native::svg::Handle, + ) -> Size { + match self { + Self::Wgpu(backend) => backend.viewport_dimensions(handle), + } + } +} diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs new file mode 100644 index 00000000..d0ba6793 --- /dev/null +++ b/renderer/src/lib.rs @@ -0,0 +1,17 @@ +pub mod window; + +mod backend; +mod settings; + +pub use backend::Backend; +pub use settings::Settings; + +pub use iced_graphics::{ + Antialiasing, Color, Error, Font, Point, Size, Viewport, +}; + +/// The default graphics renderer for [`iced`]. +/// +/// [`iced`]: https://github.com/iced-rs/iced +pub type Renderer = + iced_graphics::Renderer; diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs new file mode 100644 index 00000000..c4dc248b --- /dev/null +++ b/renderer/src/settings.rs @@ -0,0 +1,30 @@ +use crate::{Antialiasing, Font}; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::Backend +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Settings { + /// The default [`Font`] to use. + pub default_font: Font, + + /// The default size of text. + /// + /// By default, it will be set to `16.0`. + pub default_text_size: f32, + + /// The antialiasing strategy that will be used for triangle primitives. + /// + /// By default, it is `None`. + pub antialiasing: Option, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: Font::SansSerif, + default_text_size: 16.0, + antialiasing: None, + } + } +} diff --git a/renderer/src/window.rs b/renderer/src/window.rs new file mode 100644 index 00000000..a7c8911b --- /dev/null +++ b/renderer/src/window.rs @@ -0,0 +1,3 @@ +mod compositor; + +pub use compositor::Compositor; diff --git a/renderer/src/window/compositor.rs b/renderer/src/window/compositor.rs new file mode 100644 index 00000000..ad78d8bf --- /dev/null +++ b/renderer/src/window/compositor.rs @@ -0,0 +1,96 @@ +use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; + +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + +pub use iced_graphics::window::compositor::{Information, SurfaceError}; + +pub enum Compositor { + Wgpu(iced_wgpu::window::Compositor), +} + +pub enum Surface { + Wgpu(iced_wgpu::window::Surface), +} + +impl iced_graphics::window::Compositor for Compositor { + type Settings = Settings; + type Renderer = Renderer; + type Surface = Surface; + + fn new( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + let (compositor, backend) = iced_wgpu::window::compositor::new( + iced_wgpu::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..iced_wgpu::Settings::from_env() + }, + compatible_window, + )?; + + Ok(( + Self::Wgpu(compositor), + Renderer::new(Backend::Wgpu(backend)), + )) + } + + fn create_surface( + &mut self, + window: &W, + ) -> Surface { + match self { + Self::Wgpu(compositor) => { + Surface::Wgpu(compositor.create_surface(window)) + } + } + } + + fn configure_surface( + &mut self, + surface: &mut Surface, + width: u32, + height: u32, + ) { + match (self, surface) { + (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { + compositor.configure_surface(surface, width, height); + } + } + } + + fn fetch_information(&self) -> Information { + match self { + Self::Wgpu(compositor) => compositor.fetch_information(), + } + } + + fn present>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Result<(), SurfaceError> { + renderer.with_primitives(|backend, primitives| { + match (self, backend, surface) { + ( + Self::Wgpu(compositor), + Backend::Wgpu(backend), + Surface::Wgpu(surface), + ) => iced_wgpu::window::compositor::present( + compositor, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ), + } + }) + } +} -- cgit From a01bc865a0561ff1daf255c4746746acf67524f0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 25 Feb 2023 15:11:35 +0100 Subject: Trim measurements in `renderer::Backend` --- renderer/src/backend.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'renderer') diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index a9a09593..7b09eea8 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -9,7 +9,13 @@ pub enum Backend { Wgpu(iced_wgpu::Backend), } -impl iced_graphics::Backend for Backend {} +impl iced_graphics::Backend for Backend { + fn trim_measurements(&mut self) { + match self { + Self::Wgpu(backend) => backend.trim_measurements(), + } + } +} impl backend::Text for Backend { const ICON_FONT: Font = Font::Name("Iced-Icons"); -- cgit From 8c373cd497e370d356b480380482779397bdb510 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 25 Feb 2023 15:38:25 +0100 Subject: Scaffold `iced_tiny_skia` and connect it to `iced_renderer` --- renderer/Cargo.toml | 8 ++++-- renderer/src/backend.rs | 20 +++++++++++++++ renderer/src/window/compositor.rs | 54 +++++++++++++++++++++++++++++++-------- 3 files changed, 70 insertions(+), 12 deletions(-) (limited to 'renderer') diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 2a179f3a..5ba5d426 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [features] -image = ["iced_wgpu/image"] -svg = ["iced_wgpu/svg"] +image = ["iced_wgpu/image", "iced_tiny_skia/image"] +svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] tracing = ["iced_wgpu/tracing"] [dependencies] @@ -19,6 +19,10 @@ path = "../native" version = "0.7" path = "../graphics" +[dependencies.iced_tiny_skia] +version = "0.1" +path = "../tiny_skia" + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_wgpu = { version = "0.9", path = "../wgpu" } diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index 7b09eea8..a46d6f9b 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -7,12 +7,14 @@ use std::borrow::Cow; pub enum Backend { Wgpu(iced_wgpu::Backend), + TinySkia(iced_tiny_skia::Backend), } impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { match self { Self::Wgpu(backend) => backend.trim_measurements(), + Self::TinySkia(backend) => backend.trim_measurements(), } } } @@ -25,12 +27,14 @@ impl backend::Text for Backend { fn default_font(&self) -> Font { match self { Self::Wgpu(backend) => backend.default_font(), + Self::TinySkia(backend) => backend.default_font(), } } fn default_size(&self) -> f32 { match self { Self::Wgpu(backend) => backend.default_size(), + Self::TinySkia(backend) => backend.default_size(), } } @@ -45,6 +49,9 @@ impl backend::Text for Backend { Self::Wgpu(backend) => { backend.measure(contents, size, font, bounds) } + Self::TinySkia(backend) => { + backend.measure(contents, size, font, bounds) + } } } @@ -66,6 +73,14 @@ impl backend::Text for Backend { position, nearest_only, ), + Self::TinySkia(backend) => backend.hit_test( + contents, + size, + font, + bounds, + position, + nearest_only, + ), } } @@ -74,6 +89,9 @@ impl backend::Text for Backend { Self::Wgpu(backend) => { backend.load_font(font); } + Self::TinySkia(backend) => { + backend.load_font(font); + } } } } @@ -83,6 +101,7 @@ impl backend::Image for Backend { fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { match self { Self::Wgpu(backend) => backend.dimensions(handle), + Self::TinySkia(backend) => backend.dimensions(handle), } } } @@ -95,6 +114,7 @@ impl backend::Svg for Backend { ) -> Size { match self { Self::Wgpu(backend) => backend.viewport_dimensions(handle), + Self::TinySkia(backend) => backend.viewport_dimensions(handle), } } } diff --git a/renderer/src/window/compositor.rs b/renderer/src/window/compositor.rs index ad78d8bf..42afddc4 100644 --- a/renderer/src/window/compositor.rs +++ b/renderer/src/window/compositor.rs @@ -6,10 +6,12 @@ pub use iced_graphics::window::compositor::{Information, SurfaceError}; pub enum Compositor { Wgpu(iced_wgpu::window::Compositor), + TinySkia(iced_tiny_skia::window::Compositor), } pub enum Surface { Wgpu(iced_wgpu::window::Surface), + TinySkia(iced_tiny_skia::window::Surface), } impl iced_graphics::window::Compositor for Compositor { @@ -19,21 +21,31 @@ impl iced_graphics::window::Compositor for Compositor { fn new( settings: Self::Settings, - compatible_window: Option<&W>, + _compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - let (compositor, backend) = iced_wgpu::window::compositor::new( - iced_wgpu::Settings { + //let (compositor, backend) = iced_wgpu::window::compositor::new( + // iced_wgpu::Settings { + // default_font: settings.default_font, + // default_text_size: settings.default_text_size, + // antialiasing: settings.antialiasing, + // ..iced_wgpu::Settings::from_env() + // }, + // compatible_window, + //)?; + + //Ok(( + // Self::Wgpu(compositor), + // Renderer::new(Backend::Wgpu(backend)), + //)) + let (compositor, backend) = + iced_tiny_skia::window::compositor::new(iced_tiny_skia::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, - antialiasing: settings.antialiasing, - ..iced_wgpu::Settings::from_env() - }, - compatible_window, - )?; + }); Ok(( - Self::Wgpu(compositor), - Renderer::new(Backend::Wgpu(backend)), + Self::TinySkia(compositor), + Renderer::new(Backend::TinySkia(backend)), )) } @@ -45,6 +57,9 @@ impl iced_graphics::window::Compositor for Compositor { Self::Wgpu(compositor) => { Surface::Wgpu(compositor.create_surface(window)) } + Self::TinySkia(compositor) => { + Surface::TinySkia(compositor.create_surface(window)) + } } } @@ -58,12 +73,17 @@ impl iced_graphics::window::Compositor for Compositor { (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { compositor.configure_surface(surface, width, height); } + (Self::TinySkia(compositor), Surface::TinySkia(surface)) => { + compositor.configure_surface(surface, width, height); + } + _ => unreachable!(), } } fn fetch_information(&self) -> Information { match self { Self::Wgpu(compositor) => compositor.fetch_information(), + Self::TinySkia(compositor) => compositor.fetch_information(), } } @@ -90,6 +110,20 @@ impl iced_graphics::window::Compositor for Compositor { background_color, overlay, ), + ( + Self::TinySkia(compositor), + Backend::TinySkia(backend), + Surface::TinySkia(surface), + ) => iced_tiny_skia::window::compositor::present( + compositor, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ), + _ => unreachable!(), } }) } -- cgit From 535d7a4d57e131e661587b36e41820dd6ccccc3e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 25 Feb 2023 16:05:42 +0100 Subject: Implement basic presentation with `softbuffer` for `iced_tiny_skia` --- renderer/src/window/compositor.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'renderer') diff --git a/renderer/src/window/compositor.rs b/renderer/src/window/compositor.rs index 42afddc4..a11374ed 100644 --- a/renderer/src/window/compositor.rs +++ b/renderer/src/window/compositor.rs @@ -52,14 +52,16 @@ impl iced_graphics::window::Compositor for Compositor { fn create_surface( &mut self, window: &W, + width: u32, + height: u32, ) -> Surface { match self { Self::Wgpu(compositor) => { - Surface::Wgpu(compositor.create_surface(window)) - } - Self::TinySkia(compositor) => { - Surface::TinySkia(compositor.create_surface(window)) + Surface::Wgpu(compositor.create_surface(window, width, height)) } + Self::TinySkia(compositor) => Surface::TinySkia( + compositor.create_surface(window, width, height), + ), } } -- cgit From 4e615a65cab9dfc5fa4a17a72580c573c1c040d9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Feb 2023 01:12:06 +0100 Subject: Fix `clippy` lints --- renderer/src/backend.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'renderer') diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index a46d6f9b..b0a409dc 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -5,6 +5,7 @@ use iced_graphics::text; use std::borrow::Cow; +#[allow(clippy::large_enum_variant)] pub enum Backend { Wgpu(iced_wgpu::Backend), TinySkia(iced_tiny_skia::Backend), -- cgit From 3f6e28fa9b1b8d911f765c9efb5249a9e0c942d5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 28 Feb 2023 20:47:13 +0100 Subject: Use `iced_renderer` instead of `iced_graphics` in root crate --- renderer/Cargo.toml | 2 ++ renderer/src/lib.rs | 1 + renderer/src/widget.rs | 5 +++++ 3 files changed, 8 insertions(+) create mode 100644 renderer/src/widget.rs (limited to 'renderer') diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 5ba5d426..1f21f06b 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" [features] image = ["iced_wgpu/image", "iced_tiny_skia/image"] svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] +canvas = ["iced_graphics/canvas"] +qr_code = ["iced_graphics/qr_code"] tracing = ["iced_wgpu/tracing"] [dependencies] diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index d0ba6793..f9bfc373 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,3 +1,4 @@ +pub mod widget; pub mod window; mod backend; diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs new file mode 100644 index 00000000..417cc06f --- /dev/null +++ b/renderer/src/widget.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "qr_code")] +pub use iced_graphics::widget::qr_code; + +#[cfg(feature = "canvas")] +pub use iced_graphics::widget::canvas; -- cgit From 5fd5d1cdf8e5354788dc40729c4565ef377d3bba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Mar 2023 21:34:26 +0100 Subject: Implement `Canvas` support for `iced_tiny_skia` --- renderer/Cargo.toml | 10 +- renderer/src/backend.rs | 4 +- renderer/src/lib.rs | 14 +- renderer/src/widget.rs | 12 +- renderer/src/widget/canvas.rs | 177 +++++++++++++++++++++ renderer/src/widget/canvas/cache.rs | 85 ++++++++++ renderer/src/widget/qr_code.rs | 301 ++++++++++++++++++++++++++++++++++++ 7 files changed, 596 insertions(+), 7 deletions(-) create mode 100644 renderer/src/widget/canvas.rs create mode 100644 renderer/src/widget/canvas/cache.rs create mode 100644 renderer/src/widget/qr_code.rs (limited to 'renderer') diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 1f21f06b..429b55c2 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -6,12 +6,13 @@ edition = "2021" [features] image = ["iced_wgpu/image", "iced_tiny_skia/image"] svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] -canvas = ["iced_graphics/canvas"] -qr_code = ["iced_graphics/qr_code"] +canvas = ["iced_wgpu/canvas", "iced_tiny_skia/canvas"] +qr_code = ["canvas", "qrcode"] tracing = ["iced_wgpu/tracing"] [dependencies] raw-window-handle = "0.5" +thiserror = "1" [dependencies.iced_native] version = "0.9" @@ -30,3 +31,8 @@ iced_wgpu = { version = "0.9", path = "../wgpu" } [target.'cfg(target_arch = "wasm32")'.dependencies] iced_wgpu = { version = "0.9", path = "../wgpu", features = ["webgl"] } + +[dependencies.qrcode] +version = "0.12" +optional = true +default-features = false 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 = iced_graphics::Renderer; + +#[derive(Debug, Clone)] +pub struct Geometry(pub(crate) Primitive); + +impl From 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(renderer: &crate::Renderer, 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) { + 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, + ) { + 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>) { + 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) { + 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, +} + +#[derive(Debug, Default)] +enum State { + #[default] + Empty, + Filled { + bounds: Size, + primitive: Arc, + }, +} + +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( + &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 { + 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/renderer/src/widget/qr_code.rs b/renderer/src/widget/qr_code.rs new file mode 100644 index 00000000..aae4ec88 --- /dev/null +++ b/renderer/src/widget/qr_code.rs @@ -0,0 +1,301 @@ +//! Encode and display information in a QR code. +use crate::widget::canvas; +use crate::Renderer; + +use iced_graphics::renderer; + +use iced_native::layout; +use iced_native::widget::Tree; +use iced_native::{ + Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, +}; +use thiserror::Error; + +const DEFAULT_CELL_SIZE: u16 = 4; +const QUIET_ZONE: usize = 2; + +/// A type of matrix barcode consisting of squares arranged in a grid which +/// can be read by an imaging device, such as a camera. +#[derive(Debug)] +pub struct QRCode<'a> { + state: &'a State, + dark: Color, + light: Color, + cell_size: u16, +} + +impl<'a> QRCode<'a> { + /// Creates a new [`QRCode`] with the provided [`State`]. + pub fn new(state: &'a State) -> Self { + Self { + cell_size: DEFAULT_CELL_SIZE, + dark: Color::BLACK, + light: Color::WHITE, + state, + } + } + + /// Sets both the dark and light [`Color`]s of the [`QRCode`]. + pub fn color(mut self, dark: Color, light: Color) -> Self { + self.dark = dark; + self.light = light; + self + } + + /// Sets the size of the squares of the grid cell of the [`QRCode`]. + pub fn cell_size(mut self, cell_size: u16) -> Self { + self.cell_size = cell_size; + self + } +} + +impl<'a, Message, Theme> Widget> for QRCode<'a> { + fn width(&self) -> Length { + Length::Shrink + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + _renderer: &Renderer, + _limits: &layout::Limits, + ) -> layout::Node { + let side_length = (self.state.width + 2 * QUIET_ZONE) as f32 + * f32::from(self.cell_size); + + layout::Node::new(Size::new(side_length, side_length)) + } + + fn draw( + &self, + _state: &Tree, + renderer: &mut Renderer, + _theme: &Theme, + _style: &renderer::Style, + layout: Layout<'_>, + _cursor_position: Point, + _viewport: &Rectangle, + ) { + use iced_native::Renderer as _; + + let bounds = layout.bounds(); + let side_length = self.state.width + 2 * QUIET_ZONE; + + // Reuse cache if possible + 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.0); + }); + } +} + +impl<'a, Message, Theme> From> + for Element<'a, Message, Renderer> +{ + fn from(qr_code: QRCode<'a>) -> Self { + Self::new(qr_code) + } +} + +/// The state of a [`QRCode`]. +/// +/// It stores the data that will be displayed. +#[derive(Debug)] +pub struct State { + contents: Vec, + width: usize, + cache: canvas::Cache, +} + +impl State { + /// Creates a new [`State`] with the provided data. + /// + /// This method uses an [`ErrorCorrection::Medium`] and chooses the smallest + /// size to display the data. + pub fn new(data: impl AsRef<[u8]>) -> Result { + let encoded = qrcode::QrCode::new(data)?; + + Ok(Self::build(encoded)) + } + + /// Creates a new [`State`] with the provided [`ErrorCorrection`]. + pub fn with_error_correction( + data: impl AsRef<[u8]>, + error_correction: ErrorCorrection, + ) -> Result { + let encoded = qrcode::QrCode::with_error_correction_level( + data, + error_correction.into(), + )?; + + Ok(Self::build(encoded)) + } + + /// Creates a new [`State`] with the provided [`Version`] and + /// [`ErrorCorrection`]. + pub fn with_version( + data: impl AsRef<[u8]>, + version: Version, + error_correction: ErrorCorrection, + ) -> Result { + let encoded = qrcode::QrCode::with_version( + data, + version.into(), + error_correction.into(), + )?; + + Ok(Self::build(encoded)) + } + + fn build(encoded: qrcode::QrCode) -> Self { + let width = encoded.width(); + let contents = encoded.into_colors(); + + Self { + contents, + width, + cache: canvas::Cache::new(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The size of a [`QRCode`]. +/// +/// The higher the version the larger the grid of cells, and therefore the more +/// information the [`QRCode`] can carry. +pub enum Version { + /// A normal QR code version. It should be between 1 and 40. + Normal(u8), + + /// A micro QR code version. It should be between 1 and 4. + Micro(u8), +} + +impl From for qrcode::Version { + fn from(version: Version) -> Self { + match version { + Version::Normal(v) => qrcode::Version::Normal(i16::from(v)), + Version::Micro(v) => qrcode::Version::Micro(i16::from(v)), + } + } +} + +/// The error correction level. +/// +/// It controls the amount of data that can be damaged while still being able +/// to recover the original information. +/// +/// A higher error correction level allows for more corrupted data. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ErrorCorrection { + /// Low error correction. 7% of the data can be restored. + Low, + /// Medium error correction. 15% of the data can be restored. + Medium, + /// Quartile error correction. 25% of the data can be restored. + Quartile, + /// High error correction. 30% of the data can be restored. + High, +} + +impl From for qrcode::EcLevel { + fn from(ec_level: ErrorCorrection) -> Self { + match ec_level { + ErrorCorrection::Low => qrcode::EcLevel::L, + ErrorCorrection::Medium => qrcode::EcLevel::M, + ErrorCorrection::Quartile => qrcode::EcLevel::Q, + ErrorCorrection::High => qrcode::EcLevel::H, + } + } +} + +/// An error that occurred when building a [`State`] for a [`QRCode`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] +pub enum Error { + /// The data is too long to encode in a QR code for the chosen [`Version`]. + #[error( + "The data is too long to encode in a QR code for the chosen version" + )] + DataTooLong, + + /// The chosen [`Version`] and [`ErrorCorrection`] combination is invalid. + #[error( + "The chosen version and error correction level combination is invalid." + )] + InvalidVersion, + + /// One or more characters in the provided data are not supported by the + /// chosen [`Version`]. + #[error( + "One or more characters in the provided data are not supported by the \ + chosen version" + )] + UnsupportedCharacterSet, + + /// The chosen ECI designator is invalid. A valid designator should be + /// between 0 and 999999. + #[error( + "The chosen ECI designator is invalid. A valid designator should be \ + between 0 and 999999." + )] + InvalidEciDesignator, + + /// A character that does not belong to the character set was found. + #[error("A character that does not belong to the character set was found")] + InvalidCharacter, +} + +impl From for Error { + fn from(error: qrcode::types::QrError) -> Self { + use qrcode::types::QrError; + + match error { + QrError::DataTooLong => Error::DataTooLong, + QrError::InvalidVersion => Error::InvalidVersion, + QrError::UnsupportedCharacterSet => Error::UnsupportedCharacterSet, + QrError::InvalidEciDesignator => Error::InvalidEciDesignator, + QrError::InvalidCharacter => Error::InvalidCharacter, + } + } +} -- cgit From d13d19ba3569560edd67f20b48f37548d10ceee9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 Mar 2023 04:00:44 +0100 Subject: Rename `canvas::frame` to `canvas` in `iced_wgpu` --- renderer/src/widget/canvas.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'renderer') diff --git a/renderer/src/widget/canvas.rs b/renderer/src/widget/canvas.rs index f40a1097..35c8fff9 100644 --- a/renderer/src/widget/canvas.rs +++ b/renderer/src/widget/canvas.rs @@ -16,7 +16,7 @@ use crate::{Backend, Point, Rectangle, Size, Vector}; pub use crate::Geometry; pub enum Frame { - Wgpu(iced_wgpu::widget::canvas::Frame), + Wgpu(iced_wgpu::canvas::Frame), TinySkia(iced_tiny_skia::canvas::Frame), } @@ -33,7 +33,7 @@ impl Frame { pub fn new(renderer: &crate::Renderer, size: Size) -> Self { match renderer.backend() { Backend::Wgpu(_) => { - Frame::Wgpu(iced_wgpu::widget::canvas::Frame::new(size)) + Frame::Wgpu(iced_wgpu::canvas::Frame::new(size)) } Backend::TinySkia(_) => { Frame::TinySkia(iced_tiny_skia::canvas::Frame::new(size)) @@ -131,7 +131,7 @@ impl Frame { 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::Wgpu(iced_wgpu::canvas::Frame::new(region.size())) } Self::TinySkia(_) => Self::TinySkia( iced_tiny_skia::canvas::Frame::new(region.size()), -- cgit From 6cc48b5c62bac287b8f9f1c79c1fb7486c51b18f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 Mar 2023 04:57:55 +0100 Subject: Move `Canvas` and `QRCode` to `iced` crate Rename `canvas` modules to `geometry` in graphics subcrates --- renderer/Cargo.toml | 8 +- renderer/src/backend.rs | 4 +- renderer/src/geometry.rs | 168 ++++++++++++++++++++ renderer/src/geometry/cache.rs | 85 ++++++++++ renderer/src/lib.rs | 13 +- renderer/src/widget/canvas.rs | 177 --------------------- renderer/src/widget/canvas/cache.rs | 85 ---------- renderer/src/widget/qr_code.rs | 301 ------------------------------------ 8 files changed, 258 insertions(+), 583 deletions(-) create mode 100644 renderer/src/geometry.rs create mode 100644 renderer/src/geometry/cache.rs delete mode 100644 renderer/src/widget/canvas.rs delete mode 100644 renderer/src/widget/canvas/cache.rs delete mode 100644 renderer/src/widget/qr_code.rs (limited to 'renderer') diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 429b55c2..189f5309 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -6,8 +6,7 @@ edition = "2021" [features] image = ["iced_wgpu/image", "iced_tiny_skia/image"] svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] -canvas = ["iced_wgpu/canvas", "iced_tiny_skia/canvas"] -qr_code = ["canvas", "qrcode"] +geometry = ["iced_wgpu/geometry", "iced_tiny_skia/geometry"] tracing = ["iced_wgpu/tracing"] [dependencies] @@ -31,8 +30,3 @@ iced_wgpu = { version = "0.9", path = "../wgpu" } [target.'cfg(target_arch = "wasm32")'.dependencies] iced_wgpu = { version = "0.9", path = "../wgpu", features = ["webgl"] } - -[dependencies.qrcode] -version = "0.12" -optional = true -default-features = false diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index 6c0b4e5c..b0a409dc 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -1,4 +1,4 @@ -use crate::{Font, Geometry, Point, Size}; +use crate::{Font, Point, Size}; use iced_graphics::backend; use iced_graphics::text; @@ -12,8 +12,6 @@ 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/geometry.rs b/renderer/src/geometry.rs new file mode 100644 index 00000000..e491ea73 --- /dev/null +++ b/renderer/src/geometry.rs @@ -0,0 +1,168 @@ +mod cache; + +pub use cache::Cache; + +pub use iced_graphics::geometry::*; + +use crate::{Backend, Point, Rectangle, Size, Vector}; + +pub enum Frame { + Wgpu(iced_wgpu::geometry::Frame), + TinySkia(iced_tiny_skia::geometry::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(renderer: &crate::Renderer, size: Size) -> Self { + match renderer.backend() { + Backend::Wgpu(_) => { + Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) + } + Backend::TinySkia(_) => { + Frame::TinySkia(iced_tiny_skia::geometry::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) { + 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, + ) { + 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>) { + 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) { + 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::geometry::Frame::new(region.size())) + } + Self::TinySkia(_) => Self::TinySkia( + iced_tiny_skia::geometry::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/geometry/cache.rs b/renderer/src/geometry/cache.rs new file mode 100644 index 00000000..1f1febdd --- /dev/null +++ b/renderer/src/geometry/cache.rs @@ -0,0 +1,85 @@ +use crate::geometry::{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, +} + +#[derive(Debug, Default)] +enum State { + #[default] + Empty, + Filled { + bounds: Size, + primitive: Arc, + }, +} + +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( + &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 { + 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/renderer/src/lib.rs b/renderer/src/lib.rs index d9c85e82..aae3322d 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,6 +1,8 @@ -pub mod widget; pub mod window; +#[cfg(feature = "geometry")] +pub mod geometry; + mod backend; mod settings; @@ -19,12 +21,3 @@ pub use iced_graphics::{ /// [`iced`]: https://github.com/iced-rs/iced pub type Renderer = iced_graphics::Renderer; - -#[derive(Debug, Clone)] -pub struct Geometry(pub(crate) Primitive); - -impl From for Primitive { - fn from(geometry: Geometry) -> Self { - geometry.0 - } -} diff --git a/renderer/src/widget/canvas.rs b/renderer/src/widget/canvas.rs deleted file mode 100644 index 35c8fff9..00000000 --- a/renderer/src/widget/canvas.rs +++ /dev/null @@ -1,177 +0,0 @@ -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::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(renderer: &crate::Renderer, size: Size) -> Self { - match renderer.backend() { - Backend::Wgpu(_) => { - Frame::Wgpu(iced_wgpu::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) { - 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, - ) { - 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>) { - 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) { - 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::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 deleted file mode 100644 index 7d6b4811..00000000 --- a/renderer/src/widget/canvas/cache.rs +++ /dev/null @@ -1,85 +0,0 @@ -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, -} - -#[derive(Debug, Default)] -enum State { - #[default] - Empty, - Filled { - bounds: Size, - primitive: Arc, - }, -} - -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( - &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 { - 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/renderer/src/widget/qr_code.rs b/renderer/src/widget/qr_code.rs deleted file mode 100644 index aae4ec88..00000000 --- a/renderer/src/widget/qr_code.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! Encode and display information in a QR code. -use crate::widget::canvas; -use crate::Renderer; - -use iced_graphics::renderer; - -use iced_native::layout; -use iced_native::widget::Tree; -use iced_native::{ - Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, -}; -use thiserror::Error; - -const DEFAULT_CELL_SIZE: u16 = 4; -const QUIET_ZONE: usize = 2; - -/// A type of matrix barcode consisting of squares arranged in a grid which -/// can be read by an imaging device, such as a camera. -#[derive(Debug)] -pub struct QRCode<'a> { - state: &'a State, - dark: Color, - light: Color, - cell_size: u16, -} - -impl<'a> QRCode<'a> { - /// Creates a new [`QRCode`] with the provided [`State`]. - pub fn new(state: &'a State) -> Self { - Self { - cell_size: DEFAULT_CELL_SIZE, - dark: Color::BLACK, - light: Color::WHITE, - state, - } - } - - /// Sets both the dark and light [`Color`]s of the [`QRCode`]. - pub fn color(mut self, dark: Color, light: Color) -> Self { - self.dark = dark; - self.light = light; - self - } - - /// Sets the size of the squares of the grid cell of the [`QRCode`]. - pub fn cell_size(mut self, cell_size: u16) -> Self { - self.cell_size = cell_size; - self - } -} - -impl<'a, Message, Theme> Widget> for QRCode<'a> { - fn width(&self) -> Length { - Length::Shrink - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - _renderer: &Renderer, - _limits: &layout::Limits, - ) -> layout::Node { - let side_length = (self.state.width + 2 * QUIET_ZONE) as f32 - * f32::from(self.cell_size); - - layout::Node::new(Size::new(side_length, side_length)) - } - - fn draw( - &self, - _state: &Tree, - renderer: &mut Renderer, - _theme: &Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - use iced_native::Renderer as _; - - let bounds = layout.bounds(); - let side_length = self.state.width + 2 * QUIET_ZONE; - - // Reuse cache if possible - 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.0); - }); - } -} - -impl<'a, Message, Theme> From> - for Element<'a, Message, Renderer> -{ - fn from(qr_code: QRCode<'a>) -> Self { - Self::new(qr_code) - } -} - -/// The state of a [`QRCode`]. -/// -/// It stores the data that will be displayed. -#[derive(Debug)] -pub struct State { - contents: Vec, - width: usize, - cache: canvas::Cache, -} - -impl State { - /// Creates a new [`State`] with the provided data. - /// - /// This method uses an [`ErrorCorrection::Medium`] and chooses the smallest - /// size to display the data. - pub fn new(data: impl AsRef<[u8]>) -> Result { - let encoded = qrcode::QrCode::new(data)?; - - Ok(Self::build(encoded)) - } - - /// Creates a new [`State`] with the provided [`ErrorCorrection`]. - pub fn with_error_correction( - data: impl AsRef<[u8]>, - error_correction: ErrorCorrection, - ) -> Result { - let encoded = qrcode::QrCode::with_error_correction_level( - data, - error_correction.into(), - )?; - - Ok(Self::build(encoded)) - } - - /// Creates a new [`State`] with the provided [`Version`] and - /// [`ErrorCorrection`]. - pub fn with_version( - data: impl AsRef<[u8]>, - version: Version, - error_correction: ErrorCorrection, - ) -> Result { - let encoded = qrcode::QrCode::with_version( - data, - version.into(), - error_correction.into(), - )?; - - Ok(Self::build(encoded)) - } - - fn build(encoded: qrcode::QrCode) -> Self { - let width = encoded.width(); - let contents = encoded.into_colors(); - - Self { - contents, - width, - cache: canvas::Cache::new(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// The size of a [`QRCode`]. -/// -/// The higher the version the larger the grid of cells, and therefore the more -/// information the [`QRCode`] can carry. -pub enum Version { - /// A normal QR code version. It should be between 1 and 40. - Normal(u8), - - /// A micro QR code version. It should be between 1 and 4. - Micro(u8), -} - -impl From for qrcode::Version { - fn from(version: Version) -> Self { - match version { - Version::Normal(v) => qrcode::Version::Normal(i16::from(v)), - Version::Micro(v) => qrcode::Version::Micro(i16::from(v)), - } - } -} - -/// The error correction level. -/// -/// It controls the amount of data that can be damaged while still being able -/// to recover the original information. -/// -/// A higher error correction level allows for more corrupted data. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ErrorCorrection { - /// Low error correction. 7% of the data can be restored. - Low, - /// Medium error correction. 15% of the data can be restored. - Medium, - /// Quartile error correction. 25% of the data can be restored. - Quartile, - /// High error correction. 30% of the data can be restored. - High, -} - -impl From for qrcode::EcLevel { - fn from(ec_level: ErrorCorrection) -> Self { - match ec_level { - ErrorCorrection::Low => qrcode::EcLevel::L, - ErrorCorrection::Medium => qrcode::EcLevel::M, - ErrorCorrection::Quartile => qrcode::EcLevel::Q, - ErrorCorrection::High => qrcode::EcLevel::H, - } - } -} - -/// An error that occurred when building a [`State`] for a [`QRCode`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] -pub enum Error { - /// The data is too long to encode in a QR code for the chosen [`Version`]. - #[error( - "The data is too long to encode in a QR code for the chosen version" - )] - DataTooLong, - - /// The chosen [`Version`] and [`ErrorCorrection`] combination is invalid. - #[error( - "The chosen version and error correction level combination is invalid." - )] - InvalidVersion, - - /// One or more characters in the provided data are not supported by the - /// chosen [`Version`]. - #[error( - "One or more characters in the provided data are not supported by the \ - chosen version" - )] - UnsupportedCharacterSet, - - /// The chosen ECI designator is invalid. A valid designator should be - /// between 0 and 999999. - #[error( - "The chosen ECI designator is invalid. A valid designator should be \ - between 0 and 999999." - )] - InvalidEciDesignator, - - /// A character that does not belong to the character set was found. - #[error("A character that does not belong to the character set was found")] - InvalidCharacter, -} - -impl From for Error { - fn from(error: qrcode::types::QrError) -> Self { - use qrcode::types::QrError; - - match error { - QrError::DataTooLong => Error::DataTooLong, - QrError::InvalidVersion => Error::InvalidVersion, - QrError::UnsupportedCharacterSet => Error::UnsupportedCharacterSet, - QrError::InvalidEciDesignator => Error::InvalidEciDesignator, - QrError::InvalidCharacter => Error::InvalidCharacter, - } - } -} -- cgit From 3a0d34c0240f4421737a6a08761f99d6f8140d02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Mar 2023 05:37:11 +0100 Subject: Create `iced_widget` subcrate and re-organize the whole codebase --- renderer/Cargo.toml | 4 -- renderer/src/backend.rs | 11 ++-- renderer/src/compositor.rs | 133 ++++++++++++++++++++++++++++++++++++++ renderer/src/geometry.rs | 6 +- renderer/src/geometry/cache.rs | 4 +- renderer/src/lib.rs | 14 ++-- renderer/src/settings.rs | 3 +- renderer/src/window.rs | 3 - renderer/src/window/compositor.rs | 132 ------------------------------------- 9 files changed, 151 insertions(+), 159 deletions(-) create mode 100644 renderer/src/compositor.rs delete mode 100644 renderer/src/window.rs delete mode 100644 renderer/src/window/compositor.rs (limited to 'renderer') diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 189f5309..d0420ad0 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -13,10 +13,6 @@ tracing = ["iced_wgpu/tracing"] raw-window-handle = "0.5" thiserror = "1" -[dependencies.iced_native] -version = "0.9" -path = "../native" - [dependencies.iced_graphics] version = "0.7" path = "../graphics" diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index b0a409dc..bf5da322 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -1,7 +1,6 @@ -use crate::{Font, Point, Size}; - -use iced_graphics::backend; -use iced_graphics::text; +use crate::core::text; +use crate::core::{Font, Point, Size}; +use crate::graphics::backend; use std::borrow::Cow; @@ -99,7 +98,7 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { + fn dimensions(&self, handle: &crate::core::image::Handle) -> Size { match self { Self::Wgpu(backend) => backend.dimensions(handle), Self::TinySkia(backend) => backend.dimensions(handle), @@ -111,7 +110,7 @@ impl backend::Image for Backend { impl backend::Svg for Backend { fn viewport_dimensions( &self, - handle: &iced_native::svg::Handle, + handle: &crate::core::svg::Handle, ) -> Size { match self { Self::Wgpu(backend) => backend.viewport_dimensions(handle), diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs new file mode 100644 index 00000000..0cdcb293 --- /dev/null +++ b/renderer/src/compositor.rs @@ -0,0 +1,133 @@ +use crate::core::Color; +use crate::graphics::compositor::{Information, SurfaceError}; +use crate::graphics::{Error, Viewport}; +use crate::{Backend, Renderer, Settings}; + +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + +pub enum Compositor { + Wgpu(iced_wgpu::window::Compositor), + TinySkia(iced_tiny_skia::window::Compositor), +} + +pub enum Surface { + Wgpu(iced_wgpu::window::Surface), + TinySkia(iced_tiny_skia::window::Surface), +} + +impl crate::graphics::Compositor for Compositor { + type Settings = Settings; + type Renderer = Renderer; + type Surface = Surface; + + fn new( + settings: Self::Settings, + _compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + //let (compositor, backend) = iced_wgpu::window::compositor::new( + // iced_wgpu::Settings { + // default_font: settings.default_font, + // default_text_size: settings.default_text_size, + // antialiasing: settings.antialiasing, + // ..iced_wgpu::Settings::from_env() + // }, + // compatible_window, + //)?; + + //Ok(( + // Self::Wgpu(compositor), + // Renderer::new(Backend::Wgpu(backend)), + //)) + let (compositor, backend) = + iced_tiny_skia::window::compositor::new(iced_tiny_skia::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + }); + + Ok(( + Self::TinySkia(compositor), + Renderer::new(Backend::TinySkia(backend)), + )) + } + + fn create_surface( + &mut self, + window: &W, + width: u32, + height: u32, + ) -> Surface { + match self { + Self::Wgpu(compositor) => { + Surface::Wgpu(compositor.create_surface(window, width, height)) + } + Self::TinySkia(compositor) => Surface::TinySkia( + compositor.create_surface(window, width, height), + ), + } + } + + fn configure_surface( + &mut self, + surface: &mut Surface, + width: u32, + height: u32, + ) { + match (self, surface) { + (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { + compositor.configure_surface(surface, width, height); + } + (Self::TinySkia(compositor), Surface::TinySkia(surface)) => { + compositor.configure_surface(surface, width, height); + } + _ => unreachable!(), + } + } + + fn fetch_information(&self) -> Information { + match self { + Self::Wgpu(compositor) => compositor.fetch_information(), + Self::TinySkia(compositor) => compositor.fetch_information(), + } + } + + fn present>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Result<(), SurfaceError> { + renderer.with_primitives(|backend, primitives| { + match (self, backend, surface) { + ( + Self::Wgpu(compositor), + Backend::Wgpu(backend), + Surface::Wgpu(surface), + ) => iced_wgpu::window::compositor::present( + compositor, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ), + ( + Self::TinySkia(compositor), + Backend::TinySkia(backend), + Surface::TinySkia(surface), + ) => iced_tiny_skia::window::compositor::present( + compositor, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ), + _ => unreachable!(), + } + }) + } +} diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs index e491ea73..361fc86b 100644 --- a/renderer/src/geometry.rs +++ b/renderer/src/geometry.rs @@ -2,9 +2,9 @@ mod cache; pub use cache::Cache; -pub use iced_graphics::geometry::*; - -use crate::{Backend, Point, Rectangle, Size, Vector}; +use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::{Fill, Geometry, Path, Stroke, Text}; +use crate::Backend; pub enum Frame { Wgpu(iced_wgpu::geometry::Frame), diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs index 1f1febdd..2a3534d0 100644 --- a/renderer/src/geometry/cache.rs +++ b/renderer/src/geometry/cache.rs @@ -1,5 +1,7 @@ +use crate::core::Size; use crate::geometry::{Frame, Geometry}; -use crate::{Primitive, Renderer, Size}; +use crate::graphics::Primitive; +use crate::Renderer; use std::cell::RefCell; use std::sync::Arc; diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index aae3322d..22ec7bd1 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,4 +1,4 @@ -pub mod window; +pub mod compositor; #[cfg(feature = "geometry")] pub mod geometry; @@ -6,18 +6,14 @@ pub mod geometry; mod backend; mod settings; -pub use iced_graphics::primitive; +pub use iced_graphics as graphics; +pub use iced_graphics::core; pub use backend::Backend; -pub use primitive::Primitive; +pub use compositor::Compositor; pub use settings::Settings; -pub use iced_graphics::{ - Antialiasing, Color, Error, Font, Point, Rectangle, Size, Vector, Viewport, -}; - /// The default graphics renderer for [`iced`]. /// /// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer = - iced_graphics::Renderer; +pub type Renderer = iced_graphics::Renderer; diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs index c4dc248b..d32c87d3 100644 --- a/renderer/src/settings.rs +++ b/renderer/src/settings.rs @@ -1,4 +1,5 @@ -use crate::{Antialiasing, Font}; +use crate::core::Font; +use crate::graphics::Antialiasing; /// The settings of a [`Backend`]. /// diff --git a/renderer/src/window.rs b/renderer/src/window.rs deleted file mode 100644 index a7c8911b..00000000 --- a/renderer/src/window.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod compositor; - -pub use compositor::Compositor; diff --git a/renderer/src/window/compositor.rs b/renderer/src/window/compositor.rs deleted file mode 100644 index a11374ed..00000000 --- a/renderer/src/window/compositor.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; - -use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; - -pub use iced_graphics::window::compositor::{Information, SurfaceError}; - -pub enum Compositor { - Wgpu(iced_wgpu::window::Compositor), - TinySkia(iced_tiny_skia::window::Compositor), -} - -pub enum Surface { - Wgpu(iced_wgpu::window::Surface), - TinySkia(iced_tiny_skia::window::Surface), -} - -impl iced_graphics::window::Compositor for Compositor { - type Settings = Settings; - type Renderer = Renderer; - type Surface = Surface; - - fn new( - settings: Self::Settings, - _compatible_window: Option<&W>, - ) -> Result<(Self, Self::Renderer), Error> { - //let (compositor, backend) = iced_wgpu::window::compositor::new( - // iced_wgpu::Settings { - // default_font: settings.default_font, - // default_text_size: settings.default_text_size, - // antialiasing: settings.antialiasing, - // ..iced_wgpu::Settings::from_env() - // }, - // compatible_window, - //)?; - - //Ok(( - // Self::Wgpu(compositor), - // Renderer::new(Backend::Wgpu(backend)), - //)) - let (compositor, backend) = - iced_tiny_skia::window::compositor::new(iced_tiny_skia::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - }); - - Ok(( - Self::TinySkia(compositor), - Renderer::new(Backend::TinySkia(backend)), - )) - } - - fn create_surface( - &mut self, - window: &W, - width: u32, - height: u32, - ) -> Surface { - match self { - Self::Wgpu(compositor) => { - Surface::Wgpu(compositor.create_surface(window, width, height)) - } - Self::TinySkia(compositor) => Surface::TinySkia( - compositor.create_surface(window, width, height), - ), - } - } - - fn configure_surface( - &mut self, - surface: &mut Surface, - width: u32, - height: u32, - ) { - match (self, surface) { - (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { - compositor.configure_surface(surface, width, height); - } - (Self::TinySkia(compositor), Surface::TinySkia(surface)) => { - compositor.configure_surface(surface, width, height); - } - _ => unreachable!(), - } - } - - fn fetch_information(&self) -> Information { - match self { - Self::Wgpu(compositor) => compositor.fetch_information(), - Self::TinySkia(compositor) => compositor.fetch_information(), - } - } - - fn present>( - &mut self, - renderer: &mut Self::Renderer, - surface: &mut Self::Surface, - viewport: &Viewport, - background_color: Color, - overlay: &[T], - ) -> Result<(), SurfaceError> { - renderer.with_primitives(|backend, primitives| { - match (self, backend, surface) { - ( - Self::Wgpu(compositor), - Backend::Wgpu(backend), - Surface::Wgpu(surface), - ) => iced_wgpu::window::compositor::present( - compositor, - backend, - surface, - primitives, - viewport, - background_color, - overlay, - ), - ( - Self::TinySkia(compositor), - Backend::TinySkia(backend), - Surface::TinySkia(surface), - ) => iced_tiny_skia::window::compositor::present( - compositor, - backend, - surface, - primitives, - viewport, - background_color, - overlay, - ), - _ => unreachable!(), - } - }) - } -} -- cgit From 06bbcc310e6e759a0839df6ca391ea5e0f0ee609 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Mar 2023 06:40:20 +0100 Subject: Move `webgl` feature selection for `wgpu` into `iced_wgpu` --- renderer/Cargo.toml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'renderer') diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index d0420ad0..560bf2e1 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -17,12 +17,10 @@ thiserror = "1" version = "0.7" path = "../graphics" +[dependencies.iced_wgpu] +version = "0.9" +path = "../wgpu" + [dependencies.iced_tiny_skia] version = "0.1" path = "../tiny_skia" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_wgpu = { version = "0.9", path = "../wgpu" } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.9", path = "../wgpu", features = ["webgl"] } -- cgit From 9b4bcd287a7f4822314e158990d1dc023d5aab51 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 6 Mar 2023 22:10:13 +0100 Subject: Introduce backend feature flags in `iced_renderer` --- renderer/Cargo.toml | 6 ++- renderer/src/backend.rs | 76 +++++++++++------------------- renderer/src/compositor.rs | 112 +++++++++++++++++++++++++++++++++------------ renderer/src/geometry.rs | 41 +++++++++++------ renderer/src/lib.rs | 3 ++ 5 files changed, 143 insertions(+), 95 deletions(-) (limited to 'renderer') diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 560bf2e1..629c11ba 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -4,9 +4,11 @@ version = "0.1.0" edition = "2021" [features] +wgpu = ["iced_wgpu"] +tiny-skia = ["iced_tiny_skia"] image = ["iced_wgpu/image", "iced_tiny_skia/image"] svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] -geometry = ["iced_wgpu/geometry", "iced_tiny_skia/geometry"] +geometry = ["iced_graphics/geometry", "iced_wgpu?/geometry", "iced_tiny_skia?/geometry"] tracing = ["iced_wgpu/tracing"] [dependencies] @@ -20,7 +22,9 @@ path = "../graphics" [dependencies.iced_wgpu] version = "0.9" path = "../wgpu" +optional = true [dependencies.iced_tiny_skia] version = "0.1" path = "../tiny_skia" +optional = true diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index bf5da322..e77b708b 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -6,16 +6,26 @@ use std::borrow::Cow; #[allow(clippy::large_enum_variant)] pub enum Backend { + #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::Backend), + #[cfg(feature = "tiny-skia")] TinySkia(iced_tiny_skia::Backend), } +macro_rules! delegate { + ($backend:expr, $name:ident, $body:expr) => { + match $backend { + #[cfg(feature = "wgpu")] + Self::Wgpu($name) => $body, + #[cfg(feature = "tiny-skia")] + Self::TinySkia($name) => $body, + } + }; +} + impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { - match self { - Self::Wgpu(backend) => backend.trim_measurements(), - Self::TinySkia(backend) => backend.trim_measurements(), - } + delegate!(self, backend, backend.trim_measurements()); } } @@ -25,17 +35,11 @@ impl backend::Text for Backend { const ARROW_DOWN_ICON: char = '\u{e800}'; fn default_font(&self) -> Font { - match self { - Self::Wgpu(backend) => backend.default_font(), - Self::TinySkia(backend) => backend.default_font(), - } + delegate!(self, backend, backend.default_font()) } fn default_size(&self) -> f32 { - match self { - Self::Wgpu(backend) => backend.default_size(), - Self::TinySkia(backend) => backend.default_size(), - } + delegate!(self, backend, backend.default_size()) } fn measure( @@ -45,14 +49,7 @@ impl backend::Text for Backend { font: Font, bounds: Size, ) -> (f32, f32) { - match self { - Self::Wgpu(backend) => { - backend.measure(contents, size, font, bounds) - } - Self::TinySkia(backend) => { - backend.measure(contents, size, font, bounds) - } - } + delegate!(self, backend, backend.measure(contents, size, font, bounds)) } fn hit_test( @@ -64,45 +61,29 @@ impl backend::Text for Backend { position: Point, nearest_only: bool, ) -> Option { - match self { - Self::Wgpu(backend) => backend.hit_test( - contents, - size, - font, - bounds, - position, - nearest_only, - ), - Self::TinySkia(backend) => backend.hit_test( + delegate!( + self, + backend, + backend.hit_test( contents, size, font, bounds, position, - nearest_only, - ), - } + nearest_only + ) + ) } fn load_font(&mut self, font: Cow<'static, [u8]>) { - match self { - Self::Wgpu(backend) => { - backend.load_font(font); - } - Self::TinySkia(backend) => { - backend.load_font(font); - } - } + delegate!(self, backend, backend.load_font(font)); } } #[cfg(feature = "image")] impl backend::Image for Backend { fn dimensions(&self, handle: &crate::core::image::Handle) -> Size { - match self { - Self::Wgpu(backend) => backend.dimensions(handle), - Self::TinySkia(backend) => backend.dimensions(handle), - } + delegate!(self, backend, backend.dimensions(handle)) } } @@ -112,9 +93,6 @@ impl backend::Svg for Backend { &self, handle: &crate::core::svg::Handle, ) -> Size { - match self { - Self::Wgpu(backend) => backend.viewport_dimensions(handle), - Self::TinySkia(backend) => backend.viewport_dimensions(handle), - } + delegate!(self, backend, backend.viewport_dimensions(handle)) } } diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index 0cdcb293..218e7e33 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -1,17 +1,21 @@ use crate::core::Color; use crate::graphics::compositor::{Information, SurfaceError}; use crate::graphics::{Error, Viewport}; -use crate::{Backend, Renderer, Settings}; +use crate::{Renderer, Settings}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; pub enum Compositor { + #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::window::Compositor), + #[cfg(feature = "tiny-skia")] TinySkia(iced_tiny_skia::window::Compositor), } pub enum Surface { + #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::window::Surface), + #[cfg(feature = "tiny-skia")] TinySkia(iced_tiny_skia::window::Surface), } @@ -22,32 +26,65 @@ impl crate::graphics::Compositor for Compositor { fn new( settings: Self::Settings, - _compatible_window: Option<&W>, + compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - //let (compositor, backend) = iced_wgpu::window::compositor::new( - // iced_wgpu::Settings { - // default_font: settings.default_font, - // default_text_size: settings.default_text_size, - // antialiasing: settings.antialiasing, - // ..iced_wgpu::Settings::from_env() - // }, - // compatible_window, - //)?; - - //Ok(( - // Self::Wgpu(compositor), - // Renderer::new(Backend::Wgpu(backend)), - //)) - let (compositor, backend) = - iced_tiny_skia::window::compositor::new(iced_tiny_skia::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - }); - - Ok(( - Self::TinySkia(compositor), - Renderer::new(Backend::TinySkia(backend)), - )) + #[cfg(feature = "wgpu")] + let new_wgpu = |settings: Self::Settings, compatible_window| { + let (compositor, backend) = iced_wgpu::window::compositor::new( + iced_wgpu::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..iced_wgpu::Settings::from_env() + }, + compatible_window, + )?; + + Ok(( + Self::Wgpu(compositor), + Renderer::new(crate::Backend::Wgpu(backend)), + )) + }; + + #[cfg(feature = "tiny-skia")] + let new_tiny_skia = |settings: Self::Settings, _compatible_window| { + let (compositor, backend) = iced_tiny_skia::window::compositor::new( + iced_tiny_skia::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + }, + ); + + Ok(( + Self::TinySkia(compositor), + Renderer::new(crate::Backend::TinySkia(backend)), + )) + }; + + let fail = |_, _| Err(Error::GraphicsAdapterNotFound); + + let candidates = &[ + #[cfg(feature = "wgpu")] + new_wgpu, + #[cfg(feature = "tiny-skia")] + new_tiny_skia, + fail, + ]; + + let mut error = Error::GraphicsAdapterNotFound; + + for candidate in candidates { + match candidate(settings, compatible_window) { + Ok((compositor, renderer)) => { + return Ok((compositor, renderer)) + } + Err(new_error) => { + error = new_error; + } + } + } + + Err(error) } fn create_surface( @@ -57,9 +94,11 @@ impl crate::graphics::Compositor for Compositor { height: u32, ) -> Surface { match self { + #[cfg(feature = "wgpu")] Self::Wgpu(compositor) => { Surface::Wgpu(compositor.create_surface(window, width, height)) } + #[cfg(feature = "tiny-skia")] Self::TinySkia(compositor) => Surface::TinySkia( compositor.create_surface(window, width, height), ), @@ -73,19 +112,26 @@ impl crate::graphics::Compositor for Compositor { height: u32, ) { match (self, surface) { + #[cfg(feature = "wgpu")] (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { compositor.configure_surface(surface, width, height); } + #[cfg(feature = "tiny-skia")] (Self::TinySkia(compositor), Surface::TinySkia(surface)) => { compositor.configure_surface(surface, width, height); } - _ => unreachable!(), + #[allow(unreachable_patterns)] + _ => panic!( + "The provided surface is not compatible with the compositor." + ), } } fn fetch_information(&self) -> Information { match self { + #[cfg(feature = "wgpu")] Self::Wgpu(compositor) => compositor.fetch_information(), + #[cfg(feature = "tiny-skia")] Self::TinySkia(compositor) => compositor.fetch_information(), } } @@ -100,9 +146,10 @@ impl crate::graphics::Compositor for Compositor { ) -> Result<(), SurfaceError> { renderer.with_primitives(|backend, primitives| { match (self, backend, surface) { + #[cfg(feature = "wgpu")] ( Self::Wgpu(compositor), - Backend::Wgpu(backend), + crate::Backend::Wgpu(backend), Surface::Wgpu(surface), ) => iced_wgpu::window::compositor::present( compositor, @@ -113,9 +160,10 @@ impl crate::graphics::Compositor for Compositor { background_color, overlay, ), + #[cfg(feature = "tiny-skia")] ( Self::TinySkia(compositor), - Backend::TinySkia(backend), + crate::Backend::TinySkia(backend), Surface::TinySkia(surface), ) => iced_tiny_skia::window::compositor::present( compositor, @@ -126,7 +174,11 @@ impl crate::graphics::Compositor for Compositor { background_color, overlay, ), - _ => unreachable!(), + #[allow(unreachable_patterns)] + _ => panic!( + "The provided renderer or surface are not compatible \ + with the compositor." + ), } }) } diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs index 361fc86b..21ef2c06 100644 --- a/renderer/src/geometry.rs +++ b/renderer/src/geometry.rs @@ -7,14 +7,18 @@ use crate::graphics::geometry::{Fill, Geometry, Path, Stroke, Text}; use crate::Backend; pub enum Frame { + #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::geometry::Frame), + #[cfg(feature = "tiny-skia")] TinySkia(iced_tiny_skia::geometry::Frame), } macro_rules! delegate { - ($frame:expr, $name:ident => $body:expr) => { + ($frame:expr, $name:ident, $body:expr) => { match $frame { + #[cfg(feature = "wgpu")] Self::Wgpu($name) => $body, + #[cfg(feature = "tiny-skia")] Self::TinySkia($name) => $body, } }; @@ -23,9 +27,11 @@ macro_rules! delegate { impl Frame { pub fn new(renderer: &crate::Renderer, size: Size) -> Self { match renderer.backend() { + #[cfg(feature = "wgpu")] Backend::Wgpu(_) => { Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) } + #[cfg(feature = "tiny-skia")] Backend::TinySkia(_) => { Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size)) } @@ -35,31 +41,31 @@ impl Frame { /// Returns the width of the [`Frame`]. #[inline] pub fn width(&self) -> f32 { - delegate!(self, frame => frame.width()) + delegate!(self, frame, frame.width()) } /// Returns the height of the [`Frame`]. #[inline] pub fn height(&self) -> f32 { - delegate!(self, frame => frame.height()) + delegate!(self, frame, frame.height()) } /// Returns the dimensions of the [`Frame`]. #[inline] pub fn size(&self) -> Size { - delegate!(self, frame => frame.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()) + 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) { - delegate!(self, frame => frame.fill(path, fill)); + delegate!(self, frame, frame.fill(path, fill)); } /// Draws an axis-aligned rectangle given its top-left corner coordinate and @@ -70,13 +76,13 @@ impl Frame { size: Size, fill: impl Into, ) { - delegate!(self, frame => frame.fill_rectangle(top_left, size, 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>) { - delegate!(self, frame => frame.stroke(path, stroke)); + delegate!(self, frame, frame.stroke(path, stroke)); } /// Draws the characters of the given [`Text`] on the [`Frame`], filling @@ -95,7 +101,7 @@ impl Frame { /// /// [`Canvas`]: crate::widget::Canvas pub fn fill_text(&mut self, text: impl Into) { - delegate!(self, frame => frame.fill_text(text)); + delegate!(self, frame, frame.fill_text(text)); } /// Stores the current transform of the [`Frame`] and executes the given @@ -105,11 +111,11 @@ impl Frame { /// operations in different coordinate systems. #[inline] pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { - delegate!(self, frame => frame.push_transform()); + delegate!(self, frame, frame.push_transform()); f(self); - delegate!(self, frame => frame.pop_transform()); + delegate!(self, frame, frame.pop_transform()); } /// Executes the given drawing operations within a [`Rectangle`] region, @@ -121,9 +127,11 @@ impl Frame { #[inline] pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { let mut frame = match self { + #[cfg(feature = "wgpu")] Self::Wgpu(_) => { Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) } + #[cfg(feature = "tiny-skia")] Self::TinySkia(_) => Self::TinySkia( iced_tiny_skia::geometry::Frame::new(region.size()), ), @@ -134,12 +142,15 @@ impl Frame { let translation = Vector::new(region.x, region.y); match (self, frame) { + #[cfg(feature = "wgpu")] (Self::Wgpu(target), Self::Wgpu(frame)) => { target.clip(frame, translation); } + #[cfg(feature = "tiny-skia")] (Self::TinySkia(target), Self::TinySkia(frame)) => { target.clip(frame, translation); } + #[allow(unreachable_patterns)] _ => unreachable!(), }; } @@ -147,22 +158,22 @@ impl Frame { /// Applies a translation to the current transform of the [`Frame`]. #[inline] pub fn translate(&mut self, translation: Vector) { - delegate!(self, frame => frame.translate(translation)); + 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)); + 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)); + delegate!(self, frame, frame.scale(scale)); } pub fn into_geometry(self) -> Geometry { - Geometry(delegate!(self, frame => frame.into_primitive())) + Geometry(delegate!(self, frame, frame.into_primitive())) } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 22ec7bd1..ba737b11 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))] +compile_error!("No backend selected. Enable at least one backend feature: `wgpu` or `tiny-skia`."); + pub mod compositor; #[cfg(feature = "geometry")] -- cgit