From 188db4da48954b95a3fe79bcd22689ffc3a661e0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector@hecrj.dev> 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<u32>; + fn measure_image(&self, handle: &Self::Handle) -> Size<u32>; /// 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<u32>; + fn measure_svg(&self, handle: &Handle) -> Size<u32>; /// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`. - fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle); + fn draw_svg( + &mut self, + handle: Handle, + color: Option<Color>, + 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<u32> { + fn measure_image(&self, handle: &image::Handle) -> Size<u32> { self.backend().dimensions(handle) } - fn draw( + fn draw_image( &mut self, handle: image::Handle, filter_method: image::FilterMethod, @@ -233,11 +233,11 @@ impl<B> svg::Renderer for Renderer<B> where B: Backend + backend::Svg, { - fn dimensions(&self, handle: &svg::Handle) -> Size<u32> { + fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> { self.backend().viewport_dimensions(handle) } - fn draw( + fn draw_svg( &mut self, handle: svg::Handle, color: Option<Color>, 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<dyn custom::Compositor>), } pub enum Surface { TinySkia(iced_tiny_skia::window::Surface), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::window::Surface<'static>), + Custom(Box<dyn custom::Surface>), } 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<u32>; + + fn draw_image( + &mut self, + handle: image::Handle, + filter_method: image::FilterMethod, + bounds: Rectangle, + ); + + fn measure_svg(&self, handle: &svg::Handle) -> Size<u32>; + + fn draw_svg( + &mut self, + handle: crate::core::svg::Handle, + color: Option<crate::core::Color>, + bounds: Rectangle, + ); + + #[cfg(feature = "geometry")] + fn new_frame(&self, size: Size) -> Box<dyn Frame>; + + #[cfg(feature = "geometry")] + fn draw_geometry(&mut self, geometry: Box<dyn Geometry>); + + 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<dyn Frame>; + + 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<dyn Frame>, origin: Point); + + fn into_geometry(self: Box<Self>) -> Box<dyn Geometry>; +} + +#[cfg(feature = "geometry")] +pub trait Geometry: std::any::Any + std::fmt::Debug { + fn transform( + self: Box<Self>, + transformation: Transformation, + ) -> Box<dyn Geometry>; + + fn cache(self: Box<Self>) -> std::sync::Arc<dyn Geometry>; + + fn load(self: std::sync::Arc<Self>) -> Box<dyn Geometry>; +} + +pub trait Compositor: std::any::Any { + fn create_renderer(&self) -> Box<dyn Renderer>; + + fn create_surface( + &mut self, + window: Box<dyn compositor::Window>, + width: u32, + height: u32, + ) -> Box<dyn Surface>; + + fn configure_surface( + &mut self, + surface: &mut Box<dyn Surface>, + 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<dyn custom::Geometry>), } 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<dyn custom::Frame>), } 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<Fill>) { - 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<Fill>, ) { - 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<Stroke<'a>>) { - 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<Text>) { - 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<Radians>) { - 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<f32>) { - 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<Vector>) { - 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<iced_tiny_skia::Primitive>), #[cfg(feature = "wgpu")] Wgpu(Arc<iced_wgpu::Primitive>), + Custom(Arc<dyn custom::Geometry>), } 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<dyn custom::Renderer>), } 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<Background>, ) { - 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<u32> { - 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<u32> { - delegate!(self, renderer, renderer.dimensions(handle)) + fn measure_svg( + &self, + handle: &crate::core::svg::Handle, + ) -> core::Size<u32> { + delegate!(self, renderer, renderer.measure_svg(handle)) } - fn draw( + fn draw_svg( &mut self, handle: crate::core::svg::Handle, color: Option<crate::core::Color>, 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, Handle>( Renderer: image::Renderer<Handle = Handle>, 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<Renderer, Handle>( ..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<Renderer>( 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 <hector@hecrj.dev> 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<dyn custom::Compositor>), + #[cfg(feature = "custom")] + Custom(Box<dyn crate::custom::Compositor>), } pub enum Surface { TinySkia(iced_tiny_skia::window::Surface), #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::window::Surface<'static>), - Custom(Box<dyn custom::Surface>), + #[cfg(feature = "custom")] + Custom(Box<dyn crate::custom::Surface>), } 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<dyn custom::Geometry>), + #[cfg(feature = "custom")] + Custom(Box<dyn crate::custom::Geometry>), } 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<dyn custom::Frame>), + #[cfg(feature = "custom")] + Custom(Box<dyn crate::custom::Frame>), } 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<iced_tiny_skia::Primitive>), #[cfg(feature = "wgpu")] Wgpu(Arc<iced_wgpu::Primitive>), - Custom(Arc<dyn custom::Geometry>), + #[cfg(feature = "custom")] + Custom(Arc<dyn crate::custom::Geometry>), } 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<dyn custom::Renderer>), } @@ -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 <hector@hecrj.dev> Date: Thu, 21 Mar 2024 06:03:31 +0100 Subject: Use `&mut dyn Surface` instead of `&mut Box<dyn Surface>` --- 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<dyn Surface>, + 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 <hector@hecrj.dev> 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<Message> canvas::Program<Message> for Arc { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec<Geometry> { + ) { let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.palette(); @@ -104,6 +102,6 @@ impl<Message> canvas::Program<Message> 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<Geometry> { - 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<Message> canvas::Program<Message> for Clock { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec<Geometry> { + ) { let clock = self.clock.draw(renderer, bounds.size(), |frame| { let palette = theme.extended_palette(); @@ -163,7 +163,7 @@ impl<Message> canvas::Program<Message> 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<Message> canvas::Program<Message> for Theme { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &iced::Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec<Geometry> { + ) { 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<Geometry> { + ) { 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<Length> + Copy) -> Element<'a, Message> { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec<canvas::Geometry> { - 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<Length> + 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<Message> for Multitouch { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec<Geometry> { + ) { + 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<Message> 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<Message> for SierpinskiGraph { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec<canvas::Geometry> { + ) { + 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<Message> 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<Message> canvas::Program<Message> for State { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec<canvas::Geometry> { + ) { + use canvas::Frame; use std::f32::consts::PI; let background = @@ -197,7 +198,7 @@ impl<Message> canvas::Program<Message> 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<Message> canvas::Program<Message> for State { fn draw( &self, _state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec<canvas::Geometry> { + ) { + 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<Message> canvas::Program<Message> 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<Mesh, Error = &'static str>; } /// A graphics backend that supports text rendering. diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index d7d6a0aa..cd4c9267 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -14,11 +14,277 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; +use crate::core::{Point, Radians, Rectangle, Size, Vector}; +use crate::Primitive; + +use std::cell::RefCell; +use std::sync::Arc; + +pub fn frame<Renderer>(renderer: &Renderer, size: Size) -> Renderer::Frame +where + Renderer: self::Renderer, +{ + renderer.new_frame(size) +} + /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: crate::core::Renderer { /// The kind of geometry this renderer can draw. - type Geometry; + type Geometry: Geometry; + + /// The kind of [`Frame`] this renderer supports. + type Frame: Frame<Geometry = Self::Geometry>; + + fn new_frame(&self, size: Size) -> Self::Frame; + + /// Draws the given [`Self::Geometry`]. + fn draw_geometry(&mut self, geometry: Self::Geometry); +} + +pub trait Backend { + /// The kind of [`Frame`] this backend supports. + type Frame: Frame; + + fn new_frame(&self, size: Size) -> Self::Frame; +} + +pub trait Frame: Sized + Into<Self::Geometry> { + /// The kind of geometry this frame can draw. + type Geometry: Geometry; + + /// Returns the width of the [`Frame`]. + fn width(&self) -> f32; + + /// Returns the height of the [`Frame`]. + fn height(&self) -> f32; + + /// Returns the dimensions of the [`Frame`]. + fn size(&self) -> Size; + + /// Returns the coordinate of the center of the [`Frame`]. + fn center(&self) -> Point; + + /// Draws the given [`Path`] on the [`Frame`] by filling it with the + /// provided style. + fn fill(&mut self, path: &Path, fill: impl Into<Fill>); + + /// Draws an axis-aligned rectangle given its top-left corner coordinate and + /// its `Size` on the [`Frame`] by filling it with the provided style. + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into<Fill>, + ); + + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the + /// provided style. + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>); + + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. + /// + /// __Warning:__ Text currently does not work well with rotations and scale + /// transforms! The position will be correctly transformed, but the + /// resulting glyphs will not be rotated or scaled properly. + /// + /// Additionally, all text will be rendered on top of all the layers of + /// a `Canvas`. Therefore, it is currently only meant to be used for + /// overlays, which is the most common use case. + /// + /// Support for vectorial text is planned, and should address all these + /// limitations. + fn fill_text(&mut self, text: impl Into<Text>); + + /// Stores the current transform of the [`Frame`] and executes the given + /// drawing operations, restoring the transform afterwards. + /// + /// This method is useful to compose transforms and perform drawing + /// operations in different coordinate systems. + #[inline] + fn with_save<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + self.push_transform(); + + let result = f(self); + + self.pop_transform(); + + result + } + + /// Pushes the current transform in the transform stack. + fn push_transform(&mut self); + + /// Pops a transform from the transform stack and sets it as the current transform. + fn pop_transform(&mut self); + + /// Executes the given drawing operations within a [`Rectangle`] region, + /// clipping any geometry that overflows its bounds. Any transformations + /// performed are local to the provided closure. + /// + /// This method is useful to perform drawing operations that need to be + /// clipped. + #[inline] + fn with_clip<R>( + &mut self, + region: Rectangle, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + let mut frame = self.draft(region.size()); + + let result = f(&mut frame); + + let origin = Point::new(region.x, region.y); + + self.paste(frame, origin); + + result + } + + /// Creates a new [`Frame`] with the given [`Size`]. + /// + /// Draw its contents back to this [`Frame`] with [`paste`]. + /// + /// [`paste`]: Self::paste + fn draft(&mut self, size: Size) -> Self; + + /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. + fn paste(&mut self, frame: Self, at: Point); + + /// Applies a translation to the current transform of the [`Frame`]. + fn translate(&mut self, translation: Vector); + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + fn rotate(&mut self, angle: impl Into<Radians>); + + /// Applies a uniform scaling to the current transform of the [`Frame`]. + fn scale(&mut self, scale: impl Into<f32>); + + /// Applies a non-uniform scaling to the current transform of the [`Frame`]. + fn scale_nonuniform(&mut self, scale: impl Into<Vector>); +} + +pub trait Geometry: Sized { + type Cache; + + fn load(cache: &Self::Cache) -> Self; + + fn cache(self) -> Self::Cache; +} + +/// A simple cache that stores generated [`Geometry`] to avoid recomputation. +/// +/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer +/// change or it is explicitly cleared. +pub struct Cache<Renderer> +where + Renderer: self::Renderer, +{ + state: RefCell<State<Renderer::Geometry>>, +} + +impl<Renderer> Cache<Renderer> +where + Renderer: self::Renderer, +{ + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + state: RefCell::new(State::Empty), + } + } + + /// Clears the [`Cache`], forcing a redraw the next time it is used. + pub fn clear(&self) { + *self.state.borrow_mut() = State::Empty; + } + + /// Draws [`Geometry`] using the provided closure and stores it in the + /// [`Cache`]. + /// + /// The closure will only be called when + /// - the bounds have changed since the previous draw call. + /// - the [`Cache`] is empty or has been explicitly cleared. + /// + /// Otherwise, the previously stored [`Geometry`] will be returned. The + /// [`Cache`] is not cleared in this case. In other words, it will keep + /// returning the stored [`Geometry`] if needed. + pub fn draw( + &self, + renderer: &Renderer, + bounds: Size, + draw_fn: impl FnOnce(&mut Renderer::Frame), + ) -> Renderer::Geometry { + use std::ops::Deref; + + if let State::Filled { + bounds: cached_bounds, + geometry, + } = self.state.borrow().deref() + { + if *cached_bounds == bounds { + return Geometry::load(geometry); + } + } + + let mut frame = frame(renderer, bounds); + draw_fn(&mut frame); + + let geometry = frame.into().cache(); + let result = Geometry::load(&geometry); + + *self.state.borrow_mut() = State::Filled { bounds, geometry }; + + result + } +} + +impl<Renderer> std::fmt::Debug for Cache<Renderer> +where + Renderer: self::Renderer, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let state = self.state.borrow(); + + match *state { + State::Empty => write!(f, "Cache::Empty"), + State::Filled { bounds, .. } => { + write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") + } + } + } +} + +impl<Renderer> Default for Cache<Renderer> +where + Renderer: self::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +enum State<Geometry> +where + Geometry: self::Geometry, +{ + Empty, + Filled { + bounds: Size, + geometry: Geometry::Cache, + }, +} + +impl<T> Geometry for Primitive<T> { + type Cache = Arc<Self>; + + fn load(cache: &Arc<Self>) -> Self { + Self::Cache { + content: cache.clone(), + } + } - /// Draws the given layers of [`Self::Geometry`]. - fn draw(&mut self, layers: Vec<Self::Geometry>); + fn cache(self) -> Arc<Self> { + Arc::new(self) + } } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index aa9d00e8..6d0862ad 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,8 +9,8 @@ )] #![forbid(rust_2018_idioms)] #![deny( - missing_debug_implementations, - missing_docs, + // missing_debug_implementations, + // missing_docs, unsafe_code, unused_results, rustdoc::broken_intra_doc_links diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index 041986cf..5be3ee5b 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -1,6 +1,6 @@ //! Draw triangles! use crate::color; -use crate::core::{Rectangle, Size}; +use crate::core::{self, Rectangle, Size}; use crate::gradient; use crate::Damage; @@ -74,3 +74,7 @@ pub struct GradientVertex2D { /// The packed vertex data of the gradient. pub gradient: gradient::Packed, } + +pub trait Renderer: core::Renderer { + fn draw_mesh(&mut self, mesh: Mesh); +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index e7154385..3b21aa11 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -8,8 +8,9 @@ use crate::core::text::Text; use crate::core::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; +use crate::mesh; use crate::text; -use crate::Primitive; +use crate::{Mesh, Primitive}; use std::borrow::Cow; @@ -20,6 +21,7 @@ pub struct Renderer<B: Backend> { default_font: Font, default_text_size: Pixels, primitives: Vec<Primitive<B::Primitive>>, + stack: Vec<Vec<Primitive<B::Primitive>>>, } impl<B: Backend> Renderer<B> { @@ -34,6 +36,7 @@ impl<B: Backend> Renderer<B> { default_font, default_text_size, primitives: Vec::new(), + stack: Vec::new(), } } @@ -56,59 +59,45 @@ impl<B: Backend> Renderer<B> { f(&mut self.backend, &self.primitives) } - /// Starts recording a new layer. - pub fn start_layer(&mut self) -> Vec<Primitive<B::Primitive>> { - std::mem::take(&mut self.primitives) - } - - /// Ends the recording of a layer. - pub fn end_layer( - &mut self, - primitives: Vec<Primitive<B::Primitive>>, - bounds: Rectangle, - ) { - let layer = std::mem::replace(&mut self.primitives, primitives); - - self.primitives.push(Primitive::group(layer).clip(bounds)); - } - - /// Starts recording a translation. - pub fn start_transformation(&mut self) -> Vec<Primitive<B::Primitive>> { - std::mem::take(&mut self.primitives) - } - - /// Ends the recording of a translation. - pub fn end_transformation( + #[cfg(feature = "geometry")] + pub fn draw_geometry<Geometry>( &mut self, - primitives: Vec<Primitive<B::Primitive>>, - transformation: Transformation, - ) { - let layer = std::mem::replace(&mut self.primitives, primitives); - - self.primitives - .push(Primitive::group(layer).transform(transformation)); + layers: impl IntoIterator<Item = Geometry>, + ) where + Geometry: Into<Primitive<B::Primitive>>, + { + for layer in layers { + self.draw_primitive(layer.into()); + } } } impl<B: Backend> iced_core::Renderer for Renderer<B> { - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { - let current = self.start_layer(); + fn start_layer(&mut self) { + self.stack.push(std::mem::take(&mut self.primitives)); + } - f(self); + fn end_layer(&mut self, bounds: Rectangle) { + let layer = std::mem::replace( + &mut self.primitives, + self.stack.pop().expect("a layer should be recording"), + ); - self.end_layer(current, bounds); + self.primitives.push(Primitive::group(layer).clip(bounds)); } - fn with_transformation( - &mut self, - transformation: Transformation, - f: impl FnOnce(&mut Self), - ) { - let current = self.start_transformation(); + fn start_transformation(&mut self) { + self.stack.push(std::mem::take(&mut self.primitives)); + } - f(self); + fn end_transformation(&mut self, transformation: Transformation) { + let layer = std::mem::replace( + &mut self.primitives, + self.stack.pop().expect("a layer should be recording"), + ); - self.end_transformation(current, transformation); + self.primitives + .push(Primitive::group(layer).transform(transformation)); } fn fill_quad( @@ -250,3 +239,34 @@ where }); } } + +impl<B: Backend> mesh::Renderer for Renderer<B> { + fn draw_mesh(&mut self, mesh: Mesh) { + match B::Primitive::try_from(mesh) { + Ok(primitive) => { + self.draw_primitive(Primitive::Custom(primitive)); + } + Err(error) => { + log::warn!("mesh primitive could not be drawn: {error:?}"); + } + } + } +} + +#[cfg(feature = "geometry")] +impl<B> crate::geometry::Renderer for Renderer<B> +where + B: Backend + crate::geometry::Backend, + B::Frame: crate::geometry::Frame<Geometry = Primitive<B::Primitive>>, +{ + type Frame = B::Frame; + type Geometry = Primitive<B::Primitive>; + + fn new_frame(&self, size: Size) -> Self::Frame { + self.backend.new_frame(size) + } + + fn draw_geometry(&mut self, geometry: Self::Geometry) { + self.draw_primitive(geometry); + } +} 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<dyn crate::custom::Compositor>), -} - -pub enum Surface { - TinySkia(iced_tiny_skia::window::Surface), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::window::Surface<'static>), - #[cfg(feature = "custom")] - Custom(Box<dyn crate::custom::Surface>), -} - -impl crate::graphics::Compositor for Compositor { - type Settings = Settings; - type Renderer = Renderer; - type Surface = Surface; - - fn new<W: Window + Clone>( - settings: Self::Settings, - compatible_window: W, - ) -> impl Future<Output = Result<Self, Error>> { - 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<W: Window + Clone>( - &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<T: AsRef<str>>( - &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<T: AsRef<str>>( - &mut self, - renderer: &mut Self::Renderer, - surface: &mut Self::Surface, - viewport: &Viewport, - background_color: Color, - overlay: &[T], - ) -> Vec<u8> { - 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<Self> { - vec![ - #[cfg(feature = "wgpu")] - Self::Wgpu, - Self::TinySkia, - ] - } - - fn list_from_env() -> Option<Vec<Self>> { - 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<W: Window>( - self, - settings: Settings, - _compatible_window: W, - ) -> Result<Compositor, Error> { - 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<dyn Frame>; - 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<L, R> +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<L, R> Renderer<L, R> +where + L: core::Renderer, + R: core::Renderer, +{ + #[cfg(feature = "geometry")] + pub fn draw_geometry<Geometry>( + &mut self, + layers: impl IntoIterator<Item = Geometry>, + ) where + L: graphics::geometry::Renderer, + R: graphics::geometry::Renderer, + + Geometry: Into<geometry::Geometry<L::Geometry, R::Geometry>>, + { + use graphics::geometry::Renderer; + + for layer in layers { + <Self as Renderer>::draw_geometry(self, layer.into()); + } + } +} + +impl<L, R> core::Renderer for Renderer<L, R> +where + L: core::Renderer, + R: core::Renderer, +{ + fn fill_quad( + &mut self, + quad: renderer::Quad, + background: impl Into<Background>, + ) { + 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<L, R> core::text::Renderer for Renderer<L, R> +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<L, R> image::Renderer for Renderer<L, R> +where + L: image::Renderer, + R: image::Renderer<Handle = L::Handle>, +{ + type Handle = L::Handle; + + fn measure_image(&self, handle: &Self::Handle) -> Size<u32> { + 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<L, R> svg::Renderer for Renderer<L, R> +where + L: svg::Renderer, + R: svg::Renderer, +{ + fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> { + delegate!(self, renderer, renderer.measure_svg(handle)) + } + + fn draw_svg( + &mut self, + handle: svg::Handle, + color: Option<Color>, + bounds: Rectangle, + ) { + delegate!(self, renderer, renderer.draw_svg(handle, color, bounds)); + } +} + +impl<L, R> mesh::Renderer for Renderer<L, R> +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<L, R> +where + L: graphics::Compositor, + R: graphics::Compositor, +{ + Left(L), + Right(R), +} + +pub enum Surface<L, R> { + Left(L), + Right(R), +} + +impl<L, R> graphics::Compositor for Compositor<L, R> +where + L: graphics::Compositor, + R: graphics::Compositor, + L::Settings: From<crate::Settings>, + R::Settings: From<crate::Settings>, +{ + type Settings = crate::Settings; + type Renderer = Renderer<L::Renderer, R::Renderer>; + type Surface = Surface<L::Surface, R::Surface>; + + async fn new<W: compositor::Window + Clone>( + settings: Self::Settings, + compatible_window: W, + ) -> Result<Self, graphics::Error> { + 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<W: compositor::Window + Clone>( + &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<T: AsRef<str>>( + &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<T: AsRef<str>>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &graphics::Viewport, + background_color: Color, + overlay: &[T], + ) -> Vec<u8> { + 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<L, R> iced_wgpu::primitive::pipeline::Renderer for Renderer<L, R> +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<L, R> geometry::Renderer for Renderer<L, R> + where + L: geometry::Renderer, + R: geometry::Renderer, + { + type Geometry = Geometry<L::Geometry, R::Geometry>; + type Frame = Frame<L::Frame, R::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<L, R> { + Left(L), + Right(R), + } + + impl<L, R> geometry::Geometry for Geometry<L, R> + where + L: geometry::Geometry, + R: geometry::Geometry, + { + type Cache = Geometry<L::Cache, R::Cache>; + + 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<L, R> { + Left(L), + Right(R), + } + + impl<L, R> geometry::Frame for Frame<L, R> + where + L: geometry::Frame, + R: geometry::Frame, + { + type Geometry = Geometry<L::Geometry, R::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<Fill>) { + delegate!(self, frame, frame.fill(path, fill)); + } + + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into<Fill>, + ) { + delegate!(self, frame, frame.fill_rectangle(top_left, size, fill)); + } + + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { + delegate!(self, frame, frame.stroke(path, stroke)); + } + + fn fill_text(&mut self, text: impl Into<Text>) { + 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<Radians>) { + delegate!(self, frame, frame.rotate(angle)); + } + + fn scale(&mut self, scale: impl Into<f32>) { + delegate!(self, frame, frame.scale(scale)); + } + + fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { + delegate!(self, frame, frame.scale_nonuniform(scale)); + } + } + + impl<L, R> From<Frame<L, R>> for Geometry<L::Geometry, R::Geometry> + where + L: geometry::Frame, + R: geometry::Frame, + { + fn from(frame: Frame<L, R>) -> 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<dyn custom::Renderer>), -} - -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<Background>, - ) { - 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<u32> { - 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<u32> { - delegate!(self, renderer, renderer.measure_svg(handle)) - } - - fn draw_svg( - &mut self, - handle: crate::core::svg::Handle, - color: Option<crate::core::Color>, - 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<iced_wgpu::Renderer, iced_tiny_skia::Renderer>; - fn draw(&mut self, layers: Vec<Self::Geometry>) { - 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<Settings> 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<Settings> 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<Fill>) { + fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { 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<Stroke<'a>>) { + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { 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<Text>) { + fn fill_text(&mut self, text: impl Into<Text>) { 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<Radians>) { + fn rotate(&mut self, angle: impl Into<Radians>) { self.transform = self.transform.pre_concat( tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()), ); } - pub fn scale(&mut self, scale: impl Into<f32>) { + fn scale(&mut self, scale: impl Into<f32>) { let scale = scale.into(); self.scale_nonuniform(Vector { x: scale, y: scale }); } - pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { + fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { 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<Frame> 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<Custom>; @@ -42,3 +42,11 @@ impl Damage for Custom { } } } + +impl TryFrom<Mesh> for Custom { + type Error = &'static str; + + fn try_from(_mesh: Mesh) -> Result<Self, Self::Error> { + 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<mesh::SolidVertex2D, u32>), - Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>), -} - -struct BufferStack { - stack: Vec<Buffer>, -} - -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<dyn tessellation::FillGeometryBuilder + 'a> { - 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<Primitive> { + 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<dyn tessellation::StrokeGeometryBuilder + 'a> { - 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<Transform>, - 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<Fill>) { + fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { 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<Stroke<'a>>) { + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { 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<Text>) { + fn fill_text(&mut self, text: impl Into<Text>) { 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<R>(&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<Radians>) { + 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<f32>) { + 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<R>( - &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<Vector>) { + 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<Frame> 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<mesh::SolidVertex2D, u32>), + Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>), +} + +struct BufferStack { + stack: Vec<Buffer>, +} + +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<Radians>) { - 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<f32>) { - let scale = scale.into(); + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> { + 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<dyn tessellation::StrokeGeometryBuilder + 'a> { + 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<Vector>) { - let scale = scale.into(); +#[derive(Debug)] +struct Transforms { + previous: Vec<Transform>, + 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<Primitive> { - 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<Mesh> for Custom { + type Error = &'static str; + + fn try_from(mesh: Mesh) -> Result<Self, Self::Error> { + 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<Renderer = crate::Renderer> = geometry::Cache<Renderer>; + /// 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<Geometry>{ +/// 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<Renderer::Geometry>; + ); /// 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<Renderer::Geometry> { - 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<Message, Theme, Renderer> _cursor: mouse::Cursor, _viewport: &Rectangle, ) { + use canvas::Frame; + let state = tree.state.downcast_ref::<State>(); let bounds = layout.bounds(); @@ -142,7 +143,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer> 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<qrcode::Color>, width: usize, - cache: canvas::Cache, + cache: canvas::Cache<Renderer>, } 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<A, E, C>( settings: Settings<A::Flags>, - compositor_settings: C::Settings, + compositor_settings: impl Into<C::Settings>, ) -> 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<A, E, C>( settings: Settings<A::Flags>, - compositor_settings: C::Settings, + compositor_settings: impl Into<C::Settings>, ) -> 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 <hector@hecrj.dev> 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<u32>; - - fn draw_image( - &mut self, - handle: image::Handle, - filter_method: image::FilterMethod, - bounds: Rectangle, - ); - - fn measure_svg(&self, handle: &svg::Handle) -> Size<u32>; - - fn draw_svg( - &mut self, - handle: crate::core::svg::Handle, - color: Option<crate::core::Color>, - bounds: Rectangle, - ); - - #[cfg(feature = "geometry")] - fn new_frame(&self, size: Size) -> Box<dyn Frame>; - - #[cfg(feature = "geometry")] - fn draw_geometry(&mut self, geometry: Box<dyn Geometry>); - - 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<dyn Frame>, origin: Point); - - fn into_geometry(self: Box<Self>) -> Box<dyn Geometry>; -} - -#[cfg(feature = "geometry")] -pub trait Geometry: std::any::Any + std::fmt::Debug { - fn transform( - self: Box<Self>, - transformation: Transformation, - ) -> Box<dyn Geometry>; - - fn cache(self: Box<Self>) -> std::sync::Arc<dyn Geometry>; - - fn load(self: std::sync::Arc<Self>) -> Box<dyn Geometry>; -} - -pub trait Compositor: std::any::Any { - fn create_renderer(&self) -> Box<dyn Renderer>; - - fn create_surface( - &mut self, - window: Box<dyn compositor::Window>, - width: u32, - height: u32, - ) -> Box<dyn Surface>; - - 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 <hector@hecrj.dev> 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<Message> canvas::Program<Message> for Arc { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec<Geometry> { let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.palette(); @@ -102,6 +104,6 @@ impl<Message> canvas::Program<Message> 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<Geometry> { 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<Message> canvas::Program<Message> for Clock { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec<Geometry> { let clock = self.clock.draw(renderer, bounds.size(), |frame| { let palette = theme.extended_palette(); @@ -163,7 +165,7 @@ impl<Message> canvas::Program<Message> 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<Message> canvas::Program<Message> for Theme { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &iced::Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec<Geometry> { 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<Geometry> { 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<Length> + Copy) -> Element<'a, Message> { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec<canvas::Geometry> { use canvas::Frame; let mut frame = canvas::frame(renderer, bounds.size()); @@ -309,7 +309,7 @@ fn square<'a>(size: impl Into<Length> + 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<Message> for Multitouch { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec<Geometry> { use canvas::Frame; let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| { @@ -156,6 +156,6 @@ impl canvas::Program<Message> 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<Message> for SierpinskiGraph { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec<Geometry> { use canvas::Frame; let geom = self.cache.draw(renderer, bounds.size(), |frame| { @@ -141,7 +141,7 @@ impl canvas::Program<Message> 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<Message> canvas::Program<Message> for State { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec<Geometry> { use canvas::Frame; use std::f32::consts::PI; @@ -198,7 +198,7 @@ impl<Message> canvas::Program<Message> 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<Message> canvas::Program<Message> for State { fn draw( &self, _state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) { + ) -> Vec<canvas::Geometry> { use canvas::Frame; let geometry = self.cache.draw(renderer, bounds.size(), |frame| { @@ -155,7 +155,7 @@ impl<Message> canvas::Program<Message> 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<Renderer = crate::Renderer> = geometry::Cache<Renderer>; +/// The geometry supported by a renderer. +pub type Geometry<Renderer = crate::Renderer> = + <Renderer as geometry::Renderer>::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<Renderer = crate::Renderer> = geometry::Cache<Renderer>; /// 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<Geometry> { /// // We prepare a new `Frame` /// let mut frame = frame(renderer, bounds.size()); /// @@ -58,7 +62,7 @@ pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>; /// 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<Geometry<Renderer>>; /// 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<Geometry<Renderer>> { + 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 <hector@hecrj.dev> 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<Length> + Copy) -> Element<'a, Message> { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<canvas::Geometry> { - 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<Length> + 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<Message> for Multitouch { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<Geometry> { - 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<Message> for SierpinskiGraph { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<Geometry> { - 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<Message> canvas::Program<Message> for State { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<Geometry> { - 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<Message> canvas::Program<Message> for State { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<canvas::Geometry> { - 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: &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<Geometry = Self::Geometry>; + type Frame: frame::Backend<Geometry = Self::Geometry>; 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<Self::Geometry> { - /// The kind of geometry this frame can draw. - type Geometry: Geometry; - - /// Returns the width of the [`Frame`]. - fn width(&self) -> f32; - - /// Returns the height of the [`Frame`]. - fn height(&self) -> f32; - - /// Returns the dimensions of the [`Frame`]. - fn size(&self) -> Size; - - /// Returns the coordinate of the center of the [`Frame`]. - fn center(&self) -> Point; - - /// Draws the given [`Path`] on the [`Frame`] by filling it with the - /// provided style. - fn fill(&mut self, path: &Path, fill: impl Into<Fill>); - - /// Draws an axis-aligned rectangle given its top-left corner coordinate and - /// its `Size` on the [`Frame`] by filling it with the provided style. - fn fill_rectangle( - &mut self, - top_left: Point, - size: Size, - fill: impl Into<Fill>, - ); - - /// Draws the stroke of the given [`Path`] on the [`Frame`] with the - /// provided style. - fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>); - - /// Draws the characters of the given [`Text`] on the [`Frame`], filling - /// them with the given color. - /// - /// __Warning:__ Text currently does not work well with rotations and scale - /// transforms! The position will be correctly transformed, but the - /// resulting glyphs will not be rotated or scaled properly. - /// - /// Additionally, all text will be rendered on top of all the layers of - /// a `Canvas`. Therefore, it is currently only meant to be used for - /// overlays, which is the most common use case. - /// - /// Support for vectorial text is planned, and should address all these - /// limitations. - fn fill_text(&mut self, text: impl Into<Text>); - - /// Stores the current transform of the [`Frame`] and executes the given - /// drawing operations, restoring the transform afterwards. - /// - /// This method is useful to compose transforms and perform drawing - /// operations in different coordinate systems. - #[inline] - fn with_save<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { - self.push_transform(); - - let result = f(self); - - self.pop_transform(); - - result - } - - /// Pushes the current transform in the transform stack. - fn push_transform(&mut self); - - /// Pops a transform from the transform stack and sets it as the current transform. - fn pop_transform(&mut self); - - /// Executes the given drawing operations within a [`Rectangle`] region, - /// clipping any geometry that overflows its bounds. Any transformations - /// performed are local to the provided closure. - /// - /// This method is useful to perform drawing operations that need to be - /// clipped. - #[inline] - fn with_clip<R>( - &mut self, - region: Rectangle, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - let mut frame = self.draft(region.size()); - - let result = f(&mut frame); - - let origin = Point::new(region.x, region.y); - - self.paste(frame, origin); - - result - } - - /// Creates a new [`Frame`] with the given [`Size`]. - /// - /// Draw its contents back to this [`Frame`] with [`paste`]. - /// - /// [`paste`]: Self::paste - fn draft(&mut self, size: Size) -> Self; - - /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. - fn paste(&mut self, frame: Self, at: Point); - - /// Applies a translation to the current transform of the [`Frame`]. - fn translate(&mut self, translation: Vector); - - /// Applies a rotation in radians to the current transform of the [`Frame`]. - fn rotate(&mut self, angle: impl Into<Radians>); - - /// Applies a uniform scaling to the current transform of the [`Frame`]. - fn scale(&mut self, scale: impl Into<f32>); - - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. - fn scale_nonuniform(&mut self, scale: impl Into<Vector>); -} - pub trait Geometry: Sized { type Cache; @@ -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<Renderer> -where - Renderer: self::Renderer, -{ - state: RefCell<State<Renderer::Geometry>>, -} - -impl<Renderer> Cache<Renderer> -where - Renderer: self::Renderer, -{ - /// Creates a new empty [`Cache`]. - pub fn new() -> Self { - Cache { - state: RefCell::new(State::Empty), - } - } - - /// Clears the [`Cache`], forcing a redraw the next time it is used. - pub fn clear(&self) { - *self.state.borrow_mut() = State::Empty; - } - - /// Draws [`Geometry`] using the provided closure and stores it in the - /// [`Cache`]. - /// - /// The closure will only be called when - /// - the bounds have changed since the previous draw call. - /// - the [`Cache`] is empty or has been explicitly cleared. - /// - /// Otherwise, the previously stored [`Geometry`] will be returned. The - /// [`Cache`] is not cleared in this case. In other words, it will keep - /// returning the stored [`Geometry`] if needed. - pub fn draw( - &self, - renderer: &Renderer, - bounds: Size, - draw_fn: impl FnOnce(&mut Renderer::Frame), - ) -> Renderer::Geometry { - use std::ops::Deref; - - if let State::Filled { - bounds: cached_bounds, - geometry, - } = self.state.borrow().deref() - { - if *cached_bounds == bounds { - return Geometry::load(geometry); - } - } - - let mut frame = frame(renderer, bounds); - draw_fn(&mut frame); - - let geometry = frame.into().cache(); - let result = Geometry::load(&geometry); - - *self.state.borrow_mut() = State::Filled { bounds, geometry }; - - result - } -} - -impl<Renderer> std::fmt::Debug for Cache<Renderer> -where - Renderer: self::Renderer, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let state = self.state.borrow(); - - match *state { - State::Empty => write!(f, "Cache::Empty"), - State::Filled { bounds, .. } => { - write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") - } - } - } -} - -impl<Renderer> Default for Cache<Renderer> -where - Renderer: self::Renderer, -{ - fn default() -> Self { - Self::new() - } -} - -enum State<Geometry> -where - Geometry: self::Geometry, -{ - Empty, - Filled { - bounds: Size, - geometry: Geometry::Cache, - }, -} - -impl<T> Geometry for Primitive<T> { - type Cache = Arc<Self>; - - fn load(cache: &Arc<Self>) -> Self { - Self::Cache { - content: cache.clone(), - } - } - - fn cache(self) -> Arc<Self> { - 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<Renderer> +where + Renderer: geometry::Renderer, +{ + state: RefCell<State<Renderer::Geometry>>, +} + +impl<Renderer> Cache<Renderer> +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>), + ) -> 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<Renderer> std::fmt::Debug for Cache<Renderer> +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<Renderer> Default for Cache<Renderer> +where + Renderer: geometry::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +enum State<Geometry> +where + Geometry: self::Geometry, +{ + Empty, + Filled { + bounds: Size, + geometry: Geometry::Cache, + }, +} + +impl<T> Geometry for Primitive<T> { + type Cache = Arc<Self>; + + fn load(cache: &Arc<Self>) -> Self { + Self::Cache { + content: cache.clone(), + } + } + + fn cache(self) -> Arc<Self> { + 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<Renderer> +where + Renderer: geometry::Renderer, +{ + raw: Renderer::Frame, +} + +impl<Renderer> Frame<Renderer> +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<Fill>) { + 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<Fill>, + ) { + 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<Stroke<'a>>) { + 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<Text>) { + 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<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + self.push_transform(); + + let result = f(self); + + self.pop_transform(); + + result + } + + /// Pushes the current transform in the transform stack. + 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<R>( + &mut self, + region: Rectangle, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + let mut frame = self.draft(region.size()); + + let result = f(&mut frame); + + let origin = Point::new(region.x, region.y); + + self.paste(frame, origin); + + result + } + + /// Creates a new [`Frame`] with the given [`Size`]. + /// + /// Draw its contents back to this [`Frame`] with [`paste`]. + /// + /// [`paste`]: Self::paste + 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<Radians>) { + self.raw.rotate(angle); + } + + /// Applies a uniform scaling to the current transform of the [`Frame`]. + pub fn scale(&mut self, scale: impl Into<f32>) { + 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<Vector>) { + 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<Radians>); + fn scale(&mut self, scale: impl Into<f32>); + fn scale_nonuniform(&mut self, scale: impl Into<Vector>); + + 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<Stroke<'a>>); + + fn fill(&mut self, path: &Path, fill: impl Into<Fill>); + fn fill_text(&mut self, text: impl Into<Text>); + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into<Fill>, + ); + + 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<B: Backend> mesh::Renderer for Renderer<B> { impl<B> crate::geometry::Renderer for Renderer<B> where B: Backend + crate::geometry::Backend, - B::Frame: crate::geometry::Frame<Geometry = Primitive<B::Primitive>>, + B::Frame: + crate::geometry::frame::Backend<Geometry = Primitive<B::Primitive>>, { type Frame = B::Frame; type Geometry = Primitive<B::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<L, R> geometry::Frame for Frame<L, R> + impl<L, R> geometry::frame::Backend for Frame<L, R> where - L: geometry::Frame, - R: geometry::Frame, + L: geometry::frame::Backend, + R: geometry::frame::Backend, { type Geometry = Geometry<L::Geometry, R::Geometry>; @@ -545,17 +545,11 @@ mod geometry { fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { delegate!(self, frame, frame.scale_nonuniform(scale)); } - } - impl<L, R> From<Frame<L, R>> for Geometry<L::Geometry, R::Geometry> - where - L: geometry::Frame, - R: geometry::Frame, - { - fn from(frame: Frame<L, R>) -> 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<dyn crate::custom::Geometry>), -} - -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<dyn crate::custom::Frame>), -} - -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<Fill>) { - 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<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<Stroke<'a>>) { - 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<Text>) { - 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<R>(&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<R>( - &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<Radians>) { - 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<f32>) { - 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<Vector>) { - 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<State>, -} - -#[derive(Debug, Default)] -enum State { - #[default] - Empty, - Filled { - bounds: Size, - primitive: Internal, - }, -} - -#[derive(Debug, Clone)] -enum Internal { - TinySkia(Arc<iced_tiny_skia::Primitive>), - #[cfg(feature = "wgpu")] - Wgpu(Arc<iced_wgpu::Primitive>), - #[cfg(feature = "custom")] - Custom(Arc<dyn crate::custom::Geometry>), -} - -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<Frame> 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<Frame> 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<Renderer = crate::Renderer> = geometry::Cache<Renderer>; pub type Geometry<Renderer = crate::Renderer> = <Renderer as geometry::Renderer>::Geometry; +/// The frame supported by a renderer. +pub type Frame<Renderer = crate::Renderer> = geometry::Frame<Renderer>; + /// 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<Renderer = crate::Renderer> = /// /// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry> { /// // 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<Renderer = crate::Renderer> = /// 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<Message, Theme, Renderer> _cursor: mouse::Cursor, _viewport: &Rectangle, ) { - use canvas::Frame; - let state = tree.state.downcast_ref::<State>(); let bounds = layout.bounds(); -- cgit From 85800c99ab285efd244c0addfdcf3c732a98de1d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector@hecrj.dev> 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<T> Cached for Primitive<T> { + type Cache = Arc<Self>; + + fn load(cache: &Arc<Self>) -> Self { + Self::Cache { + content: cache.clone(), + } + } + + fn cache(self) -> Arc<Self> { + 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<Geometry = Self::Geometry>; + /// 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<Geometry> where - Geometry: self::Geometry, + Geometry: Cached, { Empty, Filled { @@ -107,17 +106,3 @@ where geometry: Geometry::Cache, }, } - -impl<T> Geometry for Primitive<T> { - type Cache = Arc<Self>; - - fn load(cache: &Arc<Self>) -> Self { - Self::Cache { - content: cache.clone(), - } - } - - fn cache(self) -> Arc<Self> { - 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<Renderer> where Renderer: geometry::Renderer, @@ -13,6 +16,7 @@ impl<Renderer> Frame<Renderer> 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<B: Backend> Renderer<B> { ) -> O { f(&mut self.backend, &self.primitives) } - - #[cfg(feature = "geometry")] - pub fn draw_geometry<Geometry>( - &mut self, - layers: impl IntoIterator<Item = Geometry>, - ) where - Geometry: Into<Primitive<B::Primitive>>, - { - for layer in layers { - self.draw_primitive(layer.into()); - } - } } impl<B: Backend> iced_core::Renderer for Renderer<B> { 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<L, R> geometry::Renderer for Renderer<L, R> where @@ -432,10 +433,10 @@ mod geometry { Right(R), } - impl<L, R> geometry::Geometry for Geometry<L, R> + impl<L, R> Cached for Geometry<L, R> where - L: geometry::Geometry, - R: geometry::Geometry, + L: Cached, + R: Cached, { type Cache = Geometry<L::Cache, R::Cache>; 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 <hector@hecrj.dev> 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<Text>) { self.raw.fill_text(text); } -- cgit From 1f13a91361258a1607c71f4840a26a6437f88612 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector@hecrj.dev> 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<Item = super::Element<'a, Message>>) -> 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<Message> { 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<u32> { + 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<T = f32> { /// 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<Mesh, Error = &'static str>; } 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<W: Window + Clone>( + _settings: Self::Settings, + _compatible_window: W, + ) -> Result<Self, Error> { + Ok(()) + } + + fn create_renderer(&self) -> Self::Renderer {} + + fn create_surface<W: Window + Clone>( + &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<T: AsRef<str>>( + &mut self, + _renderer: &mut Self::Renderer, + _surface: &mut Self::Surface, + _viewport: &Viewport, + _background_color: Color, + _overlay: &[T], + ) -> Result<(), SurfaceError> { + Ok(()) + } + + fn screenshot<T: AsRef<str>>( + &mut self, + _renderer: &mut Self::Renderer, + _surface: &mut Self::Surface, + _viewport: &Viewport, + _background_color: Color, + _overlay: &[T], + ) -> Vec<u8> { + 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<L, R> -where - L: core::Renderer, - R: core::Renderer, -{ +pub enum Renderer<L, R> { Left(L), Right(R), } @@ -26,29 +22,6 @@ macro_rules! delegate { }; } -impl<L, R> Renderer<L, R> -where - L: core::Renderer, - R: core::Renderer, -{ - #[cfg(feature = "geometry")] - pub fn draw_geometry<Geometry>( - &mut self, - layers: impl IntoIterator<Item = Geometry>, - ) where - L: graphics::geometry::Renderer, - R: graphics::geometry::Renderer, - - Geometry: Into<geometry::Geometry<L::Geometry, R::Geometry>>, - { - use graphics::geometry::Renderer; - - for layer in layers { - <Self as Renderer>::draw_geometry(self, layer.into()); - } - } -} - impl<L, R> core::Renderer for Renderer<L, R> 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<iced_wgpu::Renderer, iced_tiny_skia::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<Settings> for iced_tiny_skia::Settings { fn from(settings: Settings) -> Self { Self { @@ -48,3 +49,7 @@ impl From<Settings> for iced_wgpu::Settings { } } } + +impl From<Settings> 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<Message, Theme, Renderer> 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 <hector@hecrj.dev> 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<u32> { + Size::default() + } + + fn draw_svg( + &mut self, + _handle: svg::Handle, + _color: Option<Color>, + _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<T> Cached for Primitive<T> { 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<Radians>) {} + fn scale(&mut self, _scale: impl Into<f32>) {} + fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {} + + 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<Stroke<'a>>) {} + + fn fill(&mut self, _path: &Path, _fill: impl Into<Fill>) {} + fn fill_text(&mut self, _text: impl Into<Text>) {} + fn fill_rectangle( + &mut self, + _top_left: Point, + _size: Size, + _fill: impl Into<Fill>, + ) { + } + + 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 <hector@hecrj.dev> 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<Renderer = Renderer<Self>>; /// The custom kind of primitives this [`Backend`] supports. type Primitive: TryFrom<Mesh, Error = &'static str>; 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<W: Window + Clone>( - settings: Self::Settings, + settings: Settings, compatible_window: W, ) -> impl Future<Output = Result<Self, Error>>; @@ -93,6 +90,12 @@ impl<T> Window for T where { } +/// A renderer that supports composition. +pub trait Renderer: core::Renderer { + /// The compositor of the renderer. + type Compositor: Compositor<Renderer = Self>; +} + /// 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<W: Window + Clone>( - _settings: Self::Settings, + _settings: Settings, _compatible_window: W, ) -> Result<Self, Error> { 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<B> compositor::Renderer for Renderer<B> +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<Antialiasing>, +} + +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<L, R> graphics::Compositor for Compositor<L, R> where L: graphics::Compositor, R: graphics::Compositor, - L::Settings: From<crate::Settings>, - R::Settings: From<crate::Settings>, { - type Settings = crate::Settings; type Renderer = Renderer<L::Renderer, R::Renderer>; type Surface = Surface<L::Surface, R::Surface>; async fn new<W: compositor::Window + Clone>( - settings: Self::Settings, + settings: graphics::Settings, compatible_window: W, ) -> Result<Self, graphics::Error> { if let Ok(left) = L::new(settings.into(), compatible_window.clone()) @@ -528,3 +525,11 @@ mod geometry { } } } + +impl<L, R> compositor::Renderer for Renderer<L, R> +where + L: compositor::Renderer, + R: compositor::Renderer, +{ + type Compositor = Compositor<L::Compositor, R::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<Antialiasing>, -} - -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<Settings> 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<Settings> 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<Settings> 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<Self::Message>) { /// (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>, Self::Executor, - crate::renderer::Compositor, + <Self::Renderer as compositor::Renderer>::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::Message> { 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<State, Message, Theme>( +pub fn run<State, Message, Theme, Renderer>( title: impl program::Title<State> + 'static, update: impl program::Update<State, Message> + '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<State, Message, Theme>( +pub fn program<State, Message, Theme, Renderer>( title: impl Title<State>, update: impl Update<State, Message>, - view: impl for<'a> self::View<'a, State, Message, Theme>, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, ) -> Program<impl Definition<State = State, Message = Message, Theme = Theme>> where State: 'static, Message: Send + std::fmt::Debug, Theme: Default + DefaultStyle, + Renderer: compositor::Renderer + crate::core::text::Renderer, { use std::marker::PhantomData; - struct Application<State, Message, Theme, Update, View> { + struct Application<State, Message, Theme, Renderer, Update, View> { update: Update, view: View, _state: PhantomData<State>, _message: PhantomData<Message>, _theme: PhantomData<Theme>, + _renderer: PhantomData<Renderer>, } - impl<State, Message, Theme, Update, View> Definition - for Application<State, Message, Theme, Update, View> + impl<State, Message, Theme, Renderer, Update, View> Definition + for Application<State, Message, Theme, Renderer, Update, View> where Message: Send + std::fmt::Debug, Theme: Default + DefaultStyle, + Renderer: compositor::Renderer + crate::core::text::Renderer, Update: self::Update<State, Message>, - 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<Self::Message> { @@ -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<P: Definition> Program<P> { impl<P: Definition, I: Fn() -> P::State> Application for Instance<P, I> { 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<P: Definition> Program<P> { 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<P: Definition>( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = P::Executor; fn load(&self) -> Command<Self::Message> { @@ -491,7 +502,7 @@ fn with_title<P: Definition>( 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<P: Definition>( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = executor::Default; fn load(&self) -> Command<Self::Message> { @@ -551,7 +563,7 @@ fn with_load<P: Definition>( 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<P: Definition>( 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<P: Definition>( 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<P: Definition>( 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<P: Definition>( 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<P: Definition>( 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<P: Definition>( 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<Element<'_, Message>>`. -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<Element<'a, Message, Theme>>; + fn view( + &self, + state: &'a State, + ) -> impl Into<Element<'a, Message, Theme, Renderer>>; } -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<Element<'a, Message, Theme>>, + Widget: Into<Element<'a, Message, Theme, Renderer>>, { - fn view(&self, state: &'a State) -> impl Into<Element<'a, Message, Theme>> { + fn view( + &self, + state: &'a State, + ) -> impl Into<Element<'a, Message, Theme, Renderer>> { 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<graphics::Settings> 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<W: compositor::Window>( - settings: Self::Settings, + settings: graphics::Settings, compatible_window: W, ) -> impl Future<Output = Result<Self, Error>> { - 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<graphics::Settings> 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<T: AsRef<str>>( } impl graphics::Compositor for Compositor { - type Settings = Settings; type Renderer = Renderer; type Surface = wgpu::Surface<'static>; fn new<W: compositor::Window>( - settings: Self::Settings, + settings: graphics::Settings, compatible_window: W, ) -> impl Future<Output = Result<Self, Error>> { - 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<A, E, C>( settings: Settings<A::Flags>, - compositor_settings: impl Into<C::Settings>, + 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<A, E, C>( settings: Settings<A::Flags>, - compositor_settings: impl Into<C::Settings>, + 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 <hector@hecrj.dev> 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<Self, graphics::Error> { - 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 <hector@hecrj.dev> 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<Renderer = Renderer<Self>>; - /// The custom kind of primitives this [`Backend`] supports. type Primitive: TryFrom<Mesh, Error = &'static str>; + + /// The default compositor of this [`Backend`]. + type Compositor: Compositor<Renderer = Renderer<Self>>; } /// 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<T> 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<Renderer = Self>; } @@ -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<B> compositor::Renderer for Renderer<B> +impl<B> compositor::Default for Renderer<B> 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<L, R> compositor::Renderer for Renderer<L, R> +impl<L, R> compositor::Default for Renderer<L, R> where - L: compositor::Renderer, - R: compositor::Renderer, + L: compositor::Default, + R: compositor::Default, { type Compositor = Compositor<L::Compositor, R::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>, Self::Executor, - <Self::Renderer as compositor::Renderer>::Compositor, + <Self::Renderer as compositor::Default>::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<State, Message>, 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<T> 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 4f5b63f1f4cd7d3ab72289c697f4abc767114eca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector@hecrj.dev> Date: Sun, 24 Mar 2024 08:04:28 +0100 Subject: Reintroduce backend selection through `ICED_BACKEND` env var --- graphics/src/compositor.rs | 15 +++- graphics/src/error.rs | 27 ++++++- graphics/src/lib.rs | 2 +- renderer/src/fallback.rs | 51 +++++++++++-- tiny_skia/src/window/compositor.rs | 21 ++++-- wgpu/Cargo.toml | 1 + wgpu/src/settings.rs | 24 ------- wgpu/src/window/compositor.rs | 142 +++++++++++++++++++++++++------------ 8 files changed, 198 insertions(+), 85 deletions(-) diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 8c67cd16..86472a58 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -20,6 +20,18 @@ pub trait Compositor: Sized { fn new<W: Window + Clone>( settings: Settings, compatible_window: W, + ) -> impl Future<Output = Result<Self, Error>> { + Self::with_backend(settings, compatible_window, None) + } + + /// Creates a new [`Compositor`] with a backend preference. + /// + /// If the backend does not match the preference, it will return + /// [`Error::GraphicsAdapterNotFound`]. + fn with_backend<W: Window + Clone>( + _settings: Settings, + _compatible_window: W, + _backend: Option<&str>, ) -> impl Future<Output = Result<Self, Error>>; /// Creates a [`Self::Renderer`] for the [`Compositor`]. @@ -130,9 +142,10 @@ impl Compositor for () { type Renderer = (); type Surface = (); - async fn new<W: Window + Clone>( + async fn with_backend<W: Window + Clone>( _settings: Settings, _compatible_window: W, + _preffered_backend: Option<&str>, ) -> Result<Self, Error> { Ok(()) } diff --git a/graphics/src/error.rs b/graphics/src/error.rs index c6ea98a3..6ea1d3a4 100644 --- a/graphics/src/error.rs +++ b/graphics/src/error.rs @@ -1,5 +1,7 @@ +//! See what can go wrong when creating graphical backends. + /// An error that occurred while creating an application's graphical context. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum Error { /// The requested backend version is not supported. #[error("the requested backend version is not supported")] @@ -11,9 +13,30 @@ pub enum Error { /// A suitable graphics adapter or device could not be found. #[error("a suitable graphics adapter or device could not be found")] - GraphicsAdapterNotFound, + GraphicsAdapterNotFound { + /// The name of the backend where the error happened + backend: &'static str, + /// The reason why this backend could not be used + reason: Reason, + }, /// An error occurred in the context's internal backend #[error("an error occurred in the context's internal backend")] BackendError(String), + + /// Multiple errors occurred + #[error("multiple errors occurred: {0:?}")] + List(Vec<Self>), +} + +/// The reason why a graphics adapter could not be found +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Reason { + /// The backend did not match the preference + DidNotMatch { + /// The preferred backend + preferred_backend: String, + }, + /// The request to create the backend failed + RequestFailed(String), } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 2e476f8c..d7f2f439 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -18,7 +18,6 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; mod cached; -mod error; mod primitive; mod settings; mod viewport; @@ -27,6 +26,7 @@ pub mod backend; pub mod color; pub mod compositor; pub mod damage; +pub mod error; pub mod gradient; pub mod mesh; pub mod renderer; diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index ca445746..ef9cc9a9 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -204,18 +204,55 @@ where type Renderer = Renderer<L::Renderer, R::Renderer>; type Surface = Surface<L::Surface, R::Surface>; - async fn new<W: compositor::Window + Clone>( + async fn with_backend<W: compositor::Window + Clone>( settings: graphics::Settings, compatible_window: W, + backend: Option<&str>, ) -> Result<Self, graphics::Error> { - if let Ok(left) = L::new(settings, compatible_window.clone()) - .await - .map(Self::Left) - { - return Ok(left); + use std::env; + + let backends = backend + .map(str::to_owned) + .or_else(|| env::var("ICED_BACKEND").ok()); + + let mut candidates: Vec<_> = backends + .map(|backends| { + backends + .split(',') + .filter(|candidate| !candidate.is_empty()) + .map(str::to_owned) + .map(Some) + .collect() + }) + .unwrap_or_default(); + + if candidates.is_empty() { + candidates.push(None); } - R::new(settings, compatible_window).await.map(Self::Right) + let mut errors = vec![]; + + for backend in candidates.iter().map(Option::as_deref) { + match L::with_backend(settings, compatible_window.clone(), backend) + .await + { + Ok(compositor) => return Ok(Self::Left(compositor)), + Err(error) => { + errors.push(error); + } + } + + match R::with_backend(settings, compatible_window.clone(), backend) + .await + { + Ok(compositor) => return Ok(Self::Right(compositor)), + Err(error) => { + errors.push(error); + } + } + } + + Err(graphics::Error::List(errors)) } fn create_renderer(&self) -> Self::Renderer { diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 0c08097b..25c57dc1 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,11 +1,11 @@ use crate::core::{Color, Rectangle, Size}; use crate::graphics::compositor::{self, Information}; use crate::graphics::damage; -use crate::graphics::{self, Error, Viewport}; +use crate::graphics::error::{self, Error}; +use crate::graphics::{self, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; use std::collections::VecDeque; -use std::future::{self, Future}; use std::num::NonZeroU32; pub struct Compositor { @@ -28,11 +28,22 @@ impl crate::graphics::Compositor for Compositor { type Renderer = Renderer; type Surface = Surface; - fn new<W: compositor::Window>( + async fn with_backend<W: compositor::Window>( settings: graphics::Settings, compatible_window: W, - ) -> impl Future<Output = Result<Self, Error>> { - future::ready(Ok(new(settings.into(), compatible_window))) + backend: Option<&str>, + ) -> Result<Self, Error> { + match backend { + None | Some("tiny-skia") | Some("tiny_skia") => { + Ok(new(settings.into(), compatible_window)) + } + Some(backend) => Err(Error::GraphicsAdapterNotFound { + backend: "tiny-skia", + reason: error::Reason::DidNotMatch { + preferred_backend: backend.to_owned(), + }, + }), + } } fn create_renderer(&self) -> Self::Renderer { diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4a0d89f0..f6162e0f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -32,6 +32,7 @@ glyphon.workspace = true guillotiere.workspace = true log.workspace = true once_cell.workspace = true +thiserror.workspace = true wgpu.workspace = true lyon.workspace = true diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 9943aa3e..828d9e09 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -29,30 +29,6 @@ pub struct Settings { pub antialiasing: Option<Antialiasing>, } -impl Settings { - /// Creates new [`Settings`] using environment configuration. - /// - /// Specifically: - /// - /// - The `internal_backend` can be configured using the `WGPU_BACKEND` - /// environment variable. If the variable is not set, the primary backend - /// will be used. The following values are allowed: - /// - `vulkan` - /// - `metal` - /// - `dx12` - /// - `dx11` - /// - `gl` - /// - `webgpu` - /// - `primary` - pub fn from_env() -> Self { - Settings { - internal_backend: wgpu::util::backend_bits_from_env() - .unwrap_or(wgpu::Backends::all()), - ..Self::default() - } - } -} - impl Default for Settings { fn default() -> Settings { Settings { diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 74dfadba..7b769923 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -2,11 +2,10 @@ use crate::core::{Color, Size}; use crate::graphics::color; use crate::graphics::compositor; -use crate::graphics::{self, Error, Viewport}; +use crate::graphics::error; +use crate::graphics::{self, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; -use std::future::Future; - /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] pub struct Compositor { @@ -19,6 +18,32 @@ pub struct Compositor { alpha_mode: wgpu::CompositeAlphaMode, } +/// A compositor error. +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + /// The surface creation failed. + #[error("the surface creation failed: {0}")] + SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError), + /// The surface is not compatible. + #[error("the surface is not compatible")] + IncompatibleSurface, + /// No adapter was found for the options requested. + #[error("no adapter was found for the options requested: {0:?}")] + NoAdapterFound(String), + /// No device request succeeded. + #[error("no device request succeeded: {0:?}")] + RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>), +} + +impl From<Error> for graphics::Error { + fn from(error: Error) -> Self { + Self::GraphicsAdapterNotFound { + backend: "wgpu", + reason: error::Reason::RequestFailed(error.to_string()), + } + } +} + impl Compositor { /// Requests a new [`Compositor`] with the given [`Settings`]. /// @@ -26,7 +51,7 @@ impl Compositor { pub async fn request<W: compositor::Window>( settings: Settings, compatible_window: Option<W>, - ) -> Option<Self> { + ) -> Result<Self, Error> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: settings.internal_backend, ..Default::default() @@ -48,23 +73,27 @@ impl Compositor { let compatible_surface = compatible_window .and_then(|window| instance.create_surface(window).ok()); + let adapter_options = wgpu::RequestAdapterOptions { + power_preference: wgpu::util::power_preference_from_env() + .unwrap_or(if settings.antialiasing.is_none() { + wgpu::PowerPreference::LowPower + } else { + wgpu::PowerPreference::HighPerformance + }), + compatible_surface: compatible_surface.as_ref(), + force_fallback_adapter: false, + }; + let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::util::power_preference_from_env() - .unwrap_or(if settings.antialiasing.is_none() { - wgpu::PowerPreference::LowPower - } else { - wgpu::PowerPreference::HighPerformance - }), - compatible_surface: compatible_surface.as_ref(), - force_fallback_adapter: false, - }) - .await?; + .request_adapter(&adapter_options) + .await + .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?; log::info!("Selected: {:#?}", adapter.get_info()); - let (format, alpha_mode) = - compatible_surface.as_ref().and_then(|surface| { + let (format, alpha_mode) = compatible_surface + .as_ref() + .and_then(|surface| { let capabilities = surface.get_capabilities(&adapter); let mut formats = capabilities.formats.iter().copied(); @@ -96,7 +125,8 @@ impl Compositor { }; format.zip(Some(preferred_alpha)) - })?; + }) + .ok_or(Error::IncompatibleSurface)?; log::info!( "Selected format: {format:?} with alpha mode: {alpha_mode:?}" @@ -110,39 +140,46 @@ impl Compositor { let limits = [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()]; - let mut limits = limits.into_iter().map(|limits| wgpu::Limits { + let limits = limits.into_iter().map(|limits| wgpu::Limits { max_bind_groups: 2, ..limits }); - let (device, queue) = - loop { - let required_limits = limits.next()?; - let device = adapter.request_device( + let mut errors = Vec::new(); + + for required_limits in limits { + let result = adapter + .request_device( &wgpu::DeviceDescriptor { label: Some( "iced_wgpu::window::compositor device descriptor", ), required_features: wgpu::Features::empty(), - required_limits, + required_limits: required_limits.clone(), }, None, - ).await.ok(); - - if let Some(device) = device { - break Some(device); + ) + .await; + + match result { + Ok((device, queue)) => { + return Ok(Compositor { + instance, + settings, + adapter, + device, + queue, + format, + alpha_mode, + }) } - }?; - - Some(Compositor { - instance, - settings, - adapter, - device, - queue, - format, - alpha_mode, - }) + Err(error) => { + errors.push((required_limits, error)); + } + } + } + + Err(Error::RequestDeviceFailed(errors)) } /// Creates a new rendering [`Backend`] for this [`Compositor`]. @@ -163,9 +200,7 @@ pub async fn new<W: compositor::Window>( settings: Settings, compatible_window: W, ) -> Result<Compositor, Error> { - Compositor::request(settings, Some(compatible_window)) - .await - .ok_or(Error::GraphicsAdapterNotFound) + Compositor::request(settings, Some(compatible_window)).await } /// Presents the given primitives with the given [`Compositor`] and [`Backend`]. @@ -227,11 +262,28 @@ impl graphics::Compositor for Compositor { type Renderer = Renderer; type Surface = wgpu::Surface<'static>; - fn new<W: compositor::Window>( + async fn with_backend<W: compositor::Window>( settings: graphics::Settings, compatible_window: W, - ) -> impl Future<Output = Result<Self, Error>> { - new(settings.into(), compatible_window) + backend: Option<&str>, + ) -> Result<Self, graphics::Error> { + match backend { + None | Some("wgpu") => Ok(new( + Settings { + internal_backend: wgpu::util::backend_bits_from_env() + .unwrap_or(wgpu::Backends::all()), + ..settings.into() + }, + compatible_window, + ) + .await?), + Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound { + backend: "wgpu", + reason: error::Reason::DidNotMatch { + preferred_backend: backend.to_owned(), + }, + }), + } } fn create_renderer(&self) -> Self::Renderer { -- cgit From 6a4f5ac2081699f7cf20c917b367366ab49eeef1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector@hecrj.dev> Date: Sun, 24 Mar 2024 08:45:11 +0100 Subject: Remove redundant `text::Renderer` bound in `program` --- src/program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/program.rs b/src/program.rs index 705e140d..d4c2a266 100644 --- a/src/program.rs +++ b/src/program.rs @@ -426,7 +426,7 @@ pub trait Definition: Sized { type Theme: Default + DefaultStyle; /// The renderer of the program. - type Renderer: Renderer + crate::core::text::Renderer; + type Renderer: Renderer; /// The executor of the program. type Executor: Executor; -- cgit