From 5007e548ada35b56aa26d68688ded47d0ea1f25b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Mar 2024 07:08:25 +0100 Subject: Fix redundant import in `color_palette` example --- examples/color_palette/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 29337508..d9325edb 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -6,9 +6,7 @@ use iced::{ Color, Element, Font, Length, Pixels, Point, Rectangle, Renderer, Size, Vector, }; -use palette::{ - self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue, -}; +use palette::{convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue}; use std::marker::PhantomData; use std::ops::RangeInclusive; -- cgit From 8ce16aba6204cb5c02a709cdf79c309f7b7e0196 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Mar 2024 07:13:34 +0100 Subject: Fix redundant import in `window::redraw_request` --- core/src/window/redraw_request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/window/redraw_request.rs b/core/src/window/redraw_request.rs index 8a59e83c..b0c000d6 100644 --- a/core/src/window/redraw_request.rs +++ b/core/src/window/redraw_request.rs @@ -13,7 +13,7 @@ pub enum RedrawRequest { #[cfg(test)] mod tests { use super::*; - use crate::time::{Duration, Instant}; + use crate::time::Duration; #[test] fn ordering() { -- cgit From bf93c82fac4519e5320f9caee8bde2fdc1c6e41a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Mar 2024 05:41:56 +0100 Subject: Try to find an `#iced` element by default on Wasm --- core/src/window/settings/wasm.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/window/settings/wasm.rs b/core/src/window/settings/wasm.rs index 8e0f1bbc..30e60b6a 100644 --- a/core/src/window/settings/wasm.rs +++ b/core/src/window/settings/wasm.rs @@ -1,11 +1,21 @@ //! Platform specific settings for WebAssembly. /// The platform specific window settings of an application. -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct PlatformSpecific { /// The identifier of a DOM element that will be replaced with the /// application. /// /// If set to `None`, the application will be appended to the HTML body. + /// + /// By default, it is set to `"iced"`. pub target: Option, } + +impl Default for PlatformSpecific { + fn default() -> Self { + Self { + target: Some(String::from("iced")), + } + } +} -- cgit From 06df0b7b08a2448a2a6ee72fa908b1064f1101e8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Mar 2024 06:37:40 +0100 Subject: Update `winit` fork --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f446e2af..3638102b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,4 +158,4 @@ web-time = "0.2" wgpu = "0.19" winapi = "0.3" window_clipboard = "0.4.1" -winit = { git = "https://github.com/iced-rs/winit.git", rev = "b91e39ece2c0d378c3b80da7f3ab50e17bb798a5" } +winit = { git = "https://github.com/iced-rs/winit.git", rev = "592bd152f6d5786fae7d918532d7db752c0d164f" } -- cgit From 9db6ac8f202ebdc1453edee01da0b30aee0949d8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Mar 2024 23:58:17 +0100 Subject: Introduce `auto-detect-theme` feature --- Cargo.toml | 6 +++++- core/Cargo.toml | 6 ++++++ core/src/theme.rs | 25 +++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3638102b..b82c0f67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ all-features = true maintenance = { status = "actively-developed" } [features] -default = ["wgpu", "fira-sans"] +default = ["wgpu", "fira-sans", "auto-detect-theme"] # Enable the `wgpu` GPU-accelerated renderer backend wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] # Enables the `Image` widget @@ -53,8 +53,11 @@ multi-window = ["iced_winit/multi-window"] advanced = [] # Enables embedding Fira Sans as the default font on Wasm builds fira-sans = ["iced_renderer/fira-sans"] +# Enables auto-detecting light/dark mode for the built-in theme +auto-detect-theme = ["iced_core/auto-detect-theme"] [dependencies] +iced_core.workspace = true iced_futures.workspace = true iced_renderer.workspace = true iced_widget.workspace = true @@ -121,6 +124,7 @@ async-std = "1.0" bitflags = "2.0" bytemuck = { version = "1.0", features = ["derive"] } cosmic-text = "0.10" +dark-light = "1.0" futures = "0.3" glam = "0.25" glyphon = "0.5" diff --git a/core/Cargo.toml b/core/Cargo.toml index c273fcb4..29a95ad7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -10,6 +10,9 @@ homepage.workspace = true categories.workspace = true keywords.workspace = true +[features] +auto-detect-theme = ["dep:dark-light"] + [dependencies] bitflags.workspace = true glam.workspace = true @@ -22,6 +25,9 @@ thiserror.workspace = true web-time.workspace = true xxhash-rust.workspace = true +dark-light.workspace = true +dark-light.optional = true + [target.'cfg(windows)'.dependencies] raw-window-handle.workspace = true diff --git a/core/src/theme.rs b/core/src/theme.rs index 948aaf83..6b2c04da 100644 --- a/core/src/theme.rs +++ b/core/src/theme.rs @@ -7,10 +7,9 @@ use std::fmt; use std::sync::Arc; /// A built-in theme. -#[derive(Debug, Clone, PartialEq, Default)] +#[derive(Debug, Clone, PartialEq)] pub enum Theme { /// The built-in light variant. - #[default] Light, /// The built-in dark variant. Dark, @@ -161,6 +160,28 @@ impl Theme { } } +impl Default for Theme { + fn default() -> Self { + #[cfg(feature = "auto-detect-theme")] + { + use once_cell::sync::Lazy; + + static DEFAULT: Lazy = + Lazy::new(|| match dark_light::detect() { + dark_light::Mode::Dark => Theme::Dark, + dark_light::Mode::Light | dark_light::Mode::Default => { + Theme::Light + } + }); + + DEFAULT.clone() + } + + #[cfg(not(feature = "auto-detect-theme"))] + Theme::Light + } +} + impl fmt::Display for Theme { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { -- cgit From ff409ce66c03f45e13fe7db583925efb5113ce21 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Mar 2024 16:40:14 +0100 Subject: Fix empty `wgpu` draw calls in `image` pipeline --- wgpu/src/image.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index c8e4a4c2..067b77ab 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -161,13 +161,21 @@ impl Data { queue: &wgpu::Queue, instances: &[Instance], ) { + self.instance_count = instances.len(); + + if self.instance_count == 0 { + return; + } + let _ = self.instances.resize(device, instances.len()); let _ = self.instances.write(queue, 0, instances); - - self.instance_count = instances.len(); } fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + if self.instance_count == 0 { + return; + } + render_pass.set_bind_group(0, &self.constants, &[]); render_pass.set_vertex_buffer(0, self.instances.slice(..)); -- cgit From a6130790832c1e30d138ebebafce7065f957cc96 Mon Sep 17 00:00:00 2001 From: Daniel Yoon <101683475+Koranir@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:27:36 +1100 Subject: Revert "Remove `PreMultiplied` alpha mode selection in `wgpu::window::compositor`" This reverts commit 33066bca1af6c67e5188c0481403f28afabcbe1f. --- wgpu/src/window/compositor.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 328ad781..fa6b9373 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -92,6 +92,10 @@ impl Compositor { .contains(&wgpu::CompositeAlphaMode::PostMultiplied) { wgpu::CompositeAlphaMode::PostMultiplied + } else if alpha_modes + .contains(&wgpu::CompositeAlphaMode::PreMultiplied) + { + wgpu::CompositeAlphaMode::PreMultiplied } else { wgpu::CompositeAlphaMode::Auto }; -- cgit From e812276677ca7c787ffb52a078e7a238227fb522 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 00:34:26 +0100 Subject: Fix layout invalidation for `Responsive` widget --- widget/src/lazy/component.rs | 2 ++ widget/src/lazy/responsive.rs | 45 ++++++++++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index a512e0de..7ba71a02 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -478,12 +478,14 @@ where translation: Vector, ) -> Option> { self.rebuild_element_if_necessary(); + let tree = tree .state .downcast_mut::>>>() .borrow_mut() .take() .unwrap(); + let overlay = Overlay(Some( InnerBuilder { instance: self, diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 313e1edb..f612102e 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -308,10 +308,13 @@ where content_layout_node.as_ref().unwrap(), ); - element - .as_widget_mut() - .overlay(tree, content_layout, renderer, translation) - .map(|overlay| RefCell::new(Nested::new(overlay))) + ( + element + .as_widget_mut() + .overlay(tree, content_layout, renderer, translation) + .map(|overlay| RefCell::new(Nested::new(overlay))), + content_layout_node, + ) }, } .build(); @@ -341,7 +344,10 @@ struct Overlay<'a, 'b, Message, Theme, Renderer> { #[borrows(mut content, mut tree)] #[not_covariant] - overlay: Option>>, + overlay: ( + Option>>, + &'this mut Option, + ), } impl<'a, 'b, Message, Theme, Renderer> @@ -351,7 +357,7 @@ impl<'a, 'b, Message, Theme, Renderer> &self, f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T, ) -> Option { - self.with_overlay(|overlay| { + self.with_overlay(|(overlay, _layout)| { overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut())) }) } @@ -360,7 +366,7 @@ impl<'a, 'b, Message, Theme, Renderer> &mut self, f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T, ) -> Option { - self.with_overlay_mut(|overlay| { + self.with_overlay_mut(|(overlay, _layout)| { overlay.as_mut().map(|nested| (f)(nested.get_mut())) }) } @@ -412,10 +418,27 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - self.with_overlay_mut_maybe(|overlay| { - overlay.on_event(event, layout, cursor, renderer, clipboard, shell) - }) - .unwrap_or(event::Status::Ignored) + let mut is_layout_invalid = false; + + let event_status = self + .with_overlay_mut_maybe(|overlay| { + let event_status = overlay.on_event( + event, layout, cursor, renderer, clipboard, shell, + ); + + is_layout_invalid = shell.is_layout_invalid(); + + event_status + }) + .unwrap_or(event::Status::Ignored); + + if is_layout_invalid { + self.with_overlay_mut(|(_overlay, layout)| { + **layout = None; + }); + } + + event_status } fn is_over( -- cgit From 188db4da48954b95a3fe79bcd22689ffc3a661e0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 05:52:48 +0100 Subject: Draft support for dynamic custom renderer injection --- core/src/image.rs | 4 +- core/src/renderer.rs | 2 +- core/src/svg.rs | 9 ++- graphics/src/renderer.rs | 8 +- renderer/src/compositor.rs | 25 +++++++ renderer/src/custom.rs | 164 +++++++++++++++++++++++++++++++++++++++++ renderer/src/geometry.rs | 33 +++++++-- renderer/src/geometry/cache.rs | 9 +++ renderer/src/lib.rs | 74 ++++++++++++++++--- widget/src/image.rs | 10 ++- widget/src/image/viewer.rs | 7 +- widget/src/svg.rs | 6 +- 12 files changed, 316 insertions(+), 35 deletions(-) create mode 100644 renderer/src/custom.rs diff --git a/core/src/image.rs b/core/src/image.rs index e5fdcd83..32b95f03 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -186,11 +186,11 @@ pub trait Renderer: crate::Renderer { type Handle: Clone + Hash; /// Returns the dimensions of an image for the given [`Handle`]. - fn dimensions(&self, handle: &Self::Handle) -> Size; + fn measure_image(&self, handle: &Self::Handle) -> Size; /// Draws an image with the given [`Handle`] and inside the provided /// `bounds`. - fn draw( + fn draw_image( &mut self, handle: Self::Handle, filter_method: FilterMethod, diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 1139b41c..47b09d32 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -10,7 +10,7 @@ use crate::{ }; /// A component that can be used by widgets to draw themselves on a screen. -pub trait Renderer: Sized { +pub trait Renderer { /// Draws the primitives recorded in the given closure in a new layer. /// /// The layer will clip its contents to the provided `bounds`. diff --git a/core/src/svg.rs b/core/src/svg.rs index d63e3c95..ab207cca 100644 --- a/core/src/svg.rs +++ b/core/src/svg.rs @@ -91,8 +91,13 @@ impl std::fmt::Debug for Data { /// [renderer]: crate::renderer pub trait Renderer: crate::Renderer { /// Returns the default dimensions of an SVG for the given [`Handle`]. - fn dimensions(&self, handle: &Handle) -> Size; + fn measure_svg(&self, handle: &Handle) -> Size; /// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`. - fn draw(&mut self, handle: Handle, color: Option, bounds: Rectangle); + fn draw_svg( + &mut self, + handle: Handle, + color: Option, + bounds: Rectangle, + ); } diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 143f348b..e7154385 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -211,11 +211,11 @@ where { type Handle = image::Handle; - fn dimensions(&self, handle: &image::Handle) -> Size { + fn measure_image(&self, handle: &image::Handle) -> Size { self.backend().dimensions(handle) } - fn draw( + fn draw_image( &mut self, handle: image::Handle, filter_method: image::FilterMethod, @@ -233,11 +233,11 @@ impl svg::Renderer for Renderer where B: Backend + backend::Svg, { - fn dimensions(&self, handle: &svg::Handle) -> Size { + fn measure_svg(&self, handle: &svg::Handle) -> Size { self.backend().viewport_dimensions(handle) } - fn draw( + fn draw_svg( &mut self, handle: svg::Handle, color: Option, diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index c23a814c..058fa36d 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -1,4 +1,5 @@ use crate::core::Color; +use crate::custom; use crate::graphics::compositor::{Information, SurfaceError, Window}; use crate::graphics::{Error, Viewport}; use crate::{Renderer, Settings}; @@ -10,12 +11,14 @@ pub enum Compositor { TinySkia(iced_tiny_skia::window::Compositor), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::window::Compositor), + Custom(Box), } pub enum Surface { TinySkia(iced_tiny_skia::window::Surface), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::window::Surface<'static>), + Custom(Box), } impl crate::graphics::Compositor for Compositor { @@ -56,6 +59,9 @@ impl crate::graphics::Compositor for Compositor { Compositor::Wgpu(compositor) => { Renderer::Wgpu(compositor.create_renderer()) } + Compositor::Custom(compositor) => { + Renderer::Custom(compositor.create_renderer()) + } } } @@ -73,6 +79,9 @@ impl crate::graphics::Compositor for Compositor { Self::Wgpu(compositor) => { Surface::Wgpu(compositor.create_surface(window, width, height)) } + Self::Custom(compositor) => Surface::Custom( + compositor.create_surface(Box::new(window), width, height), + ), } } @@ -90,6 +99,9 @@ impl crate::graphics::Compositor for Compositor { (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { compositor.configure_surface(surface, width, height); } + (Self::Custom(compositor), Surface::Custom(surface)) => { + compositor.configure_surface(surface, width, height); + } #[allow(unreachable_patterns)] _ => panic!( "The provided surface is not compatible with the compositor." @@ -102,6 +114,7 @@ impl crate::graphics::Compositor for Compositor { Self::TinySkia(compositor) => compositor.fetch_information(), #[cfg(feature = "wgpu")] Self::Wgpu(compositor) => compositor.fetch_information(), + Self::Custom(compositor) => compositor.fetch_information(), } } @@ -144,6 +157,18 @@ impl crate::graphics::Compositor for Compositor { overlay, ) }), + + #[cfg(feature = "wgpu")] + ( + Self::Custom(compositor), + crate::Renderer::Custom(renderer), + Surface::Custom(surface), + ) => renderer.present( + surface.as_mut(), + viewport, + background_color, + compositor.as_mut(), + ), #[allow(unreachable_patterns)] _ => panic!( "The provided renderer or surface are not compatible \ diff --git a/renderer/src/custom.rs b/renderer/src/custom.rs new file mode 100644 index 00000000..680aa0b5 --- /dev/null +++ b/renderer/src/custom.rs @@ -0,0 +1,164 @@ +use crate::core::image; +use crate::core::renderer; +use crate::core::svg; +use crate::core::text::Text; +use crate::core::{ + Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, +}; +use crate::graphics::compositor; +use crate::graphics::text::{Editor, Paragraph}; +use crate::graphics::{Mesh, Viewport}; + +#[cfg(feature = "geometry")] +use crate::graphics::geometry::{self, Fill, Path, Stroke}; + +use std::borrow::Cow; + +pub trait Renderer { + fn draw_mesh(&mut self, mesh: Mesh); + + fn start_layer(&mut self); + + fn end_layer(&mut self, bounds: Rectangle); + + fn start_transformation(&mut self); + + fn end_transformation(&mut self, transformation: Transformation); + + fn fill_quad(&mut self, quad: renderer::Quad, background: Background); + + fn clear(&mut self); + + fn default_font(&self) -> Font; + + fn default_size(&self) -> Pixels; + + fn load_font(&mut self, bytes: Cow<'static, [u8]>); + + fn fill_paragraph( + &mut self, + paragraph: &Paragraph, + position: Point, + color: Color, + clip_bounds: Rectangle, + ); + + fn fill_editor( + &mut self, + editor: &Editor, + position: Point, + color: Color, + clip_bounds: Rectangle, + ); + + fn fill_text( + &mut self, + text: Text<'_, Font>, + position: Point, + color: Color, + clip_bounds: Rectangle, + ); + + fn measure_image(&self, handle: &image::Handle) -> Size; + + fn draw_image( + &mut self, + handle: image::Handle, + filter_method: image::FilterMethod, + bounds: Rectangle, + ); + + fn measure_svg(&self, handle: &svg::Handle) -> Size; + + fn draw_svg( + &mut self, + handle: crate::core::svg::Handle, + color: Option, + bounds: Rectangle, + ); + + #[cfg(feature = "geometry")] + fn new_frame(&self, size: Size) -> Box; + + #[cfg(feature = "geometry")] + fn draw_geometry(&mut self, geometry: Box); + + fn present( + &mut self, + surface: &mut dyn Surface, + viewport: &Viewport, + background_color: Color, + compositor: &mut dyn Compositor, + ) -> Result<(), compositor::SurfaceError>; +} + +#[cfg(feature = "geometry")] +pub trait Frame: std::any::Any { + fn new(&self, size: Size) -> Box; + + fn width(&self) -> f32; + + fn height(&self) -> f32; + + fn size(&self) -> Size; + + fn center(&self) -> Point; + + fn fill(&mut self, path: &Path, fill: Fill); + + fn fill_rectangle(&mut self, top_left: Point, size: Size, fill: Fill); + + fn stroke<'a>(&mut self, path: &Path, stroke: Stroke<'a>); + + fn fill_text(&mut self, text: geometry::Text); + + fn translate(&mut self, translation: crate::core::Vector); + + fn rotate(&mut self, angle: crate::core::Radians); + + fn scale(&mut self, scale: f32); + + fn scale_nonuniform(&mut self, scale: crate::core::Vector); + + fn push_transform(&mut self); + + fn pop_transform(&mut self); + + fn clip(&mut self, frame: Box, origin: Point); + + fn into_geometry(self: Box) -> Box; +} + +#[cfg(feature = "geometry")] +pub trait Geometry: std::any::Any + std::fmt::Debug { + fn transform( + self: Box, + transformation: Transformation, + ) -> Box; + + fn cache(self: Box) -> std::sync::Arc; + + fn load(self: std::sync::Arc) -> Box; +} + +pub trait Compositor: std::any::Any { + fn create_renderer(&self) -> Box; + + fn create_surface( + &mut self, + window: Box, + width: u32, + height: u32, + ) -> Box; + + fn configure_surface( + &mut self, + surface: &mut Box, + width: u32, + height: u32, + ); + + fn fetch_information(&self) -> compositor::Information; +} + +pub trait Surface: std::any::Any {} diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs index 36435148..bf70c7fa 100644 --- a/renderer/src/geometry.rs +++ b/renderer/src/geometry.rs @@ -3,6 +3,7 @@ mod cache; pub use cache::Cache; use crate::core::{Point, Radians, Rectangle, Size, Transformation, Vector}; +use crate::custom; use crate::graphics::geometry::{Fill, Path, Stroke, Text}; use crate::Renderer; @@ -12,6 +13,7 @@ macro_rules! delegate { Self::TinySkia($name) => $body, #[cfg(feature = "wgpu")] Self::Wgpu($name) => $body, + Self::Custom($name) => $body, } }; } @@ -20,6 +22,7 @@ pub enum Geometry { TinySkia(iced_tiny_skia::Primitive), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::Primitive), + Custom(Box), } impl Geometry { @@ -32,6 +35,9 @@ impl Geometry { Self::Wgpu(primitive) => { Self::Wgpu(primitive.transform(transformation)) } + Self::Custom(geometry) => { + Self::Custom(geometry.transform(transformation)) + } } } } @@ -40,6 +46,7 @@ pub enum Frame { TinySkia(iced_tiny_skia::geometry::Frame), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::geometry::Frame), + Custom(Box), } impl Frame { @@ -52,6 +59,9 @@ impl Frame { Renderer::Wgpu(_) => { Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) } + Renderer::Custom(renderer) => { + Frame::Custom(renderer.new_frame(size)) + } } } @@ -82,7 +92,7 @@ impl Frame { /// 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.into())); } /// Draws an axis-aligned rectangle given its top-left corner coordinate and @@ -93,13 +103,17 @@ 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.into()) + ); } /// 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.into())); } /// Draws the characters of the given [`Text`] on the [`Frame`], filling @@ -116,7 +130,7 @@ impl Frame { /// Support for vectorial text is planned, and should address all these /// limitations. pub fn fill_text(&mut self, text: impl Into) { - delegate!(self, frame, frame.fill_text(text)); + delegate!(self, frame, frame.fill_text(text.into())); } /// Stores the current transform of the [`Frame`] and executes the given @@ -155,6 +169,7 @@ impl Frame { Self::Wgpu(_) => { Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) } + Self::Custom(frame) => Self::Custom(frame.new(region.size())), }; let result = f(&mut frame); @@ -169,6 +184,9 @@ impl Frame { (Self::Wgpu(target), Self::Wgpu(frame)) => { target.clip(frame, origin); } + (Self::Custom(target), Self::Custom(frame)) => { + target.clip(frame, origin); + } #[allow(unreachable_patterns)] _ => unreachable!(), }; @@ -185,19 +203,19 @@ impl Frame { /// Applies a rotation in radians to the current transform of the [`Frame`]. #[inline] pub fn rotate(&mut self, angle: impl Into) { - delegate!(self, frame, frame.rotate(angle)); + delegate!(self, frame, frame.rotate(angle.into())); } /// Applies a uniform scaling to the current transform of the [`Frame`]. #[inline] pub fn scale(&mut self, scale: impl Into) { - delegate!(self, frame, frame.scale(scale)); + delegate!(self, frame, frame.scale(scale.into())); } /// Applies a non-uniform scaling to the current transform of the [`Frame`]. #[inline] pub fn scale_nonuniform(&mut self, scale: impl Into) { - delegate!(self, frame, frame.scale_nonuniform(scale)); + delegate!(self, frame, frame.scale_nonuniform(scale.into())); } pub fn into_geometry(self) -> Geometry { @@ -205,6 +223,7 @@ impl Frame { Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()), #[cfg(feature = "wgpu")] Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()), + Self::Custom(frame) => Geometry::Custom(frame.into_geometry()), } } } diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs index 3aff76b9..6a43ddb6 100644 --- a/renderer/src/geometry/cache.rs +++ b/renderer/src/geometry/cache.rs @@ -1,4 +1,5 @@ use crate::core::Size; +use crate::custom; use crate::geometry::{Frame, Geometry}; use crate::Renderer; @@ -29,6 +30,7 @@ enum Internal { TinySkia(Arc), #[cfg(feature = "wgpu")] Wgpu(Arc), + Custom(Arc), } impl Cache { @@ -82,6 +84,9 @@ impl Cache { content: primitive.clone(), }); } + Internal::Custom(geometry) => { + return Geometry::Custom(geometry.clone().load()) + } } } } @@ -100,6 +105,9 @@ impl Cache { Geometry::Wgpu(primitive) => { Internal::Wgpu(Arc::new(primitive)) } + Geometry::Custom(geometry) => { + Internal::Custom(geometry.cache()) + } } }; @@ -120,6 +128,7 @@ impl Cache { content: primitive, }) } + Internal::Custom(geometry) => Geometry::Custom(geometry.load()), } } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 757c264d..70741356 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -5,6 +5,7 @@ pub use iced_wgpu as wgpu; pub mod compositor; +pub mod custom; #[cfg(feature = "geometry")] pub mod geometry; @@ -38,6 +39,7 @@ pub enum Renderer { TinySkia(iced_tiny_skia::Renderer), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::Renderer), + Custom(Box), } macro_rules! delegate { @@ -46,6 +48,7 @@ macro_rules! delegate { Self::TinySkia($name) => $body, #[cfg(feature = "wgpu")] Self::Wgpu($name) => $body, + Self::Custom($name) => $body, } }; } @@ -62,6 +65,9 @@ impl Renderer { iced_wgpu::primitive::Custom::Mesh(mesh), )); } + Self::Custom(renderer) => { + renderer.draw_mesh(mesh); + } } } } @@ -96,6 +102,18 @@ impl core::Renderer for Renderer { _ => unreachable!(), } } + Self::Custom(renderer) => { + renderer.start_layer(); + + f(self); + + match self { + Self::Custom(renderer) => { + renderer.end_layer(bounds); + } + _ => unreachable!(), + } + } } } @@ -132,6 +150,18 @@ impl core::Renderer for Renderer { _ => unreachable!(), } } + Self::Custom(renderer) => { + renderer.start_transformation(); + + f(self); + + match self { + Self::Custom(renderer) => { + renderer.end_transformation(transformation); + } + _ => unreachable!(), + } + } } } @@ -140,7 +170,7 @@ impl core::Renderer for Renderer { quad: renderer::Quad, background: impl Into, ) { - delegate!(self, renderer, renderer.fill_quad(quad, background)); + delegate!(self, renderer, renderer.fill_quad(quad, background.into())); } fn clear(&mut self) { @@ -216,36 +246,43 @@ impl text::Renderer for Renderer { impl crate::core::image::Renderer for Renderer { type Handle = crate::core::image::Handle; - fn dimensions( + fn measure_image( &self, handle: &crate::core::image::Handle, ) -> core::Size { - delegate!(self, renderer, renderer.dimensions(handle)) + delegate!(self, renderer, renderer.measure_image(handle)) } - fn draw( + fn draw_image( &mut self, handle: crate::core::image::Handle, filter_method: crate::core::image::FilterMethod, bounds: Rectangle, ) { - delegate!(self, renderer, renderer.draw(handle, filter_method, bounds)); + delegate!( + self, + renderer, + renderer.draw_image(handle, filter_method, bounds) + ); } } #[cfg(feature = "svg")] impl crate::core::svg::Renderer for Renderer { - fn dimensions(&self, handle: &crate::core::svg::Handle) -> core::Size { - delegate!(self, renderer, renderer.dimensions(handle)) + fn measure_svg( + &self, + handle: &crate::core::svg::Handle, + ) -> core::Size { + delegate!(self, renderer, renderer.measure_svg(handle)) } - fn draw( + fn draw_svg( &mut self, handle: crate::core::svg::Handle, color: Option, bounds: Rectangle, ) { - delegate!(self, renderer, renderer.draw(handle, color, bounds)); + delegate!(self, renderer, renderer.draw_svg(handle, color, bounds)); } } @@ -263,6 +300,7 @@ impl crate::graphics::geometry::Renderer for Renderer { } #[cfg(feature = "wgpu")] crate::Geometry::Wgpu(_) => unreachable!(), + crate::Geometry::Custom(_) => unreachable!(), } } } @@ -274,6 +312,19 @@ impl crate::graphics::geometry::Renderer for Renderer { renderer.draw_primitive(primitive); } crate::Geometry::TinySkia(_) => unreachable!(), + crate::Geometry::Custom(_) => unreachable!(), + } + } + } + Self::Custom(renderer) => { + for layer in layers { + match layer { + crate::Geometry::Custom(geometry) => { + renderer.draw_geometry(geometry); + } + crate::Geometry::TinySkia(_) => unreachable!(), + #[cfg(feature = "wgpu")] + crate::Geometry::Wgpu(_) => unreachable!(), } } } @@ -297,6 +348,11 @@ impl iced_wgpu::primitive::pipeline::Renderer for Renderer { Self::Wgpu(renderer) => { renderer.draw_pipeline_primitive(bounds, primitive); } + Self::Custom(_renderer) => { + log::warn!( + "Custom shader primitive is unavailable with custom renderer." + ); + } } } } diff --git a/widget/src/image.rs b/widget/src/image.rs index ccf1f175..f673c7b3 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -93,7 +93,7 @@ where { // The raw w/h of the underlying image let image_size = { - let Size { width, height } = renderer.dimensions(handle); + let Size { width, height } = renderer.measure_image(handle); Size::new(width as f32, height as f32) }; @@ -130,7 +130,7 @@ pub fn draw( Renderer: image::Renderer, Handle: Clone + Hash, { - let Size { width, height } = renderer.dimensions(handle); + let Size { width, height } = renderer.measure_image(handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); @@ -148,7 +148,11 @@ pub fn draw( ..bounds }; - renderer.draw(handle.clone(), filter_method, drawing_bounds + offset); + renderer.draw_image( + handle.clone(), + filter_method, + drawing_bounds + offset, + ); }; if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 2e3fd713..5f7bb345 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -117,7 +117,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let Size { width, height } = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.measure_image(&self.handle); let mut size = limits.resolve( self.width, @@ -335,8 +335,7 @@ where renderer.with_layer(bounds, |renderer| { renderer.with_translation(translation, |renderer| { - image::Renderer::draw( - renderer, + renderer.draw_image( self.handle.clone(), self.filter_method, Rectangle { @@ -421,7 +420,7 @@ pub fn image_size( where Renderer: image::Renderer, { - let Size { width, height } = renderer.dimensions(handle); + let Size { width, height } = renderer.measure_image(handle); let (width, height) = { let dimensions = (width as f32, height as f32); diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 1ac07ade..53c45bcb 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -108,7 +108,7 @@ where limits: &layout::Limits, ) -> layout::Node { // The raw w/h of the underlying image - let Size { width, height } = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.measure_svg(&self.handle); let image_size = Size::new(width as f32, height as f32); // The size to be available to the widget prior to `Shrink`ing @@ -142,7 +142,7 @@ where cursor: mouse::Cursor, _viewport: &Rectangle, ) { - let Size { width, height } = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.measure_svg(&self.handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); @@ -169,7 +169,7 @@ where let appearance = (self.style)(theme, status); - renderer.draw( + renderer.draw_svg( self.handle.clone(), appearance.color, drawing_bounds + offset, -- cgit From 9171df1e356530410a7ceadadd78fd3dcf150dfd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 06:00:55 +0100 Subject: Gate `Custom` variants in `iced_renderer` behind `custom` feature --- renderer/Cargo.toml | 1 + renderer/src/compositor.rs | 13 +++++++++---- renderer/src/geometry.rs | 13 ++++++++++--- renderer/src/geometry/cache.rs | 7 +++++-- renderer/src/lib.rs | 9 +++++++++ 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 5cce2427..18e9e8f8 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -19,6 +19,7 @@ tracing = ["iced_wgpu?/tracing"] web-colors = ["iced_wgpu?/web-colors"] webgl = ["iced_wgpu?/webgl"] fira-sans = ["iced_graphics/fira-sans"] +custom = [] [dependencies] iced_graphics.workspace = true diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index 058fa36d..012ad3c0 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -1,5 +1,4 @@ use crate::core::Color; -use crate::custom; use crate::graphics::compositor::{Information, SurfaceError, Window}; use crate::graphics::{Error, Viewport}; use crate::{Renderer, Settings}; @@ -11,14 +10,16 @@ pub enum Compositor { TinySkia(iced_tiny_skia::window::Compositor), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::window::Compositor), - Custom(Box), + #[cfg(feature = "custom")] + Custom(Box), } pub enum Surface { TinySkia(iced_tiny_skia::window::Surface), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::window::Surface<'static>), - Custom(Box), + #[cfg(feature = "custom")] + Custom(Box), } impl crate::graphics::Compositor for Compositor { @@ -59,6 +60,7 @@ impl crate::graphics::Compositor for Compositor { Compositor::Wgpu(compositor) => { Renderer::Wgpu(compositor.create_renderer()) } + #[cfg(feature = "custom")] Compositor::Custom(compositor) => { Renderer::Custom(compositor.create_renderer()) } @@ -79,6 +81,7 @@ impl crate::graphics::Compositor for Compositor { Self::Wgpu(compositor) => { Surface::Wgpu(compositor.create_surface(window, width, height)) } + #[cfg(feature = "custom")] Self::Custom(compositor) => Surface::Custom( compositor.create_surface(Box::new(window), width, height), ), @@ -99,6 +102,7 @@ impl crate::graphics::Compositor for Compositor { (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { compositor.configure_surface(surface, width, height); } + #[cfg(feature = "custom")] (Self::Custom(compositor), Surface::Custom(surface)) => { compositor.configure_surface(surface, width, height); } @@ -114,6 +118,7 @@ impl crate::graphics::Compositor for Compositor { Self::TinySkia(compositor) => compositor.fetch_information(), #[cfg(feature = "wgpu")] Self::Wgpu(compositor) => compositor.fetch_information(), + #[cfg(feature = "custom")] Self::Custom(compositor) => compositor.fetch_information(), } } @@ -158,7 +163,7 @@ impl crate::graphics::Compositor for Compositor { ) }), - #[cfg(feature = "wgpu")] + #[cfg(feature = "custom")] ( Self::Custom(compositor), crate::Renderer::Custom(renderer), diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs index bf70c7fa..a16cecd5 100644 --- a/renderer/src/geometry.rs +++ b/renderer/src/geometry.rs @@ -3,7 +3,6 @@ mod cache; pub use cache::Cache; use crate::core::{Point, Radians, Rectangle, Size, Transformation, Vector}; -use crate::custom; use crate::graphics::geometry::{Fill, Path, Stroke, Text}; use crate::Renderer; @@ -13,6 +12,7 @@ macro_rules! delegate { Self::TinySkia($name) => $body, #[cfg(feature = "wgpu")] Self::Wgpu($name) => $body, + #[cfg(feature = "custom")] Self::Custom($name) => $body, } }; @@ -22,7 +22,8 @@ pub enum Geometry { TinySkia(iced_tiny_skia::Primitive), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::Primitive), - Custom(Box), + #[cfg(feature = "custom")] + Custom(Box), } impl Geometry { @@ -35,6 +36,7 @@ impl Geometry { Self::Wgpu(primitive) => { Self::Wgpu(primitive.transform(transformation)) } + #[cfg(feature = "custom")] Self::Custom(geometry) => { Self::Custom(geometry.transform(transformation)) } @@ -46,7 +48,8 @@ pub enum Frame { TinySkia(iced_tiny_skia::geometry::Frame), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::geometry::Frame), - Custom(Box), + #[cfg(feature = "custom")] + Custom(Box), } impl Frame { @@ -59,6 +62,7 @@ impl Frame { Renderer::Wgpu(_) => { Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) } + #[cfg(feature = "custom")] Renderer::Custom(renderer) => { Frame::Custom(renderer.new_frame(size)) } @@ -169,6 +173,7 @@ impl Frame { Self::Wgpu(_) => { Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) } + #[cfg(feature = "custom")] Self::Custom(frame) => Self::Custom(frame.new(region.size())), }; @@ -184,6 +189,7 @@ impl Frame { (Self::Wgpu(target), Self::Wgpu(frame)) => { target.clip(frame, origin); } + #[cfg(feature = "custom")] (Self::Custom(target), Self::Custom(frame)) => { target.clip(frame, origin); } @@ -223,6 +229,7 @@ impl Frame { Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()), #[cfg(feature = "wgpu")] Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()), + #[cfg(feature = "custom")] Self::Custom(frame) => Geometry::Custom(frame.into_geometry()), } } diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs index 6a43ddb6..20f73f22 100644 --- a/renderer/src/geometry/cache.rs +++ b/renderer/src/geometry/cache.rs @@ -1,5 +1,4 @@ use crate::core::Size; -use crate::custom; use crate::geometry::{Frame, Geometry}; use crate::Renderer; @@ -30,7 +29,8 @@ enum Internal { TinySkia(Arc), #[cfg(feature = "wgpu")] Wgpu(Arc), - Custom(Arc), + #[cfg(feature = "custom")] + Custom(Arc), } impl Cache { @@ -84,6 +84,7 @@ impl Cache { content: primitive.clone(), }); } + #[cfg(feature = "custom")] Internal::Custom(geometry) => { return Geometry::Custom(geometry.clone().load()) } @@ -105,6 +106,7 @@ impl Cache { Geometry::Wgpu(primitive) => { Internal::Wgpu(Arc::new(primitive)) } + #[cfg(feature = "custom")] Geometry::Custom(geometry) => { Internal::Custom(geometry.cache()) } @@ -128,6 +130,7 @@ impl Cache { content: primitive, }) } + #[cfg(feature = "custom")] Internal::Custom(geometry) => Geometry::Custom(geometry.load()), } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 70741356..67096115 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -39,6 +39,7 @@ pub enum Renderer { TinySkia(iced_tiny_skia::Renderer), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::Renderer), + #[cfg(feature = "custom")] Custom(Box), } @@ -48,6 +49,7 @@ macro_rules! delegate { Self::TinySkia($name) => $body, #[cfg(feature = "wgpu")] Self::Wgpu($name) => $body, + #[cfg(feature = "custom")] Self::Custom($name) => $body, } }; @@ -65,6 +67,7 @@ impl Renderer { iced_wgpu::primitive::Custom::Mesh(mesh), )); } + #[cfg(feature = "custom")] Self::Custom(renderer) => { renderer.draw_mesh(mesh); } @@ -102,6 +105,7 @@ impl core::Renderer for Renderer { _ => unreachable!(), } } + #[cfg(feature = "custom")] Self::Custom(renderer) => { renderer.start_layer(); @@ -150,6 +154,7 @@ impl core::Renderer for Renderer { _ => unreachable!(), } } + #[cfg(feature = "custom")] Self::Custom(renderer) => { renderer.start_transformation(); @@ -300,6 +305,7 @@ impl crate::graphics::geometry::Renderer for Renderer { } #[cfg(feature = "wgpu")] crate::Geometry::Wgpu(_) => unreachable!(), + #[cfg(feature = "custom")] crate::Geometry::Custom(_) => unreachable!(), } } @@ -312,10 +318,12 @@ impl crate::graphics::geometry::Renderer for Renderer { renderer.draw_primitive(primitive); } crate::Geometry::TinySkia(_) => unreachable!(), + #[cfg(feature = "custom")] crate::Geometry::Custom(_) => unreachable!(), } } } + #[cfg(feature = "custom")] Self::Custom(renderer) => { for layer in layers { match layer { @@ -348,6 +356,7 @@ impl iced_wgpu::primitive::pipeline::Renderer for Renderer { Self::Wgpu(renderer) => { renderer.draw_pipeline_primitive(bounds, primitive); } + #[cfg(feature = "custom")] Self::Custom(_renderer) => { log::warn!( "Custom shader primitive is unavailable with custom renderer." -- cgit From 7e4ae8450e1f28c15717ca5ca9748981af9c9541 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 06:03:31 +0100 Subject: Use `&mut dyn Surface` instead of `&mut Box` --- renderer/src/compositor.rs | 2 +- renderer/src/custom.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index 012ad3c0..3d0b3ad0 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -104,7 +104,7 @@ impl crate::graphics::Compositor for Compositor { } #[cfg(feature = "custom")] (Self::Custom(compositor), Surface::Custom(surface)) => { - compositor.configure_surface(surface, width, height); + compositor.configure_surface(surface.as_mut(), width, height); } #[allow(unreachable_patterns)] _ => panic!( diff --git a/renderer/src/custom.rs b/renderer/src/custom.rs index 680aa0b5..04090ccb 100644 --- a/renderer/src/custom.rs +++ b/renderer/src/custom.rs @@ -153,7 +153,7 @@ pub trait Compositor: std::any::Any { fn configure_surface( &mut self, - surface: &mut Box, + surface: &mut dyn Surface, width: u32, height: u32, ); -- cgit From 3645d34d6a1ba1247238e830e9eefd52d9e5b986 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 22:27:17 +0100 Subject: Implement composable, type-safe renderer fallback --- core/src/renderer.rs | 28 +- core/src/renderer/null.rs | 13 +- examples/arc/src/main.rs | 10 +- examples/bezier_tool/src/main.rs | 33 +- examples/clock/src/main.rs | 8 +- examples/color_palette/src/main.rs | 10 +- examples/game_of_life/src/main.rs | 18 +- examples/geometry/src/main.rs | 4 +- examples/layout/src/main.rs | 10 +- examples/loading_spinners/src/circular.rs | 6 +- examples/multitouch/src/main.rs | 10 +- examples/sierpinski_triangle/src/main.rs | 8 +- examples/solar_system/src/main.rs | 7 +- examples/vectorial_text/src/main.rs | 8 +- graphics/src/backend.rs | 3 +- graphics/src/geometry.rs | 272 ++++++++++++++- graphics/src/lib.rs | 4 +- graphics/src/mesh.rs | 6 +- graphics/src/renderer.rs | 106 +++--- renderer/src/compositor.rs | 299 ---------------- renderer/src/custom.rs | 4 +- renderer/src/fallback.rs | 562 ++++++++++++++++++++++++++++++ renderer/src/lib.rs | 368 ++----------------- renderer/src/settings.rs | 21 ++ tiny_skia/src/backend.rs | 9 + tiny_skia/src/geometry.rs | 60 ++-- tiny_skia/src/primitive.rs | 10 +- wgpu/src/backend.rs | 9 + wgpu/src/geometry.rs | 493 ++++++++++++-------------- wgpu/src/primitive.rs | 8 + widget/src/canvas.rs | 19 +- widget/src/canvas/program.rs | 19 +- widget/src/qr_code.rs | 9 +- winit/src/application.rs | 4 +- winit/src/multi_window.rs | 8 +- 35 files changed, 1365 insertions(+), 1101 deletions(-) create mode 100644 renderer/src/fallback.rs diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 47b09d32..406b33f3 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -11,17 +11,41 @@ use crate::{ /// A component that can be used by widgets to draw themselves on a screen. pub trait Renderer { + /// Starts recording a new layer. + fn start_layer(&mut self); + + /// Ends recording a new layer. + /// + /// The new layer will clip its contents to the provided `bounds`. + fn end_layer(&mut self, bounds: Rectangle); + /// Draws the primitives recorded in the given closure in a new layer. /// /// The layer will clip its contents to the provided `bounds`. - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)); + fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { + self.start_layer(); + f(self); + self.end_layer(bounds); + } + + /// Starts recording with a new [`Transformation`]. + fn start_transformation(&mut self); + + /// Ends recording a new layer. + /// + /// The new layer will clip its contents to the provided `bounds`. + fn end_transformation(&mut self, transformation: Transformation); /// Applies a [`Transformation`] to the primitives recorded in the given closure. fn with_transformation( &mut self, transformation: Transformation, f: impl FnOnce(&mut Self), - ); + ) { + self.start_transformation(); + f(self); + self.end_transformation(transformation); + } /// Applies a translation to the primitives recorded in the given closure. fn with_translation( diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 83688ff7..0d7b7c14 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -21,14 +21,13 @@ impl Null { } impl Renderer for Null { - fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {} + fn start_layer(&mut self) {} - fn with_transformation( - &mut self, - _transformation: Transformation, - _f: impl FnOnce(&mut Self), - ) { - } + fn end_layer(&mut self, _bounds: Rectangle) {} + + fn start_transformation(&mut self) {} + + fn end_transformation(&mut self, _transformation: Transformation) {} fn clear(&mut self) {} diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index 4576404f..a7893efa 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -1,9 +1,7 @@ use std::{f32::consts::PI, time::Instant}; use iced::mouse; -use iced::widget::canvas::{ - self, stroke, Cache, Canvas, Geometry, Path, Stroke, -}; +use iced::widget::canvas::{self, stroke, Cache, Canvas, Frame, Path, Stroke}; use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme}; pub fn main() -> iced::Result { @@ -57,11 +55,11 @@ impl canvas::Program for Arc { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec { + ) { let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.palette(); @@ -104,6 +102,6 @@ impl canvas::Program for Arc { ); }); - vec![geometry] + renderer.draw_geometry([geometry]); } } diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index cf70bd40..e51f2a31 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -52,7 +52,7 @@ impl Example { mod bezier { use iced::mouse; use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; + use iced::widget::canvas::{self, frame, Canvas, Frame, Path, Stroke}; use iced::{Element, Length, Point, Rectangle, Renderer, Theme}; #[derive(Default)] @@ -138,30 +138,25 @@ mod bezier { fn draw( &self, state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Vec { - let content = self.state.cache.draw( - renderer, - bounds.size(), - |frame: &mut Frame| { + ) { + let content = + self.state.cache.draw(renderer, bounds.size(), |frame| { Curve::draw_all(self.curves, frame); frame.stroke( &Path::rectangle(Point::ORIGIN, frame.size()), Stroke::default().with_width(2.0), ); - }, - ); + }); - if let Some(pending) = state { - let pending_curve = pending.draw(renderer, bounds, cursor); + renderer.draw_geometry([content]); - vec![content, pending_curve] - } else { - vec![content] + if let Some(pending) = state { + pending.draw(renderer, bounds, cursor); } } @@ -187,7 +182,7 @@ mod bezier { } impl Curve { - fn draw_all(curves: &[Curve], frame: &mut Frame) { + fn draw_all(curves: &[Curve], frame: &mut impl Frame) { let curves = Path::new(|p| { for curve in curves { p.move_to(curve.from); @@ -208,11 +203,11 @@ mod bezier { impl Pending { fn draw( &self, - renderer: &Renderer, + renderer: &mut Renderer, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Geometry { - let mut frame = Frame::new(renderer, bounds.size()); + ) { + let mut frame = frame(renderer, bounds.size()); if let Some(cursor_position) = cursor.position_in(bounds) { match *self { @@ -232,7 +227,7 @@ mod bezier { }; } - frame.into_geometry() + renderer.draw_geometry([frame]); } } } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 897f8f1b..9f78903c 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,6 +1,6 @@ use iced::alignment; use iced::mouse; -use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke}; +use iced::widget::canvas::{stroke, Cache, Frame, LineCap, Path, Stroke}; use iced::widget::{canvas, container}; use iced::{ Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription, @@ -82,11 +82,11 @@ impl canvas::Program for Clock { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec { + ) { let clock = self.clock.draw(renderer, bounds.size(), |frame| { let palette = theme.extended_palette(); @@ -163,7 +163,7 @@ impl canvas::Program for Clock { }); }); - vec![clock] + renderer.draw_geometry([clock]); } } diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index d9325edb..400766ff 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,6 +1,6 @@ use iced::alignment::{self, Alignment}; use iced::mouse; -use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path}; +use iced::widget::canvas::{self, Canvas, Frame, Path}; use iced::widget::{column, row, text, Slider}; use iced::{ Color, Element, Font, Length, Pixels, Point, Rectangle, Renderer, Size, @@ -156,7 +156,7 @@ impl Theme { .into() } - fn draw(&self, frame: &mut Frame, text_color: Color) { + fn draw(&self, frame: &mut impl Frame, text_color: Color) { let pad = 20.0; let box_size = Size { @@ -252,18 +252,18 @@ impl canvas::Program for Theme { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &iced::Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec { + ) { let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| { let palette = theme.extended_palette(); self.draw(frame, palette.background.base.text); }); - vec![theme] + renderer.draw_geometry([theme]); } } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 2b0fae0b..f681b4cc 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -193,7 +193,7 @@ mod grid { use iced::touch; use iced::widget::canvas; use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text}; + use iced::widget::canvas::{frame, Cache, Canvas, Frame, Path, Text}; use iced::{ Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; @@ -516,11 +516,11 @@ mod grid { fn draw( &self, _interaction: &Interaction, - renderer: &Renderer, + renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Vec { + ) { let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0); let life = self.life_cache.draw(renderer, bounds.size(), |frame| { @@ -546,7 +546,7 @@ mod grid { }); let overlay = { - let mut frame = Frame::new(renderer, bounds.size()); + let mut frame = frame(renderer, bounds.size()); let hovered_cell = cursor.position_in(bounds).map(|position| { Cell::at(self.project(position, frame.size())) @@ -599,12 +599,10 @@ mod grid { ..text }); - frame.into_geometry() + frame.into() }; - if self.scaling < 0.2 || !self.show_lines { - vec![life, overlay] - } else { + if self.scaling >= 0.2 && self.show_lines { let grid = self.grid_cache.draw(renderer, bounds.size(), |frame| { frame.translate(center); @@ -640,7 +638,9 @@ mod grid { } }); - vec![life, grid, overlay] + renderer.draw_geometry([life, grid, overlay]); + } else { + renderer.draw_geometry([life, overlay]); } } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 63efcbdd..16cdb86f 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -44,7 +44,9 @@ mod rainbow { cursor: mouse::Cursor, _viewport: &Rectangle, ) { - use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D}; + use iced::advanced::graphics::mesh::{ + self, Mesh, Renderer as _, SolidVertex2D, + }; use iced::advanced::Renderer as _; let bounds = layout.bounds(); diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 713e2b70..198237f5 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -292,12 +292,14 @@ fn square<'a>(size: impl Into + Copy) -> Element<'a, Message> { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec { - let mut frame = canvas::Frame::new(renderer, bounds.size()); + ) { + use canvas::Frame; + + let mut frame = canvas::frame(renderer, bounds.size()); let palette = theme.extended_palette(); @@ -307,7 +309,7 @@ fn square<'a>(size: impl Into + Copy) -> Element<'a, Message> { palette.background.strong.color, ); - vec![frame.into_geometry()] + renderer.draw_geometry([frame]); } } diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 12670ed1..306988af 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -6,7 +6,7 @@ use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; use iced::event; use iced::mouse; use iced::time::Instant; -use iced::widget::canvas; +use iced::widget::canvas::{self, Frame}; use iced::window::{self, RedrawRequest}; use iced::{ Background, Color, Element, Event, Length, Radians, Rectangle, Renderer, @@ -356,9 +356,7 @@ where renderer.with_translation( Vector::new(bounds.x, bounds.y), |renderer| { - use iced::advanced::graphics::geometry::Renderer as _; - - renderer.draw(vec![geometry]); + renderer.draw_geometry([geometry]); }, ); } diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index 2453c7f5..41bd0151 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -5,7 +5,7 @@ use iced::mouse; use iced::touch; use iced::widget::canvas::event; use iced::widget::canvas::stroke::{self, Stroke}; -use iced::widget::canvas::{self, Canvas, Geometry}; +use iced::widget::canvas::{self, Canvas}; use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme}; use std::collections::HashMap; @@ -83,11 +83,13 @@ impl canvas::Program for Multitouch { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec { + ) { + use canvas::Frame; + let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| { if self.fingers.len() < 2 { return; @@ -154,6 +156,6 @@ impl canvas::Program for Multitouch { } }); - vec![fingerweb] + renderer.draw_geometry([fingerweb]); } } diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 07ae05d6..b440b8b4 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -107,11 +107,13 @@ impl canvas::Program for SierpinskiGraph { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec { + ) { + use canvas::Frame; + let geom = self.cache.draw(renderer, bounds.size(), |frame| { frame.stroke( &canvas::Path::rectangle(Point::ORIGIN, frame.size()), @@ -139,7 +141,7 @@ impl canvas::Program for SierpinskiGraph { }); }); - vec![geom] + renderer.draw_geometry([geom]); } } diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index b5228f09..dd36b711 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -126,11 +126,12 @@ impl canvas::Program for State { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec { + ) { + use canvas::Frame; use std::f32::consts::PI; let background = @@ -197,7 +198,7 @@ impl canvas::Program for State { }); }); - vec![background, system] + renderer.draw_geometry([background, system]); } } diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs index a7391e23..9f5baac8 100644 --- a/examples/vectorial_text/src/main.rs +++ b/examples/vectorial_text/src/main.rs @@ -124,11 +124,13 @@ impl canvas::Program for State { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec { + ) { + use canvas::Frame; + let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.palette(); let center = bounds.center(); @@ -153,7 +155,7 @@ impl canvas::Program for State { }); }); - vec![geometry] + renderer.draw_geometry([geometry]); } } diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 10eb337f..e394c956 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -2,6 +2,7 @@ use crate::core::image; use crate::core::svg; use crate::core::Size; +use crate::Mesh; use std::borrow::Cow; @@ -10,7 +11,7 @@ use std::borrow::Cow; /// [`Renderer`]: crate::Renderer pub trait Backend { /// The custom kind of primitives this [`Backend`] supports. - type Primitive; + type Primitive: TryFrom; } /// A graphics backend that supports text rendering. diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index d7d6a0aa..cd4c9267 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -14,11 +14,277 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; +use crate::core::{Point, Radians, Rectangle, Size, Vector}; +use crate::Primitive; + +use std::cell::RefCell; +use std::sync::Arc; + +pub fn frame(renderer: &Renderer, size: Size) -> Renderer::Frame +where + Renderer: self::Renderer, +{ + renderer.new_frame(size) +} + /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: crate::core::Renderer { /// The kind of geometry this renderer can draw. - type Geometry; + type Geometry: Geometry; + + /// The kind of [`Frame`] this renderer supports. + type Frame: Frame; + + fn new_frame(&self, size: Size) -> Self::Frame; + + /// Draws the given [`Self::Geometry`]. + fn draw_geometry(&mut self, geometry: Self::Geometry); +} + +pub trait Backend { + /// The kind of [`Frame`] this backend supports. + type Frame: Frame; + + fn new_frame(&self, size: Size) -> Self::Frame; +} + +pub trait Frame: Sized + Into { + /// The kind of geometry this frame can draw. + type Geometry: Geometry; + + /// Returns the width of the [`Frame`]. + fn width(&self) -> f32; + + /// Returns the height of the [`Frame`]. + fn height(&self) -> f32; + + /// Returns the dimensions of the [`Frame`]. + fn size(&self) -> Size; + + /// Returns the coordinate of the center of the [`Frame`]. + fn center(&self) -> Point; + + /// Draws the given [`Path`] on the [`Frame`] by filling it with the + /// provided style. + fn fill(&mut self, path: &Path, fill: impl Into); + + /// Draws an axis-aligned rectangle given its top-left corner coordinate and + /// its `Size` on the [`Frame`] by filling it with the provided style. + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ); + + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the + /// provided style. + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>); + + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. + /// + /// __Warning:__ Text currently does not work well with rotations and scale + /// transforms! The position will be correctly transformed, but the + /// resulting glyphs will not be rotated or scaled properly. + /// + /// Additionally, all text will be rendered on top of all the layers of + /// a `Canvas`. Therefore, it is currently only meant to be used for + /// overlays, which is the most common use case. + /// + /// Support for vectorial text is planned, and should address all these + /// limitations. + fn fill_text(&mut self, text: impl Into); + + /// Stores the current transform of the [`Frame`] and executes the given + /// drawing operations, restoring the transform afterwards. + /// + /// This method is useful to compose transforms and perform drawing + /// operations in different coordinate systems. + #[inline] + fn with_save(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + self.push_transform(); + + let result = f(self); + + self.pop_transform(); + + result + } + + /// Pushes the current transform in the transform stack. + fn push_transform(&mut self); + + /// Pops a transform from the transform stack and sets it as the current transform. + fn pop_transform(&mut self); + + /// Executes the given drawing operations within a [`Rectangle`] region, + /// clipping any geometry that overflows its bounds. Any transformations + /// performed are local to the provided closure. + /// + /// This method is useful to perform drawing operations that need to be + /// clipped. + #[inline] + fn with_clip( + &mut self, + region: Rectangle, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + let mut frame = self.draft(region.size()); + + let result = f(&mut frame); + + let origin = Point::new(region.x, region.y); + + self.paste(frame, origin); + + result + } + + /// Creates a new [`Frame`] with the given [`Size`]. + /// + /// Draw its contents back to this [`Frame`] with [`paste`]. + /// + /// [`paste`]: Self::paste + fn draft(&mut self, size: Size) -> Self; + + /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. + fn paste(&mut self, frame: Self, at: Point); + + /// Applies a translation to the current transform of the [`Frame`]. + fn translate(&mut self, translation: Vector); + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + fn rotate(&mut self, angle: impl Into); + + /// Applies a uniform scaling to the current transform of the [`Frame`]. + fn scale(&mut self, scale: impl Into); + + /// Applies a non-uniform scaling to the current transform of the [`Frame`]. + fn scale_nonuniform(&mut self, scale: impl Into); +} + +pub trait Geometry: Sized { + type Cache; + + fn load(cache: &Self::Cache) -> Self; + + fn cache(self) -> Self::Cache; +} + +/// A simple cache that stores generated [`Geometry`] to avoid recomputation. +/// +/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer +/// change or it is explicitly cleared. +pub struct Cache +where + Renderer: self::Renderer, +{ + state: RefCell>, +} + +impl Cache +where + Renderer: self::Renderer, +{ + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + state: RefCell::new(State::Empty), + } + } + + /// Clears the [`Cache`], forcing a redraw the next time it is used. + pub fn clear(&self) { + *self.state.borrow_mut() = State::Empty; + } + + /// Draws [`Geometry`] using the provided closure and stores it in the + /// [`Cache`]. + /// + /// The closure will only be called when + /// - the bounds have changed since the previous draw call. + /// - the [`Cache`] is empty or has been explicitly cleared. + /// + /// Otherwise, the previously stored [`Geometry`] will be returned. The + /// [`Cache`] is not cleared in this case. In other words, it will keep + /// returning the stored [`Geometry`] if needed. + pub fn draw( + &self, + renderer: &Renderer, + bounds: Size, + draw_fn: impl FnOnce(&mut Renderer::Frame), + ) -> Renderer::Geometry { + use std::ops::Deref; + + if let State::Filled { + bounds: cached_bounds, + geometry, + } = self.state.borrow().deref() + { + if *cached_bounds == bounds { + return Geometry::load(geometry); + } + } + + let mut frame = frame(renderer, bounds); + draw_fn(&mut frame); + + let geometry = frame.into().cache(); + let result = Geometry::load(&geometry); + + *self.state.borrow_mut() = State::Filled { bounds, geometry }; + + result + } +} + +impl std::fmt::Debug for Cache +where + Renderer: self::Renderer, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let state = self.state.borrow(); + + match *state { + State::Empty => write!(f, "Cache::Empty"), + State::Filled { bounds, .. } => { + write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") + } + } + } +} + +impl Default for Cache +where + Renderer: self::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +enum State +where + Geometry: self::Geometry, +{ + Empty, + Filled { + bounds: Size, + geometry: Geometry::Cache, + }, +} + +impl Geometry for Primitive { + type Cache = Arc; + + fn load(cache: &Arc) -> Self { + Self::Cache { + content: cache.clone(), + } + } - /// Draws the given layers of [`Self::Geometry`]. - fn draw(&mut self, layers: Vec); + fn cache(self) -> Arc { + Arc::new(self) + } } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index aa9d00e8..6d0862ad 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,8 +9,8 @@ )] #![forbid(rust_2018_idioms)] #![deny( - missing_debug_implementations, - missing_docs, + // missing_debug_implementations, + // missing_docs, unsafe_code, unused_results, rustdoc::broken_intra_doc_links diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index 041986cf..5be3ee5b 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -1,6 +1,6 @@ //! Draw triangles! use crate::color; -use crate::core::{Rectangle, Size}; +use crate::core::{self, Rectangle, Size}; use crate::gradient; use crate::Damage; @@ -74,3 +74,7 @@ pub struct GradientVertex2D { /// The packed vertex data of the gradient. pub gradient: gradient::Packed, } + +pub trait Renderer: core::Renderer { + fn draw_mesh(&mut self, mesh: Mesh); +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index e7154385..3b21aa11 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -8,8 +8,9 @@ use crate::core::text::Text; use crate::core::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; +use crate::mesh; use crate::text; -use crate::Primitive; +use crate::{Mesh, Primitive}; use std::borrow::Cow; @@ -20,6 +21,7 @@ pub struct Renderer { default_font: Font, default_text_size: Pixels, primitives: Vec>, + stack: Vec>>, } impl Renderer { @@ -34,6 +36,7 @@ impl Renderer { default_font, default_text_size, primitives: Vec::new(), + stack: Vec::new(), } } @@ -56,59 +59,45 @@ impl Renderer { f(&mut self.backend, &self.primitives) } - /// Starts recording a new layer. - pub fn start_layer(&mut self) -> Vec> { - std::mem::take(&mut self.primitives) - } - - /// Ends the recording of a layer. - pub fn end_layer( - &mut self, - primitives: Vec>, - bounds: Rectangle, - ) { - let layer = std::mem::replace(&mut self.primitives, primitives); - - self.primitives.push(Primitive::group(layer).clip(bounds)); - } - - /// Starts recording a translation. - pub fn start_transformation(&mut self) -> Vec> { - std::mem::take(&mut self.primitives) - } - - /// Ends the recording of a translation. - pub fn end_transformation( + #[cfg(feature = "geometry")] + pub fn draw_geometry( &mut self, - primitives: Vec>, - transformation: Transformation, - ) { - let layer = std::mem::replace(&mut self.primitives, primitives); - - self.primitives - .push(Primitive::group(layer).transform(transformation)); + layers: impl IntoIterator, + ) where + Geometry: Into>, + { + for layer in layers { + self.draw_primitive(layer.into()); + } } } impl iced_core::Renderer for Renderer { - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { - let current = self.start_layer(); + fn start_layer(&mut self) { + self.stack.push(std::mem::take(&mut self.primitives)); + } - f(self); + fn end_layer(&mut self, bounds: Rectangle) { + let layer = std::mem::replace( + &mut self.primitives, + self.stack.pop().expect("a layer should be recording"), + ); - self.end_layer(current, bounds); + self.primitives.push(Primitive::group(layer).clip(bounds)); } - fn with_transformation( - &mut self, - transformation: Transformation, - f: impl FnOnce(&mut Self), - ) { - let current = self.start_transformation(); + fn start_transformation(&mut self) { + self.stack.push(std::mem::take(&mut self.primitives)); + } - f(self); + fn end_transformation(&mut self, transformation: Transformation) { + let layer = std::mem::replace( + &mut self.primitives, + self.stack.pop().expect("a layer should be recording"), + ); - self.end_transformation(current, transformation); + self.primitives + .push(Primitive::group(layer).transform(transformation)); } fn fill_quad( @@ -250,3 +239,34 @@ where }); } } + +impl mesh::Renderer for Renderer { + fn draw_mesh(&mut self, mesh: Mesh) { + match B::Primitive::try_from(mesh) { + Ok(primitive) => { + self.draw_primitive(Primitive::Custom(primitive)); + } + Err(error) => { + log::warn!("mesh primitive could not be drawn: {error:?}"); + } + } + } +} + +#[cfg(feature = "geometry")] +impl crate::geometry::Renderer for Renderer +where + B: Backend + crate::geometry::Backend, + B::Frame: crate::geometry::Frame>, +{ + type Frame = B::Frame; + type Geometry = Primitive; + + fn new_frame(&self, size: Size) -> Self::Frame { + self.backend.new_frame(size) + } + + fn draw_geometry(&mut self, geometry: Self::Geometry) { + self.draw_primitive(geometry); + } +} diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index 3d0b3ad0..8b137891 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -1,300 +1 @@ -use crate::core::Color; -use crate::graphics::compositor::{Information, SurfaceError, Window}; -use crate::graphics::{Error, Viewport}; -use crate::{Renderer, Settings}; -use std::env; -use std::future::Future; - -pub enum Compositor { - TinySkia(iced_tiny_skia::window::Compositor), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::window::Compositor), - #[cfg(feature = "custom")] - Custom(Box), -} - -pub enum Surface { - TinySkia(iced_tiny_skia::window::Surface), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::window::Surface<'static>), - #[cfg(feature = "custom")] - Custom(Box), -} - -impl crate::graphics::Compositor for Compositor { - type Settings = Settings; - type Renderer = Renderer; - type Surface = Surface; - - fn new( - settings: Self::Settings, - compatible_window: W, - ) -> impl Future> { - let candidates = - Candidate::list_from_env().unwrap_or(Candidate::default_list()); - - async move { - let mut error = Error::GraphicsAdapterNotFound; - - for candidate in candidates { - match candidate.build(settings, compatible_window.clone()).await - { - Ok(compositor) => return Ok(compositor), - Err(new_error) => { - error = new_error; - } - } - } - - Err(error) - } - } - - fn create_renderer(&self) -> Self::Renderer { - match self { - Compositor::TinySkia(compositor) => { - Renderer::TinySkia(compositor.create_renderer()) - } - #[cfg(feature = "wgpu")] - Compositor::Wgpu(compositor) => { - Renderer::Wgpu(compositor.create_renderer()) - } - #[cfg(feature = "custom")] - Compositor::Custom(compositor) => { - Renderer::Custom(compositor.create_renderer()) - } - } - } - - fn create_surface( - &mut self, - window: W, - width: u32, - height: u32, - ) -> Surface { - match self { - Self::TinySkia(compositor) => Surface::TinySkia( - compositor.create_surface(window, width, height), - ), - #[cfg(feature = "wgpu")] - Self::Wgpu(compositor) => { - Surface::Wgpu(compositor.create_surface(window, width, height)) - } - #[cfg(feature = "custom")] - Self::Custom(compositor) => Surface::Custom( - compositor.create_surface(Box::new(window), width, height), - ), - } - } - - fn configure_surface( - &mut self, - surface: &mut Surface, - width: u32, - height: u32, - ) { - match (self, surface) { - (Self::TinySkia(compositor), Surface::TinySkia(surface)) => { - compositor.configure_surface(surface, width, height); - } - #[cfg(feature = "wgpu")] - (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { - compositor.configure_surface(surface, width, height); - } - #[cfg(feature = "custom")] - (Self::Custom(compositor), Surface::Custom(surface)) => { - compositor.configure_surface(surface.as_mut(), width, height); - } - #[allow(unreachable_patterns)] - _ => panic!( - "The provided surface is not compatible with the compositor." - ), - } - } - - fn fetch_information(&self) -> Information { - match self { - Self::TinySkia(compositor) => compositor.fetch_information(), - #[cfg(feature = "wgpu")] - Self::Wgpu(compositor) => compositor.fetch_information(), - #[cfg(feature = "custom")] - Self::Custom(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> { - match (self, renderer, surface) { - ( - Self::TinySkia(_compositor), - crate::Renderer::TinySkia(renderer), - Surface::TinySkia(surface), - ) => renderer.with_primitives(|backend, primitives| { - iced_tiny_skia::window::compositor::present( - backend, - surface, - primitives, - viewport, - background_color, - overlay, - ) - }), - #[cfg(feature = "wgpu")] - ( - Self::Wgpu(compositor), - crate::Renderer::Wgpu(renderer), - Surface::Wgpu(surface), - ) => renderer.with_primitives(|backend, primitives| { - iced_wgpu::window::compositor::present( - compositor, - backend, - surface, - primitives, - viewport, - background_color, - overlay, - ) - }), - - #[cfg(feature = "custom")] - ( - Self::Custom(compositor), - crate::Renderer::Custom(renderer), - Surface::Custom(surface), - ) => renderer.present( - surface.as_mut(), - viewport, - background_color, - compositor.as_mut(), - ), - #[allow(unreachable_patterns)] - _ => panic!( - "The provided renderer or surface are not compatible \ - with the compositor." - ), - } - } - - fn screenshot>( - &mut self, - renderer: &mut Self::Renderer, - surface: &mut Self::Surface, - viewport: &Viewport, - background_color: Color, - overlay: &[T], - ) -> Vec { - match (self, renderer, surface) { - ( - Self::TinySkia(_compositor), - Renderer::TinySkia(renderer), - Surface::TinySkia(surface), - ) => renderer.with_primitives(|backend, primitives| { - iced_tiny_skia::window::compositor::screenshot( - surface, - backend, - primitives, - viewport, - background_color, - overlay, - ) - }), - #[cfg(feature = "wgpu")] - ( - Self::Wgpu(compositor), - Renderer::Wgpu(renderer), - Surface::Wgpu(_), - ) => renderer.with_primitives(|backend, primitives| { - iced_wgpu::window::compositor::screenshot( - compositor, - backend, - primitives, - viewport, - background_color, - overlay, - ) - }), - #[allow(unreachable_patterns)] - _ => panic!( - "The provided renderer or backend are not compatible \ - with the compositor." - ), - } - } -} - -enum Candidate { - Wgpu, - TinySkia, -} - -impl Candidate { - fn default_list() -> Vec { - vec![ - #[cfg(feature = "wgpu")] - Self::Wgpu, - Self::TinySkia, - ] - } - - fn list_from_env() -> Option> { - let backends = env::var("ICED_BACKEND").ok()?; - - Some( - backends - .split(',') - .map(str::trim) - .map(|backend| match backend { - "wgpu" => Self::Wgpu, - "tiny-skia" => Self::TinySkia, - _ => panic!("unknown backend value: \"{backend}\""), - }) - .collect(), - ) - } - - async fn build( - self, - settings: Settings, - _compatible_window: W, - ) -> Result { - match self { - Self::TinySkia => { - let compositor = iced_tiny_skia::window::compositor::new( - iced_tiny_skia::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - }, - _compatible_window, - ); - - Ok(Compositor::TinySkia(compositor)) - } - #[cfg(feature = "wgpu")] - Self::Wgpu => { - let compositor = 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, - ) - .await?; - - Ok(Compositor::Wgpu(compositor)) - } - #[cfg(not(feature = "wgpu"))] - Self::Wgpu => { - panic!("`wgpu` feature was not enabled in `iced_renderer`") - } - } - } -} diff --git a/renderer/src/custom.rs b/renderer/src/custom.rs index 04090ccb..4addeb86 100644 --- a/renderer/src/custom.rs +++ b/renderer/src/custom.rs @@ -94,8 +94,6 @@ pub trait Renderer { #[cfg(feature = "geometry")] pub trait Frame: std::any::Any { - fn new(&self, size: Size) -> Box; - fn width(&self) -> f32; fn height(&self) -> f32; @@ -108,7 +106,7 @@ pub trait Frame: std::any::Any { fn fill_rectangle(&mut self, top_left: Point, size: Size, fill: Fill); - fn stroke<'a>(&mut self, path: &Path, stroke: Stroke<'a>); + fn stroke(&mut self, path: &Path, stroke: Stroke<'_>); fn fill_text(&mut self, text: geometry::Text); diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs new file mode 100644 index 00000000..a4c725c0 --- /dev/null +++ b/renderer/src/fallback.rs @@ -0,0 +1,562 @@ +use crate::core::image; +use crate::core::renderer; +use crate::core::svg; +use crate::core::{ + self, Background, Color, Point, Rectangle, Size, Transformation, +}; +use crate::graphics; +use crate::graphics::compositor; +use crate::graphics::mesh; + +pub enum Renderer +where + L: core::Renderer, + R: core::Renderer, +{ + Left(L), + Right(R), +} + +macro_rules! delegate { + ($renderer:expr, $name:ident, $body:expr) => { + match $renderer { + Self::Left($name) => $body, + Self::Right($name) => $body, + } + }; +} + +impl Renderer +where + L: core::Renderer, + R: core::Renderer, +{ + #[cfg(feature = "geometry")] + pub fn draw_geometry( + &mut self, + layers: impl IntoIterator, + ) where + L: graphics::geometry::Renderer, + R: graphics::geometry::Renderer, + + Geometry: Into>, + { + use graphics::geometry::Renderer; + + for layer in layers { + ::draw_geometry(self, layer.into()); + } + } +} + +impl core::Renderer for Renderer +where + L: core::Renderer, + R: core::Renderer, +{ + fn fill_quad( + &mut self, + quad: renderer::Quad, + background: impl Into, + ) { + delegate!(self, renderer, renderer.fill_quad(quad, background.into())); + } + + fn clear(&mut self) { + delegate!(self, renderer, renderer.clear()); + } + + fn start_layer(&mut self) { + delegate!(self, renderer, renderer.start_layer()); + } + + fn end_layer(&mut self, bounds: Rectangle) { + delegate!(self, renderer, renderer.end_layer(bounds)); + } + + fn start_transformation(&mut self) { + delegate!(self, renderer, renderer.start_transformation()); + } + + fn end_transformation(&mut self, transformation: Transformation) { + delegate!(self, renderer, renderer.end_transformation(transformation)); + } +} + +impl core::text::Renderer for Renderer +where + L: core::text::Renderer, + R: core::text::Renderer< + Font = L::Font, + Paragraph = L::Paragraph, + Editor = L::Editor, + >, +{ + type Font = L::Font; + type Paragraph = L::Paragraph; + type Editor = L::Editor; + + const ICON_FONT: Self::Font = L::ICON_FONT; + const CHECKMARK_ICON: char = L::CHECKMARK_ICON; + const ARROW_DOWN_ICON: char = L::ARROW_DOWN_ICON; + + fn default_font(&self) -> Self::Font { + delegate!(self, renderer, renderer.default_font()) + } + + fn default_size(&self) -> core::Pixels { + delegate!(self, renderer, renderer.default_size()) + } + + fn load_font(&mut self, font: std::borrow::Cow<'static, [u8]>) { + delegate!(self, renderer, renderer.load_font(font)); + } + + fn fill_paragraph( + &mut self, + text: &Self::Paragraph, + position: Point, + color: Color, + clip_bounds: Rectangle, + ) { + delegate!( + self, + renderer, + renderer.fill_paragraph(text, position, color, clip_bounds) + ); + } + + fn fill_editor( + &mut self, + editor: &Self::Editor, + position: Point, + color: Color, + clip_bounds: Rectangle, + ) { + delegate!( + self, + renderer, + renderer.fill_editor(editor, position, color, clip_bounds) + ); + } + + fn fill_text( + &mut self, + text: core::Text<'_, Self::Font>, + position: Point, + color: Color, + clip_bounds: Rectangle, + ) { + delegate!( + self, + renderer, + renderer.fill_text(text, position, color, clip_bounds) + ); + } +} + +impl image::Renderer for Renderer +where + L: image::Renderer, + R: image::Renderer, +{ + type Handle = L::Handle; + + fn measure_image(&self, handle: &Self::Handle) -> Size { + delegate!(self, renderer, renderer.measure_image(handle)) + } + + fn draw_image( + &mut self, + handle: Self::Handle, + filter_method: image::FilterMethod, + bounds: Rectangle, + ) { + delegate!( + self, + renderer, + renderer.draw_image(handle, filter_method, bounds) + ); + } +} + +impl svg::Renderer for Renderer +where + L: svg::Renderer, + R: svg::Renderer, +{ + fn measure_svg(&self, handle: &svg::Handle) -> Size { + delegate!(self, renderer, renderer.measure_svg(handle)) + } + + fn draw_svg( + &mut self, + handle: svg::Handle, + color: Option, + bounds: Rectangle, + ) { + delegate!(self, renderer, renderer.draw_svg(handle, color, bounds)); + } +} + +impl mesh::Renderer for Renderer +where + L: mesh::Renderer, + R: mesh::Renderer, +{ + fn draw_mesh(&mut self, mesh: graphics::Mesh) { + delegate!(self, renderer, renderer.draw_mesh(mesh)); + } +} + +pub enum Compositor +where + L: graphics::Compositor, + R: graphics::Compositor, +{ + Left(L), + Right(R), +} + +pub enum Surface { + Left(L), + Right(R), +} + +impl graphics::Compositor for Compositor +where + L: graphics::Compositor, + R: graphics::Compositor, + L::Settings: From, + R::Settings: From, +{ + type Settings = crate::Settings; + type Renderer = Renderer; + type Surface = Surface; + + async fn new( + settings: Self::Settings, + compatible_window: W, + ) -> Result { + if let Ok(left) = L::new(settings.into(), compatible_window.clone()) + .await + .map(Self::Left) + { + return Ok(left); + } + + R::new(settings.into(), compatible_window) + .await + .map(Self::Right) + } + + fn create_renderer(&self) -> Self::Renderer { + match self { + Self::Left(compositor) => { + Renderer::Left(compositor.create_renderer()) + } + Self::Right(compositor) => { + Renderer::Right(compositor.create_renderer()) + } + } + } + + fn create_surface( + &mut self, + window: W, + width: u32, + height: u32, + ) -> Self::Surface { + match self { + Self::Left(compositor) => { + Surface::Left(compositor.create_surface(window, width, height)) + } + Self::Right(compositor) => { + Surface::Right(compositor.create_surface(window, width, height)) + } + } + } + + fn configure_surface( + &mut self, + surface: &mut Self::Surface, + width: u32, + height: u32, + ) { + match (self, surface) { + (Self::Left(compositor), Surface::Left(surface)) => { + compositor.configure_surface(surface, width, height); + } + (Self::Right(compositor), Surface::Right(surface)) => { + compositor.configure_surface(surface, width, height); + } + _ => unreachable!(), + } + } + + fn fetch_information(&self) -> compositor::Information { + delegate!(self, compositor, compositor.fetch_information()) + } + + fn present>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &graphics::Viewport, + background_color: Color, + overlay: &[T], + ) -> Result<(), compositor::SurfaceError> { + match (self, renderer, surface) { + ( + Self::Left(compositor), + Renderer::Left(renderer), + Surface::Left(surface), + ) => compositor.present( + renderer, + surface, + viewport, + background_color, + overlay, + ), + ( + Self::Right(compositor), + Renderer::Right(renderer), + Surface::Right(surface), + ) => compositor.present( + renderer, + surface, + viewport, + background_color, + overlay, + ), + _ => unreachable!(), + } + } + + fn screenshot>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &graphics::Viewport, + background_color: Color, + overlay: &[T], + ) -> Vec { + match (self, renderer, surface) { + ( + Self::Left(compositor), + Renderer::Left(renderer), + Surface::Left(surface), + ) => compositor.screenshot( + renderer, + surface, + viewport, + background_color, + overlay, + ), + ( + Self::Right(compositor), + Renderer::Right(renderer), + Surface::Right(surface), + ) => compositor.screenshot( + renderer, + surface, + viewport, + background_color, + overlay, + ), + _ => unreachable!(), + } + } +} + +#[cfg(feature = "wgpu")] +impl iced_wgpu::primitive::pipeline::Renderer for Renderer +where + L: iced_wgpu::primitive::pipeline::Renderer, + R: core::Renderer, +{ + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl iced_wgpu::primitive::pipeline::Primitive, + ) { + match self { + Self::Left(renderer) => { + renderer.draw_pipeline_primitive(bounds, primitive); + } + Self::Right(_) => { + log::warn!( + "Custom shader primitive is not supported with this renderer." + ); + } + } + } +} + +#[cfg(feature = "geometry")] +mod geometry { + use super::Renderer; + use crate::core::{Point, Radians, Size, Vector}; + use crate::graphics::geometry::{self, Fill, Path, Stroke, Text}; + + impl geometry::Renderer for Renderer + where + L: geometry::Renderer, + R: geometry::Renderer, + { + type Geometry = Geometry; + type Frame = Frame; + + fn new_frame(&self, size: iced_graphics::core::Size) -> Self::Frame { + match self { + Self::Left(renderer) => Frame::Left(renderer.new_frame(size)), + Self::Right(renderer) => Frame::Right(renderer.new_frame(size)), + } + } + + fn draw_geometry(&mut self, geometry: Self::Geometry) { + match (self, geometry) { + (Self::Left(renderer), Geometry::Left(geometry)) => { + renderer.draw_geometry(geometry); + } + (Self::Right(renderer), Geometry::Right(geometry)) => { + renderer.draw_geometry(geometry); + } + _ => unreachable!(), + } + } + } + + pub enum Geometry { + Left(L), + Right(R), + } + + impl geometry::Geometry for Geometry + where + L: geometry::Geometry, + R: geometry::Geometry, + { + type Cache = Geometry; + + fn load(cache: &Self::Cache) -> Self { + match cache { + Geometry::Left(cache) => Self::Left(L::load(cache)), + Geometry::Right(cache) => Self::Right(R::load(cache)), + } + } + + fn cache(self) -> Self::Cache { + match self { + Self::Left(geometry) => Geometry::Left(geometry.cache()), + Self::Right(geometry) => Geometry::Right(geometry.cache()), + } + } + } + + pub enum Frame { + Left(L), + Right(R), + } + + impl geometry::Frame for Frame + where + L: geometry::Frame, + R: geometry::Frame, + { + type Geometry = Geometry; + + fn width(&self) -> f32 { + delegate!(self, frame, frame.width()) + } + + fn height(&self) -> f32 { + delegate!(self, frame, frame.height()) + } + + fn size(&self) -> Size { + delegate!(self, frame, frame.size()) + } + + fn center(&self) -> Point { + delegate!(self, frame, frame.center()) + } + + fn fill(&mut self, path: &Path, fill: impl Into) { + delegate!(self, frame, frame.fill(path, fill)); + } + + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ) { + delegate!(self, frame, frame.fill_rectangle(top_left, size, fill)); + } + + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + delegate!(self, frame, frame.stroke(path, stroke)); + } + + fn fill_text(&mut self, text: impl Into) { + delegate!(self, frame, frame.fill_text(text)); + } + + fn push_transform(&mut self) { + delegate!(self, frame, frame.push_transform()); + } + + fn pop_transform(&mut self) { + delegate!(self, frame, frame.pop_transform()); + } + + fn draft(&mut self, size: Size) -> Self { + match self { + Self::Left(frame) => Self::Left(frame.draft(size)), + Self::Right(frame) => Self::Right(frame.draft(size)), + } + } + + fn paste(&mut self, frame: Self, at: Point) { + match (self, frame) { + (Self::Left(target), Self::Left(source)) => { + target.paste(source, at); + } + (Self::Right(target), Self::Right(source)) => { + target.paste(source, at); + } + _ => unreachable!(), + } + } + + fn translate(&mut self, translation: Vector) { + delegate!(self, frame, frame.translate(translation)); + } + + fn rotate(&mut self, angle: impl Into) { + delegate!(self, frame, frame.rotate(angle)); + } + + fn scale(&mut self, scale: impl Into) { + delegate!(self, frame, frame.scale(scale)); + } + + fn scale_nonuniform(&mut self, scale: impl Into) { + delegate!(self, frame, frame.scale_nonuniform(scale)); + } + } + + impl From> for Geometry + where + L: geometry::Frame, + R: geometry::Frame, + { + fn from(frame: Frame) -> Self { + match frame { + Frame::Left(frame) => Self::Left(frame.into()), + Frame::Right(frame) => Self::Right(frame.into()), + } + } + } +} diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 67096115..f8aa1157 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -4,364 +4,42 @@ #[cfg(feature = "wgpu")] pub use iced_wgpu as wgpu; -pub mod compositor; -pub mod custom; - -#[cfg(feature = "geometry")] -pub mod geometry; +pub mod fallback; mod settings; pub use iced_graphics as graphics; pub use iced_graphics::core; -pub use compositor::Compositor; -pub use settings::Settings; - #[cfg(feature = "geometry")] -pub use geometry::Geometry; - -use crate::core::renderer; -use crate::core::text::{self, Text}; -use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Transformation, -}; -use crate::graphics::text::Editor; -use crate::graphics::text::Paragraph; -use crate::graphics::Mesh; +pub use iced_graphics::geometry; -use std::borrow::Cow; +pub use settings::Settings; /// The default graphics renderer for [`iced`]. /// /// [`iced`]: https://github.com/iced-rs/iced -pub enum Renderer { - TinySkia(iced_tiny_skia::Renderer), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::Renderer), - #[cfg(feature = "custom")] - Custom(Box), -} - -macro_rules! delegate { - ($renderer:expr, $name:ident, $body:expr) => { - match $renderer { - Self::TinySkia($name) => $body, - #[cfg(feature = "wgpu")] - Self::Wgpu($name) => $body, - #[cfg(feature = "custom")] - Self::Custom($name) => $body, - } - }; -} - -impl Renderer { - pub fn draw_mesh(&mut self, mesh: Mesh) { - match self { - Self::TinySkia(_) => { - log::warn!("Unsupported mesh primitive: {mesh:?}"); - } - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - renderer.draw_primitive(iced_wgpu::Primitive::Custom( - iced_wgpu::primitive::Custom::Mesh(mesh), - )); - } - #[cfg(feature = "custom")] - Self::Custom(renderer) => { - renderer.draw_mesh(mesh); - } - } - } -} - -impl core::Renderer for Renderer { - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { - match self { - Self::TinySkia(renderer) => { - let primitives = renderer.start_layer(); - - f(self); - - match self { - Self::TinySkia(renderer) => { - renderer.end_layer(primitives, bounds); - } - #[cfg(feature = "wgpu")] - _ => unreachable!(), - } - } - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - let primitives = renderer.start_layer(); - - f(self); - - match self { - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - renderer.end_layer(primitives, bounds); - } - _ => unreachable!(), - } - } - #[cfg(feature = "custom")] - Self::Custom(renderer) => { - renderer.start_layer(); - - f(self); - - match self { - Self::Custom(renderer) => { - renderer.end_layer(bounds); - } - _ => unreachable!(), - } - } - } - } - - fn with_transformation( - &mut self, - transformation: Transformation, - f: impl FnOnce(&mut Self), - ) { - match self { - Self::TinySkia(renderer) => { - let primitives = renderer.start_transformation(); - - f(self); - - match self { - Self::TinySkia(renderer) => { - renderer.end_transformation(primitives, transformation); - } - #[cfg(feature = "wgpu")] - _ => unreachable!(), - } - } - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - let primitives = renderer.start_transformation(); - - f(self); - - match self { - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - renderer.end_transformation(primitives, transformation); - } - _ => unreachable!(), - } - } - #[cfg(feature = "custom")] - Self::Custom(renderer) => { - renderer.start_transformation(); - - f(self); - - match self { - Self::Custom(renderer) => { - renderer.end_transformation(transformation); - } - _ => unreachable!(), - } - } - } - } - - fn fill_quad( - &mut self, - quad: renderer::Quad, - background: impl Into, - ) { - delegate!(self, renderer, renderer.fill_quad(quad, background.into())); - } - - fn clear(&mut self) { - delegate!(self, renderer, renderer.clear()); - } -} +#[cfg(not(feature = "wgpu"))] +pub type Renderer = iced_tiny_skia::Renderer; -impl text::Renderer for Renderer { - type Font = Font; - type Paragraph = Paragraph; - type Editor = Editor; - - const ICON_FONT: Font = iced_tiny_skia::Renderer::ICON_FONT; - const CHECKMARK_ICON: char = iced_tiny_skia::Renderer::CHECKMARK_ICON; - const ARROW_DOWN_ICON: char = iced_tiny_skia::Renderer::ARROW_DOWN_ICON; - - fn default_font(&self) -> Self::Font { - delegate!(self, renderer, renderer.default_font()) - } - - fn default_size(&self) -> Pixels { - delegate!(self, renderer, renderer.default_size()) - } - - fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - delegate!(self, renderer, renderer.load_font(bytes)); - } - - fn fill_paragraph( - &mut self, - paragraph: &Self::Paragraph, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - delegate!( - self, - renderer, - renderer.fill_paragraph(paragraph, position, color, clip_bounds) - ); - } - - fn fill_editor( - &mut self, - editor: &Self::Editor, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - delegate!( - self, - renderer, - renderer.fill_editor(editor, position, color, clip_bounds) - ); - } - - fn fill_text( - &mut self, - text: Text<'_, Self::Font>, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - delegate!( - self, - renderer, - renderer.fill_text(text, position, color, clip_bounds) - ); - } -} - -#[cfg(feature = "image")] -impl crate::core::image::Renderer for Renderer { - type Handle = crate::core::image::Handle; - - fn measure_image( - &self, - handle: &crate::core::image::Handle, - ) -> core::Size { - delegate!(self, renderer, renderer.measure_image(handle)) - } - - fn draw_image( - &mut self, - handle: crate::core::image::Handle, - filter_method: crate::core::image::FilterMethod, - bounds: Rectangle, - ) { - delegate!( - self, - renderer, - renderer.draw_image(handle, filter_method, bounds) - ); - } -} - -#[cfg(feature = "svg")] -impl crate::core::svg::Renderer for Renderer { - fn measure_svg( - &self, - handle: &crate::core::svg::Handle, - ) -> core::Size { - delegate!(self, renderer, renderer.measure_svg(handle)) - } - - fn draw_svg( - &mut self, - handle: crate::core::svg::Handle, - color: Option, - bounds: Rectangle, - ) { - delegate!(self, renderer, renderer.draw_svg(handle, color, bounds)); - } -} - -#[cfg(feature = "geometry")] -impl crate::graphics::geometry::Renderer for Renderer { - type Geometry = crate::Geometry; +/// The default graphics renderer for [`iced`]. +/// +/// [`iced`]: https://github.com/iced-rs/iced +#[cfg(feature = "wgpu")] +pub type Renderer = + fallback::Renderer; - fn draw(&mut self, layers: Vec) { - match self { - Self::TinySkia(renderer) => { - for layer in layers { - match layer { - crate::Geometry::TinySkia(primitive) => { - renderer.draw_primitive(primitive); - } - #[cfg(feature = "wgpu")] - crate::Geometry::Wgpu(_) => unreachable!(), - #[cfg(feature = "custom")] - crate::Geometry::Custom(_) => unreachable!(), - } - } - } - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - for layer in layers { - match layer { - crate::Geometry::Wgpu(primitive) => { - renderer.draw_primitive(primitive); - } - crate::Geometry::TinySkia(_) => unreachable!(), - #[cfg(feature = "custom")] - crate::Geometry::Custom(_) => unreachable!(), - } - } - } - #[cfg(feature = "custom")] - Self::Custom(renderer) => { - for layer in layers { - match layer { - crate::Geometry::Custom(geometry) => { - renderer.draw_geometry(geometry); - } - crate::Geometry::TinySkia(_) => unreachable!(), - #[cfg(feature = "wgpu")] - crate::Geometry::Wgpu(_) => unreachable!(), - } - } - } - } - } -} +/// The default graphics compositor for [`iced`]. +/// +/// [`iced`]: https://github.com/iced-rs/iced +#[cfg(not(feature = "wgpu"))] +pub type Compositor = iced_tiny_skia::window::Compositor; +/// The default graphics renderer for [`iced`]. +/// +/// [`iced`]: https://github.com/iced-rs/iced #[cfg(feature = "wgpu")] -impl iced_wgpu::primitive::pipeline::Renderer for Renderer { - fn draw_pipeline_primitive( - &mut self, - bounds: Rectangle, - primitive: impl wgpu::primitive::pipeline::Primitive, - ) { - match self { - Self::TinySkia(_renderer) => { - log::warn!( - "Custom shader primitive is unavailable with tiny-skia." - ); - } - Self::Wgpu(renderer) => { - renderer.draw_pipeline_primitive(bounds, primitive); - } - #[cfg(feature = "custom")] - Self::Custom(_renderer) => { - log::warn!( - "Custom shader primitive is unavailable with custom renderer." - ); - } - } - } -} +pub type Compositor = fallback::Compositor< + iced_wgpu::window::Compositor, + iced_tiny_skia::window::Compositor, +>; diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs index 432eb8a0..940daa15 100644 --- a/renderer/src/settings.rs +++ b/renderer/src/settings.rs @@ -27,3 +27,24 @@ impl Default for Settings { } } } + +impl From for iced_tiny_skia::Settings { + fn from(settings: Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + } + } +} + +#[cfg(feature = "wgpu")] +impl From for iced_wgpu::Settings { + fn from(settings: Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..iced_wgpu::Settings::default() + } + } +} diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index b6487b38..f6bb1c86 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1018,3 +1018,12 @@ impl backend::Svg for Backend { self.vector_pipeline.viewport_dimensions(handle) } } + +#[cfg(feature = "geometry")] +impl crate::graphics::geometry::Backend for Backend { + type Frame = crate::geometry::Frame; + + fn new_frame(&self, size: Size) -> Self::Frame { + crate::geometry::Frame::new(size) + } +} diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 16787f89..6b1888d0 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -4,7 +4,7 @@ use crate::core::{ }; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::stroke::{self, Stroke}; -use crate::graphics::geometry::{Path, Style, Text}; +use crate::graphics::geometry::{self, Path, Style, Text}; use crate::graphics::Gradient; use crate::primitive::{self, Primitive}; @@ -25,23 +25,36 @@ impl Frame { } } - pub fn width(&self) -> f32 { + pub fn into_primitive(self) -> Primitive { + Primitive::Clip { + bounds: Rectangle::new(Point::ORIGIN, self.size), + content: Box::new(Primitive::Group { + primitives: self.primitives, + }), + } + } +} + +impl geometry::Frame for Frame { + type Geometry = Primitive; + + fn width(&self) -> f32 { self.size.width } - pub fn height(&self) -> f32 { + fn height(&self) -> f32 { self.size.height } - pub fn size(&self) -> Size { + fn size(&self) -> Size { self.size } - pub fn center(&self) -> Point { + fn center(&self) -> Point { Point::new(self.size.width / 2.0, self.size.height / 2.0) } - pub fn fill(&mut self, path: &Path, fill: impl Into) { + fn fill(&mut self, path: &Path, fill: impl Into) { let Some(path) = convert_path(path).and_then(|path| path.transform(self.transform)) else { @@ -61,7 +74,7 @@ impl Frame { })); } - pub fn fill_rectangle( + fn fill_rectangle( &mut self, top_left: Point, size: Size, @@ -89,7 +102,7 @@ impl Frame { })); } - pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { let Some(path) = convert_path(path).and_then(|path| path.transform(self.transform)) else { @@ -110,7 +123,7 @@ impl Frame { })); } - pub fn fill_text(&mut self, text: impl Into) { + fn fill_text(&mut self, text: impl Into) { let text = text.into(); let (scale_x, scale_y) = self.transform.get_scale(); @@ -174,51 +187,52 @@ impl Frame { } } - pub fn push_transform(&mut self) { + fn push_transform(&mut self) { self.stack.push(self.transform); } - pub fn pop_transform(&mut self) { + fn pop_transform(&mut self) { self.transform = self.stack.pop().expect("Pop transform"); } - pub fn clip(&mut self, frame: Self, at: Point) { + fn draft(&mut self, size: Size) -> Self { + Self::new(size) + } + + fn paste(&mut self, frame: Self, at: Point) { self.primitives.push(Primitive::Transform { transformation: Transformation::translate(at.x, at.y), content: Box::new(frame.into_primitive()), }); } - pub fn translate(&mut self, translation: Vector) { + fn translate(&mut self, translation: Vector) { self.transform = self.transform.pre_translate(translation.x, translation.y); } - pub fn rotate(&mut self, angle: impl Into) { + fn rotate(&mut self, angle: impl Into) { self.transform = self.transform.pre_concat( tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()), ); } - pub fn scale(&mut self, scale: impl Into) { + fn scale(&mut self, scale: impl Into) { let scale = scale.into(); self.scale_nonuniform(Vector { x: scale, y: scale }); } - pub fn scale_nonuniform(&mut self, scale: impl Into) { + fn scale_nonuniform(&mut self, scale: impl Into) { let scale = scale.into(); self.transform = self.transform.pre_scale(scale.x, scale.y); } +} - pub fn into_primitive(self) -> Primitive { - Primitive::Clip { - bounds: Rectangle::new(Point::ORIGIN, self.size), - content: Box::new(Primitive::Group { - primitives: self.primitives, - }), - } +impl From for Primitive { + fn from(frame: Frame) -> Self { + frame.into_primitive() } } diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs index 7718d542..b7c428e4 100644 --- a/tiny_skia/src/primitive.rs +++ b/tiny_skia/src/primitive.rs @@ -1,5 +1,5 @@ use crate::core::Rectangle; -use crate::graphics::Damage; +use crate::graphics::{Damage, Mesh}; pub type Primitive = crate::graphics::Primitive; @@ -42,3 +42,11 @@ impl Damage for Custom { } } } + +impl TryFrom for Custom { + type Error = &'static str; + + fn try_from(_mesh: Mesh) -> Result { + Err("unsupported") + } +} diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 09ddbe4d..924aacf1 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -397,3 +397,12 @@ impl backend::Svg for Backend { self.image_pipeline.viewport_dimensions(handle) } } + +#[cfg(feature = "geometry")] +impl crate::graphics::geometry::Backend for Backend { + type Frame = crate::geometry::Frame; + + fn new_frame(&self, size: Size) -> Self::Frame { + crate::geometry::Frame::new(size) + } +} diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f4e0fbda..4f6b67b1 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -6,7 +6,7 @@ use crate::core::{ use crate::graphics::color; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ - LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, + self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; @@ -14,6 +14,7 @@ use crate::primitive::{self, Primitive}; use lyon::geom::euclid; use lyon::tessellation; + use std::borrow::Cow; /// A frame for drawing some geometry. @@ -27,191 +28,87 @@ pub struct Frame { stroke_tessellator: tessellation::StrokeTessellator, } -enum Buffer { - Solid(tessellation::VertexBuffers), - Gradient(tessellation::VertexBuffers), -} - -struct BufferStack { - stack: Vec, -} - -impl BufferStack { - fn new() -> Self { - Self { stack: Vec::new() } - } - - fn get_mut(&mut self, style: &Style) -> &mut Buffer { - match style { - Style::Solid(_) => match self.stack.last() { - Some(Buffer::Solid(_)) => {} - _ => { - self.stack.push(Buffer::Solid( - tessellation::VertexBuffers::new(), - )); - } - }, - Style::Gradient(_) => match self.stack.last() { - Some(Buffer::Gradient(_)) => {} - _ => { - self.stack.push(Buffer::Gradient( - tessellation::VertexBuffers::new(), - )); - } +impl Frame { + /// Creates a new [`Frame`] with the given [`Size`]. + pub fn new(size: Size) -> Frame { + Frame { + size, + buffers: BufferStack::new(), + primitives: Vec::new(), + transforms: Transforms { + previous: Vec::new(), + current: Transform(lyon::math::Transform::identity()), }, + fill_tessellator: tessellation::FillTessellator::new(), + stroke_tessellator: tessellation::StrokeTessellator::new(), } - - self.stack.last_mut().unwrap() } - fn get_fill<'a>( - &'a mut self, - style: &Style, - ) -> Box { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color::pack(*color)), - )) - } - (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - GradientVertex2DBuilder { - gradient: gradient.pack(), - }, - )) + fn into_primitives(mut self) -> Vec { + for buffer in self.buffers.stack { + match buffer { + Buffer::Solid(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::Custom( + primitive::Custom::Mesh(Mesh::Solid { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }), + )); + } + } + Buffer::Gradient(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::Custom( + primitive::Custom::Mesh(Mesh::Gradient { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }), + )); + } + } } - _ => unreachable!(), } - } - fn get_stroke<'a>( - &'a mut self, - style: &Style, - ) -> Box { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color::pack(*color)), - )) - } - (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - GradientVertex2DBuilder { - gradient: gradient.pack(), - }, - )) - } - _ => unreachable!(), - } + self.primitives } } -#[derive(Debug)] -struct Transforms { - previous: Vec, - current: Transform, -} - -#[derive(Debug, Clone, Copy)] -struct Transform(lyon::math::Transform); - -impl Transform { - fn is_identity(&self) -> bool { - self.0 == lyon::math::Transform::identity() - } - - fn is_scale_translation(&self) -> bool { - self.0.m12.abs() < 2.0 * f32::EPSILON - && self.0.m21.abs() < 2.0 * f32::EPSILON - } - - fn scale(&self) -> (f32, f32) { - (self.0.m11, self.0.m22) - } +impl geometry::Frame for Frame { + type Geometry = Primitive; - fn transform_point(&self, point: Point) -> Point { - let transformed = self - .0 - .transform_point(euclid::Point2D::new(point.x, point.y)); - - Point { - x: transformed.x, - y: transformed.y, - } - } - - fn transform_style(&self, style: Style) -> Style { - match style { - Style::Solid(color) => Style::Solid(color), - Style::Gradient(gradient) => { - Style::Gradient(self.transform_gradient(gradient)) - } - } - } - - fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { - match &mut gradient { - Gradient::Linear(linear) => { - linear.start = self.transform_point(linear.start); - linear.end = self.transform_point(linear.end); - } - } - - gradient - } -} - -impl Frame { /// Creates a new empty [`Frame`] with the given dimensions. /// /// The default coordinate system of a [`Frame`] has its origin at the /// top-left corner of its bounds. - pub fn new(size: Size) -> Frame { - Frame { - size, - buffers: BufferStack::new(), - primitives: Vec::new(), - transforms: Transforms { - previous: Vec::new(), - current: Transform(lyon::math::Transform::identity()), - }, - fill_tessellator: tessellation::FillTessellator::new(), - stroke_tessellator: tessellation::StrokeTessellator::new(), - } - } - /// Returns the width of the [`Frame`]. #[inline] - pub fn width(&self) -> f32 { + fn width(&self) -> f32 { self.size.width } - /// Returns the height of the [`Frame`]. #[inline] - pub fn height(&self) -> f32 { + fn height(&self) -> f32 { self.size.height } - /// Returns the dimensions of the [`Frame`]. #[inline] - pub fn size(&self) -> Size { + fn size(&self) -> Size { self.size } - /// Returns the coordinate of the center of the [`Frame`]. #[inline] - pub fn center(&self) -> Point { + fn center(&self) -> Point { Point::new(self.size.width / 2.0, self.size.height / 2.0) } - /// Draws the given [`Path`] on the [`Frame`] by filling it with the - /// provided style. - pub fn fill(&mut self, path: &Path, fill: impl Into) { + fn fill(&mut self, path: &Path, fill: impl Into) { let Fill { style, rule } = fill.into(); let mut buffer = self @@ -239,9 +136,7 @@ impl Frame { .expect("Tessellate path."); } - /// 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( + fn fill_rectangle( &mut self, top_left: Point, size: Size, @@ -276,9 +171,7 @@ impl Frame { .expect("Fill rectangle"); } - /// 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>) { + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { let stroke = stroke.into(); let mut buffer = self @@ -315,20 +208,7 @@ impl Frame { .expect("Stroke path"); } - /// 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. - pub fn fill_text(&mut self, text: impl Into) { + fn fill_text(&mut self, text: impl Into) { let text = text.into(); let (scale_x, scale_y) = self.transforms.current.scale(); @@ -384,57 +264,55 @@ impl Frame { } } - /// 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) -> R) -> R { - self.push_transform(); - - let result = f(self); - - self.pop_transform(); - - result + fn translate(&mut self, translation: Vector) { + self.transforms.current.0 = + self.transforms + .current + .0 + .pre_translate(lyon::math::Vector::new( + translation.x, + translation.y, + )); } - /// Pushes the current transform in the transform stack. - pub fn push_transform(&mut self) { - self.transforms.previous.push(self.transforms.current); + #[inline] + fn rotate(&mut self, angle: impl Into) { + self.transforms.current.0 = self + .transforms + .current + .0 + .pre_rotate(lyon::math::Angle::radians(angle.into().0)); } - /// Pops a transform from the transform stack and sets it as the current transform. - pub fn pop_transform(&mut self) { - self.transforms.current = self.transforms.previous.pop().unwrap(); + #[inline] + fn scale(&mut self, scale: impl Into) { + let scale = scale.into(); + + self.scale_nonuniform(Vector { x: scale, y: scale }); } - /// 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) -> R, - ) -> R { - let mut frame = Frame::new(region.size()); + fn scale_nonuniform(&mut self, scale: impl Into) { + let scale = scale.into(); - let result = f(&mut frame); + self.transforms.current.0 = + self.transforms.current.0.pre_scale(scale.x, scale.y); + } - let origin = Point::new(region.x, region.y); + fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } - self.clip(frame, origin); + fn pop_transform(&mut self) { + self.transforms.current = self.transforms.previous.pop().unwrap(); + } - result + fn draft(&mut self, size: Size) -> Frame { + Frame::new(size) } - /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`]. - pub fn clip(&mut self, frame: Frame, at: Point) { + fn paste(&mut self, frame: Frame, at: Point) { let size = frame.size(); let primitives = frame.into_primitives(); let transformation = Transformation::translate(at.x, at.y); @@ -461,90 +339,153 @@ impl Frame { ], }); } +} +impl From for Primitive { + fn from(frame: Frame) -> Self { + Self::Group { + primitives: frame.into_primitives(), + } + } +} - /// Applies a translation to the current transform of the [`Frame`]. - #[inline] - pub fn translate(&mut self, translation: Vector) { - self.transforms.current.0 = - self.transforms - .current - .0 - .pre_translate(lyon::math::Vector::new( - translation.x, - translation.y, - )); +enum Buffer { + Solid(tessellation::VertexBuffers), + Gradient(tessellation::VertexBuffers), +} + +struct BufferStack { + stack: Vec, +} + +impl BufferStack { + fn new() -> Self { + Self { stack: Vec::new() } } - /// Applies a rotation in radians to the current transform of the [`Frame`]. - #[inline] - pub fn rotate(&mut self, angle: impl Into) { - self.transforms.current.0 = self - .transforms - .current - .0 - .pre_rotate(lyon::math::Angle::radians(angle.into().0)); + fn get_mut(&mut self, style: &Style) -> &mut Buffer { + match style { + Style::Solid(_) => match self.stack.last() { + Some(Buffer::Solid(_)) => {} + _ => { + self.stack.push(Buffer::Solid( + tessellation::VertexBuffers::new(), + )); + } + }, + Style::Gradient(_) => match self.stack.last() { + Some(Buffer::Gradient(_)) => {} + _ => { + self.stack.push(Buffer::Gradient( + tessellation::VertexBuffers::new(), + )); + } + }, + } + + self.stack.last_mut().unwrap() } - /// Applies a uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale(&mut self, scale: impl Into) { - let scale = scale.into(); + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color::pack(*color)), + )) + } + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.pack(), + }, + )) + } + _ => unreachable!(), + } + } - self.scale_nonuniform(Vector { x: scale, y: scale }); + fn get_stroke<'a>( + &'a mut self, + style: &Style, + ) -> Box { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color::pack(*color)), + )) + } + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.pack(), + }, + )) + } + _ => unreachable!(), + } } +} - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale_nonuniform(&mut self, scale: impl Into) { - let scale = scale.into(); +#[derive(Debug)] +struct Transforms { + previous: Vec, + current: Transform, +} - self.transforms.current.0 = - self.transforms.current.0.pre_scale(scale.x, scale.y); +#[derive(Debug, Clone, Copy)] +struct Transform(lyon::math::Transform); + +impl Transform { + fn is_identity(&self) -> bool { + self.0 == lyon::math::Transform::identity() } - /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. - pub fn into_primitive(self) -> Primitive { - Primitive::Group { - primitives: self.into_primitives(), + fn is_scale_translation(&self) -> bool { + self.0.m12.abs() < 2.0 * f32::EPSILON + && self.0.m21.abs() < 2.0 * f32::EPSILON + } + + fn scale(&self) -> (f32, f32) { + (self.0.m11, self.0.m22) + } + + fn transform_point(&self, point: Point) -> Point { + let transformed = self + .0 + .transform_point(euclid::Point2D::new(point.x, point.y)); + + Point { + x: transformed.x, + y: transformed.y, } } - fn into_primitives(mut self) -> Vec { - for buffer in self.buffers.stack { - match buffer { - Buffer::Solid(buffer) => { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::Custom( - primitive::Custom::Mesh(Mesh::Solid { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - }), - )); - } - } - Buffer::Gradient(buffer) => { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::Custom( - primitive::Custom::Mesh(Mesh::Gradient { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - }), - )); - } - } + fn transform_style(&self, style: Style) -> Style { + match style { + Style::Solid(color) => Style::Solid(color), + Style::Gradient(gradient) => { + Style::Gradient(self.transform_gradient(gradient)) } } + } - self.primitives + fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { + match &mut gradient { + Gradient::Linear(linear) => { + linear.start = self.transform_point(linear.start); + linear.end = self.transform_point(linear.end); + } + } + + gradient } } - struct GradientVertex2DBuilder { gradient: gradient::Packed, } diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index fff927ea..ee9af93c 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -28,3 +28,11 @@ impl Damage for Custom { } } } + +impl TryFrom for Custom { + type Error = &'static str; + + fn try_from(mesh: Mesh) -> Result { + Ok(Custom::Mesh(mesh)) + } +} diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 0eda0191..fcd91d17 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -7,7 +7,6 @@ pub use event::Event; pub use program::Program; pub use crate::graphics::geometry::*; -pub use crate::renderer::geometry::*; use crate::core; use crate::core::layout::{self, Layout}; @@ -21,13 +20,19 @@ use crate::graphics::geometry; use std::marker::PhantomData; +/// A simple cache that stores generated [`Geometry`] to avoid recomputation. +/// +/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer +/// change or it is explicitly cleared. +pub type Cache = geometry::Cache; + /// A widget capable of drawing 2D graphics. /// /// ## Drawing a simple circle /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// # use iced_widget::canvas::{self, Canvas, Fill, Frame, Geometry, Path, Program}; +/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Path, Program}; /// # use iced_widget::core::{Color, Rectangle}; /// # use iced_widget::core::mouse; /// # use iced_widget::{Renderer, Theme}; @@ -42,9 +47,9 @@ use std::marker::PhantomData; /// impl Program<()> for Circle { /// type State = (); /// -/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec{ +/// fn draw(&self, _state: &(), renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) { /// // We prepare a new `Frame` -/// let mut frame = Frame::new(renderer, bounds.size()); +/// let mut frame = frame(renderer, bounds.size()); /// /// // We create a `Path` representing a simple circle /// let circle = Path::circle(frame.center(), self.radius); @@ -53,7 +58,7 @@ use std::marker::PhantomData; /// frame.fill(&circle, Color::BLACK); /// /// // Finally, we produce the geometry -/// vec![frame.into_geometry()] +/// renderer.draw_geometry([frame]); /// } /// } /// @@ -210,9 +215,7 @@ where renderer.with_transformation( Transformation::translate(bounds.x, bounds.y), |renderer| { - renderer.draw( - self.program.draw(state, renderer, theme, bounds, cursor), - ); + self.program.draw(state, renderer, theme, bounds, cursor); }, ); } diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index 0bff4bda..307686de 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -37,22 +37,15 @@ where (event::Status::Ignored, None) } - /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. - /// - /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a - /// [`Cache`]. - /// - /// [`Geometry`]: crate::canvas::Geometry - /// [`Frame`]: crate::canvas::Frame - /// [`Cache`]: crate::canvas::Cache + /// Draws the state of the [`Program`] with the given [`Renderer`]. fn draw( &self, state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Vec; + ); /// Returns the current mouse interaction of the [`Program`]. /// @@ -90,12 +83,12 @@ where fn draw( &self, state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Vec { - T::draw(self, state, renderer, theme, bounds, cursor) + ) { + T::draw(self, state, renderer, theme, bounds, cursor); } fn mouse_interaction( diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 90c0c970..bc46aaaa 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -8,7 +8,6 @@ use crate::core::{ Color, Element, Layout, Length, Point, Rectangle, Size, Theme, Vector, Widget, }; -use crate::graphics::geometry::Renderer as _; use crate::Renderer; use std::cell::RefCell; @@ -92,6 +91,8 @@ impl<'a, Message, Theme> Widget _cursor: mouse::Cursor, _viewport: &Rectangle, ) { + use canvas::Frame; + let state = tree.state.downcast_ref::(); let bounds = layout.bounds(); @@ -142,7 +143,7 @@ impl<'a, Message, Theme> Widget renderer.with_translation( bounds.position() - Point::ORIGIN, |renderer| { - renderer.draw(vec![geometry]); + renderer.draw_geometry(vec![geometry]); }, ); } @@ -161,11 +162,11 @@ where /// The data of a [`QRCode`]. /// /// It stores the contents that will be displayed. -#[derive(Debug)] +#[allow(missing_debug_implementations)] pub struct Data { contents: Vec, width: usize, - cache: canvas::Cache, + cache: canvas::Cache, } impl Data { diff --git a/winit/src/application.rs b/winit/src/application.rs index 13d9282d..29786b65 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -130,7 +130,7 @@ pub fn default(theme: &Theme) -> Appearance { /// settings. pub async fn run( settings: Settings, - compositor_settings: C::Settings, + compositor_settings: impl Into, ) -> Result<(), Error> where A: Application + 'static, @@ -219,7 +219,7 @@ where }; } - let compositor = C::new(compositor_settings, window.clone()).await?; + let compositor = C::new(compositor_settings.into(), window.clone()).await?; let mut renderer = compositor.create_renderer(); for font in settings.fonts { diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 18db1fb5..c865b0ee 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -105,7 +105,7 @@ where /// settings. pub fn run( settings: Settings, - compositor_settings: C::Settings, + compositor_settings: impl Into, ) -> Result<(), Error> where A: Application + 'static, @@ -186,8 +186,10 @@ where }; } - let mut compositor = - executor::block_on(C::new(compositor_settings, main_window.clone()))?; + let mut compositor = executor::block_on(C::new( + compositor_settings.into(), + main_window.clone(), + ))?; let mut window_manager = WindowManager::new(); let _ = window_manager.insert( -- cgit From 3d17cf8790a18bd0dfe968739c9802833c0bb647 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 22:47:43 +0100 Subject: Remove `custom` module from `iced_renderer` --- renderer/src/custom.rs | 162 ------------------------------------------------- 1 file changed, 162 deletions(-) delete mode 100644 renderer/src/custom.rs diff --git a/renderer/src/custom.rs b/renderer/src/custom.rs deleted file mode 100644 index 4addeb86..00000000 --- a/renderer/src/custom.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::core::image; -use crate::core::renderer; -use crate::core::svg; -use crate::core::text::Text; -use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, -}; -use crate::graphics::compositor; -use crate::graphics::text::{Editor, Paragraph}; -use crate::graphics::{Mesh, Viewport}; - -#[cfg(feature = "geometry")] -use crate::graphics::geometry::{self, Fill, Path, Stroke}; - -use std::borrow::Cow; - -pub trait Renderer { - fn draw_mesh(&mut self, mesh: Mesh); - - fn start_layer(&mut self); - - fn end_layer(&mut self, bounds: Rectangle); - - fn start_transformation(&mut self); - - fn end_transformation(&mut self, transformation: Transformation); - - fn fill_quad(&mut self, quad: renderer::Quad, background: Background); - - fn clear(&mut self); - - fn default_font(&self) -> Font; - - fn default_size(&self) -> Pixels; - - fn load_font(&mut self, bytes: Cow<'static, [u8]>); - - fn fill_paragraph( - &mut self, - paragraph: &Paragraph, - position: Point, - color: Color, - clip_bounds: Rectangle, - ); - - fn fill_editor( - &mut self, - editor: &Editor, - position: Point, - color: Color, - clip_bounds: Rectangle, - ); - - fn fill_text( - &mut self, - text: Text<'_, Font>, - position: Point, - color: Color, - clip_bounds: Rectangle, - ); - - fn measure_image(&self, handle: &image::Handle) -> Size; - - fn draw_image( - &mut self, - handle: image::Handle, - filter_method: image::FilterMethod, - bounds: Rectangle, - ); - - fn measure_svg(&self, handle: &svg::Handle) -> Size; - - fn draw_svg( - &mut self, - handle: crate::core::svg::Handle, - color: Option, - bounds: Rectangle, - ); - - #[cfg(feature = "geometry")] - fn new_frame(&self, size: Size) -> Box; - - #[cfg(feature = "geometry")] - fn draw_geometry(&mut self, geometry: Box); - - fn present( - &mut self, - surface: &mut dyn Surface, - viewport: &Viewport, - background_color: Color, - compositor: &mut dyn Compositor, - ) -> Result<(), compositor::SurfaceError>; -} - -#[cfg(feature = "geometry")] -pub trait Frame: std::any::Any { - fn width(&self) -> f32; - - fn height(&self) -> f32; - - fn size(&self) -> Size; - - fn center(&self) -> Point; - - fn fill(&mut self, path: &Path, fill: Fill); - - fn fill_rectangle(&mut self, top_left: Point, size: Size, fill: Fill); - - fn stroke(&mut self, path: &Path, stroke: Stroke<'_>); - - fn fill_text(&mut self, text: geometry::Text); - - fn translate(&mut self, translation: crate::core::Vector); - - fn rotate(&mut self, angle: crate::core::Radians); - - fn scale(&mut self, scale: f32); - - fn scale_nonuniform(&mut self, scale: crate::core::Vector); - - fn push_transform(&mut self); - - fn pop_transform(&mut self); - - fn clip(&mut self, frame: Box, origin: Point); - - fn into_geometry(self: Box) -> Box; -} - -#[cfg(feature = "geometry")] -pub trait Geometry: std::any::Any + std::fmt::Debug { - fn transform( - self: Box, - transformation: Transformation, - ) -> Box; - - fn cache(self: Box) -> std::sync::Arc; - - fn load(self: std::sync::Arc) -> Box; -} - -pub trait Compositor: std::any::Any { - fn create_renderer(&self) -> Box; - - fn create_surface( - &mut self, - window: Box, - width: u32, - height: u32, - ) -> Box; - - fn configure_surface( - &mut self, - surface: &mut dyn Surface, - width: u32, - height: u32, - ); - - fn fetch_information(&self) -> compositor::Information; -} - -pub trait Surface: std::any::Any {} -- cgit From b972ebca8f8c23d2df1b45bb26038789766a5a65 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 23:30:36 +0100 Subject: Restore `canvas::Program` API --- examples/arc/src/main.rs | 10 ++++++---- examples/bezier_tool/src/main.rs | 20 +++++++++++--------- examples/clock/src/main.rs | 10 ++++++---- examples/color_palette/src/main.rs | 8 ++++---- examples/game_of_life/src/main.rs | 12 +++++++----- examples/layout/src/main.rs | 6 +++--- examples/multitouch/src/main.rs | 8 ++++---- examples/sierpinski_triangle/src/main.rs | 8 ++++---- examples/solar_system/src/main.rs | 8 ++++---- examples/vectorial_text/src/main.rs | 6 +++--- widget/src/canvas.rs | 17 +++++++++++++---- widget/src/canvas/program.rs | 11 ++++++----- 12 files changed, 71 insertions(+), 53 deletions(-) diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index a7893efa..0aba82a9 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -1,7 +1,9 @@ use std::{f32::consts::PI, time::Instant}; use iced::mouse; -use iced::widget::canvas::{self, stroke, Cache, Canvas, Frame, Path, Stroke}; +use iced::widget::canvas::{ + self, stroke, Cache, Canvas, Frame, Geometry, Path, Stroke, +}; use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme}; pub fn main() -> iced::Result { @@ -55,11 +57,11 @@ impl canvas::Program for Arc { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec { let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.palette(); @@ -102,6 +104,6 @@ impl canvas::Program for Arc { ); }); - renderer.draw_geometry([geometry]); + vec![geometry] } } diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index e51f2a31..31d1e29c 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -52,7 +52,9 @@ impl Example { mod bezier { use iced::mouse; use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{self, frame, Canvas, Frame, Path, Stroke}; + use iced::widget::canvas::{ + self, frame, Canvas, Frame, Geometry, Path, Stroke, + }; use iced::{Element, Length, Point, Rectangle, Renderer, Theme}; #[derive(Default)] @@ -138,11 +140,11 @@ mod bezier { fn draw( &self, state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) { + ) -> Vec { let content = self.state.cache.draw(renderer, bounds.size(), |frame| { Curve::draw_all(self.curves, frame); @@ -153,10 +155,10 @@ mod bezier { ); }); - renderer.draw_geometry([content]); - if let Some(pending) = state { - pending.draw(renderer, bounds, cursor); + vec![content, pending.draw(renderer, bounds, cursor)] + } else { + vec![content] } } @@ -203,10 +205,10 @@ mod bezier { impl Pending { fn draw( &self, - renderer: &mut Renderer, + renderer: &Renderer, bounds: Rectangle, cursor: mouse::Cursor, - ) { + ) -> Geometry { let mut frame = frame(renderer, bounds.size()); if let Some(cursor_position) = cursor.position_in(bounds) { @@ -227,7 +229,7 @@ mod bezier { }; } - renderer.draw_geometry([frame]); + frame.into() } } } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 9f78903c..468443bc 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,6 +1,8 @@ use iced::alignment; use iced::mouse; -use iced::widget::canvas::{stroke, Cache, Frame, LineCap, Path, Stroke}; +use iced::widget::canvas::{ + stroke, Cache, Frame, Geometry, LineCap, Path, Stroke, +}; use iced::widget::{canvas, container}; use iced::{ Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription, @@ -82,11 +84,11 @@ impl canvas::Program for Clock { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec { let clock = self.clock.draw(renderer, bounds.size(), |frame| { let palette = theme.extended_palette(); @@ -163,7 +165,7 @@ impl canvas::Program for Clock { }); }); - renderer.draw_geometry([clock]); + vec![clock] } } diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 400766ff..81ad6e41 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,6 +1,6 @@ use iced::alignment::{self, Alignment}; use iced::mouse; -use iced::widget::canvas::{self, Canvas, Frame, Path}; +use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path}; use iced::widget::{column, row, text, Slider}; use iced::{ Color, Element, Font, Length, Pixels, Point, Rectangle, Renderer, Size, @@ -252,18 +252,18 @@ impl canvas::Program for Theme { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &iced::Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec { let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| { let palette = theme.extended_palette(); self.draw(frame, palette.background.base.text); }); - renderer.draw_geometry([theme]); + vec![theme] } } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index f681b4cc..a3d385f3 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -193,7 +193,9 @@ mod grid { use iced::touch; use iced::widget::canvas; use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{frame, Cache, Canvas, Frame, Path, Text}; + use iced::widget::canvas::{ + frame, Cache, Canvas, Frame, Geometry, Path, Text, + }; use iced::{ Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; @@ -516,11 +518,11 @@ mod grid { fn draw( &self, _interaction: &Interaction, - renderer: &mut Renderer, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) { + ) -> Vec { let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0); let life = self.life_cache.draw(renderer, bounds.size(), |frame| { @@ -638,9 +640,9 @@ mod grid { } }); - renderer.draw_geometry([life, grid, overlay]); + vec![life, grid, overlay] } else { - renderer.draw_geometry([life, overlay]); + vec![life, overlay] } } diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 198237f5..35d2d3ba 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -292,11 +292,11 @@ fn square<'a>(size: impl Into + Copy) -> Element<'a, Message> { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec { use canvas::Frame; let mut frame = canvas::frame(renderer, bounds.size()); @@ -309,7 +309,7 @@ fn square<'a>(size: impl Into + Copy) -> Element<'a, Message> { palette.background.strong.color, ); - renderer.draw_geometry([frame]); + vec![frame.into()] } } diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index 41bd0151..6d9039fa 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -5,7 +5,7 @@ use iced::mouse; use iced::touch; use iced::widget::canvas::event; use iced::widget::canvas::stroke::{self, Stroke}; -use iced::widget::canvas::{self, Canvas}; +use iced::widget::canvas::{self, Canvas, Geometry}; use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme}; use std::collections::HashMap; @@ -83,11 +83,11 @@ impl canvas::Program for Multitouch { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec { use canvas::Frame; let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| { @@ -156,6 +156,6 @@ impl canvas::Program for Multitouch { } }); - renderer.draw_geometry([fingerweb]); + vec![fingerweb] } } diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index b440b8b4..409bc718 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -1,6 +1,6 @@ use iced::mouse; use iced::widget::canvas::event::{self, Event}; -use iced::widget::canvas::{self, Canvas}; +use iced::widget::canvas::{self, Canvas, Geometry}; use iced::widget::{column, row, slider, text}; use iced::{Color, Length, Point, Rectangle, Renderer, Size, Theme}; @@ -107,11 +107,11 @@ impl canvas::Program for SierpinskiGraph { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec { use canvas::Frame; let geom = self.cache.draw(renderer, bounds.size(), |frame| { @@ -141,7 +141,7 @@ impl canvas::Program for SierpinskiGraph { }); }); - renderer.draw_geometry([geom]); + vec![geom] } } diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index dd36b711..e8f94ed0 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -10,7 +10,7 @@ use iced::mouse; use iced::widget::canvas; use iced::widget::canvas::gradient; use iced::widget::canvas::stroke::{self, Stroke}; -use iced::widget::canvas::Path; +use iced::widget::canvas::{Geometry, Path}; use iced::window; use iced::{ Color, Element, Length, Point, Rectangle, Renderer, Size, Subscription, @@ -126,11 +126,11 @@ impl canvas::Program for State { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec { use canvas::Frame; use std::f32::consts::PI; @@ -198,7 +198,7 @@ impl canvas::Program for State { }); }); - renderer.draw_geometry([background, system]); + vec![background, system] } } diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs index 9f5baac8..9b605d23 100644 --- a/examples/vectorial_text/src/main.rs +++ b/examples/vectorial_text/src/main.rs @@ -124,11 +124,11 @@ impl canvas::Program for State { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec { use canvas::Frame; let geometry = self.cache.draw(renderer, bounds.size(), |frame| { @@ -155,7 +155,7 @@ impl canvas::Program for State { }); }); - renderer.draw_geometry([geometry]); + vec![geometry] } } diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index fcd91d17..81067491 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -26,13 +26,17 @@ use std::marker::PhantomData; /// change or it is explicitly cleared. pub type Cache = geometry::Cache; +/// The geometry supported by a renderer. +pub type Geometry = + ::Geometry; + /// A widget capable of drawing 2D graphics. /// /// ## Drawing a simple circle /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Path, Program}; +/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Geometry, Path, Program}; /// # use iced_widget::core::{Color, Rectangle}; /// # use iced_widget::core::mouse; /// # use iced_widget::{Renderer, Theme}; @@ -47,7 +51,7 @@ pub type Cache = geometry::Cache; /// impl Program<()> for Circle { /// type State = (); /// -/// fn draw(&self, _state: &(), renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) { +/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec { /// // We prepare a new `Frame` /// let mut frame = frame(renderer, bounds.size()); /// @@ -58,7 +62,7 @@ pub type Cache = geometry::Cache; /// frame.fill(&circle, Color::BLACK); /// /// // Finally, we produce the geometry -/// renderer.draw_geometry([frame]); +/// vec![frame.into()] /// } /// } /// @@ -215,7 +219,12 @@ where renderer.with_transformation( Transformation::translate(bounds.x, bounds.y), |renderer| { - self.program.draw(state, renderer, theme, bounds, cursor); + let layers = + self.program.draw(state, renderer, theme, bounds, cursor); + + for layer in layers { + renderer.draw_geometry(layer); + } }, ); } diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index 307686de..3ba31474 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -1,5 +1,6 @@ use crate::canvas::event::{self, Event}; use crate::canvas::mouse; +use crate::canvas::Geometry; use crate::core::Rectangle; use crate::graphics::geometry; @@ -41,11 +42,11 @@ where fn draw( &self, state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ); + ) -> Vec>; /// Returns the current mouse interaction of the [`Program`]. /// @@ -83,12 +84,12 @@ where fn draw( &self, state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) { - T::draw(self, state, renderer, theme, bounds, cursor); + ) -> Vec> { + T::draw(self, state, renderer, theme, bounds, cursor) } fn mouse_interaction( -- cgit From 53a183fe0d6aed460fbb8155ac9541757277aab3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:35:14 +0100 Subject: Restore `canvas::Frame` API --- examples/arc/src/main.rs | 2 +- examples/bezier_tool/src/main.rs | 10 +- examples/clock/src/main.rs | 4 +- examples/color_palette/src/main.rs | 2 +- examples/game_of_life/src/main.rs | 8 +- examples/layout/src/main.rs | 6 +- examples/loading_spinners/src/circular.rs | 2 +- examples/multitouch/src/main.rs | 2 - examples/sierpinski_triangle/src/main.rs | 2 - examples/solar_system/src/main.rs | 1 - examples/vectorial_text/src/main.rs | 2 - graphics/src/geometry.rs | 254 +----------------------------- graphics/src/geometry/cache.rs | 123 +++++++++++++++ graphics/src/geometry/frame.rs | 208 ++++++++++++++++++++++++ graphics/src/renderer.rs | 3 +- renderer/src/fallback.rs | 20 +-- renderer/src/geometry.rs | 236 --------------------------- renderer/src/geometry/cache.rs | 137 ---------------- tiny_skia/src/geometry.rs | 8 +- wgpu/src/geometry.rs | 11 +- widget/src/canvas.rs | 14 +- widget/src/qr_code.rs | 2 - 22 files changed, 378 insertions(+), 679 deletions(-) create mode 100644 graphics/src/geometry/cache.rs create mode 100644 graphics/src/geometry/frame.rs delete mode 100644 renderer/src/geometry.rs delete mode 100644 renderer/src/geometry/cache.rs diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index 0aba82a9..4576404f 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -2,7 +2,7 @@ use std::{f32::consts::PI, time::Instant}; use iced::mouse; use iced::widget::canvas::{ - self, stroke, Cache, Canvas, Frame, Geometry, Path, Stroke, + self, stroke, Cache, Canvas, Geometry, Path, Stroke, }; use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme}; diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 31d1e29c..289c919b 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -52,9 +52,7 @@ impl Example { mod bezier { use iced::mouse; use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{ - self, frame, Canvas, Frame, Geometry, Path, Stroke, - }; + use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; use iced::{Element, Length, Point, Rectangle, Renderer, Theme}; #[derive(Default)] @@ -184,7 +182,7 @@ mod bezier { } impl Curve { - fn draw_all(curves: &[Curve], frame: &mut impl Frame) { + fn draw_all(curves: &[Curve], frame: &mut Frame) { let curves = Path::new(|p| { for curve in curves { p.move_to(curve.from); @@ -209,7 +207,7 @@ mod bezier { bounds: Rectangle, cursor: mouse::Cursor, ) -> Geometry { - let mut frame = frame(renderer, bounds.size()); + let mut frame = Frame::new(renderer, bounds.size()); if let Some(cursor_position) = cursor.position_in(bounds) { match *self { @@ -229,7 +227,7 @@ mod bezier { }; } - frame.into() + frame.into_geometry() } } } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 468443bc..897f8f1b 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,8 +1,6 @@ use iced::alignment; use iced::mouse; -use iced::widget::canvas::{ - stroke, Cache, Frame, Geometry, LineCap, Path, Stroke, -}; +use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke}; use iced::widget::{canvas, container}; use iced::{ Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription, diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 81ad6e41..d9325edb 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -156,7 +156,7 @@ impl Theme { .into() } - fn draw(&self, frame: &mut impl Frame, text_color: Color) { + fn draw(&self, frame: &mut Frame, text_color: Color) { let pad = 20.0; let box_size = Size { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index a3d385f3..0716b2a4 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -193,9 +193,7 @@ mod grid { use iced::touch; use iced::widget::canvas; use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{ - frame, Cache, Canvas, Frame, Geometry, Path, Text, - }; + use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text}; use iced::{ Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; @@ -548,7 +546,7 @@ mod grid { }); let overlay = { - let mut frame = frame(renderer, bounds.size()); + let mut frame = Frame::new(renderer, bounds.size()); let hovered_cell = cursor.position_in(bounds).map(|position| { Cell::at(self.project(position, frame.size())) @@ -601,7 +599,7 @@ mod grid { ..text }); - frame.into() + frame.into_geometry() }; if self.scaling >= 0.2 && self.show_lines { diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 35d2d3ba..713e2b70 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -297,9 +297,7 @@ fn square<'a>(size: impl Into + Copy) -> Element<'a, Message> { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec { - use canvas::Frame; - - let mut frame = canvas::frame(renderer, bounds.size()); + let mut frame = canvas::Frame::new(renderer, bounds.size()); let palette = theme.extended_palette(); @@ -309,7 +307,7 @@ fn square<'a>(size: impl Into + Copy) -> Element<'a, Message> { palette.background.strong.color, ); - vec![frame.into()] + vec![frame.into_geometry()] } } diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 306988af..cdc6b7ac 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -6,7 +6,7 @@ use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; use iced::event; use iced::mouse; use iced::time::Instant; -use iced::widget::canvas::{self, Frame}; +use iced::widget::canvas; use iced::window::{self, RedrawRequest}; use iced::{ Background, Color, Element, Event, Length, Radians, Rectangle, Renderer, diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index 6d9039fa..2453c7f5 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -88,8 +88,6 @@ impl canvas::Program for Multitouch { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec { - use canvas::Frame; - let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| { if self.fingers.len() < 2 { return; diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 409bc718..9cd6237f 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -112,8 +112,6 @@ impl canvas::Program for SierpinskiGraph { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec { - use canvas::Frame; - let geom = self.cache.draw(renderer, bounds.size(), |frame| { frame.stroke( &canvas::Path::rectangle(Point::ORIGIN, frame.size()), diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index e8f94ed0..deb211d8 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -131,7 +131,6 @@ impl canvas::Program for State { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec { - use canvas::Frame; use std::f32::consts::PI; let background = diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs index 9b605d23..a7391e23 100644 --- a/examples/vectorial_text/src/main.rs +++ b/examples/vectorial_text/src/main.rs @@ -129,8 +129,6 @@ impl canvas::Program for State { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec { - use canvas::Frame; - let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.palette(); let center = bounds.center(); diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index cd4c9267..2b18243e 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -1,12 +1,16 @@ //! Build and draw geometry. pub mod fill; +pub mod frame; pub mod path; pub mod stroke; +mod cache; mod style; mod text; +pub use cache::Cache; pub use fill::Fill; +pub use frame::Frame; pub use path::Path; pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use style::Style; @@ -14,18 +18,7 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; -use crate::core::{Point, Radians, Rectangle, Size, Vector}; -use crate::Primitive; - -use std::cell::RefCell; -use std::sync::Arc; - -pub fn frame(renderer: &Renderer, size: Size) -> Renderer::Frame -where - Renderer: self::Renderer, -{ - renderer.new_frame(size) -} +use crate::core::Size; /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: crate::core::Renderer { @@ -33,7 +26,7 @@ pub trait Renderer: crate::core::Renderer { type Geometry: Geometry; /// The kind of [`Frame`] this renderer supports. - type Frame: Frame; + type Frame: frame::Backend; fn new_frame(&self, size: Size) -> Self::Frame; @@ -43,127 +36,11 @@ pub trait Renderer: crate::core::Renderer { pub trait Backend { /// The kind of [`Frame`] this backend supports. - type Frame: Frame; + type Frame: frame::Backend; fn new_frame(&self, size: Size) -> Self::Frame; } -pub trait Frame: Sized + Into { - /// The kind of geometry this frame can draw. - type Geometry: Geometry; - - /// Returns the width of the [`Frame`]. - fn width(&self) -> f32; - - /// Returns the height of the [`Frame`]. - fn height(&self) -> f32; - - /// Returns the dimensions of the [`Frame`]. - fn size(&self) -> Size; - - /// Returns the coordinate of the center of the [`Frame`]. - fn center(&self) -> Point; - - /// Draws the given [`Path`] on the [`Frame`] by filling it with the - /// provided style. - fn fill(&mut self, path: &Path, fill: impl Into); - - /// Draws an axis-aligned rectangle given its top-left corner coordinate and - /// its `Size` on the [`Frame`] by filling it with the provided style. - fn fill_rectangle( - &mut self, - top_left: Point, - size: Size, - fill: impl Into, - ); - - /// Draws the stroke of the given [`Path`] on the [`Frame`] with the - /// provided style. - fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>); - - /// Draws the characters of the given [`Text`] on the [`Frame`], filling - /// them with the given color. - /// - /// __Warning:__ Text currently does not work well with rotations and scale - /// transforms! The position will be correctly transformed, but the - /// resulting glyphs will not be rotated or scaled properly. - /// - /// Additionally, all text will be rendered on top of all the layers of - /// a `Canvas`. Therefore, it is currently only meant to be used for - /// overlays, which is the most common use case. - /// - /// Support for vectorial text is planned, and should address all these - /// limitations. - fn fill_text(&mut self, text: impl Into); - - /// Stores the current transform of the [`Frame`] and executes the given - /// drawing operations, restoring the transform afterwards. - /// - /// This method is useful to compose transforms and perform drawing - /// operations in different coordinate systems. - #[inline] - fn with_save(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { - self.push_transform(); - - let result = f(self); - - self.pop_transform(); - - result - } - - /// Pushes the current transform in the transform stack. - fn push_transform(&mut self); - - /// Pops a transform from the transform stack and sets it as the current transform. - fn pop_transform(&mut self); - - /// Executes the given drawing operations within a [`Rectangle`] region, - /// clipping any geometry that overflows its bounds. Any transformations - /// performed are local to the provided closure. - /// - /// This method is useful to perform drawing operations that need to be - /// clipped. - #[inline] - fn with_clip( - &mut self, - region: Rectangle, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - let mut frame = self.draft(region.size()); - - let result = f(&mut frame); - - let origin = Point::new(region.x, region.y); - - self.paste(frame, origin); - - result - } - - /// Creates a new [`Frame`] with the given [`Size`]. - /// - /// Draw its contents back to this [`Frame`] with [`paste`]. - /// - /// [`paste`]: Self::paste - fn draft(&mut self, size: Size) -> Self; - - /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. - fn paste(&mut self, frame: Self, at: Point); - - /// Applies a translation to the current transform of the [`Frame`]. - fn translate(&mut self, translation: Vector); - - /// Applies a rotation in radians to the current transform of the [`Frame`]. - fn rotate(&mut self, angle: impl Into); - - /// Applies a uniform scaling to the current transform of the [`Frame`]. - fn scale(&mut self, scale: impl Into); - - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. - fn scale_nonuniform(&mut self, scale: impl Into); -} - pub trait Geometry: Sized { type Cache; @@ -171,120 +48,3 @@ pub trait Geometry: Sized { fn cache(self) -> Self::Cache; } - -/// A simple cache that stores generated [`Geometry`] to avoid recomputation. -/// -/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer -/// change or it is explicitly cleared. -pub struct Cache -where - Renderer: self::Renderer, -{ - state: RefCell>, -} - -impl Cache -where - Renderer: self::Renderer, -{ - /// Creates a new empty [`Cache`]. - pub fn new() -> Self { - Cache { - state: RefCell::new(State::Empty), - } - } - - /// Clears the [`Cache`], forcing a redraw the next time it is used. - pub fn clear(&self) { - *self.state.borrow_mut() = State::Empty; - } - - /// Draws [`Geometry`] using the provided closure and stores it in the - /// [`Cache`]. - /// - /// The closure will only be called when - /// - the bounds have changed since the previous draw call. - /// - the [`Cache`] is empty or has been explicitly cleared. - /// - /// Otherwise, the previously stored [`Geometry`] will be returned. The - /// [`Cache`] is not cleared in this case. In other words, it will keep - /// returning the stored [`Geometry`] if needed. - pub fn draw( - &self, - renderer: &Renderer, - bounds: Size, - draw_fn: impl FnOnce(&mut Renderer::Frame), - ) -> Renderer::Geometry { - use std::ops::Deref; - - if let State::Filled { - bounds: cached_bounds, - geometry, - } = self.state.borrow().deref() - { - if *cached_bounds == bounds { - return Geometry::load(geometry); - } - } - - let mut frame = frame(renderer, bounds); - draw_fn(&mut frame); - - let geometry = frame.into().cache(); - let result = Geometry::load(&geometry); - - *self.state.borrow_mut() = State::Filled { bounds, geometry }; - - result - } -} - -impl std::fmt::Debug for Cache -where - Renderer: self::Renderer, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let state = self.state.borrow(); - - match *state { - State::Empty => write!(f, "Cache::Empty"), - State::Filled { bounds, .. } => { - write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") - } - } - } -} - -impl Default for Cache -where - Renderer: self::Renderer, -{ - fn default() -> Self { - Self::new() - } -} - -enum State -where - Geometry: self::Geometry, -{ - Empty, - Filled { - bounds: Size, - geometry: Geometry::Cache, - }, -} - -impl Geometry for Primitive { - type Cache = Arc; - - fn load(cache: &Arc) -> Self { - Self::Cache { - content: cache.clone(), - } - } - - fn cache(self) -> Arc { - Arc::new(self) - } -} diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs new file mode 100644 index 00000000..490e69e2 --- /dev/null +++ b/graphics/src/geometry/cache.rs @@ -0,0 +1,123 @@ +use crate::core::Size; +use crate::geometry::{self, Frame, Geometry}; +use crate::Primitive; + +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. +pub struct Cache +where + Renderer: geometry::Renderer, +{ + state: RefCell>, +} + +impl Cache +where + Renderer: geometry::Renderer, +{ + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + state: RefCell::new(State::Empty), + } + } + + /// Clears the [`Cache`], forcing a redraw the next time it is used. + pub fn clear(&self) { + *self.state.borrow_mut() = State::Empty; + } + + /// Draws [`Geometry`] using the provided closure and stores it in the + /// [`Cache`]. + /// + /// The closure will only be called when + /// - the bounds have changed since the previous draw call. + /// - the [`Cache`] is empty or has been explicitly cleared. + /// + /// Otherwise, the previously stored [`Geometry`] will be returned. The + /// [`Cache`] is not cleared in this case. In other words, it will keep + /// returning the stored [`Geometry`] if needed. + pub fn draw( + &self, + renderer: &Renderer, + bounds: Size, + draw_fn: impl FnOnce(&mut Frame), + ) -> Renderer::Geometry { + use std::ops::Deref; + + if let State::Filled { + bounds: cached_bounds, + geometry, + } = self.state.borrow().deref() + { + if *cached_bounds == bounds { + return Geometry::load(geometry); + } + } + + let mut frame = Frame::new(renderer, bounds); + draw_fn(&mut frame); + + let geometry = frame.into_geometry().cache(); + let result = Geometry::load(&geometry); + + *self.state.borrow_mut() = State::Filled { bounds, geometry }; + + result + } +} + +impl std::fmt::Debug for Cache +where + Renderer: geometry::Renderer, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let state = self.state.borrow(); + + match *state { + State::Empty => write!(f, "Cache::Empty"), + State::Filled { bounds, .. } => { + write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") + } + } + } +} + +impl Default for Cache +where + Renderer: geometry::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +enum State +where + Geometry: self::Geometry, +{ + Empty, + Filled { + bounds: Size, + geometry: Geometry::Cache, + }, +} + +impl Geometry for Primitive { + type Cache = Arc; + + fn load(cache: &Arc) -> Self { + Self::Cache { + content: cache.clone(), + } + } + + fn cache(self) -> Arc { + Arc::new(self) + } +} diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs new file mode 100644 index 00000000..e88c43b0 --- /dev/null +++ b/graphics/src/geometry/frame.rs @@ -0,0 +1,208 @@ +use crate::core::{Point, Radians, Rectangle, Size, Vector}; +use crate::geometry::{self, Geometry}; +use crate::geometry::{Fill, Path, Stroke, Text}; + +pub struct Frame +where + Renderer: geometry::Renderer, +{ + raw: Renderer::Frame, +} + +impl Frame +where + Renderer: geometry::Renderer, +{ + pub fn new(renderer: &Renderer, size: Size) -> Self { + Self { + raw: renderer.new_frame(size), + } + } + + /// Returns the width of the [`Frame`]. + pub fn width(&self) -> f32 { + self.raw.width() + } + + /// Returns the height of the [`Frame`]. + pub fn height(&self) -> f32 { + self.raw.height() + } + + /// Returns the dimensions of the [`Frame`]. + pub fn size(&self) -> Size { + self.raw.size() + } + + /// Returns the coordinate of the center of the [`Frame`]. + pub fn center(&self) -> Point { + self.raw.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) { + self.raw.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, + ) { + self.raw.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>) { + self.raw.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. + pub fn fill_text(&mut self, text: impl Into) { + self.raw.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 Self) -> R) -> R { + self.push_transform(); + + let result = f(self); + + self.pop_transform(); + + result + } + + /// Pushes the current transform in the transform stack. + pub fn push_transform(&mut self) { + self.raw.push_transform(); + } + + /// Pops a transform from the transform stack and sets it as the current transform. + pub fn pop_transform(&mut self) { + self.raw.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 Self) -> R, + ) -> R { + let mut frame = self.draft(region.size()); + + let result = f(&mut frame); + + let origin = Point::new(region.x, region.y); + + self.paste(frame, origin); + + result + } + + /// Creates a new [`Frame`] with the given [`Size`]. + /// + /// Draw its contents back to this [`Frame`] with [`paste`]. + /// + /// [`paste`]: Self::paste + pub fn draft(&mut self, size: Size) -> Self { + Self { + raw: self.raw.draft(size), + } + } + + /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. + pub fn paste(&mut self, frame: Self, at: Point) { + self.raw.paste(frame.raw, at); + } + + /// Applies a translation to the current transform of the [`Frame`]. + pub fn translate(&mut self, translation: Vector) { + self.raw.translate(translation); + } + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + pub fn rotate(&mut self, angle: impl Into) { + self.raw.rotate(angle); + } + + /// Applies a uniform scaling to the current transform of the [`Frame`]. + pub fn scale(&mut self, scale: impl Into) { + self.raw.scale(scale); + } + + /// Applies a non-uniform scaling to the current transform of the [`Frame`]. + pub fn scale_nonuniform(&mut self, scale: impl Into) { + self.raw.scale_nonuniform(scale); + } + + pub fn into_geometry(self) -> Renderer::Geometry { + self.raw.into_geometry() + } +} + +/// The internal implementation of a [`Frame`]. +/// +/// Analogous to [`Frame`]. See [`Frame`] for the documentation +/// of each method. +#[allow(missing_docs)] +pub trait Backend: Sized { + type Geometry: Geometry; + + fn width(&self) -> f32; + fn height(&self) -> f32; + fn size(&self) -> Size; + fn center(&self) -> Point; + + fn push_transform(&mut self); + fn pop_transform(&mut self); + + fn translate(&mut self, translation: Vector); + fn rotate(&mut self, angle: impl Into); + fn scale(&mut self, scale: impl Into); + fn scale_nonuniform(&mut self, scale: impl Into); + + fn draft(&mut self, size: Size) -> Self; + fn paste(&mut self, frame: Self, at: Point); + + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>); + + fn fill(&mut self, path: &Path, fill: impl Into); + fn fill_text(&mut self, text: impl Into); + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ); + + fn into_geometry(self) -> Self::Geometry; +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 3b21aa11..2fcb55aa 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -257,7 +257,8 @@ impl mesh::Renderer for Renderer { impl crate::geometry::Renderer for Renderer where B: Backend + crate::geometry::Backend, - B::Frame: crate::geometry::Frame>, + B::Frame: + crate::geometry::frame::Backend>, { type Frame = B::Frame; type Geometry = Primitive; diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index a4c725c0..659f253d 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -459,10 +459,10 @@ mod geometry { Right(R), } - impl geometry::Frame for Frame + impl geometry::frame::Backend for Frame where - L: geometry::Frame, - R: geometry::Frame, + L: geometry::frame::Backend, + R: geometry::frame::Backend, { type Geometry = Geometry; @@ -545,17 +545,11 @@ mod geometry { fn scale_nonuniform(&mut self, scale: impl Into) { delegate!(self, frame, frame.scale_nonuniform(scale)); } - } - impl From> for Geometry - where - L: geometry::Frame, - R: geometry::Frame, - { - fn from(frame: Frame) -> Self { - match frame { - Frame::Left(frame) => Self::Left(frame.into()), - Frame::Right(frame) => Self::Right(frame.into()), + fn into_geometry(self) -> Self::Geometry { + match self { + Frame::Left(frame) => Geometry::Left(frame.into_geometry()), + Frame::Right(frame) => Geometry::Right(frame.into_geometry()), } } } diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs deleted file mode 100644 index a16cecd5..00000000 --- a/renderer/src/geometry.rs +++ /dev/null @@ -1,236 +0,0 @@ -mod cache; - -pub use cache::Cache; - -use crate::core::{Point, Radians, Rectangle, Size, Transformation, Vector}; -use crate::graphics::geometry::{Fill, Path, Stroke, Text}; -use crate::Renderer; - -macro_rules! delegate { - ($frame:expr, $name:ident, $body:expr) => { - match $frame { - Self::TinySkia($name) => $body, - #[cfg(feature = "wgpu")] - Self::Wgpu($name) => $body, - #[cfg(feature = "custom")] - Self::Custom($name) => $body, - } - }; -} - -pub enum Geometry { - TinySkia(iced_tiny_skia::Primitive), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::Primitive), - #[cfg(feature = "custom")] - Custom(Box), -} - -impl Geometry { - pub fn transform(self, transformation: Transformation) -> Self { - match self { - Self::TinySkia(primitive) => { - Self::TinySkia(primitive.transform(transformation)) - } - #[cfg(feature = "wgpu")] - Self::Wgpu(primitive) => { - Self::Wgpu(primitive.transform(transformation)) - } - #[cfg(feature = "custom")] - Self::Custom(geometry) => { - Self::Custom(geometry.transform(transformation)) - } - } - } -} - -pub enum Frame { - TinySkia(iced_tiny_skia::geometry::Frame), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::geometry::Frame), - #[cfg(feature = "custom")] - Custom(Box), -} - -impl Frame { - pub fn new(renderer: &Renderer, size: Size) -> Self { - match renderer { - Renderer::TinySkia(_) => { - Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size)) - } - #[cfg(feature = "wgpu")] - Renderer::Wgpu(_) => { - Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) - } - #[cfg(feature = "custom")] - Renderer::Custom(renderer) => { - Frame::Custom(renderer.new_frame(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.into())); - } - - /// 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.into()) - ); - } - - /// 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.into())); - } - - /// 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. - pub fn fill_text(&mut self, text: impl Into) { - delegate!(self, frame, frame.fill_text(text.into())); - } - - /// 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) -> R) -> R { - delegate!(self, frame, frame.push_transform()); - - let result = f(self); - - delegate!(self, frame, frame.pop_transform()); - - result - } - - /// 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) -> R, - ) -> R { - let mut frame = match self { - Self::TinySkia(_) => Self::TinySkia( - iced_tiny_skia::geometry::Frame::new(region.size()), - ), - #[cfg(feature = "wgpu")] - Self::Wgpu(_) => { - Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) - } - #[cfg(feature = "custom")] - Self::Custom(frame) => Self::Custom(frame.new(region.size())), - }; - - let result = f(&mut frame); - - let origin = Point::new(region.x, region.y); - - match (self, frame) { - (Self::TinySkia(target), Self::TinySkia(frame)) => { - target.clip(frame, origin); - } - #[cfg(feature = "wgpu")] - (Self::Wgpu(target), Self::Wgpu(frame)) => { - target.clip(frame, origin); - } - #[cfg(feature = "custom")] - (Self::Custom(target), Self::Custom(frame)) => { - target.clip(frame, origin); - } - #[allow(unreachable_patterns)] - _ => unreachable!(), - }; - - result - } - - /// 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: impl Into) { - delegate!(self, frame, frame.rotate(angle.into())); - } - - /// Applies a uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale(&mut self, scale: impl Into) { - delegate!(self, frame, frame.scale(scale.into())); - } - - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale_nonuniform(&mut self, scale: impl Into) { - delegate!(self, frame, frame.scale_nonuniform(scale.into())); - } - - pub fn into_geometry(self) -> Geometry { - match self { - Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()), - #[cfg(feature = "wgpu")] - Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()), - #[cfg(feature = "custom")] - Self::Custom(frame) => Geometry::Custom(frame.into_geometry()), - } - } -} diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs deleted file mode 100644 index 20f73f22..00000000 --- a/renderer/src/geometry/cache.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::core::Size; -use crate::geometry::{Frame, Geometry}; -use crate::Renderer; - -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: Internal, - }, -} - -#[derive(Debug, Clone)] -enum Internal { - TinySkia(Arc), - #[cfg(feature = "wgpu")] - Wgpu(Arc), - #[cfg(feature = "custom")] - Custom(Arc), -} - -impl Cache { - /// Creates a new empty [`Cache`]. - pub fn new() -> Self { - Cache { - state: RefCell::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 { - match primitive { - Internal::TinySkia(primitive) => { - return Geometry::TinySkia( - iced_tiny_skia::Primitive::Cache { - content: primitive.clone(), - }, - ); - } - #[cfg(feature = "wgpu")] - Internal::Wgpu(primitive) => { - return Geometry::Wgpu(iced_wgpu::Primitive::Cache { - content: primitive.clone(), - }); - } - #[cfg(feature = "custom")] - Internal::Custom(geometry) => { - return Geometry::Custom(geometry.clone().load()) - } - } - } - } - - let mut frame = Frame::new(renderer, bounds); - draw_fn(&mut frame); - - let primitive = { - let geometry = frame.into_geometry(); - - match geometry { - Geometry::TinySkia(primitive) => { - Internal::TinySkia(Arc::new(primitive)) - } - #[cfg(feature = "wgpu")] - Geometry::Wgpu(primitive) => { - Internal::Wgpu(Arc::new(primitive)) - } - #[cfg(feature = "custom")] - Geometry::Custom(geometry) => { - Internal::Custom(geometry.cache()) - } - } - }; - - *self.state.borrow_mut() = State::Filled { - bounds, - primitive: primitive.clone(), - }; - - match primitive { - Internal::TinySkia(primitive) => { - Geometry::TinySkia(iced_tiny_skia::Primitive::Cache { - content: primitive, - }) - } - #[cfg(feature = "wgpu")] - Internal::Wgpu(primitive) => { - Geometry::Wgpu(iced_wgpu::Primitive::Cache { - content: primitive, - }) - } - #[cfg(feature = "custom")] - Internal::Custom(geometry) => Geometry::Custom(geometry.load()), - } - } -} diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 6b1888d0..76482e12 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -35,7 +35,7 @@ impl Frame { } } -impl geometry::Frame for Frame { +impl geometry::frame::Backend for Frame { type Geometry = Primitive; fn width(&self) -> f32 { @@ -228,11 +228,9 @@ impl geometry::Frame for Frame { self.transform = self.transform.pre_scale(scale.x, scale.y); } -} -impl From for Primitive { - fn from(frame: Frame) -> Self { - frame.into_primitive() + fn into_geometry(self) -> Self::Geometry { + self.into_primitive() } } diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 4f6b67b1..ba56c59d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -80,7 +80,7 @@ impl Frame { } } -impl geometry::Frame for Frame { +impl geometry::frame::Backend for Frame { type Geometry = Primitive; /// Creates a new empty [`Frame`] with the given dimensions. @@ -339,11 +339,10 @@ impl geometry::Frame for Frame { ], }); } -} -impl From for Primitive { - fn from(frame: Frame) -> Self { - Self::Group { - primitives: frame.into_primitives(), + + fn into_geometry(self) -> Self::Geometry { + Primitive::Group { + primitives: self.into_primitives(), } } } diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 81067491..7a21895a 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -6,7 +6,10 @@ mod program; pub use event::Event; pub use program::Program; -pub use crate::graphics::geometry::*; +pub use crate::graphics::geometry::{ + fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin, + Path, Stroke, Style, Text, +}; use crate::core; use crate::core::layout::{self, Layout}; @@ -30,13 +33,16 @@ pub type Cache = geometry::Cache; pub type Geometry = ::Geometry; +/// The frame supported by a renderer. +pub type Frame = geometry::Frame; + /// A widget capable of drawing 2D graphics. /// /// ## Drawing a simple circle /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Geometry, Path, Program}; +/// # use iced_widget::canvas::{self, Canvas, Fill, Frame, Geometry, Path, Program}; /// # use iced_widget::core::{Color, Rectangle}; /// # use iced_widget::core::mouse; /// # use iced_widget::{Renderer, Theme}; @@ -53,7 +59,7 @@ pub type Geometry = /// /// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec { /// // We prepare a new `Frame` -/// let mut frame = frame(renderer, bounds.size()); +/// let mut frame = Frame::new(renderer, bounds.size()); /// /// // We create a `Path` representing a simple circle /// let circle = Path::circle(frame.center(), self.radius); @@ -62,7 +68,7 @@ pub type Geometry = /// frame.fill(&circle, Color::BLACK); /// /// // Finally, we produce the geometry -/// vec![frame.into()] +/// vec![frame.into_geometry()] /// } /// } /// diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index bc46aaaa..84898dc0 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -91,8 +91,6 @@ impl<'a, Message, Theme> Widget _cursor: mouse::Cursor, _viewport: &Rectangle, ) { - use canvas::Frame; - let state = tree.state.downcast_ref::(); let bounds = layout.bounds(); -- cgit From 85800c99ab285efd244c0addfdcf3c732a98de1d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:53:48 +0100 Subject: Fix broken links in documentation --- graphics/src/cached.rs | 33 +++++++++++++++++++++++++++++++++ graphics/src/geometry.rs | 14 +++++--------- graphics/src/geometry/cache.rs | 33 +++++++++------------------------ graphics/src/geometry/frame.rs | 11 ++++++++--- graphics/src/lib.rs | 6 ++++-- graphics/src/mesh.rs | 2 ++ graphics/src/renderer.rs | 12 ------------ renderer/src/fallback.rs | 7 ++++--- widget/src/canvas/program.rs | 9 ++++++++- 9 files changed, 73 insertions(+), 54 deletions(-) create mode 100644 graphics/src/cached.rs diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs new file mode 100644 index 00000000..f7968c9f --- /dev/null +++ b/graphics/src/cached.rs @@ -0,0 +1,33 @@ +use crate::Primitive; + +use std::sync::Arc; + +/// A piece of data that can be cached. +pub trait Cached: Sized { + /// The type of cache produced. + type Cache; + + /// Loads the [`Cache`] into a proper instance. + /// + /// [`Cache`]: Self::Cache + fn load(cache: &Self::Cache) -> Self; + + /// Caches this value, producing its corresponding [`Cache`]. + /// + /// [`Cache`]: Self::Cache + fn cache(self) -> Self::Cache; +} + +impl Cached for Primitive { + type Cache = Arc; + + fn load(cache: &Arc) -> Self { + Self::Cache { + content: cache.clone(), + } + } + + fn cache(self) -> Arc { + Arc::new(self) + } +} diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index 2b18243e..cc2359b6 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -19,32 +19,28 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; use crate::core::Size; +use crate::Cached; /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: crate::core::Renderer { /// The kind of geometry this renderer can draw. - type Geometry: Geometry; + type Geometry: Cached; /// The kind of [`Frame`] this renderer supports. type Frame: frame::Backend; + /// Creates a new [`Self::Frame`]. fn new_frame(&self, size: Size) -> Self::Frame; /// Draws the given [`Self::Geometry`]. fn draw_geometry(&mut self, geometry: Self::Geometry); } +/// The graphics backend of a geometry renderer. pub trait Backend { /// The kind of [`Frame`] this backend supports. type Frame: frame::Backend; + /// Creates a new [`Self::Frame`]. fn new_frame(&self, size: Size) -> Self::Frame; } - -pub trait Geometry: Sized { - type Cache; - - fn load(cache: &Self::Cache) -> Self; - - fn cache(self) -> Self::Cache; -} diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs index 490e69e2..37d433c2 100644 --- a/graphics/src/geometry/cache.rs +++ b/graphics/src/geometry/cache.rs @@ -1,11 +1,10 @@ use crate::core::Size; -use crate::geometry::{self, Frame, Geometry}; -use crate::Primitive; +use crate::geometry::{self, Frame}; +use crate::Cached; use std::cell::RefCell; -use std::sync::Arc; -/// A simple cache that stores generated [`Geometry`] to avoid recomputation. +/// 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. @@ -32,16 +31,16 @@ where *self.state.borrow_mut() = State::Empty; } - /// Draws [`Geometry`] using the provided closure and stores it in the + /// 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 + /// 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. + /// returning the stored geometry if needed. pub fn draw( &self, renderer: &Renderer, @@ -56,7 +55,7 @@ where } = self.state.borrow().deref() { if *cached_bounds == bounds { - return Geometry::load(geometry); + return Cached::load(geometry); } } @@ -64,7 +63,7 @@ where draw_fn(&mut frame); let geometry = frame.into_geometry().cache(); - let result = Geometry::load(&geometry); + let result = Cached::load(&geometry); *self.state.borrow_mut() = State::Filled { bounds, geometry }; @@ -99,7 +98,7 @@ where enum State where - Geometry: self::Geometry, + Geometry: Cached, { Empty, Filled { @@ -107,17 +106,3 @@ where geometry: Geometry::Cache, }, } - -impl Geometry for Primitive { - type Cache = Arc; - - fn load(cache: &Arc) -> Self { - Self::Cache { - content: cache.clone(), - } - } - - fn cache(self) -> Arc { - Arc::new(self) - } -} diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index e88c43b0..37e0df38 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -1,7 +1,10 @@ +//! Draw and generate geometry. use crate::core::{Point, Radians, Rectangle, Size, Vector}; -use crate::geometry::{self, Geometry}; -use crate::geometry::{Fill, Path, Stroke, Text}; +use crate::geometry::{self, Fill, Path, Stroke, Text}; +use crate::Cached; +/// The region of a surface that can be used to draw geometry. +#[allow(missing_debug_implementations)] pub struct Frame where Renderer: geometry::Renderer, @@ -13,6 +16,7 @@ impl Frame where Renderer: geometry::Renderer, { + /// Creates a new [`Frame`] with the given dimensions. pub fn new(renderer: &Renderer, size: Size) -> Self { Self { raw: renderer.new_frame(size), @@ -164,6 +168,7 @@ where self.raw.scale_nonuniform(scale); } + /// Turns the [`Frame`] into its underlying geometry. pub fn into_geometry(self) -> Renderer::Geometry { self.raw.into_geometry() } @@ -175,7 +180,7 @@ where /// of each method. #[allow(missing_docs)] pub trait Backend: Sized { - type Geometry: Geometry; + type Geometry: Cached; fn width(&self) -> f32; fn height(&self) -> f32; diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 6d0862ad..a682b89b 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,14 +9,15 @@ )] #![forbid(rust_2018_idioms)] #![deny( - // missing_debug_implementations, - // missing_docs, + missing_debug_implementations, + missing_docs, unsafe_code, unused_results, rustdoc::broken_intra_doc_links )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; +mod cached; mod error; mod primitive; mod viewport; @@ -38,6 +39,7 @@ pub mod image; pub use antialiasing::Antialiasing; pub use backend::Backend; +pub use cached::Cached; pub use compositor::Compositor; pub use damage::Damage; pub use error::Error; diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index 5be3ee5b..d671f494 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -75,6 +75,8 @@ pub struct GradientVertex2D { pub gradient: gradient::Packed, } +/// A renderer capable of drawing a [`Mesh`]. pub trait Renderer: core::Renderer { + /// Draws the given [`Mesh`]. fn draw_mesh(&mut self, mesh: Mesh); } diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 2fcb55aa..eb720495 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -58,18 +58,6 @@ impl Renderer { ) -> O { f(&mut self.backend, &self.primitives) } - - #[cfg(feature = "geometry")] - pub fn draw_geometry( - &mut self, - layers: impl IntoIterator, - ) where - Geometry: Into>, - { - for layer in layers { - self.draw_primitive(layer.into()); - } - } } impl iced_core::Renderer for Renderer { diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 659f253d..249da9e9 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -398,6 +398,7 @@ mod geometry { use super::Renderer; use crate::core::{Point, Radians, Size, Vector}; use crate::graphics::geometry::{self, Fill, Path, Stroke, Text}; + use crate::graphics::Cached; impl geometry::Renderer for Renderer where @@ -432,10 +433,10 @@ mod geometry { Right(R), } - impl geometry::Geometry for Geometry + impl Cached for Geometry where - L: geometry::Geometry, - R: geometry::Geometry, + L: Cached, + R: Cached, { type Cache = Geometry; diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index 3ba31474..a7ded0f4 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -38,7 +38,14 @@ where (event::Status::Ignored, None) } - /// Draws the state of the [`Program`] with the given [`Renderer`]. + /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. + /// + /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a + /// [`Cache`]. + /// + /// [`Geometry`]: crate::canvas::Geometry + /// [`Frame`]: crate::canvas::Frame + /// [`Cache`]: crate::canvas::Cache fn draw( &self, state: &Self::State, -- cgit From bbafeed13d20f2cbd6fc18b949b34596aa0c6c2e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:55:28 +0100 Subject: Fix outdated warning in docs of `Frame::fill_text` --- graphics/src/geometry/frame.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index 37e0df38..b54fca5d 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -69,16 +69,9 @@ where /// 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 + /// __Warning:__ 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. pub fn fill_text(&mut self, text: impl Into) { self.raw.fill_text(text); } -- cgit From 1f13a91361258a1607c71f4840a26a6437f88612 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 05:27:31 +0100 Subject: Make `iced_tiny_skia` optional with a `tiny-skia` feature --- Cargo.toml | 4 +- core/src/element.rs | 4 +- core/src/renderer.rs | 4 -- core/src/renderer/null.rs | 34 +++++++++-------- core/src/size.rs | 2 +- examples/loading_spinners/src/circular.rs | 4 +- graphics/src/backend.rs | 3 ++ graphics/src/compositor.rs | 62 ++++++++++++++++++++++++++++++- graphics/src/geometry.rs | 4 +- graphics/src/geometry/frame.rs | 3 +- graphics/src/mesh.rs | 4 +- renderer/Cargo.toml | 10 +++-- renderer/src/fallback.rs | 29 +-------------- renderer/src/lib.rs | 53 ++++++++++++++++---------- renderer/src/settings.rs | 5 +++ runtime/src/user_interface.rs | 12 +++--- tiny_skia/src/backend.rs | 3 ++ wgpu/src/backend.rs | 2 + wgpu/src/primitive/pipeline.rs | 4 +- widget/src/qr_code.rs | 4 +- 20 files changed, 158 insertions(+), 92 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b82c0f67..56b5a911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,11 @@ all-features = true maintenance = { status = "actively-developed" } [features] -default = ["wgpu", "fira-sans", "auto-detect-theme"] +default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"] # Enable the `wgpu` GPU-accelerated renderer backend wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] +# Enable the `tiny-skia` software renderer backend +tiny-skia = ["iced_renderer/tiny-skia"] # Enables the `Image` widget image = ["iced_widget/image", "dep:image"] # Enables the `Svg` widget diff --git a/core/src/element.rs b/core/src/element.rs index 989eaa3b..7d918a2e 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -95,7 +95,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> { /// /// ```no_run /// # mod iced { - /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>; + /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>; /// # /// # pub mod widget { /// # pub fn row<'a, Message>(iter: impl IntoIterator>) -> super::Element<'a, Message> { @@ -109,7 +109,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> { /// # pub enum Message {} /// # pub struct Counter; /// # - /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>; + /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>; /// # /// # impl Counter { /// # pub fn view(&self) -> Element { diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 406b33f3..dfedcd45 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -1,10 +1,6 @@ //! Write your own renderer. -#[cfg(debug_assertions)] mod null; -#[cfg(debug_assertions)] -pub use null::Null; - use crate::{ Background, Border, Color, Rectangle, Shadow, Size, Transformation, Vector, }; diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 0d7b7c14..af7dc15f 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -1,4 +1,5 @@ use crate::alignment; +use crate::image; use crate::renderer::{self, Renderer}; use crate::text::{self, Text}; use crate::{ @@ -7,20 +8,7 @@ use crate::{ use std::borrow::Cow; -/// A renderer that does nothing. -/// -/// It can be useful if you are writing tests! -#[derive(Debug, Clone, Copy, Default)] -pub struct Null; - -impl Null { - /// Creates a new [`Null`] renderer. - pub fn new() -> Null { - Null - } -} - -impl Renderer for Null { +impl Renderer for () { fn start_layer(&mut self) {} fn end_layer(&mut self, _bounds: Rectangle) {} @@ -39,7 +27,7 @@ impl Renderer for Null { } } -impl text::Renderer for Null { +impl text::Renderer for () { type Font = Font; type Paragraph = (); type Editor = (); @@ -173,3 +161,19 @@ impl text::Editor for () { ) { } } + +impl image::Renderer for () { + type Handle = (); + + fn measure_image(&self, _handle: &Self::Handle) -> Size { + Size::default() + } + + fn draw_image( + &mut self, + _handle: Self::Handle, + _filter_method: image::FilterMethod, + _bounds: Rectangle, + ) { + } +} diff --git a/core/src/size.rs b/core/src/size.rs index 267fc90e..55db759d 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -1,7 +1,7 @@ use crate::Vector; /// An amount of space in 2 dimensions. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct Size { /// The width. pub width: T, diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index cdc6b7ac..de728af2 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -356,7 +356,9 @@ where renderer.with_translation( Vector::new(bounds.x, bounds.y), |renderer| { - renderer.draw_geometry([geometry]); + use iced::advanced::graphics::geometry::Renderer as _; + + renderer.draw_geometry(geometry); }, ); } diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index e394c956..e982b54a 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -10,6 +10,9 @@ use std::borrow::Cow; /// /// [`Renderer`]: crate::Renderer pub trait Backend { + /// The compositor of this [`Backend`]. + type Compositor; + /// The custom kind of primitives this [`Backend`] supports. type Primitive: TryFrom; } diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 91951a8e..32cea46a 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -15,7 +15,7 @@ pub trait Compositor: Sized { type Settings: Default; /// The iced renderer of the backend. - type Renderer: iced_core::Renderer; + type Renderer; /// The surface of the backend. type Surface; @@ -122,3 +122,63 @@ pub struct Information { /// Contains the graphics backend. pub backend: String, } + +impl Compositor for () { + type Settings = (); + type Renderer = (); + type Surface = (); + + async fn new( + _settings: Self::Settings, + _compatible_window: W, + ) -> Result { + Ok(()) + } + + fn create_renderer(&self) -> Self::Renderer {} + + fn create_surface( + &mut self, + _window: W, + _width: u32, + _height: u32, + ) -> Self::Surface { + } + + fn configure_surface( + &mut self, + _surface: &mut Self::Surface, + _width: u32, + _height: u32, + ) { + } + + fn fetch_information(&self) -> Information { + Information { + adapter: String::from("Null Renderer"), + backend: String::from("Null"), + } + } + + fn present>( + &mut self, + _renderer: &mut Self::Renderer, + _surface: &mut Self::Surface, + _viewport: &Viewport, + _background_color: Color, + _overlay: &[T], + ) -> Result<(), SurfaceError> { + Ok(()) + } + + fn screenshot>( + &mut self, + _renderer: &mut Self::Renderer, + _surface: &mut Self::Surface, + _viewport: &Viewport, + _background_color: Color, + _overlay: &[T], + ) -> Vec { + vec![] + } +} diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index cc2359b6..194f37b2 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -18,11 +18,11 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; -use crate::core::Size; +use crate::core::{self, Size}; use crate::Cached; /// A renderer capable of drawing some [`Self::Geometry`]. -pub trait Renderer: crate::core::Renderer { +pub trait Renderer: core::Renderer { /// The kind of geometry this renderer can draw. type Geometry: Cached; diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index b54fca5d..635012d0 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -1,7 +1,6 @@ //! Draw and generate geometry. use crate::core::{Point, Radians, Rectangle, Size, Vector}; use crate::geometry::{self, Fill, Path, Stroke, Text}; -use crate::Cached; /// The region of a surface that can be used to draw geometry. #[allow(missing_debug_implementations)] @@ -173,7 +172,7 @@ where /// of each method. #[allow(missing_docs)] pub trait Backend: Sized { - type Geometry: Cached; + type Geometry; fn width(&self) -> f32; fn height(&self) -> f32; diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index d671f494..20692b07 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -1,6 +1,6 @@ //! Draw triangles! use crate::color; -use crate::core::{self, Rectangle, Size}; +use crate::core::{Rectangle, Size}; use crate::gradient; use crate::Damage; @@ -76,7 +76,7 @@ pub struct GradientVertex2D { } /// A renderer capable of drawing a [`Mesh`]. -pub trait Renderer: core::Renderer { +pub trait Renderer { /// Draws the given [`Mesh`]. fn draw_mesh(&mut self, mesh: Mesh); } diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 18e9e8f8..39c19fa3 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -12,18 +12,20 @@ keywords.workspace = true [features] wgpu = ["iced_wgpu"] -image = ["iced_tiny_skia/image", "iced_wgpu?/image"] -svg = ["iced_tiny_skia/svg", "iced_wgpu?/svg"] -geometry = ["iced_graphics/geometry", "iced_tiny_skia/geometry", "iced_wgpu?/geometry"] +tiny-skia = ["iced_tiny_skia"] +image = ["iced_tiny_skia?/image", "iced_wgpu?/image"] +svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"] +geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"] tracing = ["iced_wgpu?/tracing"] web-colors = ["iced_wgpu?/web-colors"] webgl = ["iced_wgpu?/webgl"] fira-sans = ["iced_graphics/fira-sans"] -custom = [] [dependencies] iced_graphics.workspace = true + iced_tiny_skia.workspace = true +iced_tiny_skia.optional = true iced_wgpu.workspace = true iced_wgpu.optional = true diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 249da9e9..4431606a 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -8,11 +8,7 @@ use crate::graphics; use crate::graphics::compositor; use crate::graphics::mesh; -pub enum Renderer -where - L: core::Renderer, - R: core::Renderer, -{ +pub enum Renderer { Left(L), Right(R), } @@ -26,29 +22,6 @@ macro_rules! delegate { }; } -impl Renderer -where - L: core::Renderer, - R: core::Renderer, -{ - #[cfg(feature = "geometry")] - pub fn draw_geometry( - &mut self, - layers: impl IntoIterator, - ) where - L: graphics::geometry::Renderer, - R: graphics::geometry::Renderer, - - Geometry: Into>, - { - use graphics::geometry::Renderer; - - for layer in layers { - ::draw_geometry(self, layer.into()); - } - } -} - impl core::Renderer for Renderer where L: core::Renderer, diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index f8aa1157..199b431e 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -19,27 +19,40 @@ pub use settings::Settings; /// The default graphics renderer for [`iced`]. /// /// [`iced`]: https://github.com/iced-rs/iced -#[cfg(not(feature = "wgpu"))] -pub type Renderer = iced_tiny_skia::Renderer; - -/// The default graphics renderer for [`iced`]. -/// -/// [`iced`]: https://github.com/iced-rs/iced -#[cfg(feature = "wgpu")] -pub type Renderer = - fallback::Renderer; +pub type Renderer = renderer::Renderer; /// The default graphics compositor for [`iced`]. /// /// [`iced`]: https://github.com/iced-rs/iced -#[cfg(not(feature = "wgpu"))] -pub type Compositor = iced_tiny_skia::window::Compositor; - -/// The default graphics renderer for [`iced`]. -/// -/// [`iced`]: https://github.com/iced-rs/iced -#[cfg(feature = "wgpu")] -pub type Compositor = fallback::Compositor< - iced_wgpu::window::Compositor, - iced_tiny_skia::window::Compositor, ->; +pub type Compositor = renderer::Compositor; + +#[cfg(all(feature = "wgpu", feature = "tiny-skia"))] +mod renderer { + pub type Renderer = crate::fallback::Renderer< + iced_wgpu::Renderer, + iced_tiny_skia::Renderer, + >; + + pub type Compositor = crate::fallback::Compositor< + iced_wgpu::window::Compositor, + iced_tiny_skia::window::Compositor, + >; +} + +#[cfg(all(feature = "wgpu", not(feature = "tiny-skia")))] +mod renderer { + pub type Renderer = iced_wgpu::Renderer; + pub type Compositor = iced_wgpu::window::Compositor; +} + +#[cfg(all(not(feature = "wgpu"), feature = "tiny-skia"))] +mod renderer { + pub type Renderer = iced_tiny_skia::Renderer; + pub type Compositor = iced_tiny_skia::window::Compositor; +} + +#[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))] +mod renderer { + pub type Renderer = (); + pub type Compositor = (); +} diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs index 940daa15..27788db9 100644 --- a/renderer/src/settings.rs +++ b/renderer/src/settings.rs @@ -28,6 +28,7 @@ impl Default for Settings { } } +#[cfg(feature = "tiny-skia")] impl From for iced_tiny_skia::Settings { fn from(settings: Settings) -> Self { Self { @@ -48,3 +49,7 @@ impl From for iced_wgpu::Settings { } } } + +impl From for () { + fn from(_settings: Settings) -> Self {} +} diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 748fb651..006225ed 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -45,7 +45,7 @@ where /// /// ```no_run /// # mod iced_wgpu { - /// # pub use iced_runtime::core::renderer::Null as Renderer; + /// # pub type Renderer = (); /// # } /// # /// # pub struct Counter; @@ -62,7 +62,7 @@ where /// // Initialization /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); - /// let mut renderer = Renderer::new(); + /// let mut renderer = Renderer::default(); /// let mut window_size = Size::new(1024.0, 768.0); /// /// // Application loop @@ -121,7 +121,7 @@ where /// /// ```no_run /// # mod iced_wgpu { - /// # pub use iced_runtime::core::renderer::Null as Renderer; + /// # pub type Renderer = (); /// # } /// # /// # pub struct Counter; @@ -139,7 +139,7 @@ where /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); - /// let mut renderer = Renderer::new(); + /// let mut renderer = Renderer::default(); /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; @@ -374,7 +374,7 @@ where /// /// ```no_run /// # mod iced_wgpu { - /// # pub use iced_runtime::core::renderer::Null as Renderer; + /// # pub type Renderer = (); /// # pub type Theme = (); /// # } /// # @@ -394,7 +394,7 @@ where /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); - /// let mut renderer = Renderer::new(); + /// let mut renderer = Renderer::default(); /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index f6bb1c86..b0dd4759 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -5,6 +5,7 @@ use crate::graphics::backend; use crate::graphics::text; use crate::graphics::{Damage, Viewport}; use crate::primitive::{self, Primitive}; +use crate::window; use std::borrow::Cow; @@ -990,6 +991,8 @@ fn rounded_box_sdf( } impl iced_graphics::Backend for Backend { + type Compositor = window::Compositor; + type Primitive = primitive::Custom; } diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 924aacf1..3675d50b 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -7,6 +7,7 @@ use crate::primitive::{self, Primitive}; use crate::quad; use crate::text; use crate::triangle; +use crate::window; use crate::{Layer, Settings}; #[cfg(feature = "tracing")] @@ -372,6 +373,7 @@ impl Backend { } impl crate::graphics::Backend for Backend { + type Compositor = window::Compositor; type Primitive = primitive::Custom; } diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index c6b7c5e2..814440ba 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -1,5 +1,5 @@ //! Draw primitives using custom pipelines. -use crate::core::{Rectangle, Size}; +use crate::core::{self, Rectangle, Size}; use std::any::{Any, TypeId}; use std::collections::HashMap; @@ -58,7 +58,7 @@ pub trait Primitive: Debug + Send + Sync + 'static { } /// A renderer than can draw custom pipeline primitives. -pub trait Renderer: crate::core::Renderer { +pub trait Renderer: core::Renderer { /// Draws a custom pipeline primitive. fn draw_pipeline_primitive( &mut self, diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 84898dc0..601e5808 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -141,7 +141,9 @@ impl<'a, Message, Theme> Widget renderer.with_translation( bounds.position() - Point::ORIGIN, |renderer| { - renderer.draw_geometry(vec![geometry]); + use crate::graphics::geometry::Renderer as _; + + renderer.draw_geometry(geometry); }, ); } -- cgit From 4f2f40c68b4647f281d34034beb159a41422aa06 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 05:41:15 +0100 Subject: Fix standalone compilation of `iced_widget` crate --- core/src/renderer.rs | 1 + core/src/renderer/null.rs | 15 ++++++++++++++ graphics/src/cached.rs | 9 +++++++++ graphics/src/geometry.rs | 10 +++++++++ graphics/src/geometry/frame.rs | 46 ++++++++++++++++++++++++++++++++++++++++++ tiny_skia/src/backend.rs | 1 - 6 files changed, 81 insertions(+), 1 deletion(-) diff --git a/core/src/renderer.rs b/core/src/renderer.rs index dfedcd45..6712314e 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -1,4 +1,5 @@ //! Write your own renderer. +#[cfg(debug_assertions)] mod null; use crate::{ diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index af7dc15f..c26ce1a5 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -1,6 +1,7 @@ use crate::alignment; use crate::image; use crate::renderer::{self, Renderer}; +use crate::svg; use crate::text::{self, Text}; use crate::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, @@ -177,3 +178,17 @@ impl image::Renderer for () { ) { } } + +impl svg::Renderer for () { + fn measure_svg(&self, _handle: &svg::Handle) -> Size { + Size::default() + } + + fn draw_svg( + &mut self, + _handle: svg::Handle, + _color: Option, + _bounds: Rectangle, + ) { + } +} diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs index f7968c9f..b52f9d9d 100644 --- a/graphics/src/cached.rs +++ b/graphics/src/cached.rs @@ -31,3 +31,12 @@ impl Cached for Primitive { Arc::new(self) } } + +#[cfg(debug_assertions)] +impl Cached for () { + type Cache = (); + + fn load(_cache: &Self::Cache) -> Self {} + + fn cache(self) -> Self::Cache {} +} diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index 194f37b2..d251efb8 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -44,3 +44,13 @@ pub trait Backend { /// Creates a new [`Self::Frame`]. fn new_frame(&self, size: Size) -> Self::Frame; } + +#[cfg(debug_assertions)] +impl Renderer for () { + type Geometry = (); + type Frame = (); + + fn new_frame(&self, _size: Size) -> Self::Frame {} + + fn draw_geometry(&mut self, _geometry: Self::Geometry) {} +} diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index 635012d0..ad35e8ec 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -203,3 +203,49 @@ pub trait Backend: Sized { fn into_geometry(self) -> Self::Geometry; } + +#[cfg(debug_assertions)] +impl Backend for () { + type Geometry = (); + + fn width(&self) -> f32 { + 0.0 + } + + fn height(&self) -> f32 { + 0.0 + } + + fn size(&self) -> Size { + Size::ZERO + } + + fn center(&self) -> Point { + Point::ORIGIN + } + + fn push_transform(&mut self) {} + fn pop_transform(&mut self) {} + + fn translate(&mut self, _translation: Vector) {} + fn rotate(&mut self, _angle: impl Into) {} + fn scale(&mut self, _scale: impl Into) {} + fn scale_nonuniform(&mut self, _scale: impl Into) {} + + fn draft(&mut self, _size: Size) -> Self {} + fn paste(&mut self, _frame: Self, _at: Point) {} + + fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into>) {} + + fn fill(&mut self, _path: &Path, _fill: impl Into) {} + fn fill_text(&mut self, _text: impl Into) {} + fn fill_rectangle( + &mut self, + _top_left: Point, + _size: Size, + _fill: impl Into, + ) { + } + + fn into_geometry(self) -> Self::Geometry {} +} diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index b0dd4759..6d4e6cda 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -992,7 +992,6 @@ fn rounded_box_sdf( impl iced_graphics::Backend for Backend { type Compositor = window::Compositor; - type Primitive = primitive::Custom; } -- cgit From 5137d655e6bbd29581fc1469d0385515113f2999 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 07:09:51 +0100 Subject: Allow custom renderers in `Program` and `Application` --- graphics/src/backend.rs | 6 ++-- graphics/src/compositor.rs | 24 ++++++++++----- graphics/src/lib.rs | 2 ++ graphics/src/renderer.rs | 8 +++++ graphics/src/settings.rs | 29 ++++++++++++++++++ renderer/src/fallback.rs | 13 ++++++--- renderer/src/lib.rs | 4 --- renderer/src/settings.rs | 55 ---------------------------------- runtime/src/program.rs | 4 +-- src/application.rs | 18 ++++++++---- src/lib.rs | 5 ++-- src/multi_window.rs | 4 +-- src/program.rs | 60 ++++++++++++++++++++++++++------------ tiny_skia/src/settings.rs | 10 +++++++ tiny_skia/src/window/compositor.rs | 7 ++--- wgpu/src/settings.rs | 13 ++++++++- wgpu/src/window/compositor.rs | 8 ++--- winit/src/application.rs | 5 ++-- winit/src/multi_window.rs | 9 +++--- 19 files changed, 162 insertions(+), 122 deletions(-) create mode 100644 graphics/src/settings.rs delete mode 100644 renderer/src/settings.rs diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index e982b54a..aa7bf4e8 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -2,16 +2,16 @@ use crate::core::image; use crate::core::svg; use crate::core::Size; -use crate::Mesh; +use crate::{Compositor, Mesh, Renderer}; use std::borrow::Cow; /// The graphics backend of a [`Renderer`]. /// /// [`Renderer`]: crate::Renderer -pub trait Backend { +pub trait Backend: Sized { /// The compositor of this [`Backend`]. - type Compositor; + type Compositor: Compositor>; /// The custom kind of primitives this [`Backend`] supports. type Primitive: TryFrom; diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 32cea46a..4d548f30 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -1,9 +1,9 @@ //! A compositor is responsible for initializing a renderer and managing window //! surfaces. -use crate::{Error, Viewport}; - +use crate::core; use crate::core::Color; use crate::futures::{MaybeSend, MaybeSync}; +use crate::{Error, Settings, Viewport}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::future::Future; @@ -11,9 +11,6 @@ use thiserror::Error; /// A graphics compositor that can draw to windows. pub trait Compositor: Sized { - /// The settings of the backend. - type Settings: Default; - /// The iced renderer of the backend. type Renderer; @@ -22,7 +19,7 @@ pub trait Compositor: Sized { /// Creates a new [`Compositor`]. fn new( - settings: Self::Settings, + settings: Settings, compatible_window: W, ) -> impl Future>; @@ -93,6 +90,12 @@ impl Window for T where { } +/// A renderer that supports composition. +pub trait Renderer: core::Renderer { + /// The compositor of the renderer. + type Compositor: Compositor; +} + /// Result of an unsuccessful call to [`Compositor::present`]. #[derive(Clone, PartialEq, Eq, Debug, Error)] pub enum SurfaceError { @@ -123,13 +126,13 @@ pub struct Information { pub backend: String, } +#[cfg(debug_assertions)] impl Compositor for () { - type Settings = (); type Renderer = (); type Surface = (); async fn new( - _settings: Self::Settings, + _settings: Settings, _compatible_window: W, ) -> Result { Ok(()) @@ -182,3 +185,8 @@ impl Compositor for () { vec![] } } + +#[cfg(debug_assertions)] +impl Renderer for () { + type Compositor = (); +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index a682b89b..2e476f8c 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -20,6 +20,7 @@ mod antialiasing; mod cached; mod error; mod primitive; +mod settings; mod viewport; pub mod backend; @@ -47,6 +48,7 @@ pub use gradient::Gradient; pub use mesh::Mesh; pub use primitive::Primitive; pub use renderer::Renderer; +pub use settings::Settings; pub use viewport::Viewport; pub use iced_core as core; diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index eb720495..5de7f97f 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,5 +1,6 @@ //! Create a renderer from a [`Backend`]. use crate::backend::{self, Backend}; +use crate::compositor; use crate::core; use crate::core::image; use crate::core::renderer; @@ -259,3 +260,10 @@ where self.draw_primitive(geometry); } } + +impl compositor::Renderer for Renderer +where + B: Backend, +{ + type Compositor = B::Compositor; +} diff --git a/graphics/src/settings.rs b/graphics/src/settings.rs new file mode 100644 index 00000000..68673536 --- /dev/null +++ b/graphics/src/settings.rs @@ -0,0 +1,29 @@ +use crate::core::{Font, Pixels}; +use crate::Antialiasing; + +/// The settings of a 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: Pixels, + + /// 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::default(), + default_text_size: Pixels(16.0), + antialiasing: None, + } + } +} diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 4431606a..28e73dd8 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -200,15 +200,12 @@ impl graphics::Compositor for Compositor where L: graphics::Compositor, R: graphics::Compositor, - L::Settings: From, - R::Settings: From, { - type Settings = crate::Settings; type Renderer = Renderer; type Surface = Surface; async fn new( - settings: Self::Settings, + settings: graphics::Settings, compatible_window: W, ) -> Result { if let Ok(left) = L::new(settings.into(), compatible_window.clone()) @@ -528,3 +525,11 @@ mod geometry { } } } + +impl compositor::Renderer for Renderer +where + L: compositor::Renderer, + R: compositor::Renderer, +{ + type Compositor = Compositor; +} diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 199b431e..7c48995d 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -6,16 +6,12 @@ pub use iced_wgpu as wgpu; pub mod fallback; -mod settings; - pub use iced_graphics as graphics; pub use iced_graphics::core; #[cfg(feature = "geometry")] pub use iced_graphics::geometry; -pub use settings::Settings; - /// The default graphics renderer for [`iced`]. /// /// [`iced`]: https://github.com/iced-rs/iced diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs deleted file mode 100644 index 27788db9..00000000 --- a/renderer/src/settings.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::core::{Font, Pixels}; -use crate::graphics::Antialiasing; - -/// The settings of a 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: Pixels, - - /// 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::default(), - default_text_size: Pixels(16.0), - antialiasing: None, - } - } -} - -#[cfg(feature = "tiny-skia")] -impl From for iced_tiny_skia::Settings { - fn from(settings: Settings) -> Self { - Self { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - } - } -} - -#[cfg(feature = "wgpu")] -impl From for iced_wgpu::Settings { - fn from(settings: Settings) -> Self { - Self { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - antialiasing: settings.antialiasing, - ..iced_wgpu::Settings::default() - } - } -} - -impl From for () { - fn from(_settings: Settings) -> Self {} -} diff --git a/runtime/src/program.rs b/runtime/src/program.rs index 6c1b8f07..0ea94d3b 100644 --- a/runtime/src/program.rs +++ b/runtime/src/program.rs @@ -2,7 +2,7 @@ use crate::Command; use iced_core::text; -use iced_core::{Element, Renderer}; +use iced_core::Element; mod state; @@ -11,7 +11,7 @@ pub use state::State; /// The core of a user interface application following The Elm Architecture. pub trait Program: Sized { /// The graphics backend to use to draw the [`Program`]. - type Renderer: Renderer + text::Renderer; + type Renderer: text::Renderer; /// The theme used to draw the [`Program`]. type Theme; diff --git a/src/application.rs b/src/application.rs index 8317abcb..62fe77da 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,6 @@ //! Build interactive cross-platform applications. +use crate::core::text; +use crate::graphics::compositor; use crate::shell::application; use crate::{Command, Element, Executor, Settings, Subscription}; @@ -60,7 +62,7 @@ pub use application::{Appearance, DefaultStyle}; /// ```no_run /// use iced::advanced::Application; /// use iced::executor; -/// use iced::{Command, Element, Settings, Theme}; +/// use iced::{Command, Element, Settings, Theme, Renderer}; /// /// pub fn main() -> iced::Result { /// Hello::run(Settings::default()) @@ -73,6 +75,7 @@ pub use application::{Appearance, DefaultStyle}; /// type Flags = (); /// type Message = (); /// type Theme = Theme; +/// type Renderer = Renderer; /// /// fn new(_flags: ()) -> (Hello, Command) { /// (Hello, Command::none()) @@ -109,6 +112,9 @@ where /// The theme of your [`Application`]. type Theme: Default; + /// The renderer of your [`Application`]. + type Renderer: text::Renderer + compositor::Renderer; + /// The data needed to initialize your [`Application`]. type Flags; @@ -142,7 +148,7 @@ where /// Returns the widgets to display in the [`Application`]. /// /// These widgets can produce __messages__ based on user interaction. - fn view(&self) -> Element<'_, Self::Message, Self::Theme, crate::Renderer>; + fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>; /// Returns the current [`Theme`] of the [`Application`]. /// @@ -195,7 +201,7 @@ where Self: 'static, { #[allow(clippy::needless_update)] - let renderer_settings = crate::renderer::Settings { + let renderer_settings = crate::graphics::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, antialiasing: if settings.antialiasing { @@ -203,13 +209,13 @@ where } else { None }, - ..crate::renderer::Settings::default() + ..crate::graphics::Settings::default() }; let run = crate::shell::application::run::< Instance, Self::Executor, - crate::renderer::Compositor, + ::Compositor, >(settings.into(), renderer_settings); #[cfg(target_arch = "wasm32")] @@ -241,7 +247,7 @@ where { type Message = A::Message; type Theme = A::Theme; - type Renderer = crate::Renderer; + type Renderer = A::Renderer; fn update(&mut self, message: Self::Message) -> Command { self.0.update(message) diff --git a/src/lib.rs b/src/lib.rs index 0e9566e2..171109e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -372,15 +372,16 @@ pub type Result = std::result::Result<(), Error>; /// ] /// } /// ``` -pub fn run( +pub fn run( title: impl program::Title + 'static, update: impl program::Update + 'static, - view: impl for<'a> program::View<'a, State, Message, Theme> + 'static, + view: impl for<'a> program::View<'a, State, Message, Theme, Renderer> + 'static, ) -> Result where State: Default + 'static, Message: std::fmt::Debug + Send + 'static, Theme: Default + program::DefaultStyle + 'static, + Renderer: graphics::compositor::Renderer + core::text::Renderer + 'static, { program(title, update, view).run() } diff --git a/src/multi_window.rs b/src/multi_window.rs index fca0be46..b81297dc 100644 --- a/src/multi_window.rs +++ b/src/multi_window.rs @@ -174,7 +174,7 @@ where Self: 'static, { #[allow(clippy::needless_update)] - let renderer_settings = crate::renderer::Settings { + let renderer_settings = crate::graphics::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, antialiasing: if settings.antialiasing { @@ -182,7 +182,7 @@ where } else { None }, - ..crate::renderer::Settings::default() + ..crate::graphics::Settings::default() }; Ok(crate::shell::multi_window::run::< diff --git a/src/program.rs b/src/program.rs index 7a366585..f5a2bc53 100644 --- a/src/program.rs +++ b/src/program.rs @@ -32,6 +32,7 @@ //! ``` use crate::application::Application; use crate::executor::{self, Executor}; +use crate::graphics::compositor; use crate::window; use crate::{Command, Element, Font, Result, Settings, Size, Subscription}; @@ -67,37 +68,41 @@ use std::borrow::Cow; /// ] /// } /// ``` -pub fn program( +pub fn program( title: impl Title, update: impl Update, - view: impl for<'a> self::View<'a, State, Message, Theme>, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, ) -> Program> where State: 'static, Message: Send + std::fmt::Debug, Theme: Default + DefaultStyle, + Renderer: compositor::Renderer + crate::core::text::Renderer, { use std::marker::PhantomData; - struct Application { + struct Application { update: Update, view: View, _state: PhantomData, _message: PhantomData, _theme: PhantomData, + _renderer: PhantomData, } - impl Definition - for Application + impl Definition + for Application where Message: Send + std::fmt::Debug, Theme: Default + DefaultStyle, + Renderer: compositor::Renderer + crate::core::text::Renderer, Update: self::Update, - View: for<'a> self::View<'a, State, Message, Theme>, + View: for<'a> self::View<'a, State, Message, Theme, Renderer>, { type State = State; type Message = Message; type Theme = Theme; + type Renderer = Renderer; type Executor = executor::Default; fn load(&self) -> Command { @@ -115,7 +120,7 @@ where fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.view.view(state).into() } } @@ -127,6 +132,7 @@ where _state: PhantomData, _message: PhantomData, _theme: PhantomData, + _renderer: PhantomData, }, settings: Settings::default(), } @@ -184,6 +190,7 @@ impl Program

{ impl P::State> Application for Instance { type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Flags = (P, I); type Executor = P::Executor; @@ -216,7 +223,7 @@ impl Program

{ fn view( &self, - ) -> crate::Element<'_, Self::Message, Self::Theme, crate::Renderer> + ) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer> { self.program.view(&self.state) } @@ -417,6 +424,9 @@ pub trait Definition: Sized { /// The theme of the program. type Theme: Default + DefaultStyle; + /// The renderer of the program. + type Renderer: compositor::Renderer + crate::core::text::Renderer; + /// The executor of the program. type Executor: Executor; @@ -431,7 +441,7 @@ pub trait Definition: Sized { fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme>; + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>; fn title(&self, _state: &Self::State) -> String { String::from("A cool iced application!") @@ -470,6 +480,7 @@ fn with_title( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = P::Executor; fn load(&self) -> Command { @@ -491,7 +502,7 @@ fn with_title( fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.program.view(state) } @@ -534,6 +545,7 @@ fn with_load( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = executor::Default; fn load(&self) -> Command { @@ -551,7 +563,7 @@ fn with_load( fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.program.view(state) } @@ -598,6 +610,7 @@ fn with_subscription( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = executor::Default; fn subscription( @@ -622,7 +635,7 @@ fn with_subscription( fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.program.view(state) } @@ -665,6 +678,7 @@ fn with_theme( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = P::Executor; fn theme(&self, state: &Self::State) -> Self::Theme { @@ -690,7 +704,7 @@ fn with_theme( fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.program.view(state) } @@ -729,6 +743,7 @@ fn with_style( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = P::Executor; fn style( @@ -758,7 +773,7 @@ fn with_style( fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.program.view(state) } @@ -834,18 +849,25 @@ where /// /// This trait allows the [`program`] builder to take any closure that /// returns any `Into>`. -pub trait View<'a, State, Message, Theme> { +pub trait View<'a, State, Message, Theme, Renderer> { /// Produces the widget of the [`Program`]. - fn view(&self, state: &'a State) -> impl Into>; + fn view( + &self, + state: &'a State, + ) -> impl Into>; } -impl<'a, T, State, Message, Theme, Widget> View<'a, State, Message, Theme> for T +impl<'a, T, State, Message, Theme, Renderer, Widget> + View<'a, State, Message, Theme, Renderer> for T where T: Fn(&'a State) -> Widget, State: 'static, - Widget: Into>, + Widget: Into>, { - fn view(&self, state: &'a State) -> impl Into> { + fn view( + &self, + state: &'a State, + ) -> impl Into> { self(state) } } diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs index ec27b218..01d015b4 100644 --- a/tiny_skia/src/settings.rs +++ b/tiny_skia/src/settings.rs @@ -1,4 +1,5 @@ use crate::core::{Font, Pixels}; +use crate::graphics; /// The settings of a [`Backend`]. /// @@ -22,3 +23,12 @@ impl Default for Settings { } } } + +impl From for Settings { + fn from(settings: graphics::Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + } + } +} diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index a98825f1..0c08097b 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,7 +1,7 @@ use crate::core::{Color, Rectangle, Size}; use crate::graphics::compositor::{self, Information}; use crate::graphics::damage; -use crate::graphics::{Error, Viewport}; +use crate::graphics::{self, Error, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; use std::collections::VecDeque; @@ -25,15 +25,14 @@ pub struct Surface { } impl crate::graphics::Compositor for Compositor { - type Settings = Settings; type Renderer = Renderer; type Surface = Surface; fn new( - settings: Self::Settings, + settings: graphics::Settings, compatible_window: W, ) -> impl Future> { - future::ready(Ok(new(settings, compatible_window))) + future::ready(Ok(new(settings.into(), compatible_window))) } fn create_renderer(&self) -> Self::Renderer { diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index c9338fec..9943aa3e 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,6 +1,6 @@ //! Configure a renderer. use crate::core::{Font, Pixels}; -use crate::graphics::Antialiasing; +use crate::graphics::{self, Antialiasing}; /// The settings of a [`Backend`]. /// @@ -64,3 +64,14 @@ impl Default for Settings { } } } + +impl From for Settings { + fn from(settings: graphics::Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..Settings::default() + } + } +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 328ad781..74dfadba 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,9 +1,8 @@ //! Connect a window with a renderer. use crate::core::{Color, Size}; -use crate::graphics; use crate::graphics::color; use crate::graphics::compositor; -use crate::graphics::{Error, Viewport}; +use crate::graphics::{self, Error, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; use std::future::Future; @@ -225,15 +224,14 @@ pub fn present>( } impl graphics::Compositor for Compositor { - type Settings = Settings; type Renderer = Renderer; type Surface = wgpu::Surface<'static>; fn new( - settings: Self::Settings, + settings: graphics::Settings, compatible_window: W, ) -> impl Future> { - new(settings, compatible_window) + new(settings.into(), compatible_window) } fn create_renderer(&self) -> Self::Renderer { diff --git a/winit/src/application.rs b/winit/src/application.rs index 29786b65..d68523fa 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -13,6 +13,7 @@ use crate::core::window; use crate::core::{Color, Event, Point, Size, Theme}; use crate::futures::futures; use crate::futures::{Executor, Runtime, Subscription}; +use crate::graphics; use crate::graphics::compositor::{self, Compositor}; use crate::runtime::clipboard; use crate::runtime::program::Program; @@ -130,7 +131,7 @@ pub fn default(theme: &Theme) -> Appearance { /// settings. pub async fn run( settings: Settings, - compositor_settings: impl Into, + graphics_settings: graphics::Settings, ) -> Result<(), Error> where A: Application + 'static, @@ -219,7 +220,7 @@ where }; } - let compositor = C::new(compositor_settings.into(), window.clone()).await?; + let compositor = C::new(graphics_settings, window.clone()).await?; let mut renderer = compositor.create_renderer(); for font in settings.fonts { diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index c865b0ee..b4c25411 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -16,6 +16,7 @@ use crate::futures::futures::executor; use crate::futures::futures::task; use crate::futures::futures::{Future, StreamExt}; use crate::futures::{Executor, Runtime, Subscription}; +use crate::graphics; use crate::graphics::{compositor, Compositor}; use crate::multi_window::window_manager::WindowManager; use crate::runtime::command::{self, Command}; @@ -105,7 +106,7 @@ where /// settings. pub fn run( settings: Settings, - compositor_settings: impl Into, + graphics_settings: graphics::Settings, ) -> Result<(), Error> where A: Application + 'static, @@ -186,10 +187,8 @@ where }; } - let mut compositor = executor::block_on(C::new( - compositor_settings.into(), - main_window.clone(), - ))?; + let mut compositor = + executor::block_on(C::new(graphics_settings, main_window.clone()))?; let mut window_manager = WindowManager::new(); let _ = window_manager.insert( -- cgit From a2c897792ccb8f91a8479c1eca9146c439e9173b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 07:12:46 +0100 Subject: Fix unnecessary `into` calls in `iced_renderer::fallback` --- renderer/src/fallback.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 28e73dd8..8daab74f 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -208,16 +208,14 @@ where settings: graphics::Settings, compatible_window: W, ) -> Result { - if let Ok(left) = L::new(settings.into(), compatible_window.clone()) + if let Ok(left) = L::new(settings, compatible_window.clone()) .await .map(Self::Left) { return Ok(left); } - R::new(settings.into(), compatible_window) - .await - .map(Self::Right) + R::new(settings, compatible_window).await.map(Self::Right) } fn create_renderer(&self) -> Self::Renderer { -- cgit From 441e9237cd1c9c9b61d9b144b5b4dafa236ace28 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 19:35:19 +0100 Subject: Rename `compositor::Renderer` to `Default` --- graphics/src/backend.rs | 6 +++--- graphics/src/compositor.rs | 7 +++---- graphics/src/renderer.rs | 2 +- renderer/src/fallback.rs | 6 +++--- src/application.rs | 4 ++-- src/lib.rs | 2 +- src/program.rs | 12 +++++++++--- tiny_skia/src/backend.rs | 4 ++-- wgpu/src/backend.rs | 4 ++-- 9 files changed, 26 insertions(+), 21 deletions(-) diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index aa7bf4e8..7abc42c5 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -10,11 +10,11 @@ use std::borrow::Cow; /// /// [`Renderer`]: crate::Renderer pub trait Backend: Sized { - /// The compositor of this [`Backend`]. - type Compositor: Compositor>; - /// The custom kind of primitives this [`Backend`] supports. type Primitive: TryFrom; + + /// The default compositor of this [`Backend`]. + type Compositor: Compositor>; } /// A graphics backend that supports text rendering. diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 4d548f30..8c67cd16 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -1,6 +1,5 @@ //! A compositor is responsible for initializing a renderer and managing window //! surfaces. -use crate::core; use crate::core::Color; use crate::futures::{MaybeSend, MaybeSync}; use crate::{Error, Settings, Viewport}; @@ -90,8 +89,8 @@ impl Window for T where { } -/// A renderer that supports composition. -pub trait Renderer: core::Renderer { +/// Defines the default compositor of a renderer. +pub trait Default { /// The compositor of the renderer. type Compositor: Compositor; } @@ -187,6 +186,6 @@ impl Compositor for () { } #[cfg(debug_assertions)] -impl Renderer for () { +impl Default for () { type Compositor = (); } diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 5de7f97f..f517ff3e 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -261,7 +261,7 @@ where } } -impl compositor::Renderer for Renderer +impl compositor::Default for Renderer where B: Backend, { diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 8daab74f..ca445746 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -524,10 +524,10 @@ mod geometry { } } -impl compositor::Renderer for Renderer +impl compositor::Default for Renderer where - L: compositor::Renderer, - R: compositor::Renderer, + L: compositor::Default, + R: compositor::Default, { type Compositor = Compositor; } diff --git a/src/application.rs b/src/application.rs index 62fe77da..9197834b 100644 --- a/src/application.rs +++ b/src/application.rs @@ -113,7 +113,7 @@ where type Theme: Default; /// The renderer of your [`Application`]. - type Renderer: text::Renderer + compositor::Renderer; + type Renderer: text::Renderer + compositor::Default; /// The data needed to initialize your [`Application`]. type Flags; @@ -215,7 +215,7 @@ where let run = crate::shell::application::run::< Instance, Self::Executor, - ::Compositor, + ::Compositor, >(settings.into(), renderer_settings); #[cfg(target_arch = "wasm32")] diff --git a/src/lib.rs b/src/lib.rs index 171109e7..e67b46e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -381,7 +381,7 @@ where State: Default + 'static, Message: std::fmt::Debug + Send + 'static, Theme: Default + program::DefaultStyle + 'static, - Renderer: graphics::compositor::Renderer + core::text::Renderer + 'static, + Renderer: program::Renderer + 'static, { program(title, update, view).run() } diff --git a/src/program.rs b/src/program.rs index f5a2bc53..705e140d 100644 --- a/src/program.rs +++ b/src/program.rs @@ -31,6 +31,7 @@ //! } //! ``` use crate::application::Application; +use crate::core::text; use crate::executor::{self, Executor}; use crate::graphics::compositor; use crate::window; @@ -77,7 +78,7 @@ where State: 'static, Message: Send + std::fmt::Debug, Theme: Default + DefaultStyle, - Renderer: compositor::Renderer + crate::core::text::Renderer, + Renderer: self::Renderer, { use std::marker::PhantomData; @@ -95,7 +96,7 @@ where where Message: Send + std::fmt::Debug, Theme: Default + DefaultStyle, - Renderer: compositor::Renderer + crate::core::text::Renderer, + Renderer: self::Renderer, Update: self::Update, View: for<'a> self::View<'a, State, Message, Theme, Renderer>, { @@ -425,7 +426,7 @@ pub trait Definition: Sized { type Theme: Default + DefaultStyle; /// The renderer of the program. - type Renderer: compositor::Renderer + crate::core::text::Renderer; + type Renderer: Renderer + crate::core::text::Renderer; /// The executor of the program. type Executor: Executor; @@ -871,3 +872,8 @@ where self(state) } } + +/// The renderer of some [`Program`]. +pub trait Renderer: text::Renderer + compositor::Default {} + +impl Renderer for T where T: text::Renderer + compositor::Default {} diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 6d4e6cda..8c8781e3 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -990,9 +990,9 @@ fn rounded_box_sdf( (x.powf(2.0) + y.powf(2.0)).sqrt() - radius } -impl iced_graphics::Backend for Backend { - type Compositor = window::Compositor; +impl backend::Backend for Backend { type Primitive = primitive::Custom; + type Compositor = window::Compositor; } impl backend::Text for Backend { diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 3675d50b..5019191c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -372,9 +372,9 @@ impl Backend { } } -impl crate::graphics::Backend for Backend { - type Compositor = window::Compositor; +impl backend::Backend for Backend { type Primitive = primitive::Custom; + type Compositor = window::Compositor; } impl backend::Text for Backend { -- cgit From 999ad2d288a9354f045bb2e1b838014b3d302779 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 23 Mar 2024 19:23:08 +0100 Subject: Try catalog theming approach with `Button` --- core/src/closure.rs | 33 +++++++++++++ core/src/lib.rs | 1 + examples/component/src/main.rs | 4 +- widget/src/button.rs | 102 ++++++++++++++++++++--------------------- widget/src/helpers.rs | 2 +- 5 files changed, 88 insertions(+), 54 deletions(-) create mode 100644 core/src/closure.rs diff --git a/core/src/closure.rs b/core/src/closure.rs new file mode 100644 index 00000000..dc7b4fee --- /dev/null +++ b/core/src/closure.rs @@ -0,0 +1,33 @@ +//! Box closures with ease. +//! +//! These are just a bunch of types that wrap boxed closures with +//! blanket [`From`] implementations for easy conversions. +//! +//! Mainly, it allows functions to take `Into` where `T` may end +//! up being a boxed closure. + +/// A boxed closure that takes `A` by reference and produces `O`. +#[allow(missing_debug_implementations)] +pub struct Unary<'a, A, O>(pub Box O + 'a>); + +impl<'a, A, O, T> From for Unary<'a, A, O> +where + T: Fn(&A) -> O + 'a, +{ + fn from(f: T) -> Self { + Self(Box::new(f)) + } +} + +/// A boxed closure that takes `A` by reference and `B` by value and produces `O`. +#[allow(missing_debug_implementations)] +pub struct Binary<'a, A, B, O>(pub Box O + 'a>); + +impl<'a, A, B, O, T> From for Binary<'a, A, B, O> +where + T: Fn(&A, B) -> O + 'a, +{ + fn from(f: T) -> Self { + Self(Box::new(f)) + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index d076413e..36b47d80 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -19,6 +19,7 @@ pub mod alignment; pub mod border; pub mod clipboard; +pub mod closure; pub mod event; pub mod font; pub mod gradient; diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs index 43ba3187..d5626087 100644 --- a/examples/component/src/main.rs +++ b/examples/component/src/main.rs @@ -74,7 +74,7 @@ mod numeric_input { impl Component for NumericInput where Theme: text::DefaultStyle - + button::DefaultStyle + + button::Catalog + text_input::DefaultStyle + 'static, { @@ -152,7 +152,7 @@ mod numeric_input { for Element<'a, Message, Theme> where Theme: text::DefaultStyle - + button::DefaultStyle + + button::Catalog + text_input::DefaultStyle + 'static, Message: 'a, diff --git a/widget/src/button.rs b/widget/src/button.rs index 5790f811..8754622f 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -1,4 +1,5 @@ //! Allow your users to perform actions by pressing a button. +use crate::core::closure; use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -49,6 +50,7 @@ use crate::core::{ pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> where Renderer: crate::core::Renderer, + Theme: Catalog, { content: Element<'a, Message, Theme, Renderer>, on_press: Option, @@ -56,20 +58,18 @@ where height: Length, padding: Padding, clip: bool, - style: Style<'a, Theme>, + style: Theme::Item<'a>, } impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, + Theme: Catalog, { /// Creates a new [`Button`] with the given content. pub fn new( content: impl Into>, - ) -> Self - where - Theme: DefaultStyle + 'a, - { + ) -> Self { let content = content.into(); let size = content.as_widget().size_hint(); @@ -80,7 +80,7 @@ where height: size.height.fluid(), padding: DEFAULT_PADDING, clip: false, - style: Box::new(Theme::default_style), + style: Theme::default(), } } @@ -120,11 +120,8 @@ where } /// Sets the style variant of this [`Button`]. - pub fn style( - mut self, - style: impl Fn(&Theme, Status) -> Appearance + 'a, - ) -> Self { - self.style = Box::new(style); + pub fn style(mut self, style: impl Into>) -> Self { + self.style = style.into(); self } @@ -146,6 +143,7 @@ impl<'a, Message, Theme, Renderer> Widget where Message: 'a + Clone, Renderer: 'a + crate::core::Renderer, + Theme: Catalog, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -304,7 +302,7 @@ where Status::Active }; - let styling = (self.style)(theme, status); + let styling = theme.style(&self.style, status); if styling.background.is_some() || styling.border.width > 0.0 @@ -378,7 +376,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: Clone + 'a, - Theme: 'a, + Theme: Catalog + 'a, Renderer: crate::core::Renderer + 'a, { fn from(button: Button<'a, Message, Theme, Renderer>) -> Self { @@ -409,7 +407,7 @@ pub enum Status { /// The appearance of a button. #[derive(Debug, Clone, Copy, PartialEq)] -pub struct Appearance { +pub struct Style { /// The [`Background`] of the button. pub background: Option, /// The text [`Color`] of the button. @@ -420,7 +418,7 @@ pub struct Appearance { pub shadow: Shadow, } -impl Appearance { +impl Style { /// Updates the [`Appearance`] with the given [`Background`]. pub fn with_background(self, background: impl Into) -> Self { Self { @@ -430,7 +428,7 @@ impl Appearance { } } -impl std::default::Default for Appearance { +impl std::default::Default for Style { fn default() -> Self { Self { background: None, @@ -441,41 +439,43 @@ impl std::default::Default for Appearance { } } -/// The style of a [`Button`]. -pub type Style<'a, Theme> = Box Appearance + 'a>; +/// The theme catalog of a [`Button`]. +pub trait Catalog: Sized { + /// The item type of this [`Catalog`]. + type Item<'a>; -/// The default style of a [`Button`]. -pub trait DefaultStyle { - /// Returns the default style of a [`Button`]. - fn default_style(&self, status: Status) -> Appearance; -} + /// The default item produced by this [`Catalog`]. + fn default<'a>() -> Self::Item<'a>; -impl DefaultStyle for Theme { - fn default_style(&self, status: Status) -> Appearance { - primary(self, status) - } + /// The [`Style`] of an item with the given status. + fn style(&self, item: &Self::Item<'_>, status: Status) -> Style; } -impl DefaultStyle for Appearance { - fn default_style(&self, _status: Status) -> Appearance { - *self +/// The item of a button [`Catalog`] for the built-in [`Theme`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type Item<'a, Theme> = closure::Binary<'a, Theme, Status, Style>; + +impl Catalog for Theme { + type Item<'a> = Item<'a, Self>; + + fn default<'a>() -> Self::Item<'a> { + closure::Binary::from(primary) } -} -impl DefaultStyle for Color { - fn default_style(&self, _status: Status) -> Appearance { - Appearance::default().with_background(*self) + fn style(&self, item: &Self::Item<'_>, status: Status) -> Style { + (item.0)(self, status) } } /// A primary button; denoting a main action. -pub fn primary(theme: &Theme, status: Status) -> Appearance { +pub fn primary(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); let base = styled(palette.primary.strong); match status { Status::Active | Status::Pressed => base, - Status::Hovered => Appearance { + Status::Hovered => Style { background: Some(Background::Color(palette.primary.base.color)), ..base }, @@ -484,13 +484,13 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance { } /// A secondary button; denoting a complementary action. -pub fn secondary(theme: &Theme, status: Status) -> Appearance { +pub fn secondary(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); let base = styled(palette.secondary.base); match status { Status::Active | Status::Pressed => base, - Status::Hovered => Appearance { + Status::Hovered => Style { background: Some(Background::Color(palette.secondary.strong.color)), ..base }, @@ -499,13 +499,13 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance { } /// A success button; denoting a good outcome. -pub fn success(theme: &Theme, status: Status) -> Appearance { +pub fn success(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); let base = styled(palette.success.base); match status { Status::Active | Status::Pressed => base, - Status::Hovered => Appearance { + Status::Hovered => Style { background: Some(Background::Color(palette.success.strong.color)), ..base }, @@ -514,13 +514,13 @@ pub fn success(theme: &Theme, status: Status) -> Appearance { } /// A danger button; denoting a destructive action. -pub fn danger(theme: &Theme, status: Status) -> Appearance { +pub fn danger(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); let base = styled(palette.danger.base); match status { Status::Active | Status::Pressed => base, - Status::Hovered => Appearance { + Status::Hovered => Style { background: Some(Background::Color(palette.danger.strong.color)), ..base }, @@ -529,17 +529,17 @@ pub fn danger(theme: &Theme, status: Status) -> Appearance { } /// A text button; useful for links. -pub fn text(theme: &Theme, status: Status) -> Appearance { +pub fn text(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); - let base = Appearance { + let base = Style { text_color: palette.background.base.text, - ..Appearance::default() + ..Style::default() }; match status { Status::Active | Status::Pressed => base, - Status::Hovered => Appearance { + Status::Hovered => Style { text_color: palette.background.base.text.scale_alpha(0.8), ..base }, @@ -547,17 +547,17 @@ pub fn text(theme: &Theme, status: Status) -> Appearance { } } -fn styled(pair: palette::Pair) -> Appearance { - Appearance { +fn styled(pair: palette::Pair) -> Style { + Style { background: Some(Background::Color(pair.color)), text_color: pair.text, border: Border::rounded(2), - ..Appearance::default() + ..Style::default() } } -fn disabled(appearance: Appearance) -> Appearance { - Appearance { +fn disabled(appearance: Style) -> Style { + Style { background: appearance .background .map(|background| background.scale_alpha(0.5)), diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 4863e550..e60096d1 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -117,7 +117,7 @@ pub fn button<'a, Message, Theme, Renderer>( content: impl Into>, ) -> Button<'a, Message, Theme, Renderer> where - Theme: button::DefaultStyle + 'a, + Theme: button::Catalog + 'a, Renderer: core::Renderer, { Button::new(content) -- cgit From e657dc2ecd2ffa72c0abd87f9a59dcc1acbc7083 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 24 Mar 2024 02:08:20 +0100 Subject: Fine-tune `Catalog` approach for `button`, `checkbox`, and `svg` --- Cargo.toml | 2 +- core/src/closure.rs | 33 ---------------- core/src/lib.rs | 1 - examples/svg/src/main.rs | 2 +- widget/Cargo.toml | 1 + widget/src/button.rs | 85 +++++++++++++++++++++++------------------ widget/src/checkbox.rs | 99 ++++++++++++++++++++++++++++-------------------- widget/src/helpers.rs | 4 +- widget/src/svg.rs | 89 +++++++++++++++++++++++++++---------------- 9 files changed, 167 insertions(+), 149 deletions(-) delete mode 100644 core/src/closure.rs diff --git a/Cargo.toml b/Cargo.toml index b82c0f67..fdd503c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ highlighter = ["iced_highlighter"] # Enables experimental multi-window support. multi-window = ["iced_winit/multi-window"] # Enables the advanced module -advanced = [] +advanced = ["iced_widget/advanced"] # Enables embedding Fira Sans as the default font on Wasm builds fira-sans = ["iced_renderer/fira-sans"] # Enables auto-detecting light/dark mode for the built-in theme diff --git a/core/src/closure.rs b/core/src/closure.rs deleted file mode 100644 index dc7b4fee..00000000 --- a/core/src/closure.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Box closures with ease. -//! -//! These are just a bunch of types that wrap boxed closures with -//! blanket [`From`] implementations for easy conversions. -//! -//! Mainly, it allows functions to take `Into` where `T` may end -//! up being a boxed closure. - -/// A boxed closure that takes `A` by reference and produces `O`. -#[allow(missing_debug_implementations)] -pub struct Unary<'a, A, O>(pub Box O + 'a>); - -impl<'a, A, O, T> From for Unary<'a, A, O> -where - T: Fn(&A) -> O + 'a, -{ - fn from(f: T) -> Self { - Self(Box::new(f)) - } -} - -/// A boxed closure that takes `A` by reference and `B` by value and produces `O`. -#[allow(missing_debug_implementations)] -pub struct Binary<'a, A, B, O>(pub Box O + 'a>); - -impl<'a, A, B, O, T> From for Binary<'a, A, B, O> -where - T: Fn(&A, B) -> O + 'a, -{ - fn from(f: T) -> Self { - Self(Box::new(f)) - } -} diff --git a/core/src/lib.rs b/core/src/lib.rs index 36b47d80..d076413e 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -19,7 +19,6 @@ pub mod alignment; pub mod border; pub mod clipboard; -pub mod closure; pub mod event; pub mod font; pub mod gradient; diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index cc686dca..0dcf9bc1 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -31,7 +31,7 @@ impl Tiger { )); let svg = svg(handle).width(Length::Fill).height(Length::Fill).style( - |_theme, _status| svg::Appearance { + |_theme, _status| svg::Style { color: if self.apply_color_filter { Some(color!(0x0000ff)) } else { diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 3c9ffddb..a45f47ef 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -21,6 +21,7 @@ svg = ["iced_renderer/svg"] canvas = ["iced_renderer/geometry"] qr_code = ["canvas", "qrcode"] wgpu = ["iced_renderer/wgpu"] +advanced = [] [dependencies] iced_renderer.workspace = true diff --git a/widget/src/button.rs b/widget/src/button.rs index 8754622f..80a18e82 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -1,5 +1,4 @@ //! Allow your users to perform actions by pressing a button. -use crate::core::closure; use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -58,7 +57,7 @@ where height: Length, padding: Padding, clip: bool, - style: Theme::Item<'a>, + class: Theme::Class<'a>, } impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> @@ -80,7 +79,7 @@ where height: size.height.fluid(), padding: DEFAULT_PADDING, clip: false, - style: Theme::default(), + class: Theme::default(), } } @@ -119,18 +118,30 @@ where self } - /// Sets the style variant of this [`Button`]. - pub fn style(mut self, style: impl Into>) -> Self { - self.style = style.into(); - self - } - /// Sets whether the contents of the [`Button`] should be clipped on /// overflow. pub fn clip(mut self, clip: bool) -> Self { self.clip = clip; self } + + /// Sets the style of this [`Button`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of this [`Button`]. + #[cfg(feature = "advanced")] + #[must_use] + pub fn class(mut self, class: impl Into>) -> Self { + self.class = class.into(); + self + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -302,19 +313,19 @@ where Status::Active }; - let styling = theme.style(&self.style, status); + let style = theme.style(&self.class, status); - if styling.background.is_some() - || styling.border.width > 0.0 - || styling.shadow.color.a > 0.0 + if style.background.is_some() + || style.border.width > 0.0 + || style.shadow.color.a > 0.0 { renderer.fill_quad( renderer::Quad { bounds, - border: styling.border, - shadow: styling.shadow, + border: style.border, + shadow: style.shadow, }, - styling + style .background .unwrap_or(Background::Color(Color::TRANSPARENT)), ); @@ -331,7 +342,7 @@ where renderer, theme, &renderer::Style { - text_color: styling.text_color, + text_color: style.text_color, }, content_layout, cursor, @@ -405,7 +416,7 @@ pub enum Status { Disabled, } -/// The appearance of a button. +/// The style of a button. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { /// The [`Background`] of the button. @@ -419,7 +430,7 @@ pub struct Style { } impl Style { - /// Updates the [`Appearance`] with the given [`Background`]. + /// Updates the [`Style`] with the given [`Background`]. pub fn with_background(self, background: impl Into) -> Self { Self { background: Some(background.into()), @@ -428,7 +439,7 @@ impl Style { } } -impl std::default::Default for Style { +impl Default for Style { fn default() -> Self { Self { background: None, @@ -441,30 +452,30 @@ impl std::default::Default for Style { /// The theme catalog of a [`Button`]. pub trait Catalog: Sized { - /// The item type of this [`Catalog`]. - type Item<'a>; + /// The item class of this [`Catalog`]. + type Class<'a>; - /// The default item produced by this [`Catalog`]. - fn default<'a>() -> Self::Item<'a>; + /// The default class produced by this [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; - /// The [`Style`] of an item with the given status. - fn style(&self, item: &Self::Item<'_>, status: Status) -> Style; + /// The [`Style`] of a class with the given status. + fn style(&self, item: &Self::Class<'_>, status: Status) -> Style; } -/// The item of a button [`Catalog`] for the built-in [`Theme`]. +/// A styling function for a [`Button`]. /// /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. -pub type Item<'a, Theme> = closure::Binary<'a, Theme, Status, Style>; +pub type StyleFn<'a, Theme> = Box Style + 'a>; impl Catalog for Theme { - type Item<'a> = Item<'a, Self>; + type Class<'a> = StyleFn<'a, Self>; - fn default<'a>() -> Self::Item<'a> { - closure::Binary::from(primary) + fn default<'a>() -> Self::Class<'a> { + Box::new(primary) } - fn style(&self, item: &Self::Item<'_>, status: Status) -> Style { - (item.0)(self, status) + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { + class(self, status) } } @@ -556,12 +567,12 @@ fn styled(pair: palette::Pair) -> Style { } } -fn disabled(appearance: Style) -> Style { +fn disabled(style: Style) -> Style { Style { - background: appearance + background: style .background .map(|background| background.scale_alpha(0.5)), - text_color: appearance.text_color.scale_alpha(0.5), - ..appearance + text_color: style.text_color.scale_alpha(0.5), + ..style } } diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 15fb8f58..a90914b8 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -39,6 +39,7 @@ pub struct Checkbox< Renderer = crate::Renderer, > where Renderer: text::Renderer, + Theme: Catalog, { is_checked: bool, on_toggle: Option Message + 'a>>, @@ -51,12 +52,13 @@ pub struct Checkbox< text_shaping: text::Shaping, font: Option, icon: Icon, - style: Style<'a, Theme>, + class: Theme::Class<'a>, } impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer> where Renderer: text::Renderer, + Theme: Catalog, { /// The default size of a [`Checkbox`]. const DEFAULT_SIZE: f32 = 16.0; @@ -69,10 +71,7 @@ where /// It expects: /// * the label of the [`Checkbox`] /// * a boolean describing whether the [`Checkbox`] is checked or not - pub fn new(label: impl Into, is_checked: bool) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn new(label: impl Into, is_checked: bool) -> Self { Checkbox { is_checked, on_toggle: None, @@ -91,7 +90,7 @@ where line_height: text::LineHeight::default(), shaping: text::Shaping::Basic, }, - style: Box::new(Theme::default_style), + class: Theme::default(), } } @@ -173,12 +172,21 @@ where self } - /// Sets the style of the [`Checkbox`]. - pub fn style( - mut self, - style: impl Fn(&Theme, Status) -> Appearance + 'a, - ) -> Self { - self.style = Box::new(style); + /// Sets the style of this [`Checkbox`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of this [`Checkbox`]. + #[cfg(feature = "advanced")] + #[must_use] + pub fn class(mut self, class: impl Into>) -> Self { + self.class = class.into(); self } } @@ -187,6 +195,7 @@ impl<'a, Message, Theme, Renderer> Widget for Checkbox<'a, Message, Theme, Renderer> where Renderer: text::Renderer, + Theme: Catalog, { fn tag(&self) -> tree::Tag { tree::Tag::of::>() @@ -285,7 +294,7 @@ where tree: &Tree, renderer: &mut Renderer, theme: &Theme, - style: &renderer::Style, + defaults: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, @@ -304,7 +313,7 @@ where Status::Active { is_checked } }; - let appearance = (self.style)(theme, status); + let style = theme.style(&self.class, status); { let layout = children.next().unwrap(); @@ -313,10 +322,10 @@ where renderer.fill_quad( renderer::Quad { bounds, - border: appearance.border, + border: style.border, ..renderer::Quad::default() }, - appearance.background, + style.background, ); let Icon { @@ -341,7 +350,7 @@ where shaping: *shaping, }, bounds.center(), - appearance.icon_color, + style.icon_color, *viewport, ); } @@ -352,11 +361,11 @@ where crate::text::draw( renderer, - style, + defaults, label_layout, tree.state.downcast_ref(), crate::text::Appearance { - color: appearance.text_color, + color: style.text_color, }, viewport, ); @@ -368,7 +377,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: 'a, + Theme: 'a + Catalog, Renderer: 'a + text::Renderer, { fn from( @@ -413,9 +422,9 @@ pub enum Status { }, } -/// The appearance of a checkbox. +/// The style of a checkbox. #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style { /// The [`Background`] of the checkbox. pub background: Background, /// The icon [`Color`] of the checkbox. @@ -426,29 +435,37 @@ pub struct Appearance { pub text_color: Option, } -/// The style of a [`Checkbox`]. -pub type Style<'a, Theme> = Box Appearance + 'a>; +/// The theme catalog of a [`Checkbox`]. +pub trait Catalog: Sized { + /// The item class of this [`Catalog`]. + type Class<'a>; -/// The default style of a [`Checkbox`]. -pub trait DefaultStyle { - /// Returns the default style of a [`Checkbox`]. - fn default_style(&self, status: Status) -> Appearance; + /// The default class produced by this [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; + + /// The [`Style`] of a class with the given status. + fn style(&self, item: &Self::Class<'_>, status: Status) -> Style; } -impl DefaultStyle for Theme { - fn default_style(&self, status: Status) -> Appearance { - primary(self, status) +/// A styling function for a [`Checkbox`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box Style + 'a>; + +impl Catalog for Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(primary) } -} -impl DefaultStyle for Appearance { - fn default_style(&self, _status: Status) -> Appearance { - *self + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { + class(self, status) } } /// A primary checkbox; denoting a main toggle. -pub fn primary(theme: &Theme, status: Status) -> Appearance { +pub fn primary(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -474,7 +491,7 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance { } /// A secondary checkbox; denoting a complementary toggle. -pub fn secondary(theme: &Theme, status: Status) -> Appearance { +pub fn secondary(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -500,7 +517,7 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance { } /// A success checkbox; denoting a positive toggle. -pub fn success(theme: &Theme, status: Status) -> Appearance { +pub fn success(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -526,7 +543,7 @@ pub fn success(theme: &Theme, status: Status) -> Appearance { } /// A danger checkbox; denoting a negaive toggle. -pub fn danger(theme: &Theme, status: Status) -> Appearance { +pub fn danger(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -556,8 +573,8 @@ fn styled( base: palette::Pair, accent: palette::Pair, is_checked: bool, -) -> Appearance { - Appearance { +) -> Style { + Style { background: Background::Color(if is_checked { accent.color } else { diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index e60096d1..4c4d1012 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -161,7 +161,7 @@ pub fn checkbox<'a, Message, Theme, Renderer>( is_checked: bool, ) -> Checkbox<'a, Message, Theme, Renderer> where - Theme: checkbox::DefaultStyle + 'a, + Theme: checkbox::Catalog + 'a, Renderer: core::text::Renderer, { Checkbox::new(label, is_checked) @@ -367,7 +367,7 @@ pub fn svg<'a, Theme>( handle: impl Into, ) -> crate::Svg<'a, Theme> where - Theme: crate::svg::DefaultStyle + 'a, + Theme: crate::svg::Catalog, { crate::Svg::new(handle) } diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 1ac07ade..d2fd0aee 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -20,36 +20,36 @@ pub use crate::core::svg::Handle; /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. #[allow(missing_debug_implementations)] -pub struct Svg<'a, Theme = crate::Theme> { +pub struct Svg<'a, Theme = crate::Theme> +where + Theme: Catalog, +{ handle: Handle, width: Length, height: Length, content_fit: ContentFit, - style: Style<'a, Theme>, + class: Theme::Class<'a>, } -impl<'a, Theme> Svg<'a, Theme> { +impl<'a, Theme> Svg<'a, Theme> +where + Theme: Catalog, +{ /// Creates a new [`Svg`] from the given [`Handle`]. - pub fn new(handle: impl Into) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn new(handle: impl Into) -> Self { Svg { handle: handle.into(), width: Length::Fill, height: Length::Shrink, content_fit: ContentFit::Contain, - style: Box::new(Theme::default_style), + class: Theme::default(), } } /// Creates a new [`Svg`] that will display the contents of the file at the /// provided path. #[must_use] - pub fn from_path(path: impl Into) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn from_path(path: impl Into) -> Self { Self::new(Handle::from_path(path)) } @@ -78,13 +78,21 @@ impl<'a, Theme> Svg<'a, Theme> { } } - /// Sets the style variant of this [`Svg`]. + /// Sets the style of this [`Svg`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of this [`Svg`]. + #[cfg(feature = "advanced")] #[must_use] - pub fn style( - mut self, - style: impl Fn(&Theme, Status) -> Appearance + 'a, - ) -> Self { - self.style = Box::new(style); + pub fn class(mut self, class: impl Into>) -> Self { + self.class = class.into(); self } } @@ -93,6 +101,7 @@ impl<'a, Message, Theme, Renderer> Widget for Svg<'a, Theme> where Renderer: svg::Renderer, + Theme: Catalog, { fn size(&self) -> Size { Size { @@ -167,7 +176,7 @@ where Status::Idle }; - let appearance = (self.style)(theme, status); + let appearance = theme.style(&self.class, status); renderer.draw( self.handle.clone(), @@ -189,7 +198,7 @@ where impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where - Theme: 'a, + Theme: Catalog + 'a, Renderer: svg::Renderer + 'a, { fn from(icon: Svg<'a, Theme>) -> Element<'a, Message, Theme, Renderer> { @@ -208,7 +217,7 @@ pub enum Status { /// The appearance of an [`Svg`]. #[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct Appearance { +pub struct Style { /// The [`Color`] filter of an [`Svg`]. /// /// Useful for coloring a symbolic icon. @@ -217,23 +226,37 @@ pub struct Appearance { pub color: Option, } -/// The style of an [`Svg`]. -pub type Style<'a, Theme> = Box Appearance + 'a>; +/// The theme catalog of an [`Svg`]. +pub trait Catalog { + /// The item class of this [`Catalog`]. + type Class<'a>; + + /// The default class produced by this [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; -/// The default style of an [`Svg`]. -pub trait DefaultStyle { - /// Returns the default style of an [`Svg`]. - fn default_style(&self, status: Status) -> Appearance; + /// The [`Style`] of a class with the given status. + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style; } -impl DefaultStyle for Theme { - fn default_style(&self, _status: Status) -> Appearance { - Appearance::default() +impl Catalog for Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(|_theme, _status| Style::default()) + } + + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { + class(self, status) } } -impl DefaultStyle for Appearance { - fn default_style(&self, _status: Status) -> Appearance { - *self +/// A styling function for an [`Svg`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box Style + 'a>; + +impl<'a, Theme> From - - diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index c2833210..e1c7d62f 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -18,305 +18,342 @@ use iced_winit::winit; use iced_winit::Clipboard; use winit::{ - event::{Event, WindowEvent}, + event::WindowEvent, event_loop::{ControlFlow, EventLoop}, keyboard::ModifiersState, }; use std::sync::Arc; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::JsCast; -#[cfg(target_arch = "wasm32")] -use web_sys::HtmlCanvasElement; -#[cfg(target_arch = "wasm32")] -use winit::platform::web::WindowBuilderExtWebSys; - -pub fn main() -> Result<(), Box> { - #[cfg(target_arch = "wasm32")] - let canvas_element = { - console_log::init().expect("Initialize logger"); - - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| doc.get_element_by_id("iced_canvas")) - .and_then(|element| element.dyn_into::().ok()) - .expect("Get canvas element") - }; - - #[cfg(not(target_arch = "wasm32"))] +pub fn main() -> Result<(), winit::error::EventLoopError> { tracing_subscriber::fmt::init(); // Initialize winit let event_loop = EventLoop::new()?; - #[cfg(target_arch = "wasm32")] - let window = winit::window::WindowBuilder::new() - .with_canvas(Some(canvas_element)) - .build(&event_loop)?; - - #[cfg(not(target_arch = "wasm32"))] - let window = winit::window::Window::new(&event_loop)?; - - let window = Arc::new(window); - - let physical_size = window.inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - window.scale_factor(), - ); - let mut cursor_position = None; - let mut modifiers = ModifiersState::default(); - let mut clipboard = Clipboard::connect(&window); - - // Initialize wgpu - #[cfg(target_arch = "wasm32")] - let default_backend = wgpu::Backends::GL; - #[cfg(not(target_arch = "wasm32"))] - let default_backend = wgpu::Backends::PRIMARY; - - let backend = - wgpu::util::backend_bits_from_env().unwrap_or(default_backend); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: backend, - ..Default::default() - }); - let surface = instance.create_surface(window.clone())?; - - let (format, adapter, device, queue) = - futures::futures::executor::block_on(async { - let adapter = wgpu::util::initialize_adapter_from_env_or_default( - &instance, - Some(&surface), - ) - .await - .expect("Create adapter"); - - let adapter_features = adapter.features(); - - #[cfg(target_arch = "wasm32")] - let needed_limits = wgpu::Limits::downlevel_webgl2_defaults() - .using_resolution(adapter.limits()); - - #[cfg(not(target_arch = "wasm32"))] - let needed_limits = wgpu::Limits::default(); - - let capabilities = surface.get_capabilities(&adapter); - - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - required_features: adapter_features - & wgpu::Features::default(), - required_limits: needed_limits, + #[allow(clippy::large_enum_variant)] + enum Runner { + Loading, + Ready { + window: Arc, + device: wgpu::Device, + queue: wgpu::Queue, + surface: wgpu::Surface<'static>, + format: wgpu::TextureFormat, + engine: Engine, + renderer: Renderer, + scene: Scene, + state: program::State, + cursor_position: Option>, + clipboard: Clipboard, + viewport: Viewport, + modifiers: ModifiersState, + resized: bool, + debug: Debug, + }, + } + + impl winit::application::ApplicationHandler for Runner { + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + if let Self::Loading = self { + let window = Arc::new( + event_loop + .create_window( + winit::window::WindowAttributes::default(), + ) + .expect("Create window"), + ); + + let physical_size = window.inner_size(); + let viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor(), + ); + let clipboard = Clipboard::connect(&window); + + let backend = + wgpu::util::backend_bits_from_env().unwrap_or_default(); + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: backend, + ..Default::default() + }); + let surface = instance + .create_surface(window.clone()) + .expect("Create window surface"); + + let (format, adapter, device, queue) = + futures::futures::executor::block_on(async { + let adapter = + wgpu::util::initialize_adapter_from_env_or_default( + &instance, + Some(&surface), + ) + .await + .expect("Create adapter"); + + let adapter_features = adapter.features(); + + let capabilities = surface.get_capabilities(&adapter); + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: adapter_features + & wgpu::Features::default(), + required_limits: wgpu::Limits::default(), + }, + None, + ) + .await + .expect("Request device"); + + ( + capabilities + .formats + .iter() + .copied() + .find(wgpu::TextureFormat::is_srgb) + .or_else(|| { + capabilities.formats.first().copied() + }) + .expect("Get preferred format"), + adapter, + device, + queue, + ) + }); + + surface.configure( + &device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width: physical_size.width, + height: physical_size.height, + present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], + desired_maximum_frame_latency: 2, }, - None, - ) - .await - .expect("Request device"); - - ( - capabilities - .formats - .iter() - .copied() - .find(wgpu::TextureFormat::is_srgb) - .or_else(|| capabilities.formats.first().copied()) - .expect("Get preferred format"), - adapter, + ); + + // Initialize scene and GUI controls + let scene = Scene::new(&device, format); + let controls = Controls::new(); + + // Initialize iced + let mut debug = Debug::new(); + let engine = + Engine::new(&adapter, &device, &queue, format, None); + let mut renderer = Renderer::new( + &device, + &engine, + Font::default(), + Pixels::from(16), + ); + + let state = program::State::new( + controls, + viewport.logical_size(), + &mut renderer, + &mut debug, + ); + + // You should change this if you want to render continuously + event_loop.set_control_flow(ControlFlow::Wait); + + *self = Self::Ready { + window, + device, + queue, + surface, + format, + engine, + renderer, + scene, + state, + cursor_position: None, + modifiers: ModifiersState::default(), + clipboard, + viewport, + resized: false, + debug, + }; + } + } + + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + _window_id: winit::window::WindowId, + event: WindowEvent, + ) { + let Self::Ready { + window, device, queue, - ) - }); - - surface.configure( - &device, - &wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format, - width: physical_size.width, - height: physical_size.height, - present_mode: wgpu::PresentMode::AutoVsync, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![], - desired_maximum_frame_latency: 2, - }, - ); - - let mut resized = false; - - // Initialize scene and GUI controls - let scene = Scene::new(&device, format); - let controls = Controls::new(); - - // Initialize iced - let mut debug = Debug::new(); - let mut engine = Engine::new(&adapter, &device, &queue, format, None); - let mut renderer = - Renderer::new(&device, &engine, Font::default(), Pixels::from(16)); - - let mut state = program::State::new( - controls, - viewport.logical_size(), - &mut renderer, - &mut debug, - ); - - // Run event loop - event_loop.run(move |event, window_target| { - // You should change this if you want to render continuously - window_target.set_control_flow(ControlFlow::Wait); - - match event { - Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - } => { - if resized { - let size = window.inner_size(); - - viewport = Viewport::with_physical_size( - Size::new(size.width, size.height), - window.scale_factor(), - ); - - surface.configure( - &device, - &wgpu::SurfaceConfiguration { - format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::AutoVsync, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![], - desired_maximum_frame_latency: 2, - }, - ); - - resized = false; - } - - match surface.get_current_texture() { - Ok(frame) => { - let mut encoder = device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { label: None }, + surface, + format, + engine, + renderer, + scene, + state, + viewport, + cursor_position, + modifiers, + clipboard, + resized, + debug, + } = self + else { + return; + }; + + match event { + WindowEvent::RedrawRequested => { + if *resized { + let size = window.inner_size(); + + *viewport = Viewport::with_physical_size( + Size::new(size.width, size.height), + window.scale_factor(), ); - let program = state.program(); - - let view = frame.texture.create_view( - &wgpu::TextureViewDescriptor::default(), + surface.configure( + device, + &wgpu::SurfaceConfiguration { + format: *format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], + desired_maximum_frame_latency: 2, + }, ); - { - // We clear the frame - let mut render_pass = Scene::clear( - &view, - &mut encoder, - program.background_color(), + *resized = false; + } + + match surface.get_current_texture() { + Ok(frame) => { + let mut encoder = device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { label: None }, ); - // Draw the scene - scene.draw(&mut render_pass); - } + let program = state.program(); - // And then iced on top - renderer.present( - &mut engine, - &device, - &queue, - &mut encoder, - None, - frame.texture.format(), - &view, - &viewport, - &debug.overlay(), - ); + let view = frame.texture.create_view( + &wgpu::TextureViewDescriptor::default(), + ); + + { + // We clear the frame + let mut render_pass = Scene::clear( + &view, + &mut encoder, + program.background_color(), + ); + + // Draw the scene + scene.draw(&mut render_pass); + } + + // And then iced on top + renderer.present( + engine, + device, + queue, + &mut encoder, + None, + frame.texture.format(), + &view, + viewport, + &debug.overlay(), + ); - // Then we submit the work - queue.submit(Some(encoder.finish())); - frame.present(); + // Then we submit the work + engine.submit(queue, encoder); + frame.present(); - // Update the mouse cursor - window.set_cursor_icon( - iced_winit::conversion::mouse_interaction( - state.mouse_interaction(), - ), - ); - } - Err(error) => match error { - wgpu::SurfaceError::OutOfMemory => { - panic!( - "Swapchain error: {error}. \ - Rendering cannot continue." - ) - } - _ => { - // Try rendering again next frame. - window.request_redraw(); + // Update the mouse cursor + window.set_cursor( + iced_winit::conversion::mouse_interaction( + state.mouse_interaction(), + ), + ); } - }, - } - } - Event::WindowEvent { event, .. } => { - match event { - WindowEvent::CursorMoved { position, .. } => { - cursor_position = Some(position); - } - WindowEvent::ModifiersChanged(new_modifiers) => { - modifiers = new_modifiers.state(); - } - WindowEvent::Resized(_) => { - resized = true; - } - WindowEvent::CloseRequested => { - window_target.exit(); + Err(error) => match error { + wgpu::SurfaceError::OutOfMemory => { + panic!( + "Swapchain error: {error}. \ + Rendering cannot continue." + ) + } + _ => { + // Try rendering again next frame. + window.request_redraw(); + } + }, } - _ => {} } - - // Map window event to iced event - if let Some(event) = iced_winit::conversion::window_event( - window::Id::MAIN, - event, - window.scale_factor(), - modifiers, - ) { - state.queue_event(event); + WindowEvent::CursorMoved { position, .. } => { + *cursor_position = Some(position); + } + WindowEvent::ModifiersChanged(new_modifiers) => { + *modifiers = new_modifiers.state(); } + WindowEvent::Resized(_) => { + *resized = true; + } + WindowEvent::CloseRequested => { + event_loop.exit(); + } + _ => {} } - _ => {} - } - // If there are events pending - if !state.is_queue_empty() { - // We update iced - let _ = state.update( - viewport.logical_size(), - cursor_position - .map(|p| { - conversion::cursor_position(p, viewport.scale_factor()) - }) - .map(mouse::Cursor::Available) - .unwrap_or(mouse::Cursor::Unavailable), - &mut renderer, - &Theme::Dark, - &renderer::Style { - text_color: Color::WHITE, - }, - &mut clipboard, - &mut debug, - ); - - // and request a redraw - window.request_redraw(); + // Map window event to iced event + if let Some(event) = iced_winit::conversion::window_event( + window::Id::MAIN, + event, + window.scale_factor(), + *modifiers, + ) { + state.queue_event(event); + } + + // If there are events pending + if !state.is_queue_empty() { + // We update iced + let _ = state.update( + viewport.logical_size(), + cursor_position + .map(|p| { + conversion::cursor_position( + p, + viewport.scale_factor(), + ) + }) + .map(mouse::Cursor::Available) + .unwrap_or(mouse::Cursor::Unavailable), + renderer, + &Theme::Dark, + &renderer::Style { + text_color: Color::WHITE, + }, + clipboard, + debug, + ); + + // and request a redraw + window.request_redraw(); + } } - })?; + } - Ok(()) + let mut runner = Runner::Loading; + event_loop.run_app(&mut runner) } diff --git a/src/application.rs b/src/application.rs index 9197834b..d12ba73d 100644 --- a/src/application.rs +++ b/src/application.rs @@ -212,26 +212,11 @@ where ..crate::graphics::Settings::default() }; - let run = crate::shell::application::run::< + Ok(crate::shell::application::run::< Instance, Self::Executor, ::Compositor, - >(settings.into(), renderer_settings); - - #[cfg(target_arch = "wasm32")] - { - use crate::futures::FutureExt; - use iced_futures::backend::wasm::wasm_bindgen::Executor; - - Executor::new() - .expect("Create Wasm executor") - .spawn(run.map(|_| ())); - - Ok(()) - } - - #[cfg(not(target_arch = "wasm32"))] - Ok(crate::futures::executor::block_on(run)?) + >(settings.into(), renderer_settings)?) } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index ad8ac591..1fbdbe9a 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -67,8 +67,6 @@ use crate::core::{ use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::Viewport; -use std::cell::RefCell; - /// A [`wgpu`] graphics renderer for [`iced`]. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs @@ -84,7 +82,7 @@ pub struct Renderer { // TODO: Centralize all the image feature handling #[cfg(any(feature = "svg", feature = "image"))] - image_cache: RefCell, + image_cache: std::cell::RefCell, } impl Renderer { @@ -103,7 +101,9 @@ impl Renderer { text_storage: text::Storage::new(), #[cfg(any(feature = "svg", feature = "image"))] - image_cache: RefCell::new(_engine.create_image_cache(_device)), + image_cache: std::cell::RefCell::new( + _engine.create_image_cache(_device), + ), } } diff --git a/winit/Cargo.toml b/winit/Cargo.toml index dccb7c07..6d3dddde 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -25,6 +25,7 @@ wayland-csd-adwaita = ["winit/wayland-csd-adwaita"] multi-window = ["iced_runtime/multi-window"] [dependencies] +iced_futures.workspace = true iced_graphics.workspace = true iced_runtime.workspace = true @@ -32,6 +33,7 @@ log.workspace = true rustc-hash.workspace = true thiserror.workspace = true tracing.workspace = true +wasm-bindgen-futures.workspace = true window_clipboard.workspace = true winit.workspace = true diff --git a/winit/src/application.rs b/winit/src/application.rs index a447c9da..e056f4c5 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -22,7 +22,9 @@ use crate::runtime::{Command, Debug}; use crate::{Clipboard, Error, Proxy, Settings}; use futures::channel::mpsc; +use futures::channel::oneshot; +use std::borrow::Cow; use std::mem::ManuallyDrop; use std::sync::Arc; @@ -129,7 +131,7 @@ pub fn default(theme: &Theme) -> Appearance { /// Runs an [`Application`] with an executor, compositor, and the provided /// settings. -pub async fn run( +pub fn run( settings: Settings, graphics_settings: graphics::Settings, ) -> Result<(), Error> @@ -141,12 +143,12 @@ where { use futures::task; use futures::Future; - use winit::event_loop::EventLoopBuilder; + use winit::event_loop::EventLoop; let mut debug = Debug::new(); debug.startup_started(); - let event_loop = EventLoopBuilder::with_user_event() + let event_loop = EventLoop::with_user_event() .build() .expect("Create event loop"); @@ -165,102 +167,267 @@ where runtime.enter(|| A::new(flags)) }; - #[cfg(target_arch = "wasm32")] - let target = settings.window.platform_specific.target.clone(); + let id = settings.id; + let title = application.title(); - let should_be_visible = settings.window.visible; - let exit_on_close_request = settings.window.exit_on_close_request; + let (boot_sender, boot_receiver) = oneshot::channel(); + let (event_sender, event_receiver) = mpsc::unbounded(); + let (control_sender, control_receiver) = mpsc::unbounded(); - let builder = conversion::window_settings( - settings.window, - &application.title(), - event_loop.primary_monitor(), - settings.id, - ) - .with_visible(false); + let instance = Box::pin(run_instance::( + application, + runtime, + proxy, + debug, + boot_receiver, + event_receiver, + control_sender, + init_command, + settings.fonts, + )); - log::debug!("Window builder: {builder:#?}"); + let context = task::Context::from_waker(task::noop_waker_ref()); + + struct Runner { + instance: std::pin::Pin>, + context: task::Context<'static>, + boot: Option>, + sender: mpsc::UnboundedSender>, + receiver: mpsc::UnboundedReceiver, + error: Option, + #[cfg(target_arch = "wasm32")] + is_booted: std::rc::Rc>, + #[cfg(target_arch = "wasm32")] + queued_events: Vec>, + } - let window = Arc::new( - builder - .build(&event_loop) - .map_err(Error::WindowCreationFailed)?, - ); + struct BootConfig { + sender: oneshot::Sender>, + id: Option, + title: String, + window_settings: window::Settings, + graphics_settings: graphics::Settings, + } - #[cfg(target_arch = "wasm32")] + let runner = Runner { + instance, + context, + boot: Some(BootConfig { + sender: boot_sender, + id, + title, + window_settings: settings.window, + graphics_settings, + }), + sender: event_sender, + receiver: control_receiver, + error: None, + #[cfg(target_arch = "wasm32")] + is_booted: std::rc::Rc::new(std::cell::RefCell::new(false)), + #[cfg(target_arch = "wasm32")] + queued_events: Vec::new(), + }; + + impl winit::application::ApplicationHandler + for Runner + where + F: Future, + C: Compositor + 'static, { - use winit::platform::web::WindowExtWebSys; + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + let Some(BootConfig { + sender, + id, + title, + window_settings, + graphics_settings, + }) = self.boot.take() + else { + return; + }; - let canvas = window.canvas().expect("Get window canvas"); - let _ = canvas.set_attribute( - "style", - "display: block; width: 100%; height: 100%", - ); + let should_be_visible = window_settings.visible; + let exit_on_close_request = window_settings.exit_on_close_request; + + #[cfg(target_arch = "wasm32")] + let target = window_settings.platform_specific.target.clone(); + + let window_attributes = conversion::window_attributes( + window_settings, + &title, + event_loop.primary_monitor(), + id, + ) + .with_visible(false); + + log::debug!("Window attributes: {window_attributes:#?}"); + + let window = match event_loop.create_window(window_attributes) { + Ok(window) => Arc::new(window), + Err(error) => { + self.error = Some(Error::WindowCreationFailed(error)); + event_loop.exit(); + return; + } + }; + + let finish_boot = { + let window = window.clone(); - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().unwrap(); - - let target = target.and_then(|target| { - body.query_selector(&format!("#{target}")) - .ok() - .unwrap_or(None) - }); - - match target { - Some(node) => { - let _ = node - .replace_with_with_node_1(&canvas) - .expect(&format!("Could not replace #{}", node.id())); + async move { + let compositor = + C::new(graphics_settings, window.clone()).await?; + + sender + .send(Boot { + window, + compositor, + should_be_visible, + exit_on_close_request, + }) + .ok() + .expect("Send boot event"); + + Ok::<_, graphics::Error>(()) + } + }; + + #[cfg(not(target_arch = "wasm32"))] + if let Err(error) = futures::executor::block_on(finish_boot) { + self.error = Some(Error::GraphicsCreationFailed(error)); + event_loop.exit(); } - None => { - let _ = body - .append_child(&canvas) - .expect("Append canvas to HTML body"); + + #[cfg(target_arch = "wasm32")] + { + use winit::platform::web::WindowExtWebSys; + + let canvas = window.canvas().expect("Get window canvas"); + let _ = canvas.set_attribute( + "style", + "display: block; width: 100%; height: 100%", + ); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + + let target = target.and_then(|target| { + body.query_selector(&format!("#{target}")) + .ok() + .unwrap_or(None) + }); + + match target { + Some(node) => { + let _ = node.replace_with_with_node_1(&canvas).expect( + &format!("Could not replace #{}", node.id()), + ); + } + None => { + let _ = body + .append_child(&canvas) + .expect("Append canvas to HTML body"); + } + }; + + let is_booted = self.is_booted.clone(); + + wasm_bindgen_futures::spawn_local(async move { + finish_boot.await.expect("Finish boot!"); + + *is_booted.borrow_mut() = true; + }); } - }; - } + } + + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + window_id: winit::window::WindowId, + event: winit::event::WindowEvent, + ) { + #[cfg(target_os = "windows")] + let is_move_or_resize = matches!( + event, + winit::event::WindowEvent::Resized(_) + | winit::event::WindowEvent::Moved(_) + ); + + self.process_event( + event_loop, + winit::event::Event::WindowEvent { window_id, event }, + ); + + // TODO: Remove when unnecessary + // On Windows, we emulate an `AboutToWait` event after every `Resized` event + // since the event loop does not resume during resize interaction. + // More details: https://github.com/rust-windowing/winit/issues/3272 + #[cfg(target_os = "windows")] + { + if is_move_or_resize { + self.process_event( + event_loop, + winit::event::Event::AboutToWait, + ); + } + } + } - let mut compositor = C::new(graphics_settings, window.clone()).await?; - let renderer = compositor.create_renderer(); + fn user_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + message: Message, + ) { + self.process_event( + event_loop, + winit::event::Event::UserEvent(message), + ); + } - for font in settings.fonts { - compositor.load_font(font); + fn about_to_wait( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + ) { + self.process_event(event_loop, winit::event::Event::AboutToWait); + } } - let (mut event_sender, event_receiver) = mpsc::unbounded(); - let (control_sender, mut control_receiver) = mpsc::unbounded(); - - let mut instance = Box::pin(run_instance::( - application, - compositor, - renderer, - runtime, - proxy, - debug, - event_receiver, - control_sender, - init_command, - window, - should_be_visible, - exit_on_close_request, - )); + impl Runner + where + F: Future, + { + fn process_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + event: winit::event::Event, + ) { + // On Wasm, events may start being processed before the compositor + // boots up. We simply queue them and process them once ready. + #[cfg(target_arch = "wasm32")] + if !*self.is_booted.borrow() { + self.queued_events.push(event); + return; + } else if !self.queued_events.is_empty() { + let queued_events = std::mem::take(&mut self.queued_events); - let mut context = task::Context::from_waker(task::noop_waker_ref()); + // This won't infinitely recurse, since we `mem::take` + for event in queued_events { + self.process_event(event_loop, event); + } + } - let process_event = - move |event, event_loop: &winit::event_loop::EventLoopWindowTarget<_>| { if event_loop.exiting() { return; } - event_sender.start_send(event).expect("Send event"); + self.sender.start_send(event).expect("Send event"); - let poll = instance.as_mut().poll(&mut context); + let poll = self.instance.as_mut().poll(&mut self.context); match poll { task::Poll::Pending => { - if let Ok(Some(flow)) = control_receiver.try_next() { + if let Ok(Some(flow)) = self.receiver.try_next() { event_loop.set_control_flow(flow); } } @@ -268,54 +435,45 @@ where event_loop.exit(); } } - }; + } + } - #[cfg(not(target_os = "windows"))] - let _ = event_loop.run(process_event); + #[cfg(not(target_arch = "wasm32"))] + { + let mut runner = runner; + let _ = event_loop.run_app(&mut runner); + + runner.error.map(Err).unwrap_or(Ok(())) + } - // TODO: Remove when unnecessary - // On Windows, we emulate an `AboutToWait` event after every `Resized` event - // since the event loop does not resume during resize interaction. - // More details: https://github.com/rust-windowing/winit/issues/3272 - #[cfg(target_os = "windows")] + #[cfg(target_arch = "wasm32")] { - let mut process_event = process_event; + use winit::platform::web::EventLoopExtWebSys; + let _ = event_loop.spawn_app(runner); - let _ = event_loop.run(move |event, event_loop| { - if matches!( - event, - winit::event::Event::WindowEvent { - event: winit::event::WindowEvent::Resized(_) - | winit::event::WindowEvent::Moved(_), - .. - } - ) { - process_event(event, event_loop); - process_event(winit::event::Event::AboutToWait, event_loop); - } else { - process_event(event, event_loop); - } - }); + Ok(()) } +} - Ok(()) +struct Boot { + window: Arc, + compositor: C, + should_be_visible: bool, + exit_on_close_request: bool, } async fn run_instance( mut application: A, - mut compositor: C, - mut renderer: A::Renderer, mut runtime: Runtime, A::Message>, mut proxy: Proxy, mut debug: Debug, + mut boot: oneshot::Receiver>, mut event_receiver: mpsc::UnboundedReceiver< winit::event::Event, >, mut control_sender: mpsc::UnboundedSender, init_command: Command, - window: Arc, - should_be_visible: bool, - exit_on_close_request: bool, + fonts: Vec>, ) where A: Application + 'static, E: Executor + 'static, @@ -326,6 +484,19 @@ async fn run_instance( use winit::event; use winit::event_loop::ControlFlow; + let Boot { + window, + mut compositor, + should_be_visible, + exit_on_close_request, + } = boot.try_recv().ok().flatten().expect("Receive boot"); + + let mut renderer = compositor.create_renderer(); + + for font in fonts { + compositor.load_font(font); + } + let mut state = State::new(&application, &window); let mut viewport_version = state.viewport_version(); let physical_size = state.physical_size(); @@ -480,7 +651,7 @@ async fn run_instance( debug.draw_finished(); if new_mouse_interaction != mouse_interaction { - window.set_cursor_icon(conversion::mouse_interaction( + window.set_cursor(conversion::mouse_interaction( new_mouse_interaction, )); diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 0f83dac3..d04fc2f1 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -8,16 +8,16 @@ use crate::core::touch; use crate::core::window; use crate::core::{Event, Point, Size}; -/// Converts some [`window::Settings`] into a `WindowBuilder` from `winit`. -pub fn window_settings( +/// Converts some [`window::Settings`] into some `WindowAttributes` from `winit`. +pub fn window_attributes( settings: window::Settings, title: &str, primary_monitor: Option, _id: Option, -) -> winit::window::WindowBuilder { - let mut window_builder = winit::window::WindowBuilder::new(); +) -> winit::window::WindowAttributes { + let mut attributes = winit::window::WindowAttributes::default(); - window_builder = window_builder + attributes = attributes .with_title(title) .with_inner_size(winit::dpi::LogicalSize { width: settings.size.width, @@ -39,23 +39,21 @@ pub fn window_settings( if let Some(position) = position(primary_monitor.as_ref(), settings.size, settings.position) { - window_builder = window_builder.with_position(position); + attributes = attributes.with_position(position); } if let Some(min_size) = settings.min_size { - window_builder = - window_builder.with_min_inner_size(winit::dpi::LogicalSize { - width: min_size.width, - height: min_size.height, - }); + attributes = attributes.with_min_inner_size(winit::dpi::LogicalSize { + width: min_size.width, + height: min_size.height, + }); } if let Some(max_size) = settings.max_size { - window_builder = - window_builder.with_max_inner_size(winit::dpi::LogicalSize { - width: max_size.width, - height: max_size.height, - }); + attributes = attributes.with_max_inner_size(winit::dpi::LogicalSize { + width: max_size.width, + height: max_size.height, + }); } #[cfg(any( @@ -65,35 +63,33 @@ pub fn window_settings( target_os = "openbsd" ))] { - // `with_name` is available on both `WindowBuilderExtWayland` and `WindowBuilderExtX11` and they do - // exactly the same thing. We arbitrarily choose `WindowBuilderExtWayland` here. - use ::winit::platform::wayland::WindowBuilderExtWayland; + use ::winit::platform::wayland::WindowAttributesExtWayland; if let Some(id) = _id { - window_builder = window_builder.with_name(id.clone(), id); + attributes = attributes.with_name(id.clone(), id); } } #[cfg(target_os = "windows")] { - use winit::platform::windows::WindowBuilderExtWindows; + use winit::platform::windows::WindowAttributesExtWindows; #[allow(unsafe_code)] unsafe { - window_builder = window_builder + attributes = attributes .with_parent_window(settings.platform_specific.parent); } - window_builder = window_builder + attributes = attributes .with_drag_and_drop(settings.platform_specific.drag_and_drop); - window_builder = window_builder + attributes = attributes .with_skip_taskbar(settings.platform_specific.skip_taskbar); } #[cfg(target_os = "macos")] { - use winit::platform::macos::WindowBuilderExtMacOS; + use winit::platform::macos::WindowAttributesExtMacOS; - window_builder = window_builder + attributes = attributes .with_title_hidden(settings.platform_specific.title_hidden) .with_titlebar_transparent( settings.platform_specific.titlebar_transparent, @@ -107,25 +103,25 @@ pub fn window_settings( { #[cfg(feature = "x11")] { - use winit::platform::x11::WindowBuilderExtX11; + use winit::platform::x11::WindowAttributesExtX11; - window_builder = window_builder.with_name( + attributes = attributes.with_name( &settings.platform_specific.application_id, &settings.platform_specific.application_id, ); } #[cfg(feature = "wayland")] { - use winit::platform::wayland::WindowBuilderExtWayland; + use winit::platform::wayland::WindowAttributesExtWayland; - window_builder = window_builder.with_name( + attributes = attributes.with_name( &settings.platform_specific.application_id, &settings.platform_specific.application_id, ); } } - window_builder + attributes } /// Converts a winit window event into an iced event. diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 2c148031..145b1569 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -12,6 +12,7 @@ use crate::core::widget::operation; use crate::core::window; use crate::core::{Point, Size}; use crate::futures::futures::channel::mpsc; +use crate::futures::futures::channel::oneshot; use crate::futures::futures::executor; use crate::futures::futures::task; use crate::futures::futures::{Future, StreamExt}; @@ -114,12 +115,12 @@ where C: Compositor + 'static, A::Theme: DefaultStyle, { - use winit::event_loop::EventLoopBuilder; + use winit::event_loop::EventLoop; let mut debug = Debug::new(); debug.startup_started(); - let event_loop = EventLoopBuilder::with_user_event() + let event_loop = EventLoop::with_user_event() .build() .expect("Create event loop"); @@ -138,187 +139,277 @@ where runtime.enter(|| A::new(flags)) }; - let should_main_be_visible = settings.window.visible; - let exit_on_close_request = settings.window.exit_on_close_request; + let id = settings.id; + let title = application.title(window::Id::MAIN); - let builder = conversion::window_settings( - settings.window, - &application.title(window::Id::MAIN), - event_loop.primary_monitor(), - settings.id, - ) - .with_visible(false); + let (boot_sender, boot_receiver) = oneshot::channel(); + let (event_sender, event_receiver) = mpsc::unbounded(); + let (control_sender, control_receiver) = mpsc::unbounded(); - log::info!("Window builder: {:#?}", builder); + let instance = Box::pin(run_instance::( + application, + runtime, + proxy, + debug, + boot_receiver, + event_receiver, + control_sender, + init_command, + )); - let main_window = Arc::new( - builder - .build(&event_loop) - .map_err(Error::WindowCreationFailed)?, - ); + let context = task::Context::from_waker(task::noop_waker_ref()); - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; + struct Runner { + instance: std::pin::Pin>, + context: task::Context<'static>, + boot: Option>, + sender: mpsc::UnboundedSender>, + receiver: mpsc::UnboundedReceiver, + error: Option, + } - let canvas = main_window.canvas(); + struct BootConfig { + sender: oneshot::Sender>, + id: Option, + title: String, + window_settings: window::Settings, + graphics_settings: graphics::Settings, + } - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().unwrap(); + let mut runner = Runner { + instance, + context, + boot: Some(BootConfig { + sender: boot_sender, + id, + title, + window_settings: settings.window, + graphics_settings, + }), + sender: event_sender, + receiver: control_receiver, + error: None, + }; - let target = target.and_then(|target| { - body.query_selector(&format!("#{}", target)) - .ok() - .unwrap_or(None) - }); + impl winit::application::ApplicationHandler + for Runner + where + F: Future, + C: Compositor, + { + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + let Some(BootConfig { + sender, + id, + title, + window_settings, + graphics_settings, + }) = self.boot.take() + else { + return; + }; - match target { - Some(node) => { - let _ = node - .replace_with_with_node_1(&canvas) - .expect(&format!("Could not replace #{}", node.id())); - } - None => { - let _ = body - .append_child(&canvas) - .expect("Append canvas to HTML body"); - } - }; - } + let should_be_visible = window_settings.visible; + let exit_on_close_request = window_settings.exit_on_close_request; - let mut compositor = - executor::block_on(C::new(graphics_settings, main_window.clone()))?; + let window_attributes = conversion::window_attributes( + window_settings, + &title, + event_loop.primary_monitor(), + id, + ) + .with_visible(false); - let mut window_manager = WindowManager::new(); - let _ = window_manager.insert( - window::Id::MAIN, - main_window, - &application, - &mut compositor, - exit_on_close_request, - ); + log::debug!("Window attributes: {window_attributes:#?}"); - let (mut event_sender, event_receiver) = mpsc::unbounded(); - let (control_sender, mut control_receiver) = mpsc::unbounded(); + let window = match event_loop.create_window(window_attributes) { + Ok(window) => Arc::new(window), + Err(error) => { + self.error = Some(Error::WindowCreationFailed(error)); + event_loop.exit(); + return; + } + }; - let mut instance = Box::pin(run_instance::( - application, - compositor, - runtime, - proxy, - debug, - event_receiver, - control_sender, - init_command, - window_manager, - should_main_be_visible, - )); + let finish_boot = async move { + let compositor = + C::new(graphics_settings, window.clone()).await?; + + sender + .send(Boot { + window, + compositor, + should_be_visible, + exit_on_close_request, + }) + .ok() + .expect("Send boot event"); + + Ok::<_, graphics::Error>(()) + }; - let mut context = task::Context::from_waker(task::noop_waker_ref()); + if let Err(error) = executor::block_on(finish_boot) { + self.error = Some(Error::GraphicsCreationFailed(error)); + event_loop.exit(); + } + } - let process_event = move |event, event_loop: &winit::event_loop::EventLoopWindowTarget<_>| { - if event_loop.exiting() { - return; + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + window_id: winit::window::WindowId, + event: winit::event::WindowEvent, + ) { + #[cfg(target_os = "windows")] + let is_move_or_resize = matches!( + event, + winit::event::WindowEvent::Resized(_) + | winit::event::WindowEvent::Moved(_) + ); + + self.process_event( + event_loop, + Event::EventLoopAwakened(winit::event::Event::WindowEvent { + window_id, + event, + }), + ); + + // TODO: Remove when unnecessary + // On Windows, we emulate an `AboutToWait` event after every `Resized` event + // since the event loop does not resume during resize interaction. + // More details: https://github.com/rust-windowing/winit/issues/3272 + #[cfg(target_os = "windows")] + { + if is_move_or_resize { + self.process_event( + event_loop, + Event::EventLoopAwakened( + winit::event::Event::AboutToWait, + ), + ); + } + } } - event_sender - .start_send(Event::EventLoopAwakened(event)) - .expect("Send event"); - - loop { - let poll = instance.as_mut().poll(&mut context); - - match poll { - task::Poll::Pending => match control_receiver.try_next() { - Ok(Some(control)) => match control { - Control::ChangeFlow(flow) => { - use winit::event_loop::ControlFlow; - - match (event_loop.control_flow(), flow) { - ( - ControlFlow::WaitUntil(current), - ControlFlow::WaitUntil(new), - ) if new < current => {} - ( - ControlFlow::WaitUntil(target), - ControlFlow::Wait, - ) if target > Instant::now() => {} - _ => { - event_loop.set_control_flow(flow); + fn user_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + message: Message, + ) { + self.process_event( + event_loop, + Event::EventLoopAwakened(winit::event::Event::UserEvent( + message, + )), + ); + } + + fn about_to_wait( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + ) { + self.process_event( + event_loop, + Event::EventLoopAwakened(winit::event::Event::AboutToWait), + ); + } + } + + impl Runner + where + F: Future, + C: Compositor, + { + fn process_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + event: Event, + ) { + if event_loop.exiting() { + return; + } + + self.sender.start_send(event).expect("Send event"); + + loop { + let poll = self.instance.as_mut().poll(&mut self.context); + + match poll { + task::Poll::Pending => match self.receiver.try_next() { + Ok(Some(control)) => match control { + Control::ChangeFlow(flow) => { + use winit::event_loop::ControlFlow; + + match (event_loop.control_flow(), flow) { + ( + ControlFlow::WaitUntil(current), + ControlFlow::WaitUntil(new), + ) if new < current => {} + ( + ControlFlow::WaitUntil(target), + ControlFlow::Wait, + ) if target > Instant::now() => {} + _ => { + event_loop.set_control_flow(flow); + } } } - } - Control::CreateWindow { - id, - settings, - title, - monitor, - } => { - let exit_on_close_request = - settings.exit_on_close_request; - - let window = conversion::window_settings( - settings, &title, monitor, None, - ) - .build(event_loop) - .expect("Failed to build window"); - - event_sender - .start_send(Event::WindowCreated { - id, - window, - exit_on_close_request, - }) - .expect("Send event"); - } - Control::Exit => { - event_loop.exit(); + Control::CreateWindow { + id, + settings, + title, + monitor, + } => { + let exit_on_close_request = + settings.exit_on_close_request; + + let window = event_loop + .create_window( + conversion::window_attributes( + settings, &title, monitor, None, + ), + ) + .expect("Create window"); + + self.process_event( + event_loop, + Event::WindowCreated { + id, + window, + exit_on_close_request, + }, + ); + } + Control::Exit => { + event_loop.exit(); + } + }, + _ => { + break; } }, - _ => { + task::Poll::Ready(_) => { + event_loop.exit(); break; } - }, - task::Poll::Ready(_) => { - event_loop.exit(); - break; - } - }; - } - }; - - #[cfg(not(target_os = "windows"))] - let _ = event_loop.run(process_event); - - // TODO: Remove when unnecessary - // On Windows, we emulate an `AboutToWait` event after every `Resized` event - // since the event loop does not resume during resize interaction. - // More details: https://github.com/rust-windowing/winit/issues/3272 - #[cfg(target_os = "windows")] - { - let mut process_event = process_event; - - let _ = event_loop.run(move |event, event_loop| { - if matches!( - event, - winit::event::Event::WindowEvent { - event: winit::event::WindowEvent::Resized(_) - | winit::event::WindowEvent::Moved(_), - .. - } - ) { - process_event(event, event_loop); - process_event(winit::event::Event::AboutToWait, event_loop); - } else { - process_event(event, event_loop); + }; } - }); + } } + let _ = event_loop.run_app(&mut runner); + Ok(()) } +struct Boot { + window: Arc, + compositor: C, + should_be_visible: bool, + exit_on_close_request: bool, +} + enum Event { WindowCreated { id: window::Id, @@ -341,15 +432,13 @@ enum Control { async fn run_instance( mut application: A, - mut compositor: C, mut runtime: Runtime, A::Message>, mut proxy: Proxy, mut debug: Debug, + mut boot: oneshot::Receiver>, mut event_receiver: mpsc::UnboundedReceiver>, mut control_sender: mpsc::UnboundedSender, init_command: Command, - mut window_manager: WindowManager, - should_main_window_be_visible: bool, ) where A: Application + 'static, E: Executor + 'static, @@ -359,11 +448,28 @@ async fn run_instance( use winit::event; use winit::event_loop::ControlFlow; + let Boot { + window: main_window, + mut compositor, + should_be_visible, + exit_on_close_request, + } = boot.try_recv().ok().flatten().expect("Receive boot"); + + let mut window_manager = WindowManager::new(); + + let _ = window_manager.insert( + window::Id::MAIN, + main_window.clone(), + &application, + &mut compositor, + exit_on_close_request, + ); + let main_window = window_manager .get_mut(window::Id::MAIN) .expect("Get main window"); - if should_main_window_be_visible { + if should_be_visible { main_window.raw.set_visible(true); } @@ -532,7 +638,7 @@ async fn run_instance( debug.draw_finished(); if new_mouse_interaction != window.mouse_interaction { - window.raw.set_cursor_icon( + window.raw.set_cursor( conversion::mouse_interaction( new_mouse_interaction, ), @@ -603,7 +709,7 @@ async fn run_instance( if new_mouse_interaction != window.mouse_interaction { - window.raw.set_cursor_icon( + window.raw.set_cursor( conversion::mouse_interaction( new_mouse_interaction, ), diff --git a/winit/src/multi_window/window_manager.rs b/winit/src/multi_window/window_manager.rs index 3a0c8556..57a7dc7e 100644 --- a/winit/src/multi_window/window_manager.rs +++ b/winit/src/multi_window/window_manager.rs @@ -9,8 +9,9 @@ use std::sync::Arc; use winit::monitor::MonitorHandle; #[allow(missing_debug_implementations)] -pub struct WindowManager +pub struct WindowManager where + A: Application, C: Compositor, A::Theme: DefaultStyle, { -- cgit From 7e7285d60f08e9d32a796e9adf7e37535a67fc43 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 May 2024 17:00:55 +0200 Subject: Plug `new_events` handler to event loop --- winit/src/application.rs | 15 +++++++++++++++ winit/src/multi_window.rs | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/winit/src/application.rs b/winit/src/application.rs index e056f4c5..3bc29255 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -341,6 +341,21 @@ where } } + fn new_events( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + cause: winit::event::StartCause, + ) { + if self.boot.is_some() { + return; + } + + self.process_event( + event_loop, + winit::event::Event::NewEvents(cause), + ); + } + fn window_event( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 145b1569..673a6f30 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -254,6 +254,21 @@ where } } + fn new_events( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + cause: winit::event::StartCause, + ) { + if self.boot.is_some() { + return; + } + + self.process_event( + event_loop, + Event::EventLoopAwakened(winit::event::Event::NewEvents(cause)), + ); + } + fn window_event( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, -- cgit From 8a0701a0d95989769341846b05ffcc705ae4ee5f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 May 2024 21:05:29 +0200 Subject: Introduce `ICED_PRESENT_MODE` env var to pick a `wgpu::PresentMode` --- wgpu/src/settings.rs | 14 ++++++++++++++ wgpu/src/window/compositor.rs | 25 +++++++++++++++---------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index a6aea0a5..5e2603ee 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -51,3 +51,17 @@ impl From for Settings { } } } + +pub fn present_mode_from_env() -> Option { + let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?; + + match present_mode.to_lowercase().as_str() { + "vsync" => Some(wgpu::PresentMode::AutoVsync), + "no_vsync" => Some(wgpu::PresentMode::AutoNoVsync), + "immediate" => Some(wgpu::PresentMode::Immediate), + "fifo" => Some(wgpu::PresentMode::Fifo), + "fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed), + "mailbox" => Some(wgpu::PresentMode::Mailbox), + _ => None, + } +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index baf9f315..2e938c77 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -4,7 +4,8 @@ use crate::graphics::color; use crate::graphics::compositor; use crate::graphics::error; use crate::graphics::{self, Viewport}; -use crate::{Engine, Renderer, Settings}; +use crate::settings::{self, Settings}; +use crate::{Engine, Renderer}; /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] @@ -270,15 +271,19 @@ impl graphics::Compositor for Compositor { backend: Option<&str>, ) -> Result { match backend { - None | Some("wgpu") => Ok(new( - Settings { - backends: wgpu::util::backend_bits_from_env() - .unwrap_or(wgpu::Backends::all()), - ..settings.into() - }, - compatible_window, - ) - .await?), + None | Some("wgpu") => { + let mut settings = Settings::from(settings); + + if let Some(backends) = wgpu::util::backend_bits_from_env() { + settings.backends = backends; + } + + if let Some(present_mode) = settings::present_mode_from_env() { + settings.present_mode = present_mode; + } + + Ok(new(settings, compatible_window).await?) + } Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound { backend: "wgpu", reason: error::Reason::DidNotMatch { -- cgit From 5b6f3499e114c1694a5878466f8d46e7022e1bba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 May 2024 21:13:51 +0200 Subject: Document `present_mode_from_env` in `iced_wgpu` --- wgpu/src/settings.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 5e2603ee..b3c3cf6a 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -52,6 +52,18 @@ impl From for Settings { } } +/// Obtains a [`wgpu::PresentMode`] from the current environment +/// configuration, if set. +/// +/// The value returned by this function can be changed by setting +/// the `ICED_PRESENT_MODE` env variable. The possible values are: +/// +/// - `vsync` → [`wgpu::PresentMode::AutoVsync`] +/// - `no_vsync` → [`wgpu::PresentMode::AutoNoVsync`] +/// - `immediate` → [`wgpu::PresentMode::Immediate`] +/// - `fifo` → [`wgpu::PresentMode::Fifo`] +/// - `fifo_relaxed` → [`wgpu::PresentMode::FifoRelaxed`] +/// - `mailbox` → [`wgpu::PresentMode::Mailbox`] pub fn present_mode_from_env() -> Option { let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?; -- cgit From 247870f83bc12f3f4b549bb582f1058f5adcce25 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 May 2024 21:38:31 +0200 Subject: Redirect `docs.iced.rs` to actual docs --- .github/workflows/document.yml | 2 ++ docs/redirect.html | 13 +++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 docs/redirect.html diff --git a/.github/workflows/document.yml b/.github/workflows/document.yml index ba482215..827a2ca8 100644 --- a/.github/workflows/document.yml +++ b/.github/workflows/document.yml @@ -27,6 +27,8 @@ jobs: -p iced - name: Write CNAME file run: echo 'docs.iced.rs' > ./target/doc/CNAME + - name: Copy redirect file as index.html + run: cp docs/redirect.html target/doc/index.html - name: Publish documentation if: github.ref == 'refs/heads/master' uses: peaceiris/actions-gh-pages@v3 diff --git a/docs/redirect.html b/docs/redirect.html new file mode 100644 index 00000000..7b2cef51 --- /dev/null +++ b/docs/redirect.html @@ -0,0 +1,13 @@ + + + + + + + Redirecting... + + + +

If you are not redirected automatically, follow this link.

+ + -- cgit From 447f3a2d14ab1c4bc20e232df1aa2375623af2de Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 12:29:17 +0200 Subject: Reuse `glyphon::Pipeline` state in `iced_wgpu` --- Cargo.toml | 2 +- wgpu/src/text.rs | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06f7d198..4f50f018 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,7 +142,7 @@ cosmic-text = "0.10" dark-light = "1.0" futures = "0.3" glam = "0.25" -glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "ceed55403ce53e120ce9d1fae17dcfe388726118" } +glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "cd66a24859cf30b0b8cabf06256dacad362ed44a" } guillotiere = "0.6" half = "2.2" image = "0.24" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 7e683c77..68741461 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -115,6 +115,7 @@ impl Storage { queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, format: wgpu::TextureFormat, + pipeline: &glyphon::Pipeline, cache: &Cache, new_transformation: Transformation, bounds: Rectangle, @@ -131,7 +132,7 @@ impl Storage { Group { atlas: glyphon::TextAtlas::with_color_mode( - device, queue, format, COLOR_MODE, + device, queue, pipeline, format, COLOR_MODE, ), version: 0, should_trim: false, @@ -259,6 +260,7 @@ impl Storage { #[allow(missing_debug_implementations)] pub struct Pipeline { + state: glyphon::Pipeline, format: wgpu::TextureFormat, atlas: glyphon::TextAtlas, renderers: Vec, @@ -272,12 +274,16 @@ impl Pipeline { queue: &wgpu::Queue, format: wgpu::TextureFormat, ) -> Self { + let state = glyphon::Pipeline::new(device); + let atlas = glyphon::TextAtlas::with_color_mode( + device, queue, &state, format, COLOR_MODE, + ); + Pipeline { + state, format, renderers: Vec::new(), - atlas: glyphon::TextAtlas::with_color_mode( - device, queue, format, COLOR_MODE, - ), + atlas, prepare_layer: 0, cache: BufferCache::new(), } @@ -343,6 +349,7 @@ impl Pipeline { queue, encoder, self.format, + &self.state, cache, layer_transformation * *transformation, layer_bounds * layer_transformation, -- cgit From bed53f81436c595e479009427e1fa4ff4a1048d3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 13:22:22 +0200 Subject: Reuse `glyphon::Viewport` explicitly --- Cargo.toml | 2 +- wgpu/src/lib.rs | 13 +++++++++---- wgpu/src/text.rs | 42 +++++++++++++++++++++++++++++------------- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4f50f018..1bd26ea6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,7 +142,7 @@ cosmic-text = "0.10" dark-light = "1.0" futures = "0.3" glam = "0.25" -glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "cd66a24859cf30b0b8cabf06256dacad362ed44a" } +glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "f410f3ab4fc7b484003684881124cb0ee7ef2e01" } guillotiere = "0.6" half = "2.2" image = "0.24" diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1fbdbe9a..ad88ce3e 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -79,6 +79,7 @@ pub struct Renderer { triangle_storage: triangle::Storage, text_storage: text::Storage, + text_viewport: text::Viewport, // TODO: Centralize all the image feature handling #[cfg(any(feature = "svg", feature = "image"))] @@ -87,8 +88,8 @@ pub struct Renderer { impl Renderer { pub fn new( - _device: &wgpu::Device, - _engine: &Engine, + device: &wgpu::Device, + engine: &Engine, default_font: Font, default_text_size: Pixels, ) -> Self { @@ -99,10 +100,11 @@ impl Renderer { triangle_storage: triangle::Storage::new(), text_storage: text::Storage::new(), + text_viewport: engine.text_pipeline.create_viewport(device), #[cfg(any(feature = "svg", feature = "image"))] image_cache: std::cell::RefCell::new( - _engine.create_image_cache(_device), + engine.create_image_cache(device), ), } } @@ -141,6 +143,8 @@ impl Renderer { ) { let scale_factor = viewport.scale_factor() as f32; + self.text_viewport.update(queue, viewport.physical_size()); + for layer in self.layers.iter_mut() { if !layer.quads.is_empty() { engine.quad_pipeline.prepare( @@ -182,12 +186,12 @@ impl Renderer { engine.text_pipeline.prepare( device, queue, + &self.text_viewport, encoder, &mut self.text_storage, &layer.text, layer.bounds, Transformation::scale(scale_factor), - viewport.physical_size(), ); } @@ -357,6 +361,7 @@ impl Renderer { if !layer.text.is_empty() { text_layer += engine.text_pipeline.render( + &self.text_viewport, &self.text_storage, text_layer, &layer.text, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 68741461..9ecd5c99 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -113,13 +113,13 @@ impl Storage { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + viewport: &glyphon::Viewport, encoder: &mut wgpu::CommandEncoder, format: wgpu::TextureFormat, pipeline: &glyphon::Pipeline, cache: &Cache, new_transformation: Transformation, bounds: Rectangle, - target_size: Size, ) { let group_count = self.groups.len(); @@ -152,6 +152,7 @@ impl Storage { let _ = prepare( device, queue, + viewport, encoder, &mut upload.renderer, &mut group.atlas, @@ -159,7 +160,6 @@ impl Storage { &cache.text, bounds, new_transformation, - target_size, ); } @@ -189,6 +189,7 @@ impl Storage { let _ = prepare( device, queue, + viewport, encoder, &mut renderer, &mut group.atlas, @@ -196,7 +197,6 @@ impl Storage { &cache.text, bounds, new_transformation, - target_size, ); } @@ -258,6 +258,20 @@ impl Storage { } } +pub struct Viewport(glyphon::Viewport); + +impl Viewport { + pub fn update(&mut self, queue: &wgpu::Queue, resolution: Size) { + self.0.update( + queue, + glyphon::Resolution { + width: resolution.width, + height: resolution.height, + }, + ); + } +} + #[allow(missing_debug_implementations)] pub struct Pipeline { state: glyphon::Pipeline, @@ -293,12 +307,12 @@ impl Pipeline { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + viewport: &Viewport, encoder: &mut wgpu::CommandEncoder, storage: &mut Storage, batch: &Batch, layer_bounds: Rectangle, layer_transformation: Transformation, - target_size: Size, ) { for item in batch { match item { @@ -319,6 +333,7 @@ impl Pipeline { let result = prepare( device, queue, + &viewport.0, encoder, renderer, &mut self.atlas, @@ -326,7 +341,6 @@ impl Pipeline { text, layer_bounds * layer_transformation, layer_transformation * *transformation, - target_size, ); match result { @@ -347,13 +361,13 @@ impl Pipeline { storage.prepare( device, queue, + &viewport.0, encoder, self.format, &self.state, cache, layer_transformation * *transformation, layer_bounds * layer_transformation, - target_size, ); } } @@ -362,6 +376,7 @@ impl Pipeline { pub fn render<'a>( &'a self, + viewport: &'a Viewport, storage: &'a Storage, start: usize, batch: &'a Batch, @@ -383,7 +398,7 @@ impl Pipeline { let renderer = &self.renderers[start + layer_count]; renderer - .render(&self.atlas, render_pass) + .render(&self.atlas, &viewport.0, render_pass) .expect("Render text"); layer_count += 1; @@ -392,7 +407,7 @@ impl Pipeline { if let Some((atlas, upload)) = storage.get(cache) { upload .renderer - .render(atlas, render_pass) + .render(atlas, &viewport.0, render_pass) .expect("Render cached text"); } } @@ -402,6 +417,10 @@ impl Pipeline { layer_count } + pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport { + Viewport(glyphon::Viewport::new(device, &self.state)) + } + pub fn end_frame(&mut self) { self.atlas.trim(); self.cache.trim(); @@ -413,6 +432,7 @@ impl Pipeline { fn prepare( device: &wgpu::Device, queue: &wgpu::Queue, + viewport: &glyphon::Viewport, encoder: &mut wgpu::CommandEncoder, renderer: &mut glyphon::TextRenderer, atlas: &mut glyphon::TextAtlas, @@ -420,7 +440,6 @@ fn prepare( sections: &[Text], layer_bounds: Rectangle, layer_transformation: Transformation, - target_size: Size, ) -> Result<(), glyphon::PrepareError> { let mut font_system = font_system().write().expect("Write font system"); let font_system = font_system.raw(); @@ -617,10 +636,7 @@ fn prepare( encoder, font_system, atlas, - glyphon::Resolution { - width: target_size.width, - height: target_size.height, - }, + viewport, text_areas, &mut glyphon::SwashCache::new(), ) -- cgit From 99c1464cc16979381b3a531d293ee34fd6697480 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 19:34:43 +0200 Subject: Update `glyphon` fork to a cleaner branch --- Cargo.toml | 2 +- wgpu/src/text.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1bd26ea6..fc35fee8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,7 +142,7 @@ cosmic-text = "0.10" dark-light = "1.0" futures = "0.3" glam = "0.25" -glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "f410f3ab4fc7b484003684881124cb0ee7ef2e01" } +glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "f07e7bab705e69d39a5e6e52c73039a93c4552f8" } guillotiere = "0.6" half = "2.2" image = "0.24" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 9ecd5c99..05db5f80 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -116,7 +116,7 @@ impl Storage { viewport: &glyphon::Viewport, encoder: &mut wgpu::CommandEncoder, format: wgpu::TextureFormat, - pipeline: &glyphon::Pipeline, + state: &glyphon::Cache, cache: &Cache, new_transformation: Transformation, bounds: Rectangle, @@ -132,7 +132,7 @@ impl Storage { Group { atlas: glyphon::TextAtlas::with_color_mode( - device, queue, pipeline, format, COLOR_MODE, + device, queue, state, format, COLOR_MODE, ), version: 0, should_trim: false, @@ -274,7 +274,7 @@ impl Viewport { #[allow(missing_debug_implementations)] pub struct Pipeline { - state: glyphon::Pipeline, + state: glyphon::Cache, format: wgpu::TextureFormat, atlas: glyphon::TextAtlas, renderers: Vec, @@ -288,7 +288,7 @@ impl Pipeline { queue: &wgpu::Queue, format: wgpu::TextureFormat, ) -> Self { - let state = glyphon::Pipeline::new(device); + let state = glyphon::Cache::new(device); let atlas = glyphon::TextAtlas::with_color_mode( device, queue, &state, format, COLOR_MODE, ); -- cgit From fb5f1ba0bc728069b63d299e98411fad483b4177 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 20:29:27 +0200 Subject: Create dynamic text `wgpu` benchmark --- benches/ipsum.txt | 9 ++++++++ benches/wgpu.rs | 63 +++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 benches/ipsum.txt diff --git a/benches/ipsum.txt b/benches/ipsum.txt new file mode 100644 index 00000000..3e2d6396 --- /dev/null +++ b/benches/ipsum.txt @@ -0,0 +1,9 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at elit mollis, dictum nunc non, tempus metus. Sed iaculis ac mauris eu lobortis. Integer elementum venenatis eros, id placerat odio feugiat vel. Maecenas consequat convallis tincidunt. Nunc eu lorem justo. Praesent quis ornare sapien. Aliquam interdum tortor ut rhoncus faucibus. Suspendisse molestie scelerisque nulla, eget sodales lacus sodales vel. Nunc placerat id arcu sodales venenatis. Praesent ullamcorper viverra nibh eget efficitur. Aliquam molestie felis vehicula, finibus sapien eget, accumsan purus. Praesent vestibulum eleifend consectetur. Sed tincidunt lectus a libero efficitur, non scelerisque lectus tincidunt. + +Cras ullamcorper tincidunt tellus non tempor. Integer pulvinar turpis quam, nec pharetra purus egestas non. Vivamus sed ipsum consequat, dignissim ante et, suscipit nibh. Quisque et mauris eu erat rutrum cursus. Pellentesque ut neque eu neque eleifend auctor ac hendrerit dolor. Morbi eget egestas ex. Integer hendrerit ipsum in enim bibendum, at vehicula ipsum dapibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tempus consectetur tortor, vel fermentum sem pulvinar eget. Maecenas rutrum fringilla eros a pellentesque. Cras quis magna consectetur, tristique massa vel, aliquet nunc. Aliquam erat volutpat. Suspendisse porttitor risus id auctor fermentum. Vivamus efficitur tellus sed tortor cursus tincidunt. Sed auctor varius arcu, non congue tellus vehicula finibus. + +Fusce a tincidunt urna. Nunc at quam ac enim tempor vehicula imperdiet in sapien. Donec lobortis tristique felis vel semper. Quisque vulputate felis eu enim vestibulum malesuada. Fusce a lobortis mauris, iaculis eleifend ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus sodales vel elit dignissim mattis. + +Aliquam placerat vulputate dignissim. Proin pellentesque vitae arcu ut feugiat. Nunc mi felis, ornare at gravida sed, vestibulum sed urna. Duis fermentum maximus viverra. Donec imperdiet pellentesque sollicitudin. Cras non sem quis metus bibendum molestie. Duis imperdiet nec lectus eu rutrum. Mauris congue enim purus, in iaculis arcu dapibus ut. Nullam id erat tincidunt, iaculis dolor non, lobortis magna. Proin convallis scelerisque maximus. Morbi at lorem fringilla libero blandit fringilla. Ut aliquet tellus non sem dictum viverra. Aenean venenatis purus eget lacus placerat, non mollis mauris pellentesque. + +Etiam elit diam, aliquet quis suscipit non, condimentum viverra odio. Praesent mi enim, suscipit id mi in, rhoncus ultricies lorem. Nulla facilisi. Integer convallis sagittis euismod. Vestibulum porttitor sodales turpis ac accumsan. Nullam molestie turpis vel lacus tincidunt, sed finibus erat pharetra. Nullam vestibulum turpis id sollicitudin accumsan. Praesent eget posuere lacus. Donec vehicula, nisl nec suscipit porta, felis lorem gravida orci, a hendrerit tellus nibh sit amet elit. diff --git a/benches/wgpu.rs b/benches/wgpu.rs index 2d308666..02c7b1f9 100644 --- a/benches/wgpu.rs +++ b/benches/wgpu.rs @@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, Bencher, Criterion}; use iced::alignment; use iced::mouse; -use iced::widget::{canvas, stack, text}; +use iced::widget::{canvas, scrollable, stack, text}; use iced::{ Color, Element, Font, Length, Pixels, Point, Rectangle, Size, Theme, }; @@ -14,20 +14,31 @@ criterion_group!(benches, wgpu_benchmark); #[allow(unused_results)] pub fn wgpu_benchmark(c: &mut Criterion) { - c.bench_function("wgpu — canvas (light)", |b| benchmark(b, scene(10))); - c.bench_function("wgpu — canvas (heavy)", |b| benchmark(b, scene(1_000))); + c.bench_function("wgpu — canvas (light)", |b| { + benchmark(b, |_| scene(10)) + }); + c.bench_function("wgpu — canvas (heavy)", |b| { + benchmark(b, |_| scene(1_000)) + }); c.bench_function("wgpu - layered text (light)", |b| { - benchmark(b, layered_text(10)); + benchmark(b, |_| layered_text(10)); }); c.bench_function("wgpu - layered text (heavy)", |b| { - benchmark(b, layered_text(1_000)); + benchmark(b, |_| layered_text(1_000)); + }); + + c.bench_function("wgpu - dynamic text (light)", |b| { + benchmark(b, |i| dynamic_text(1_000, i)); + }); + c.bench_function("wgpu - dynamic text (heavy)", |b| { + benchmark(b, |i| dynamic_text(100_000, i)); }); } -fn benchmark( +fn benchmark<'a>( bencher: &mut Bencher<'_>, - widget: Element<'_, (), Theme, Renderer>, + view: impl Fn(usize) -> Element<'a, (), Theme, Renderer>, ) { use iced_futures::futures::executor; use iced_wgpu::graphics; @@ -94,14 +105,17 @@ fn benchmark( let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let mut user_interface = runtime::UserInterface::build( - widget, - viewport.logical_size(), - runtime::user_interface::Cache::default(), - &mut renderer, - ); + let mut i = 0; + let mut cache = Some(runtime::user_interface::Cache::default()); bencher.iter(|| { + let mut user_interface = runtime::UserInterface::build( + view(i), + viewport.logical_size(), + cache.take().unwrap(), + &mut renderer, + ); + let _ = user_interface.draw( &mut renderer, &Theme::Dark, @@ -111,6 +125,8 @@ fn benchmark( mouse::Cursor::Unavailable, ); + cache = Some(user_interface.into_cache()); + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None, @@ -130,6 +146,8 @@ fn benchmark( let submission = engine.submit(&queue, encoder); let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission)); + + i += 1; }); } @@ -189,3 +207,22 @@ fn layered_text<'a, Message: 'a>( .height(Length::Fill) .into() } + +fn dynamic_text<'a, Message: 'a>( + n: usize, + i: usize, +) -> Element<'a, Message, Theme, Renderer> { + const LOREM_IPSUM: &'static str = include_str!("ipsum.txt"); + + scrollable( + text(format!( + "{}... Iteration {i}", + std::iter::repeat(LOREM_IPSUM.chars()) + .flatten() + .take(n) + .collect::(), + )) + .size(10), + ) + .into() +} -- cgit From 7e7af91e6f7dc187e8fb95746d8811289a088217 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 20:34:31 +0200 Subject: Fix `clippy` lints in `wgpu` benchmark --- benches/wgpu.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benches/wgpu.rs b/benches/wgpu.rs index 02c7b1f9..0e407253 100644 --- a/benches/wgpu.rs +++ b/benches/wgpu.rs @@ -15,10 +15,10 @@ criterion_group!(benches, wgpu_benchmark); #[allow(unused_results)] pub fn wgpu_benchmark(c: &mut Criterion) { c.bench_function("wgpu — canvas (light)", |b| { - benchmark(b, |_| scene(10)) + benchmark(b, |_| scene(10)); }); c.bench_function("wgpu — canvas (heavy)", |b| { - benchmark(b, |_| scene(1_000)) + benchmark(b, |_| scene(1_000)); }); c.bench_function("wgpu - layered text (light)", |b| { @@ -212,7 +212,7 @@ fn dynamic_text<'a, Message: 'a>( n: usize, i: usize, ) -> Element<'a, Message, Theme, Renderer> { - const LOREM_IPSUM: &'static str = include_str!("ipsum.txt"); + const LOREM_IPSUM: &str = include_str!("ipsum.txt"); scrollable( text(format!( -- cgit From 718fe5b7de6c705b1797c49d382182182acdbe80 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 23:14:37 +0200 Subject: Pass `WindowHandle` by value to `window::run_with_handle` --- runtime/src/window.rs | 2 +- runtime/src/window/action.rs | 2 +- winit/src/application.rs | 2 +- winit/src/multi_window.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 24171e3e..e32465d3 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -197,7 +197,7 @@ pub fn change_icon(id: Id, icon: Icon) -> Command { /// Note that if the window closes before this call is processed the callback will not be run. pub fn run_with_handle( id: Id, - f: impl FnOnce(&WindowHandle<'_>) -> Message + 'static, + f: impl FnOnce(WindowHandle<'_>) -> Message + 'static, ) -> Command { Command::single(command::Action::Window(Action::RunWithHandle( id, diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index e44ff5a6..07e77872 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -106,7 +106,7 @@ pub enum Action { /// said, it's usually in the same ballpark as on Windows. ChangeIcon(Id, Icon), /// Runs the closure with the native window handle of the window with the given [`Id`]. - RunWithHandle(Id, Box) -> T + 'static>), + RunWithHandle(Id, Box) -> T + 'static>), /// Screenshot the viewport of the window. Screenshot(Id, Box T + 'static>), } diff --git a/winit/src/application.rs b/winit/src/application.rs index 3bc29255..f7508b4c 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1050,7 +1050,7 @@ pub fn run_command( use window::raw_window_handle::HasWindowHandle; if let Ok(handle) = window.window_handle() { - proxy.send(tag(&handle)); + proxy.send(tag(handle)); } } diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 673a6f30..4cc08d18 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -1223,7 +1223,7 @@ fn run_command( .get_mut(id) .and_then(|window| window.raw.window_handle().ok()) { - proxy.send(tag(&handle)); + proxy.send(tag(handle)); } } window::Action::Screenshot(id, tag) => { -- cgit From b19e95fa1844ca726aa5761f2d331fd780854bc6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 May 2024 22:16:35 +0200 Subject: Add `SpecificWith` variant to `window::Position` --- core/src/window/position.rs | 8 +++++++- winit/src/conversion.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/src/window/position.rs b/core/src/window/position.rs index 73391e75..1c8e86b6 100644 --- a/core/src/window/position.rs +++ b/core/src/window/position.rs @@ -1,4 +1,4 @@ -use crate::Point; +use crate::{Point, Size}; /// The position of a window in a given screen. #[derive(Debug, Clone, Copy, PartialEq)] @@ -15,6 +15,12 @@ pub enum Position { /// at (0, 0) you would have to set the position to /// `(PADDING_X, PADDING_Y)`. Specific(Point), + /// Like [`Specific`], but the window is positioned with the specific coordinates returned by the function. + /// + /// The function receives the window size and the monitor's resolution as input. + /// + /// [`Specific`]: Self::Specific + SpecificWith(fn(Size, Size) -> Point), } impl Default for Position { diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index d04fc2f1..ea33e610 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -323,6 +323,35 @@ pub fn position( y: f64::from(position.y), })) } + window::Position::SpecificWith(to_position) => { + if let Some(monitor) = monitor { + let start = monitor.position(); + + let resolution: winit::dpi::LogicalSize = + monitor.size().to_logical(monitor.scale_factor()); + + let position = to_position( + size, + Size::new(resolution.width, resolution.height), + ); + + let centered: winit::dpi::PhysicalPosition = + winit::dpi::LogicalPosition { + x: position.x, + y: position.y, + } + .to_physical(monitor.scale_factor()); + + Some(winit::dpi::Position::Physical( + winit::dpi::PhysicalPosition { + x: start.x + centered.x, + y: start.y + centered.y, + }, + )) + } else { + None + } + } window::Position::Centered => { if let Some(monitor) = monitor { let start = monitor.position(); -- cgit From 74a4ba65d6fea0c68dfbf721230c4abf36505e61 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 12 May 2024 13:44:13 +0200 Subject: Align images to the pixel grid in `iced_wgpu` This should fix some graphical glitches, at the expense of potential alignment issues with small icons / images. --- wgpu/src/shader/image.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl index accefc17..0eeb100f 100644 --- a/wgpu/src/shader/image.wgsl +++ b/wgpu/src/shader/image.wgsl @@ -38,7 +38,7 @@ fn vs_main(input: VertexInput) -> VertexOutput { out.opacity = input.opacity; // Calculate the vertex position and move the center to the origin - v_pos = input.pos + v_pos * input.scale - input.center; + v_pos = round(input.pos) + v_pos * input.scale - input.center; // Apply the rotation around the center of the image let cos_rot = cos(input.rotation); -- cgit From 05f69f495e9b52e9a7d7bada420558d4c4f6730c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 13 May 2024 17:56:02 +0200 Subject: Ask for explicit `Length` in `center_*` methods --- examples/editor/src/main.rs | 2 +- examples/geometry/src/main.rs | 6 +++--- examples/layout/src/main.rs | 2 +- examples/multi_window/src/main.rs | 2 +- examples/pane_grid/src/main.rs | 5 ++++- examples/screenshot/src/main.rs | 7 +++---- examples/slider/src/main.rs | 8 ++++---- examples/svg/src/main.rs | 2 +- examples/system_information/src/main.rs | 4 ++-- examples/todos/src/main.rs | 5 ++++- examples/tour/src/main.rs | 6 +++--- widget/src/container.rs | 27 ++++++++++----------------- widget/src/helpers.rs | 5 +++-- 13 files changed, 40 insertions(+), 41 deletions(-) diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index af05031a..c20a7d67 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -277,7 +277,7 @@ fn action<'a, Message: Clone + 'a>( label: &'a str, on_press: Option, ) -> Element<'a, Message> { - let action = button(container(content).center_x().width(30)); + let action = button(container(content).center_x(30)); if let Some(on_press) = on_press { tooltip( diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index bf7801a9..3c7969c5 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -153,7 +153,7 @@ mod rainbow { } use iced::widget::{column, container, scrollable}; -use iced::Element; +use iced::{Element, Length}; use rainbow::rainbow; pub fn main() -> iced::Result { @@ -176,7 +176,7 @@ fn view(_state: &()) -> Element<'_, ()> { .spacing(20) .max_width(500); - let scrollable = scrollable(container(content).center_x()); + let scrollable = scrollable(container(content).center_x(Length::Fill)); - container(scrollable).center_y().into() + container(scrollable).center_y(Length::Fill).into() } diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 5f14c03b..c40ac820 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -251,7 +251,7 @@ fn application<'a>() -> Element<'a, Message> { .align_items(Alignment::Center), ) .style(container::rounded_box) - .center_y(); + .center_y(Length::Fill); let content = container( scrollable( diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs index 74339e0c..31c2e4f6 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -207,6 +207,6 @@ impl Window { .align_items(Alignment::Center), ); - container(content).center_x().width(200).into() + container(content).center_x(200).into() } } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index e74ea1ee..0def1c2b 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -291,7 +291,10 @@ fn view_content<'a>( .spacing(10) .align_items(Alignment::Center); - container(scrollable(content)).center_y().padding(5).into() + container(scrollable(content)) + .center_y(Length::Fill) + .padding(5) + .into() } fn view_controls<'a>( diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 82495a1a..bd670322 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -123,10 +123,9 @@ impl Example { }; let image = container(image) - .center_y() + .center_y(Length::FillPortion(2)) .padding(10) - .style(container::rounded_box) - .width(Length::FillPortion(2)); + .style(container::rounded_box); let crop_origin_controls = row![ text("X:") @@ -211,7 +210,7 @@ impl Example { .spacing(40) }; - let side_content = container(controls).center_y(); + let side_content = container(controls).center_y(Length::Fill); let content = row![side_content, image] .spacing(10) diff --git a/examples/slider/src/main.rs b/examples/slider/src/main.rs index 5ffdc9c6..0b4c29aa 100644 --- a/examples/slider/src/main.rs +++ b/examples/slider/src/main.rs @@ -1,5 +1,5 @@ use iced::widget::{center, column, container, slider, text, vertical_slider}; -use iced::Element; +use iced::{Element, Length}; pub fn main() -> iced::Result { iced::run("Slider - Iced", Slider::update, Slider::view) @@ -56,9 +56,9 @@ impl Slider { center( column![ - container(v_slider).center_x(), - container(h_slider).center_x(), - container(text).center_x() + container(v_slider).center_x(Length::Fill), + container(h_slider).center_x(Length::Fill), + container(text).center_x(Length::Fill) ] .spacing(25), ) diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index 45b46716..e071c3af 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -45,7 +45,7 @@ impl Tiger { .on_toggle(Message::ToggleColorFilter); center( - column![svg, container(apply_color_filter).center_x()] + column![svg, container(apply_color_filter).center_x(Length::Fill)] .spacing(20) .height(Length::Fill), ) diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index 89a8383a..66bc4979 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -1,4 +1,4 @@ -use iced::widget::{button, column, container, text}; +use iced::widget::{button, center, column, text}; use iced::{system, Command, Element}; pub fn main() -> iced::Result { @@ -136,6 +136,6 @@ impl Example { } }; - container(content).center().into() + center(content).into() } } diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 8119bc91..e7e05b29 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -238,7 +238,10 @@ impl Todos { .spacing(20) .max_width(800); - scrollable(container(content).center_x().padding(40)).into() + scrollable( + container(content).center_x(Length::Fill).padding(40), + ) + .into() } } } diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index bae6490d..59107258 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -76,10 +76,10 @@ impl Tour { } else { content }) - .center_x(), + .center_x(Length::Fill), ); - container(scrollable).center_y().into() + container(scrollable).center_y(Length::Fill).into() } } @@ -669,7 +669,7 @@ fn ferris<'a>( .filter_method(filter_method) .width(width), ) - .center_x() + .center_x(Length::Fill) } fn padded_button(label: &str) -> Button<'_, Message> { diff --git a/widget/src/container.rs b/widget/src/container.rs index 8b6638d4..51967707 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -122,9 +122,6 @@ where /// Sets the [`Container`] to fill all the available space. /// - /// This can be useful to quickly position content when chained with - /// alignment functions—like [`center`]. - /// /// Calling this method is equivalent to chaining [`fill_x`] and /// [`fill_y`]. /// @@ -159,20 +156,14 @@ where self } - /// Sets the [`Container`] to fill the available space in the horizontal axis - /// and centers its contents there. - pub fn center_x(mut self) -> Self { - self.width = Length::Fill; - self.horizontal_alignment = alignment::Horizontal::Center; - self + /// Sets the width of the [`Container`] and centers its contents horizontally. + pub fn center_x(self, width: impl Into) -> Self { + self.width(width).align_x(alignment::Horizontal::Center) } - /// Sets the [`Container`] to fill the available space in the vertical axis - /// and centers its contents there. - pub fn center_y(mut self) -> Self { - self.height = Length::Fill; - self.vertical_alignment = alignment::Vertical::Center; - self + /// Sets the height of the [`Container`] and centers its contents vertically. + pub fn center_y(self, height: impl Into) -> Self { + self.height(height).align_y(alignment::Vertical::Center) } /// Centers the contents in both the horizontal and vertical axes of the @@ -182,8 +173,10 @@ where /// /// [`center_x`]: Self::center_x /// [`center_y`]: Self::center_y - pub fn center(self) -> Self { - self.center_x().center_y() + pub fn center(self, length: impl Into) -> Self { + let length = length.into(); + + self.center_x(length).center_y(length) } /// Sets whether the contents of the [`Container`] should be clipped on diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index fd8614f5..a1ecd9d1 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -83,9 +83,10 @@ where /// /// This is equivalent to: /// ```rust,no_run +/// # use iced_widget::core::Length; /// # use iced_widget::Container; /// # fn container(x: A) -> Container<'static, ()> { unreachable!() } -/// let centered = container("Centered!").center(); +/// let centered = container("Centered!").center(Length::Fill); /// ``` /// /// [`Container`]: crate::Container @@ -96,7 +97,7 @@ where Theme: container::Catalog + 'a, Renderer: core::Renderer, { - container(content).fill().center() + container(content).center(Length::Fill) } /// Creates a new [`Column`] with the given children. -- cgit From fb23e4c3ff7aec13725aa3814630b436cd94cab3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 16 May 2024 17:20:21 +0200 Subject: Fix main window not closing in multi-window runtime --- winit/src/multi_window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 4cc08d18..95d78b83 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -474,7 +474,7 @@ async fn run_instance( let _ = window_manager.insert( window::Id::MAIN, - main_window.clone(), + main_window, &application, &mut compositor, exit_on_close_request, -- cgit From d265cc133efbe02cab890260dbce16768f3d06dd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 18 May 2024 11:29:41 +0200 Subject: Simplify `clock` example a bit --- examples/clock/src/main.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 3ffc9f07..7c4685c4 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,5 +1,6 @@ use iced::alignment; use iced::mouse; +use iced::time; use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke}; use iced::widget::{canvas, container}; use iced::{ @@ -7,9 +8,6 @@ use iced::{ Theme, Vector, }; -use chrono as time; -use time::Timelike; - pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); @@ -21,13 +19,13 @@ pub fn main() -> iced::Result { } struct Clock { - now: time::DateTime, + now: chrono::DateTime, clock: Cache, } #[derive(Debug, Clone, Copy)] enum Message { - Tick(time::DateTime), + Tick(chrono::DateTime), } impl Clock { @@ -57,8 +55,8 @@ impl Clock { } fn subscription(&self) -> Subscription { - iced::time::every(std::time::Duration::from_millis(500)) - .map(|_| Message::Tick(time::offset::Local::now())) + time::every(time::Duration::from_millis(500)) + .map(|_| Message::Tick(chrono::offset::Local::now())) } fn theme(&self) -> Theme { @@ -70,7 +68,7 @@ impl Clock { impl Default for Clock { fn default() -> Self { Self { - now: time::offset::Local::now(), + now: chrono::offset::Local::now(), clock: Cache::default(), } } @@ -87,6 +85,8 @@ impl canvas::Program for Clock { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec { + use chrono::Timelike; + let clock = self.clock.draw(renderer, bounds.size(), |frame| { let palette = theme.extended_palette(); @@ -125,17 +125,17 @@ impl canvas::Program for Clock { frame.translate(Vector::new(center.x, center.y)); frame.with_save(|frame| { - frame.rotate(hand_rotation(self.now.hour() as u8, 12)); + frame.rotate(hand_rotation(self.now.hour(), 12)); frame.stroke(&short_hand, wide_stroke()); }); frame.with_save(|frame| { - frame.rotate(hand_rotation(self.now.minute() as u8, 60)); + frame.rotate(hand_rotation(self.now.minute(), 60)); frame.stroke(&long_hand, wide_stroke()); }); frame.with_save(|frame| { - let rotation = hand_rotation(self.now.second() as u8, 60); + let rotation = hand_rotation(self.now.second(), 60); frame.rotate(rotation); frame.stroke(&long_hand, thin_stroke()); @@ -167,7 +167,7 @@ impl canvas::Program for Clock { } } -fn hand_rotation(n: u8, total: u8) -> Degrees { +fn hand_rotation(n: u32, total: u32) -> Degrees { let turns = n as f32 / total as f32; Degrees(360.0 * turns) -- cgit From 4936efc3751b769984bff4344a9fbb198a7c1ea2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 18 May 2024 11:32:26 +0200 Subject: Remove redundant default `chrono` feature in `clock` example --- examples/clock/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index 2fddc7da..bc6c202b 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -8,5 +8,5 @@ publish = false [dependencies] iced.workspace = true iced.features = ["canvas", "tokio", "debug"] -chrono = { version = "0.4", features = [ "clock" ] } +chrono = "0.4" tracing-subscriber = "0.3" -- cgit From d92e0f7bba8959384467048c7eca84b4b8a7195f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 22 May 2024 12:29:31 +0200 Subject: Fix compilation of `integration` example in `release` mode Fixes #2447. --- examples/integration/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index a4a961f8..7f8feb3f 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -8,7 +8,9 @@ publish = false [dependencies] iced_winit.workspace = true iced_wgpu.workspace = true + iced_widget.workspace = true +iced_widget.features = ["wgpu"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tracing-subscriber = "0.3" -- cgit From 468794d918eb06c1dbebb33c32b10017ad335f05 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 22 May 2024 12:36:04 +0200 Subject: Produce a compile error in `iced_renderer` when no backend is enabled --- renderer/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 056da5ed..220542e1 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -48,6 +48,13 @@ mod renderer { #[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))] mod renderer { + #[cfg(not(debug_assertions))] + compile_error!( + "Cannot compile `iced_renderer` in release mode \ + without a renderer feature enabled. \ + Enable either the `wgpu` or `tiny-skia` feature, or both." + ); + pub type Renderer = (); pub type Compositor = (); } -- cgit