From 188db4da48954b95a3fe79bcd22689ffc3a661e0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 05:52:48 +0100 Subject: Draft support for dynamic custom renderer injection --- graphics/src/renderer.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 143f348b..e7154385 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -211,11 +211,11 @@ where { type Handle = image::Handle; - fn dimensions(&self, handle: &image::Handle) -> Size { + fn measure_image(&self, handle: &image::Handle) -> Size { self.backend().dimensions(handle) } - fn draw( + fn draw_image( &mut self, handle: image::Handle, filter_method: image::FilterMethod, @@ -233,11 +233,11 @@ impl svg::Renderer for Renderer where B: Backend + backend::Svg, { - fn dimensions(&self, handle: &svg::Handle) -> Size { + fn measure_svg(&self, handle: &svg::Handle) -> Size { self.backend().viewport_dimensions(handle) } - fn draw( + fn draw_svg( &mut self, handle: svg::Handle, color: Option, -- cgit From 3645d34d6a1ba1247238e830e9eefd52d9e5b986 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 22:27:17 +0100 Subject: Implement composable, type-safe renderer fallback --- 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 ++++++++++-------- 5 files changed, 341 insertions(+), 50 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 10eb337f..e394c956 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -2,6 +2,7 @@ use crate::core::image; use crate::core::svg; use crate::core::Size; +use crate::Mesh; use std::borrow::Cow; @@ -10,7 +11,7 @@ use std::borrow::Cow; /// [`Renderer`]: crate::Renderer pub trait Backend { /// The custom kind of primitives this [`Backend`] supports. - type Primitive; + type Primitive: TryFrom; } /// A graphics backend that supports text rendering. diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index d7d6a0aa..cd4c9267 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -14,11 +14,277 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; +use crate::core::{Point, Radians, Rectangle, Size, Vector}; +use crate::Primitive; + +use std::cell::RefCell; +use std::sync::Arc; + +pub fn frame(renderer: &Renderer, size: Size) -> Renderer::Frame +where + Renderer: self::Renderer, +{ + renderer.new_frame(size) +} + /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: crate::core::Renderer { /// The kind of geometry this renderer can draw. - type Geometry; + type Geometry: Geometry; + + /// The kind of [`Frame`] this renderer supports. + type Frame: Frame; + + fn new_frame(&self, size: Size) -> Self::Frame; + + /// Draws the given [`Self::Geometry`]. + fn draw_geometry(&mut self, geometry: Self::Geometry); +} + +pub trait Backend { + /// The kind of [`Frame`] this backend supports. + type Frame: Frame; + + fn new_frame(&self, size: Size) -> Self::Frame; +} + +pub trait Frame: Sized + Into { + /// The kind of geometry this frame can draw. + type Geometry: Geometry; + + /// Returns the width of the [`Frame`]. + fn width(&self) -> f32; + + /// Returns the height of the [`Frame`]. + fn height(&self) -> f32; + + /// Returns the dimensions of the [`Frame`]. + fn size(&self) -> Size; + + /// Returns the coordinate of the center of the [`Frame`]. + fn center(&self) -> Point; + + /// Draws the given [`Path`] on the [`Frame`] by filling it with the + /// provided style. + fn fill(&mut self, path: &Path, fill: impl Into); + + /// Draws an axis-aligned rectangle given its top-left corner coordinate and + /// its `Size` on the [`Frame`] by filling it with the provided style. + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ); + + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the + /// provided style. + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>); + + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. + /// + /// __Warning:__ Text currently does not work well with rotations and scale + /// transforms! The position will be correctly transformed, but the + /// resulting glyphs will not be rotated or scaled properly. + /// + /// Additionally, all text will be rendered on top of all the layers of + /// a `Canvas`. Therefore, it is currently only meant to be used for + /// overlays, which is the most common use case. + /// + /// Support for vectorial text is planned, and should address all these + /// limitations. + fn fill_text(&mut self, text: impl Into); + + /// Stores the current transform of the [`Frame`] and executes the given + /// drawing operations, restoring the transform afterwards. + /// + /// This method is useful to compose transforms and perform drawing + /// operations in different coordinate systems. + #[inline] + fn with_save(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + self.push_transform(); + + let result = f(self); + + self.pop_transform(); + + result + } + + /// Pushes the current transform in the transform stack. + fn push_transform(&mut self); + + /// Pops a transform from the transform stack and sets it as the current transform. + fn pop_transform(&mut self); + + /// Executes the given drawing operations within a [`Rectangle`] region, + /// clipping any geometry that overflows its bounds. Any transformations + /// performed are local to the provided closure. + /// + /// This method is useful to perform drawing operations that need to be + /// clipped. + #[inline] + fn with_clip( + &mut self, + region: Rectangle, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + let mut frame = self.draft(region.size()); + + let result = f(&mut frame); + + let origin = Point::new(region.x, region.y); + + self.paste(frame, origin); + + result + } + + /// Creates a new [`Frame`] with the given [`Size`]. + /// + /// Draw its contents back to this [`Frame`] with [`paste`]. + /// + /// [`paste`]: Self::paste + fn draft(&mut self, size: Size) -> Self; + + /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. + fn paste(&mut self, frame: Self, at: Point); + + /// Applies a translation to the current transform of the [`Frame`]. + fn translate(&mut self, translation: Vector); + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + fn rotate(&mut self, angle: impl Into); + + /// Applies a uniform scaling to the current transform of the [`Frame`]. + fn scale(&mut self, scale: impl Into); + + /// Applies a non-uniform scaling to the current transform of the [`Frame`]. + fn scale_nonuniform(&mut self, scale: impl Into); +} + +pub trait Geometry: Sized { + type Cache; + + fn load(cache: &Self::Cache) -> Self; + + fn cache(self) -> Self::Cache; +} + +/// A simple cache that stores generated [`Geometry`] to avoid recomputation. +/// +/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer +/// change or it is explicitly cleared. +pub struct Cache +where + Renderer: self::Renderer, +{ + state: RefCell>, +} + +impl Cache +where + Renderer: self::Renderer, +{ + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + state: RefCell::new(State::Empty), + } + } + + /// Clears the [`Cache`], forcing a redraw the next time it is used. + pub fn clear(&self) { + *self.state.borrow_mut() = State::Empty; + } + + /// Draws [`Geometry`] using the provided closure and stores it in the + /// [`Cache`]. + /// + /// The closure will only be called when + /// - the bounds have changed since the previous draw call. + /// - the [`Cache`] is empty or has been explicitly cleared. + /// + /// Otherwise, the previously stored [`Geometry`] will be returned. The + /// [`Cache`] is not cleared in this case. In other words, it will keep + /// returning the stored [`Geometry`] if needed. + pub fn draw( + &self, + renderer: &Renderer, + bounds: Size, + draw_fn: impl FnOnce(&mut Renderer::Frame), + ) -> Renderer::Geometry { + use std::ops::Deref; + + if let State::Filled { + bounds: cached_bounds, + geometry, + } = self.state.borrow().deref() + { + if *cached_bounds == bounds { + return Geometry::load(geometry); + } + } + + let mut frame = frame(renderer, bounds); + draw_fn(&mut frame); + + let geometry = frame.into().cache(); + let result = Geometry::load(&geometry); + + *self.state.borrow_mut() = State::Filled { bounds, geometry }; + + result + } +} + +impl std::fmt::Debug for Cache +where + Renderer: self::Renderer, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let state = self.state.borrow(); + + match *state { + State::Empty => write!(f, "Cache::Empty"), + State::Filled { bounds, .. } => { + write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") + } + } + } +} + +impl Default for Cache +where + Renderer: self::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +enum State +where + Geometry: self::Geometry, +{ + Empty, + Filled { + bounds: Size, + geometry: Geometry::Cache, + }, +} + +impl Geometry for Primitive { + type Cache = Arc; + + fn load(cache: &Arc) -> Self { + Self::Cache { + content: cache.clone(), + } + } - /// Draws the given layers of [`Self::Geometry`]. - fn draw(&mut self, layers: Vec); + fn cache(self) -> Arc { + Arc::new(self) + } } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index aa9d00e8..6d0862ad 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,8 +9,8 @@ )] #![forbid(rust_2018_idioms)] #![deny( - missing_debug_implementations, - missing_docs, + // missing_debug_implementations, + // missing_docs, unsafe_code, unused_results, rustdoc::broken_intra_doc_links diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index 041986cf..5be3ee5b 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -1,6 +1,6 @@ //! Draw triangles! use crate::color; -use crate::core::{Rectangle, Size}; +use crate::core::{self, Rectangle, Size}; use crate::gradient; use crate::Damage; @@ -74,3 +74,7 @@ pub struct GradientVertex2D { /// The packed vertex data of the gradient. pub gradient: gradient::Packed, } + +pub trait Renderer: core::Renderer { + fn draw_mesh(&mut self, mesh: Mesh); +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index e7154385..3b21aa11 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -8,8 +8,9 @@ use crate::core::text::Text; use crate::core::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; +use crate::mesh; use crate::text; -use crate::Primitive; +use crate::{Mesh, Primitive}; use std::borrow::Cow; @@ -20,6 +21,7 @@ pub struct Renderer { default_font: Font, default_text_size: Pixels, primitives: Vec>, + stack: Vec>>, } impl Renderer { @@ -34,6 +36,7 @@ impl Renderer { default_font, default_text_size, primitives: Vec::new(), + stack: Vec::new(), } } @@ -56,59 +59,45 @@ impl Renderer { f(&mut self.backend, &self.primitives) } - /// Starts recording a new layer. - pub fn start_layer(&mut self) -> Vec> { - std::mem::take(&mut self.primitives) - } - - /// Ends the recording of a layer. - pub fn end_layer( - &mut self, - primitives: Vec>, - bounds: Rectangle, - ) { - let layer = std::mem::replace(&mut self.primitives, primitives); - - self.primitives.push(Primitive::group(layer).clip(bounds)); - } - - /// Starts recording a translation. - pub fn start_transformation(&mut self) -> Vec> { - std::mem::take(&mut self.primitives) - } - - /// Ends the recording of a translation. - pub fn end_transformation( + #[cfg(feature = "geometry")] + pub fn draw_geometry( &mut self, - primitives: Vec>, - transformation: Transformation, - ) { - let layer = std::mem::replace(&mut self.primitives, primitives); - - self.primitives - .push(Primitive::group(layer).transform(transformation)); + layers: impl IntoIterator, + ) where + Geometry: Into>, + { + for layer in layers { + self.draw_primitive(layer.into()); + } } } impl iced_core::Renderer for Renderer { - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { - let current = self.start_layer(); + fn start_layer(&mut self) { + self.stack.push(std::mem::take(&mut self.primitives)); + } - f(self); + fn end_layer(&mut self, bounds: Rectangle) { + let layer = std::mem::replace( + &mut self.primitives, + self.stack.pop().expect("a layer should be recording"), + ); - self.end_layer(current, bounds); + self.primitives.push(Primitive::group(layer).clip(bounds)); } - fn with_transformation( - &mut self, - transformation: Transformation, - f: impl FnOnce(&mut Self), - ) { - let current = self.start_transformation(); + fn start_transformation(&mut self) { + self.stack.push(std::mem::take(&mut self.primitives)); + } - f(self); + fn end_transformation(&mut self, transformation: Transformation) { + let layer = std::mem::replace( + &mut self.primitives, + self.stack.pop().expect("a layer should be recording"), + ); - self.end_transformation(current, transformation); + self.primitives + .push(Primitive::group(layer).transform(transformation)); } fn fill_quad( @@ -250,3 +239,34 @@ where }); } } + +impl mesh::Renderer for Renderer { + fn draw_mesh(&mut self, mesh: Mesh) { + match B::Primitive::try_from(mesh) { + Ok(primitive) => { + self.draw_primitive(Primitive::Custom(primitive)); + } + Err(error) => { + log::warn!("mesh primitive could not be drawn: {error:?}"); + } + } + } +} + +#[cfg(feature = "geometry")] +impl crate::geometry::Renderer for Renderer +where + B: Backend + crate::geometry::Backend, + B::Frame: crate::geometry::Frame>, +{ + type Frame = B::Frame; + type Geometry = Primitive; + + fn new_frame(&self, size: Size) -> Self::Frame { + self.backend.new_frame(size) + } + + fn draw_geometry(&mut self, geometry: Self::Geometry) { + self.draw_primitive(geometry); + } +} -- cgit From 53a183fe0d6aed460fbb8155ac9541757277aab3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:35:14 +0100 Subject: Restore `canvas::Frame` API --- graphics/src/geometry.rs | 254 ++--------------------------------------- graphics/src/geometry/cache.rs | 123 ++++++++++++++++++++ graphics/src/geometry/frame.rs | 208 +++++++++++++++++++++++++++++++++ graphics/src/renderer.rs | 3 +- 4 files changed, 340 insertions(+), 248 deletions(-) create mode 100644 graphics/src/geometry/cache.rs create mode 100644 graphics/src/geometry/frame.rs (limited to 'graphics/src') diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index cd4c9267..2b18243e 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -1,12 +1,16 @@ //! Build and draw geometry. pub mod fill; +pub mod frame; pub mod path; pub mod stroke; +mod cache; mod style; mod text; +pub use cache::Cache; pub use fill::Fill; +pub use frame::Frame; pub use path::Path; pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use style::Style; @@ -14,18 +18,7 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; -use crate::core::{Point, Radians, Rectangle, Size, Vector}; -use crate::Primitive; - -use std::cell::RefCell; -use std::sync::Arc; - -pub fn frame(renderer: &Renderer, size: Size) -> Renderer::Frame -where - Renderer: self::Renderer, -{ - renderer.new_frame(size) -} +use crate::core::Size; /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: crate::core::Renderer { @@ -33,7 +26,7 @@ pub trait Renderer: crate::core::Renderer { type Geometry: Geometry; /// The kind of [`Frame`] this renderer supports. - type Frame: Frame; + type Frame: frame::Backend; fn new_frame(&self, size: Size) -> Self::Frame; @@ -43,127 +36,11 @@ pub trait Renderer: crate::core::Renderer { pub trait Backend { /// The kind of [`Frame`] this backend supports. - type Frame: Frame; + type Frame: frame::Backend; fn new_frame(&self, size: Size) -> Self::Frame; } -pub trait Frame: Sized + Into { - /// The kind of geometry this frame can draw. - type Geometry: Geometry; - - /// Returns the width of the [`Frame`]. - fn width(&self) -> f32; - - /// Returns the height of the [`Frame`]. - fn height(&self) -> f32; - - /// Returns the dimensions of the [`Frame`]. - fn size(&self) -> Size; - - /// Returns the coordinate of the center of the [`Frame`]. - fn center(&self) -> Point; - - /// Draws the given [`Path`] on the [`Frame`] by filling it with the - /// provided style. - fn fill(&mut self, path: &Path, fill: impl Into); - - /// Draws an axis-aligned rectangle given its top-left corner coordinate and - /// its `Size` on the [`Frame`] by filling it with the provided style. - fn fill_rectangle( - &mut self, - top_left: Point, - size: Size, - fill: impl Into, - ); - - /// Draws the stroke of the given [`Path`] on the [`Frame`] with the - /// provided style. - fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>); - - /// Draws the characters of the given [`Text`] on the [`Frame`], filling - /// them with the given color. - /// - /// __Warning:__ Text currently does not work well with rotations and scale - /// transforms! The position will be correctly transformed, but the - /// resulting glyphs will not be rotated or scaled properly. - /// - /// Additionally, all text will be rendered on top of all the layers of - /// a `Canvas`. Therefore, it is currently only meant to be used for - /// overlays, which is the most common use case. - /// - /// Support for vectorial text is planned, and should address all these - /// limitations. - fn fill_text(&mut self, text: impl Into); - - /// Stores the current transform of the [`Frame`] and executes the given - /// drawing operations, restoring the transform afterwards. - /// - /// This method is useful to compose transforms and perform drawing - /// operations in different coordinate systems. - #[inline] - fn with_save(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { - self.push_transform(); - - let result = f(self); - - self.pop_transform(); - - result - } - - /// Pushes the current transform in the transform stack. - fn push_transform(&mut self); - - /// Pops a transform from the transform stack and sets it as the current transform. - fn pop_transform(&mut self); - - /// Executes the given drawing operations within a [`Rectangle`] region, - /// clipping any geometry that overflows its bounds. Any transformations - /// performed are local to the provided closure. - /// - /// This method is useful to perform drawing operations that need to be - /// clipped. - #[inline] - fn with_clip( - &mut self, - region: Rectangle, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - let mut frame = self.draft(region.size()); - - let result = f(&mut frame); - - let origin = Point::new(region.x, region.y); - - self.paste(frame, origin); - - result - } - - /// Creates a new [`Frame`] with the given [`Size`]. - /// - /// Draw its contents back to this [`Frame`] with [`paste`]. - /// - /// [`paste`]: Self::paste - fn draft(&mut self, size: Size) -> Self; - - /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. - fn paste(&mut self, frame: Self, at: Point); - - /// Applies a translation to the current transform of the [`Frame`]. - fn translate(&mut self, translation: Vector); - - /// Applies a rotation in radians to the current transform of the [`Frame`]. - fn rotate(&mut self, angle: impl Into); - - /// Applies a uniform scaling to the current transform of the [`Frame`]. - fn scale(&mut self, scale: impl Into); - - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. - fn scale_nonuniform(&mut self, scale: impl Into); -} - pub trait Geometry: Sized { type Cache; @@ -171,120 +48,3 @@ pub trait Geometry: Sized { fn cache(self) -> Self::Cache; } - -/// A simple cache that stores generated [`Geometry`] to avoid recomputation. -/// -/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer -/// change or it is explicitly cleared. -pub struct Cache -where - Renderer: self::Renderer, -{ - state: RefCell>, -} - -impl Cache -where - Renderer: self::Renderer, -{ - /// Creates a new empty [`Cache`]. - pub fn new() -> Self { - Cache { - state: RefCell::new(State::Empty), - } - } - - /// Clears the [`Cache`], forcing a redraw the next time it is used. - pub fn clear(&self) { - *self.state.borrow_mut() = State::Empty; - } - - /// Draws [`Geometry`] using the provided closure and stores it in the - /// [`Cache`]. - /// - /// The closure will only be called when - /// - the bounds have changed since the previous draw call. - /// - the [`Cache`] is empty or has been explicitly cleared. - /// - /// Otherwise, the previously stored [`Geometry`] will be returned. The - /// [`Cache`] is not cleared in this case. In other words, it will keep - /// returning the stored [`Geometry`] if needed. - pub fn draw( - &self, - renderer: &Renderer, - bounds: Size, - draw_fn: impl FnOnce(&mut Renderer::Frame), - ) -> Renderer::Geometry { - use std::ops::Deref; - - if let State::Filled { - bounds: cached_bounds, - geometry, - } = self.state.borrow().deref() - { - if *cached_bounds == bounds { - return Geometry::load(geometry); - } - } - - let mut frame = frame(renderer, bounds); - draw_fn(&mut frame); - - let geometry = frame.into().cache(); - let result = Geometry::load(&geometry); - - *self.state.borrow_mut() = State::Filled { bounds, geometry }; - - result - } -} - -impl std::fmt::Debug for Cache -where - Renderer: self::Renderer, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let state = self.state.borrow(); - - match *state { - State::Empty => write!(f, "Cache::Empty"), - State::Filled { bounds, .. } => { - write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") - } - } - } -} - -impl Default for Cache -where - Renderer: self::Renderer, -{ - fn default() -> Self { - Self::new() - } -} - -enum State -where - Geometry: self::Geometry, -{ - Empty, - Filled { - bounds: Size, - geometry: Geometry::Cache, - }, -} - -impl Geometry for Primitive { - type Cache = Arc; - - fn load(cache: &Arc) -> Self { - Self::Cache { - content: cache.clone(), - } - } - - fn cache(self) -> Arc { - Arc::new(self) - } -} diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs new file mode 100644 index 00000000..490e69e2 --- /dev/null +++ b/graphics/src/geometry/cache.rs @@ -0,0 +1,123 @@ +use crate::core::Size; +use crate::geometry::{self, Frame, Geometry}; +use crate::Primitive; + +use std::cell::RefCell; +use std::sync::Arc; + +/// A simple cache that stores generated [`Geometry`] to avoid recomputation. +/// +/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer +/// change or it is explicitly cleared. +pub struct Cache +where + Renderer: geometry::Renderer, +{ + state: RefCell>, +} + +impl Cache +where + Renderer: geometry::Renderer, +{ + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + state: RefCell::new(State::Empty), + } + } + + /// Clears the [`Cache`], forcing a redraw the next time it is used. + pub fn clear(&self) { + *self.state.borrow_mut() = State::Empty; + } + + /// Draws [`Geometry`] using the provided closure and stores it in the + /// [`Cache`]. + /// + /// The closure will only be called when + /// - the bounds have changed since the previous draw call. + /// - the [`Cache`] is empty or has been explicitly cleared. + /// + /// Otherwise, the previously stored [`Geometry`] will be returned. The + /// [`Cache`] is not cleared in this case. In other words, it will keep + /// returning the stored [`Geometry`] if needed. + pub fn draw( + &self, + renderer: &Renderer, + bounds: Size, + draw_fn: impl FnOnce(&mut Frame), + ) -> Renderer::Geometry { + use std::ops::Deref; + + if let State::Filled { + bounds: cached_bounds, + geometry, + } = self.state.borrow().deref() + { + if *cached_bounds == bounds { + return Geometry::load(geometry); + } + } + + let mut frame = Frame::new(renderer, bounds); + draw_fn(&mut frame); + + let geometry = frame.into_geometry().cache(); + let result = Geometry::load(&geometry); + + *self.state.borrow_mut() = State::Filled { bounds, geometry }; + + result + } +} + +impl std::fmt::Debug for Cache +where + Renderer: geometry::Renderer, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let state = self.state.borrow(); + + match *state { + State::Empty => write!(f, "Cache::Empty"), + State::Filled { bounds, .. } => { + write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") + } + } + } +} + +impl Default for Cache +where + Renderer: geometry::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +enum State +where + Geometry: self::Geometry, +{ + Empty, + Filled { + bounds: Size, + geometry: Geometry::Cache, + }, +} + +impl Geometry for Primitive { + type Cache = Arc; + + fn load(cache: &Arc) -> Self { + Self::Cache { + content: cache.clone(), + } + } + + fn cache(self) -> Arc { + Arc::new(self) + } +} diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs new file mode 100644 index 00000000..e88c43b0 --- /dev/null +++ b/graphics/src/geometry/frame.rs @@ -0,0 +1,208 @@ +use crate::core::{Point, Radians, Rectangle, Size, Vector}; +use crate::geometry::{self, Geometry}; +use crate::geometry::{Fill, Path, Stroke, Text}; + +pub struct Frame +where + Renderer: geometry::Renderer, +{ + raw: Renderer::Frame, +} + +impl Frame +where + Renderer: geometry::Renderer, +{ + pub fn new(renderer: &Renderer, size: Size) -> Self { + Self { + raw: renderer.new_frame(size), + } + } + + /// Returns the width of the [`Frame`]. + pub fn width(&self) -> f32 { + self.raw.width() + } + + /// Returns the height of the [`Frame`]. + pub fn height(&self) -> f32 { + self.raw.height() + } + + /// Returns the dimensions of the [`Frame`]. + pub fn size(&self) -> Size { + self.raw.size() + } + + /// Returns the coordinate of the center of the [`Frame`]. + pub fn center(&self) -> Point { + self.raw.center() + } + + /// Draws the given [`Path`] on the [`Frame`] by filling it with the + /// provided style. + pub fn fill(&mut self, path: &Path, fill: impl Into) { + self.raw.fill(path, fill); + } + + /// Draws an axis-aligned rectangle given its top-left corner coordinate and + /// its `Size` on the [`Frame`] by filling it with the provided style. + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ) { + self.raw.fill_rectangle(top_left, size, fill); + } + + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the + /// provided style. + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + self.raw.stroke(path, stroke); + } + + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. + /// + /// __Warning:__ Text currently does not work well with rotations and scale + /// transforms! The position will be correctly transformed, but the + /// resulting glyphs will not be rotated or scaled properly. + /// + /// Additionally, all text will be rendered on top of all the layers of + /// a `Canvas`. Therefore, it is currently only meant to be used for + /// overlays, which is the most common use case. + /// + /// Support for vectorial text is planned, and should address all these + /// limitations. + pub fn fill_text(&mut self, text: impl Into) { + self.raw.fill_text(text); + } + + /// Stores the current transform of the [`Frame`] and executes the given + /// drawing operations, restoring the transform afterwards. + /// + /// This method is useful to compose transforms and perform drawing + /// operations in different coordinate systems. + #[inline] + pub fn with_save(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + self.push_transform(); + + let result = f(self); + + self.pop_transform(); + + result + } + + /// Pushes the current transform in the transform stack. + pub fn push_transform(&mut self) { + self.raw.push_transform(); + } + + /// Pops a transform from the transform stack and sets it as the current transform. + pub fn pop_transform(&mut self) { + self.raw.pop_transform(); + } + + /// Executes the given drawing operations within a [`Rectangle`] region, + /// clipping any geometry that overflows its bounds. Any transformations + /// performed are local to the provided closure. + /// + /// This method is useful to perform drawing operations that need to be + /// clipped. + #[inline] + pub fn with_clip( + &mut self, + region: Rectangle, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + let mut frame = self.draft(region.size()); + + let result = f(&mut frame); + + let origin = Point::new(region.x, region.y); + + self.paste(frame, origin); + + result + } + + /// Creates a new [`Frame`] with the given [`Size`]. + /// + /// Draw its contents back to this [`Frame`] with [`paste`]. + /// + /// [`paste`]: Self::paste + pub fn draft(&mut self, size: Size) -> Self { + Self { + raw: self.raw.draft(size), + } + } + + /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. + pub fn paste(&mut self, frame: Self, at: Point) { + self.raw.paste(frame.raw, at); + } + + /// Applies a translation to the current transform of the [`Frame`]. + pub fn translate(&mut self, translation: Vector) { + self.raw.translate(translation); + } + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + pub fn rotate(&mut self, angle: impl Into) { + self.raw.rotate(angle); + } + + /// Applies a uniform scaling to the current transform of the [`Frame`]. + pub fn scale(&mut self, scale: impl Into) { + self.raw.scale(scale); + } + + /// Applies a non-uniform scaling to the current transform of the [`Frame`]. + pub fn scale_nonuniform(&mut self, scale: impl Into) { + self.raw.scale_nonuniform(scale); + } + + pub fn into_geometry(self) -> Renderer::Geometry { + self.raw.into_geometry() + } +} + +/// The internal implementation of a [`Frame`]. +/// +/// Analogous to [`Frame`]. See [`Frame`] for the documentation +/// of each method. +#[allow(missing_docs)] +pub trait Backend: Sized { + type Geometry: Geometry; + + fn width(&self) -> f32; + fn height(&self) -> f32; + fn size(&self) -> Size; + fn center(&self) -> Point; + + fn push_transform(&mut self); + fn pop_transform(&mut self); + + fn translate(&mut self, translation: Vector); + fn rotate(&mut self, angle: impl Into); + fn scale(&mut self, scale: impl Into); + fn scale_nonuniform(&mut self, scale: impl Into); + + fn draft(&mut self, size: Size) -> Self; + fn paste(&mut self, frame: Self, at: Point); + + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>); + + fn fill(&mut self, path: &Path, fill: impl Into); + fn fill_text(&mut self, text: impl Into); + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ); + + fn into_geometry(self) -> Self::Geometry; +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 3b21aa11..2fcb55aa 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -257,7 +257,8 @@ impl mesh::Renderer for Renderer { impl crate::geometry::Renderer for Renderer where B: Backend + crate::geometry::Backend, - B::Frame: crate::geometry::Frame>, + B::Frame: + crate::geometry::frame::Backend>, { type Frame = B::Frame; type Geometry = Primitive; -- cgit From 85800c99ab285efd244c0addfdcf3c732a98de1d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:53:48 +0100 Subject: Fix broken links in documentation --- graphics/src/cached.rs | 33 +++++++++++++++++++++++++++++++++ graphics/src/geometry.rs | 14 +++++--------- graphics/src/geometry/cache.rs | 33 +++++++++------------------------ graphics/src/geometry/frame.rs | 11 ++++++++--- graphics/src/lib.rs | 6 ++++-- graphics/src/mesh.rs | 2 ++ graphics/src/renderer.rs | 12 ------------ 7 files changed, 61 insertions(+), 50 deletions(-) create mode 100644 graphics/src/cached.rs (limited to 'graphics/src') diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs new file mode 100644 index 00000000..f7968c9f --- /dev/null +++ b/graphics/src/cached.rs @@ -0,0 +1,33 @@ +use crate::Primitive; + +use std::sync::Arc; + +/// A piece of data that can be cached. +pub trait Cached: Sized { + /// The type of cache produced. + type Cache; + + /// Loads the [`Cache`] into a proper instance. + /// + /// [`Cache`]: Self::Cache + fn load(cache: &Self::Cache) -> Self; + + /// Caches this value, producing its corresponding [`Cache`]. + /// + /// [`Cache`]: Self::Cache + fn cache(self) -> Self::Cache; +} + +impl Cached for Primitive { + type Cache = Arc; + + fn load(cache: &Arc) -> Self { + Self::Cache { + content: cache.clone(), + } + } + + fn cache(self) -> Arc { + Arc::new(self) + } +} diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index 2b18243e..cc2359b6 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -19,32 +19,28 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; use crate::core::Size; +use crate::Cached; /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: crate::core::Renderer { /// The kind of geometry this renderer can draw. - type Geometry: Geometry; + type Geometry: Cached; /// The kind of [`Frame`] this renderer supports. type Frame: frame::Backend; + /// Creates a new [`Self::Frame`]. fn new_frame(&self, size: Size) -> Self::Frame; /// Draws the given [`Self::Geometry`]. fn draw_geometry(&mut self, geometry: Self::Geometry); } +/// The graphics backend of a geometry renderer. pub trait Backend { /// The kind of [`Frame`] this backend supports. type Frame: frame::Backend; + /// Creates a new [`Self::Frame`]. fn new_frame(&self, size: Size) -> Self::Frame; } - -pub trait Geometry: Sized { - type Cache; - - fn load(cache: &Self::Cache) -> Self; - - fn cache(self) -> Self::Cache; -} diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs index 490e69e2..37d433c2 100644 --- a/graphics/src/geometry/cache.rs +++ b/graphics/src/geometry/cache.rs @@ -1,11 +1,10 @@ use crate::core::Size; -use crate::geometry::{self, Frame, Geometry}; -use crate::Primitive; +use crate::geometry::{self, Frame}; +use crate::Cached; use std::cell::RefCell; -use std::sync::Arc; -/// A simple cache that stores generated [`Geometry`] to avoid recomputation. +/// A simple cache that stores generated geometry to avoid recomputation. /// /// A [`Cache`] will not redraw its geometry unless the dimensions of its layer /// change or it is explicitly cleared. @@ -32,16 +31,16 @@ where *self.state.borrow_mut() = State::Empty; } - /// Draws [`Geometry`] using the provided closure and stores it in the + /// Draws geometry using the provided closure and stores it in the /// [`Cache`]. /// /// The closure will only be called when /// - the bounds have changed since the previous draw call. /// - the [`Cache`] is empty or has been explicitly cleared. /// - /// Otherwise, the previously stored [`Geometry`] will be returned. The + /// Otherwise, the previously stored geometry will be returned. The /// [`Cache`] is not cleared in this case. In other words, it will keep - /// returning the stored [`Geometry`] if needed. + /// returning the stored geometry if needed. pub fn draw( &self, renderer: &Renderer, @@ -56,7 +55,7 @@ where } = self.state.borrow().deref() { if *cached_bounds == bounds { - return Geometry::load(geometry); + return Cached::load(geometry); } } @@ -64,7 +63,7 @@ where draw_fn(&mut frame); let geometry = frame.into_geometry().cache(); - let result = Geometry::load(&geometry); + let result = Cached::load(&geometry); *self.state.borrow_mut() = State::Filled { bounds, geometry }; @@ -99,7 +98,7 @@ where enum State where - Geometry: self::Geometry, + Geometry: Cached, { Empty, Filled { @@ -107,17 +106,3 @@ where geometry: Geometry::Cache, }, } - -impl Geometry for Primitive { - type Cache = Arc; - - fn load(cache: &Arc) -> Self { - Self::Cache { - content: cache.clone(), - } - } - - fn cache(self) -> Arc { - Arc::new(self) - } -} diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index e88c43b0..37e0df38 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -1,7 +1,10 @@ +//! Draw and generate geometry. use crate::core::{Point, Radians, Rectangle, Size, Vector}; -use crate::geometry::{self, Geometry}; -use crate::geometry::{Fill, Path, Stroke, Text}; +use crate::geometry::{self, Fill, Path, Stroke, Text}; +use crate::Cached; +/// The region of a surface that can be used to draw geometry. +#[allow(missing_debug_implementations)] pub struct Frame where Renderer: geometry::Renderer, @@ -13,6 +16,7 @@ impl Frame where Renderer: geometry::Renderer, { + /// Creates a new [`Frame`] with the given dimensions. pub fn new(renderer: &Renderer, size: Size) -> Self { Self { raw: renderer.new_frame(size), @@ -164,6 +168,7 @@ where self.raw.scale_nonuniform(scale); } + /// Turns the [`Frame`] into its underlying geometry. pub fn into_geometry(self) -> Renderer::Geometry { self.raw.into_geometry() } @@ -175,7 +180,7 @@ where /// of each method. #[allow(missing_docs)] pub trait Backend: Sized { - type Geometry: Geometry; + type Geometry: Cached; fn width(&self) -> f32; fn height(&self) -> f32; diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 6d0862ad..a682b89b 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,14 +9,15 @@ )] #![forbid(rust_2018_idioms)] #![deny( - // missing_debug_implementations, - // missing_docs, + missing_debug_implementations, + missing_docs, unsafe_code, unused_results, rustdoc::broken_intra_doc_links )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; +mod cached; mod error; mod primitive; mod viewport; @@ -38,6 +39,7 @@ pub mod image; pub use antialiasing::Antialiasing; pub use backend::Backend; +pub use cached::Cached; pub use compositor::Compositor; pub use damage::Damage; pub use error::Error; diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index 5be3ee5b..d671f494 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -75,6 +75,8 @@ pub struct GradientVertex2D { pub gradient: gradient::Packed, } +/// A renderer capable of drawing a [`Mesh`]. pub trait Renderer: core::Renderer { + /// Draws the given [`Mesh`]. fn draw_mesh(&mut self, mesh: Mesh); } diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 2fcb55aa..eb720495 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -58,18 +58,6 @@ impl Renderer { ) -> O { f(&mut self.backend, &self.primitives) } - - #[cfg(feature = "geometry")] - pub fn draw_geometry( - &mut self, - layers: impl IntoIterator, - ) where - Geometry: Into>, - { - for layer in layers { - self.draw_primitive(layer.into()); - } - } } impl iced_core::Renderer for Renderer { -- cgit From bbafeed13d20f2cbd6fc18b949b34596aa0c6c2e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:55:28 +0100 Subject: Fix outdated warning in docs of `Frame::fill_text` --- graphics/src/geometry/frame.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index 37e0df38..b54fca5d 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -69,16 +69,9 @@ where /// Draws the characters of the given [`Text`] on the [`Frame`], filling /// them with the given color. /// - /// __Warning:__ Text currently does not work well with rotations and scale - /// transforms! The position will be correctly transformed, but the - /// resulting glyphs will not be rotated or scaled properly. - /// - /// Additionally, all text will be rendered on top of all the layers of + /// __Warning:__ All text will be rendered on top of all the layers of /// a `Canvas`. Therefore, it is currently only meant to be used for /// overlays, which is the most common use case. - /// - /// Support for vectorial text is planned, and should address all these - /// limitations. pub fn fill_text(&mut self, text: impl Into) { self.raw.fill_text(text); } -- cgit From 1f13a91361258a1607c71f4840a26a6437f88612 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 05:27:31 +0100 Subject: Make `iced_tiny_skia` optional with a `tiny-skia` feature --- 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 +-- 5 files changed, 69 insertions(+), 7 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index e394c956..e982b54a 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -10,6 +10,9 @@ use std::borrow::Cow; /// /// [`Renderer`]: crate::Renderer pub trait Backend { + /// The compositor of this [`Backend`]. + type Compositor; + /// The custom kind of primitives this [`Backend`] supports. type Primitive: TryFrom; } diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 91951a8e..32cea46a 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -15,7 +15,7 @@ pub trait Compositor: Sized { type Settings: Default; /// The iced renderer of the backend. - type Renderer: iced_core::Renderer; + type Renderer; /// The surface of the backend. type Surface; @@ -122,3 +122,63 @@ pub struct Information { /// Contains the graphics backend. pub backend: String, } + +impl Compositor for () { + type Settings = (); + type Renderer = (); + type Surface = (); + + async fn new( + _settings: Self::Settings, + _compatible_window: W, + ) -> Result { + Ok(()) + } + + fn create_renderer(&self) -> Self::Renderer {} + + fn create_surface( + &mut self, + _window: W, + _width: u32, + _height: u32, + ) -> Self::Surface { + } + + fn configure_surface( + &mut self, + _surface: &mut Self::Surface, + _width: u32, + _height: u32, + ) { + } + + fn fetch_information(&self) -> Information { + Information { + adapter: String::from("Null Renderer"), + backend: String::from("Null"), + } + } + + fn present>( + &mut self, + _renderer: &mut Self::Renderer, + _surface: &mut Self::Surface, + _viewport: &Viewport, + _background_color: Color, + _overlay: &[T], + ) -> Result<(), SurfaceError> { + Ok(()) + } + + fn screenshot>( + &mut self, + _renderer: &mut Self::Renderer, + _surface: &mut Self::Surface, + _viewport: &Viewport, + _background_color: Color, + _overlay: &[T], + ) -> Vec { + vec![] + } +} diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index cc2359b6..194f37b2 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -18,11 +18,11 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; -use crate::core::Size; +use crate::core::{self, Size}; use crate::Cached; /// A renderer capable of drawing some [`Self::Geometry`]. -pub trait Renderer: crate::core::Renderer { +pub trait Renderer: core::Renderer { /// The kind of geometry this renderer can draw. type Geometry: Cached; diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index b54fca5d..635012d0 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -1,7 +1,6 @@ //! Draw and generate geometry. use crate::core::{Point, Radians, Rectangle, Size, Vector}; use crate::geometry::{self, Fill, Path, Stroke, Text}; -use crate::Cached; /// The region of a surface that can be used to draw geometry. #[allow(missing_debug_implementations)] @@ -173,7 +172,7 @@ where /// of each method. #[allow(missing_docs)] pub trait Backend: Sized { - type Geometry: Cached; + type Geometry; fn width(&self) -> f32; fn height(&self) -> f32; diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index d671f494..20692b07 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -1,6 +1,6 @@ //! Draw triangles! use crate::color; -use crate::core::{self, Rectangle, Size}; +use crate::core::{Rectangle, Size}; use crate::gradient; use crate::Damage; @@ -76,7 +76,7 @@ pub struct GradientVertex2D { } /// A renderer capable of drawing a [`Mesh`]. -pub trait Renderer: core::Renderer { +pub trait Renderer { /// Draws the given [`Mesh`]. fn draw_mesh(&mut self, mesh: Mesh); } -- cgit From 4f2f40c68b4647f281d34034beb159a41422aa06 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 05:41:15 +0100 Subject: Fix standalone compilation of `iced_widget` crate --- graphics/src/cached.rs | 9 +++++++++ graphics/src/geometry.rs | 10 +++++++++ graphics/src/geometry/frame.rs | 46 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) (limited to 'graphics/src') diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs index f7968c9f..b52f9d9d 100644 --- a/graphics/src/cached.rs +++ b/graphics/src/cached.rs @@ -31,3 +31,12 @@ impl Cached for Primitive { Arc::new(self) } } + +#[cfg(debug_assertions)] +impl Cached for () { + type Cache = (); + + fn load(_cache: &Self::Cache) -> Self {} + + fn cache(self) -> Self::Cache {} +} diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index 194f37b2..d251efb8 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -44,3 +44,13 @@ pub trait Backend { /// Creates a new [`Self::Frame`]. fn new_frame(&self, size: Size) -> Self::Frame; } + +#[cfg(debug_assertions)] +impl Renderer for () { + type Geometry = (); + type Frame = (); + + fn new_frame(&self, _size: Size) -> Self::Frame {} + + fn draw_geometry(&mut self, _geometry: Self::Geometry) {} +} diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index 635012d0..ad35e8ec 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -203,3 +203,49 @@ pub trait Backend: Sized { fn into_geometry(self) -> Self::Geometry; } + +#[cfg(debug_assertions)] +impl Backend for () { + type Geometry = (); + + fn width(&self) -> f32 { + 0.0 + } + + fn height(&self) -> f32 { + 0.0 + } + + fn size(&self) -> Size { + Size::ZERO + } + + fn center(&self) -> Point { + Point::ORIGIN + } + + fn push_transform(&mut self) {} + fn pop_transform(&mut self) {} + + fn translate(&mut self, _translation: Vector) {} + fn rotate(&mut self, _angle: impl Into) {} + fn scale(&mut self, _scale: impl Into) {} + fn scale_nonuniform(&mut self, _scale: impl Into) {} + + fn draft(&mut self, _size: Size) -> Self {} + fn paste(&mut self, _frame: Self, _at: Point) {} + + fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into>) {} + + fn fill(&mut self, _path: &Path, _fill: impl Into) {} + fn fill_text(&mut self, _text: impl Into) {} + fn fill_rectangle( + &mut self, + _top_left: Point, + _size: Size, + _fill: impl Into, + ) { + } + + fn into_geometry(self) -> Self::Geometry {} +} -- cgit From 5137d655e6bbd29581fc1469d0385515113f2999 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 07:09:51 +0100 Subject: Allow custom renderers in `Program` and `Application` --- graphics/src/backend.rs | 6 +++--- graphics/src/compositor.rs | 24 ++++++++++++++++-------- graphics/src/lib.rs | 2 ++ graphics/src/renderer.rs | 8 ++++++++ graphics/src/settings.rs | 29 +++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 graphics/src/settings.rs (limited to 'graphics/src') diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index e982b54a..aa7bf4e8 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -2,16 +2,16 @@ use crate::core::image; use crate::core::svg; use crate::core::Size; -use crate::Mesh; +use crate::{Compositor, Mesh, Renderer}; use std::borrow::Cow; /// The graphics backend of a [`Renderer`]. /// /// [`Renderer`]: crate::Renderer -pub trait Backend { +pub trait Backend: Sized { /// The compositor of this [`Backend`]. - type Compositor; + type Compositor: Compositor>; /// The custom kind of primitives this [`Backend`] supports. type Primitive: TryFrom; diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 32cea46a..4d548f30 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -1,9 +1,9 @@ //! A compositor is responsible for initializing a renderer and managing window //! surfaces. -use crate::{Error, Viewport}; - +use crate::core; use crate::core::Color; use crate::futures::{MaybeSend, MaybeSync}; +use crate::{Error, Settings, Viewport}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::future::Future; @@ -11,9 +11,6 @@ use thiserror::Error; /// A graphics compositor that can draw to windows. pub trait Compositor: Sized { - /// The settings of the backend. - type Settings: Default; - /// The iced renderer of the backend. type Renderer; @@ -22,7 +19,7 @@ pub trait Compositor: Sized { /// Creates a new [`Compositor`]. fn new( - settings: Self::Settings, + settings: Settings, compatible_window: W, ) -> impl Future>; @@ -93,6 +90,12 @@ impl Window for T where { } +/// A renderer that supports composition. +pub trait Renderer: core::Renderer { + /// The compositor of the renderer. + type Compositor: Compositor; +} + /// Result of an unsuccessful call to [`Compositor::present`]. #[derive(Clone, PartialEq, Eq, Debug, Error)] pub enum SurfaceError { @@ -123,13 +126,13 @@ pub struct Information { pub backend: String, } +#[cfg(debug_assertions)] impl Compositor for () { - type Settings = (); type Renderer = (); type Surface = (); async fn new( - _settings: Self::Settings, + _settings: Settings, _compatible_window: W, ) -> Result { Ok(()) @@ -182,3 +185,8 @@ impl Compositor for () { vec![] } } + +#[cfg(debug_assertions)] +impl Renderer for () { + type Compositor = (); +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index a682b89b..2e476f8c 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -20,6 +20,7 @@ mod antialiasing; mod cached; mod error; mod primitive; +mod settings; mod viewport; pub mod backend; @@ -47,6 +48,7 @@ pub use gradient::Gradient; pub use mesh::Mesh; pub use primitive::Primitive; pub use renderer::Renderer; +pub use settings::Settings; pub use viewport::Viewport; pub use iced_core as core; diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index eb720495..5de7f97f 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,5 +1,6 @@ //! Create a renderer from a [`Backend`]. use crate::backend::{self, Backend}; +use crate::compositor; use crate::core; use crate::core::image; use crate::core::renderer; @@ -259,3 +260,10 @@ where self.draw_primitive(geometry); } } + +impl compositor::Renderer for Renderer +where + B: Backend, +{ + type Compositor = B::Compositor; +} diff --git a/graphics/src/settings.rs b/graphics/src/settings.rs new file mode 100644 index 00000000..68673536 --- /dev/null +++ b/graphics/src/settings.rs @@ -0,0 +1,29 @@ +use crate::core::{Font, Pixels}; +use crate::Antialiasing; + +/// The settings of a Backend. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Settings { + /// The default [`Font`] to use. + pub default_font: Font, + + /// The default size of text. + /// + /// By default, it will be set to `16.0`. + pub default_text_size: Pixels, + + /// The antialiasing strategy that will be used for triangle primitives. + /// + /// By default, it is `None`. + pub antialiasing: Option, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: Font::default(), + default_text_size: Pixels(16.0), + antialiasing: None, + } + } +} -- cgit From 441e9237cd1c9c9b61d9b144b5b4dafa236ace28 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 19:35:19 +0100 Subject: Rename `compositor::Renderer` to `Default` --- graphics/src/backend.rs | 6 +++--- graphics/src/compositor.rs | 7 +++---- graphics/src/renderer.rs | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index aa7bf4e8..7abc42c5 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -10,11 +10,11 @@ use std::borrow::Cow; /// /// [`Renderer`]: crate::Renderer pub trait Backend: Sized { - /// The compositor of this [`Backend`]. - type Compositor: Compositor>; - /// The custom kind of primitives this [`Backend`] supports. type Primitive: TryFrom; + + /// The default compositor of this [`Backend`]. + type Compositor: Compositor>; } /// A graphics backend that supports text rendering. diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 4d548f30..8c67cd16 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -1,6 +1,5 @@ //! A compositor is responsible for initializing a renderer and managing window //! surfaces. -use crate::core; use crate::core::Color; use crate::futures::{MaybeSend, MaybeSync}; use crate::{Error, Settings, Viewport}; @@ -90,8 +89,8 @@ impl Window for T where { } -/// A renderer that supports composition. -pub trait Renderer: core::Renderer { +/// Defines the default compositor of a renderer. +pub trait Default { /// The compositor of the renderer. type Compositor: Compositor; } @@ -187,6 +186,6 @@ impl Compositor for () { } #[cfg(debug_assertions)] -impl Renderer for () { +impl Default for () { type Compositor = (); } diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 5de7f97f..f517ff3e 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -261,7 +261,7 @@ where } } -impl compositor::Renderer for Renderer +impl compositor::Default for Renderer where B: Backend, { -- cgit From 4f5b63f1f4cd7d3ab72289c697f4abc767114eca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez 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 +- 3 files changed, 40 insertions(+), 4 deletions(-) (limited to 'graphics/src') 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( settings: Settings, compatible_window: W, + ) -> impl Future> { + 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( + _settings: Settings, + _compatible_window: W, + _backend: Option<&str>, ) -> impl Future>; /// Creates a [`Self::Renderer`] for the [`Compositor`]. @@ -130,9 +142,10 @@ impl Compositor for () { type Renderer = (); type Surface = (); - async fn new( + async fn with_backend( _settings: Settings, _compatible_window: W, + _preffered_backend: Option<&str>, ) -> Result { 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), +} + +/// 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; -- cgit From faa53647cc83577e1ecb81a450c948b3fa4203e0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 30 Mar 2024 15:57:12 +0100 Subject: Replace `xxhash-rust` with `rustc-hash` --- graphics/src/text/cache.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs index 7fb33567..b6473f85 100644 --- a/graphics/src/text/cache.rs +++ b/graphics/src/text/cache.rs @@ -2,9 +2,9 @@ use crate::core::{Font, Size}; use crate::text; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; use std::collections::hash_map; -use std::hash::{BuildHasher, Hash, Hasher}; +use std::hash::{Hash, Hasher}; /// A store of recently used sections of text. #[allow(missing_debug_implementations)] @@ -13,11 +13,8 @@ pub struct Cache { entries: FxHashMap, aliases: FxHashMap, recently_used: FxHashSet, - hasher: HashBuilder, } -type HashBuilder = xxhash_rust::xxh3::Xxh3Builder; - impl Cache { /// Creates a new empty [`Cache`]. pub fn new() -> Self { @@ -35,7 +32,7 @@ impl Cache { font_system: &mut cosmic_text::FontSystem, key: Key<'_>, ) -> (KeyHash, &mut Entry) { - let hash = key.hash(self.hasher.build_hasher()); + let hash = key.hash(FxHasher::default()); if let Some(hash) = self.aliases.get(&hash) { let _ = self.recently_used.insert(*hash); @@ -77,7 +74,7 @@ impl Cache { ] { if key.bounds != bounds { let _ = self.aliases.insert( - Key { bounds, ..key }.hash(self.hasher.build_hasher()), + Key { bounds, ..key }.hash(FxHasher::default()), hash, ); } -- cgit From 6216c513d5e5853bf1d43342094e91a74981f4f2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 1 Apr 2024 11:30:01 +0200 Subject: Use generic `Content` in `Text` to avoid reallocation in `fill_text` --- graphics/src/renderer.rs | 4 ++-- graphics/src/text/paragraph.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index f517ff3e..fb1a0d73 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -163,13 +163,13 @@ where fn fill_text( &mut self, - text: Text<'_, Self::Font>, + text: Text, position: Point, color: Color, clip_bounds: Rectangle, ) { self.primitives.push(Primitive::Text { - content: text.content.to_string(), + content: text.content, bounds: Rectangle::new(position, text.bounds), size: text.size, line_height: text.line_height, diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 5d027542..31a323ac 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -61,7 +61,7 @@ impl Paragraph { impl core::text::Paragraph for Paragraph { type Font = Font; - fn with_text(text: Text<'_, Font>) -> Self { + fn with_text(text: Text<&str>) -> Self { log::trace!("Allocating paragraph: {}", text.content); let mut font_system = @@ -146,7 +146,7 @@ impl core::text::Paragraph for Paragraph { } } - fn compare(&self, text: Text<'_, Font>) -> core::text::Difference { + fn compare(&self, text: Text<&str>) -> core::text::Difference { let font_system = text::font_system().read().expect("Read font system"); let paragraph = self.internal(); let metrics = paragraph.buffer.metrics(); -- cgit From b05e61f5c8ae61c9f3c7cc08cded53901ebbccfd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 21:07:54 +0200 Subject: Redesign `iced_wgpu` layering architecture --- graphics/src/cached.rs | 8 +-- graphics/src/geometry/cache.rs | 10 ++- graphics/src/image.rs | 154 +++++++++++++++++++++++++---------------- graphics/src/layer.rs | 47 +++++++++++++ graphics/src/lib.rs | 6 +- graphics/src/mesh.rs | 90 ++++++++++++++++++++---- graphics/src/renderer.rs | 4 +- graphics/src/text.rs | 57 ++++++++++++++- 8 files changed, 290 insertions(+), 86 deletions(-) create mode 100644 graphics/src/layer.rs (limited to 'graphics/src') diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs index b52f9d9d..1ba82f9f 100644 --- a/graphics/src/cached.rs +++ b/graphics/src/cached.rs @@ -5,7 +5,7 @@ use std::sync::Arc; /// A piece of data that can be cached. pub trait Cached: Sized { /// The type of cache produced. - type Cache; + type Cache: Clone; /// Loads the [`Cache`] into a proper instance. /// @@ -15,7 +15,7 @@ pub trait Cached: Sized { /// Caches this value, producing its corresponding [`Cache`]. /// /// [`Cache`]: Self::Cache - fn cache(self) -> Self::Cache; + fn cache(self, previous: Option) -> Self::Cache; } impl Cached for Primitive { @@ -27,7 +27,7 @@ impl Cached for Primitive { } } - fn cache(self) -> Arc { + fn cache(self, _previous: Option>) -> Arc { Arc::new(self) } } @@ -38,5 +38,5 @@ impl Cached for () { fn load(_cache: &Self::Cache) -> Self {} - fn cache(self) -> Self::Cache {} + fn cache(self, _previous: Option) -> Self::Cache {} } diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs index 37d433c2..ebbafd14 100644 --- a/graphics/src/geometry/cache.rs +++ b/graphics/src/geometry/cache.rs @@ -49,7 +49,7 @@ where ) -> Renderer::Geometry { use std::ops::Deref; - if let State::Filled { + let previous = if let State::Filled { bounds: cached_bounds, geometry, } = self.state.borrow().deref() @@ -57,12 +57,16 @@ where if *cached_bounds == bounds { return Cached::load(geometry); } - } + + Some(geometry.clone()) + } else { + None + }; let mut frame = Frame::new(renderer, bounds); draw_fn(&mut frame); - let geometry = frame.into_geometry().cache(); + let geometry = frame.into_geometry().cache(previous); let result = Cached::load(&geometry); *self.state.borrow_mut() = State::Filled { bounds, geometry }; diff --git a/graphics/src/image.rs b/graphics/src/image.rs index d89caace..e8626717 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -1,14 +1,94 @@ //! Load and operate on images. -use crate::core::image::{Data, Handle}; +#[cfg(feature = "image")] +pub use ::image as image_rs; -use bitflags::bitflags; +use crate::core::image; +use crate::core::svg; +use crate::core::{Color, Rectangle}; -pub use ::image as image_rs; +/// A raster or vector image. +#[derive(Debug, Clone)] +pub enum Image { + /// A raster image. + Raster { + /// The handle of a raster image. + handle: image::Handle, + + /// The filter method of a raster image. + filter_method: image::FilterMethod, + + /// The bounds of the image. + bounds: Rectangle, + }, + /// A vector image. + Vector { + /// The handle of a vector image. + handle: svg::Handle, + + /// The [`Color`] filter + color: Option, + /// The bounds of the image. + bounds: Rectangle, + }, +} + +#[cfg(feature = "image")] /// Tries to load an image by its [`Handle`]. -pub fn load(handle: &Handle) -> image_rs::ImageResult { +pub fn load( + handle: &image::Handle, +) -> ::image::ImageResult<::image::DynamicImage> { + use bitflags::bitflags; + + bitflags! { + struct Operation: u8 { + const FLIP_HORIZONTALLY = 0b001; + const ROTATE_180 = 0b010; + const FLIP_DIAGONALLY = 0b100; + } + } + + impl Operation { + // Meaning of the returned value is described e.g. at: + // https://magnushoff.com/articles/jpeg-orientation/ + fn from_exif(reader: &mut R) -> Result + where + R: std::io::BufRead + std::io::Seek, + { + let exif = exif::Reader::new().read_from_container(reader)?; + + Ok(exif + .get_field(exif::Tag::Orientation, exif::In::PRIMARY) + .and_then(|field| field.value.get_uint(0)) + .and_then(|value| u8::try_from(value).ok()) + .and_then(|value| Self::from_bits(value.saturating_sub(1))) + .unwrap_or_else(Self::empty)) + } + + fn perform( + self, + mut image: ::image::DynamicImage, + ) -> ::image::DynamicImage { + use ::image::imageops; + + if self.contains(Self::FLIP_DIAGONALLY) { + imageops::flip_vertical_in_place(&mut image); + } + + if self.contains(Self::ROTATE_180) { + imageops::rotate180_in_place(&mut image); + } + + if self.contains(Self::FLIP_HORIZONTALLY) { + imageops::flip_horizontal_in_place(&mut image); + } + + image + } + } + match handle.data() { - Data::Path(path) => { + image::Data::Path(path) => { let image = ::image::open(path)?; let operation = std::fs::File::open(path) @@ -19,7 +99,7 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult { Ok(operation.perform(image)) } - Data::Bytes(bytes) => { + image::Data::Bytes(bytes) => { let image = ::image::load_from_memory(bytes)?; let operation = Operation::from_exif(&mut std::io::Cursor::new(bytes)) @@ -28,68 +108,22 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult { Ok(operation.perform(image)) } - Data::Rgba { + image::Data::Rgba { width, height, pixels, } => { - if let Some(image) = image_rs::ImageBuffer::from_vec( - *width, - *height, - pixels.to_vec(), - ) { - Ok(image_rs::DynamicImage::ImageRgba8(image)) + if let Some(image) = + ::image::ImageBuffer::from_vec(*width, *height, pixels.to_vec()) + { + Ok(::image::DynamicImage::ImageRgba8(image)) } else { - Err(image_rs::error::ImageError::Limits( - image_rs::error::LimitError::from_kind( - image_rs::error::LimitErrorKind::DimensionError, + Err(::image::error::ImageError::Limits( + ::image::error::LimitError::from_kind( + ::image::error::LimitErrorKind::DimensionError, ), )) } } } } - -bitflags! { - struct Operation: u8 { - const FLIP_HORIZONTALLY = 0b001; - const ROTATE_180 = 0b010; - const FLIP_DIAGONALLY = 0b100; - } -} - -impl Operation { - // Meaning of the returned value is described e.g. at: - // https://magnushoff.com/articles/jpeg-orientation/ - fn from_exif(reader: &mut R) -> Result - where - R: std::io::BufRead + std::io::Seek, - { - let exif = exif::Reader::new().read_from_container(reader)?; - - Ok(exif - .get_field(exif::Tag::Orientation, exif::In::PRIMARY) - .and_then(|field| field.value.get_uint(0)) - .and_then(|value| u8::try_from(value).ok()) - .and_then(|value| Self::from_bits(value.saturating_sub(1))) - .unwrap_or_else(Self::empty)) - } - - fn perform(self, mut image: image::DynamicImage) -> image::DynamicImage { - use image::imageops; - - if self.contains(Self::FLIP_DIAGONALLY) { - imageops::flip_vertical_in_place(&mut image); - } - - if self.contains(Self::ROTATE_180) { - imageops::rotate180_in_place(&mut image); - } - - if self.contains(Self::FLIP_HORIZONTALLY) { - imageops::flip_horizontal_in_place(&mut image); - } - - image - } -} diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs new file mode 100644 index 00000000..5b8aacab --- /dev/null +++ b/graphics/src/layer.rs @@ -0,0 +1,47 @@ +pub trait Layer { + type Cache; + + fn new() -> Self; + + fn clear(&mut self); +} + +pub struct Recorder { + layers: Vec, + caches: Vec, + stack: Vec, + current: usize, +} + +enum Kind { + Fresh(usize), + Cache(usize), +} + +impl Recorder { + pub fn new() -> Self { + Self { + layers: vec![Layer::new()], + caches: Vec::new(), + stack: Vec::new(), + current: 0, + } + } + + pub fn fill_quad(&mut self) {} + + pub fn push_cache(&mut self, cache: T::Cache) { + self.caches.push(cache); + } + + pub fn clear(&mut self) { + self.caches.clear(); + self.stack.clear(); + + for mut layer in self.layers { + layer.clear(); + } + + self.current = 0; + } +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index d7f2f439..5857aea5 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -28,6 +28,7 @@ pub mod compositor; pub mod damage; pub mod error; pub mod gradient; +pub mod image; pub mod mesh; pub mod renderer; pub mod text; @@ -35,9 +36,6 @@ pub mod text; #[cfg(feature = "geometry")] pub mod geometry; -#[cfg(feature = "image")] -pub mod image; - pub use antialiasing::Antialiasing; pub use backend::Backend; pub use cached::Cached; @@ -45,10 +43,12 @@ pub use compositor::Compositor; pub use damage::Damage; pub use error::Error; pub use gradient::Gradient; +pub use image::Image; pub use mesh::Mesh; pub use primitive::Primitive; pub use renderer::Renderer; pub use settings::Settings; +pub use text::Text; pub use viewport::Viewport; pub use iced_core as core; diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index 20692b07..d3e7ffaf 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -1,8 +1,7 @@ //! Draw triangles! use crate::color; -use crate::core::{Rectangle, Size}; +use crate::core::{Rectangle, Size, Transformation}; use crate::gradient; -use crate::Damage; use bytemuck::{Pod, Zeroable}; @@ -14,9 +13,10 @@ pub enum Mesh { /// The vertices and indices of the mesh. buffers: Indexed, - /// The size of the drawable region of the mesh. - /// - /// Any geometry that falls out of this region will be clipped. + /// The [`Transformation`] for the vertices of the [`Mesh`]. + transformation: Transformation, + + /// The [`Size`] of the [`Mesh`]. size: Size, }, /// A mesh with a gradient. @@ -24,19 +24,44 @@ pub enum Mesh { /// The vertices and indices of the mesh. buffers: Indexed, - /// The size of the drawable region of the mesh. - /// - /// Any geometry that falls out of this region will be clipped. + /// The [`Transformation`] for the vertices of the [`Mesh`]. + transformation: Transformation, + + /// The [`Size`] of the [`Mesh`]. size: Size, }, } -impl Damage for Mesh { - fn bounds(&self) -> Rectangle { +impl Mesh { + /// Returns the indices of the [`Mesh`]. + pub fn indices(&self) -> &[u32] { + match self { + Self::Solid { buffers, .. } => &buffers.indices, + Self::Gradient { buffers, .. } => &buffers.indices, + } + } + + /// Returns the [`Transformation`] of the [`Mesh`]. + pub fn transformation(&self) -> Transformation { + match self { + Self::Solid { transformation, .. } + | Self::Gradient { transformation, .. } => *transformation, + } + } + + /// Returns the clip bounds of the [`Mesh`]. + pub fn clip_bounds(&self) -> Rectangle { match self { - Self::Solid { size, .. } | Self::Gradient { size, .. } => { - Rectangle::with_size(*size) + Self::Solid { + size, + transformation, + .. } + | Self::Gradient { + size, + transformation, + .. + } => Rectangle::with_size(*size) * *transformation, } } } @@ -75,6 +100,47 @@ pub struct GradientVertex2D { pub gradient: gradient::Packed, } +/// The result of counting the attributes of a set of meshes. +#[derive(Debug, Clone, Copy, Default)] +pub struct AttributeCount { + /// The total amount of solid vertices. + pub solid_vertices: usize, + + /// The total amount of solid meshes. + pub solids: usize, + + /// The total amount of gradient vertices. + pub gradient_vertices: usize, + + /// The total amount of gradient meshes. + pub gradients: usize, + + /// The total amount of indices. + pub indices: usize, +} + +/// Returns the number of total vertices & total indices of all [`Mesh`]es. +pub fn attribute_count_of(meshes: &[Mesh]) -> AttributeCount { + meshes + .iter() + .fold(AttributeCount::default(), |mut count, mesh| { + match mesh { + Mesh::Solid { buffers, .. } => { + count.solids += 1; + count.solid_vertices += buffers.vertices.len(); + count.indices += buffers.indices.len(); + } + Mesh::Gradient { buffers, .. } => { + count.gradients += 1; + count.gradient_vertices += buffers.vertices.len(); + count.indices += buffers.indices.len(); + } + } + + count + }) +} + /// A renderer capable of drawing a [`Mesh`]. pub trait Renderer { /// Draws the given [`Mesh`]. diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index fb1a0d73..d4f91dab 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -62,7 +62,7 @@ impl Renderer { } impl iced_core::Renderer for Renderer { - fn start_layer(&mut self) { + fn start_layer(&mut self, _bounds: Rectangle) { self.stack.push(std::mem::take(&mut self.primitives)); } @@ -75,7 +75,7 @@ impl iced_core::Renderer for Renderer { self.primitives.push(Primitive::group(layer).clip(bounds)); } - fn start_transformation(&mut self) { + fn start_transformation(&mut self, _transformation: Transformation) { self.stack.push(std::mem::take(&mut self.primitives)); } diff --git a/graphics/src/text.rs b/graphics/src/text.rs index 0310ead7..c9c821c0 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -9,14 +9,67 @@ pub use paragraph::Paragraph; pub use cosmic_text; +use crate::core::alignment; use crate::core::font::{self, Font}; -use crate::core::text::Shaping; -use crate::core::{Color, Point, Rectangle, Size}; +use crate::core::text::{LineHeight, Shaping}; +use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation}; use once_cell::sync::OnceCell; use std::borrow::Cow; use std::sync::{Arc, RwLock, Weak}; +/// A text primitive. +#[derive(Debug, Clone)] +pub enum Text { + /// A paragraph. + #[allow(missing_docs)] + Paragraph { + paragraph: paragraph::Weak, + position: Point, + color: Color, + clip_bounds: Rectangle, + transformation: Transformation, + }, + /// An editor. + #[allow(missing_docs)] + Editor { + editor: editor::Weak, + position: Point, + color: Color, + clip_bounds: Rectangle, + transformation: Transformation, + }, + /// Some cached text. + Cached { + /// The contents of the text. + content: String, + /// The bounds of the text. + bounds: Rectangle, + /// The color of the text. + color: Color, + /// The size of the text in logical pixels. + size: Pixels, + /// The line height of the text. + line_height: LineHeight, + /// The font of the text. + font: Font, + /// The horizontal alignment of the text. + horizontal_alignment: alignment::Horizontal, + /// The vertical alignment of the text. + vertical_alignment: alignment::Vertical, + /// The shaping strategy of the text. + shaping: Shaping, + /// The clip bounds of the text. + clip_bounds: Rectangle, + }, + /// Some raw text. + #[allow(missing_docs)] + Raw { + raw: Raw, + transformation: Transformation, + }, +} + /// The regular variant of the [Fira Sans] font. /// /// It is loaded as part of the default fonts in Wasm builds. -- cgit From 09af6773bdfe3039f6bf1720da945ae874496b81 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 21:09:59 +0200 Subject: Remove unused `layer` module in `iced_graphics` --- graphics/src/layer.rs | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 graphics/src/layer.rs (limited to 'graphics/src') diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs deleted file mode 100644 index 5b8aacab..00000000 --- a/graphics/src/layer.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub trait Layer { - type Cache; - - fn new() -> Self; - - fn clear(&mut self); -} - -pub struct Recorder { - layers: Vec, - caches: Vec, - stack: Vec, - current: usize, -} - -enum Kind { - Fresh(usize), - Cache(usize), -} - -impl Recorder { - pub fn new() -> Self { - Self { - layers: vec![Layer::new()], - caches: Vec::new(), - stack: Vec::new(), - current: 0, - } - } - - pub fn fill_quad(&mut self) {} - - pub fn push_cache(&mut self, cache: T::Cache) { - self.caches.push(cache); - } - - pub fn clear(&mut self) { - self.caches.clear(); - self.stack.clear(); - - for mut layer in self.layers { - layer.clear(); - } - - self.current = 0; - } -} -- cgit From e2c129c057b521009d451e474a6820601877cf11 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 23:14:16 +0200 Subject: Fix `geometry::Cache` not reusing previous geometry --- graphics/src/geometry/cache.rs | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs index ebbafd14..665e996b 100644 --- a/graphics/src/geometry/cache.rs +++ b/graphics/src/geometry/cache.rs @@ -22,13 +22,20 @@ where /// Creates a new empty [`Cache`]. pub fn new() -> Self { Cache { - state: RefCell::new(State::Empty), + state: RefCell::new(State::Empty { previous: None }), } } /// Clears the [`Cache`], forcing a redraw the next time it is used. pub fn clear(&self) { - *self.state.borrow_mut() = State::Empty; + use std::ops::Deref; + + let previous = match self.state.borrow().deref() { + State::Empty { previous } => previous.clone(), + State::Filled { geometry, .. } => Some(geometry.clone()), + }; + + *self.state.borrow_mut() = State::Empty { previous }; } /// Draws geometry using the provided closure and stores it in the @@ -49,18 +56,18 @@ where ) -> Renderer::Geometry { use std::ops::Deref; - let previous = if let State::Filled { - bounds: cached_bounds, - geometry, - } = self.state.borrow().deref() - { - if *cached_bounds == bounds { - return Cached::load(geometry); - } + let previous = match self.state.borrow().deref() { + State::Empty { previous } => previous.clone(), + State::Filled { + bounds: cached_bounds, + geometry, + } => { + if *cached_bounds == bounds { + return Cached::load(geometry); + } - Some(geometry.clone()) - } else { - None + Some(geometry.clone()) + } }; let mut frame = Frame::new(renderer, bounds); @@ -83,7 +90,7 @@ where let state = self.state.borrow(); match *state { - State::Empty => write!(f, "Cache::Empty"), + State::Empty { .. } => write!(f, "Cache::Empty"), State::Filled { bounds, .. } => { write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") } @@ -104,7 +111,9 @@ enum State where Geometry: Cached, { - Empty, + Empty { + previous: Option, + }, Filled { bounds: Size, geometry: Geometry::Cache, -- cgit From cc05cb9be4a1de5f0427f93ce64e658be0703f8b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 23:39:38 +0200 Subject: Fix broken doc links in `iced_wgpu` and `iced_graphics` --- graphics/src/image.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index e8626717..47a7b30e 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -35,6 +35,8 @@ pub enum Image { #[cfg(feature = "image")] /// Tries to load an image by its [`Handle`]. +/// +/// [`Handle`]: image::Handle pub fn load( handle: &image::Handle, ) -> ::image::ImageResult<::image::DynamicImage> { -- cgit From 6d3e1d835e1688fbc58622a03a784ed25ed3f0e1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Apr 2024 23:59:21 +0200 Subject: Decouple caching from layering and simplify everything --- graphics/src/geometry/frame.rs | 16 +++++++--------- graphics/src/mesh.rs | 16 ++++++++-------- graphics/src/text.rs | 4 ++-- 3 files changed, 17 insertions(+), 19 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index ad35e8ec..377589d7 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -113,13 +113,11 @@ where region: Rectangle, f: impl FnOnce(&mut Self) -> R, ) -> R { - let mut frame = self.draft(region.size()); + let mut frame = self.draft(region); let result = f(&mut frame); - let origin = Point::new(region.x, region.y); - - self.paste(frame, origin); + self.paste(frame, Point::new(region.x, region.y)); result } @@ -129,14 +127,14 @@ where /// Draw its contents back to this [`Frame`] with [`paste`]. /// /// [`paste`]: Self::paste - pub fn draft(&mut self, size: Size) -> Self { + fn draft(&mut self, clip_bounds: Rectangle) -> Self { Self { - raw: self.raw.draft(size), + raw: self.raw.draft(clip_bounds), } } /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. - pub fn paste(&mut self, frame: Self, at: Point) { + fn paste(&mut self, frame: Self, at: Point) { self.raw.paste(frame.raw, at); } @@ -187,7 +185,7 @@ pub trait Backend: Sized { fn scale(&mut self, scale: impl Into); fn scale_nonuniform(&mut self, scale: impl Into); - fn draft(&mut self, size: Size) -> Self; + fn draft(&mut self, clip_bounds: Rectangle) -> Self; fn paste(&mut self, frame: Self, at: Point); fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>); @@ -232,7 +230,7 @@ impl Backend for () { fn scale(&mut self, _scale: impl Into) {} fn scale_nonuniform(&mut self, _scale: impl Into) {} - fn draft(&mut self, _size: Size) -> Self {} + fn draft(&mut self, _clip_bounds: Rectangle) -> Self {} fn paste(&mut self, _frame: Self, _at: Point) {} fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into>) {} diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index d3e7ffaf..76602319 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, Transformation}; +use crate::core::{Rectangle, Transformation}; use crate::gradient; use bytemuck::{Pod, Zeroable}; @@ -16,8 +16,8 @@ pub enum Mesh { /// The [`Transformation`] for the vertices of the [`Mesh`]. transformation: Transformation, - /// The [`Size`] of the [`Mesh`]. - size: Size, + /// The clip bounds of the [`Mesh`]. + clip_bounds: Rectangle, }, /// A mesh with a gradient. Gradient { @@ -27,8 +27,8 @@ pub enum Mesh { /// The [`Transformation`] for the vertices of the [`Mesh`]. transformation: Transformation, - /// The [`Size`] of the [`Mesh`]. - size: Size, + /// The clip bounds of the [`Mesh`]. + clip_bounds: Rectangle, }, } @@ -53,15 +53,15 @@ impl Mesh { pub fn clip_bounds(&self) -> Rectangle { match self { Self::Solid { - size, + clip_bounds, transformation, .. } | Self::Gradient { - size, + clip_bounds, transformation, .. - } => Rectangle::with_size(*size) * *transformation, + } => *clip_bounds * *transformation, } } } diff --git a/graphics/src/text.rs b/graphics/src/text.rs index c9c821c0..f9fc1fec 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -11,7 +11,7 @@ pub use cosmic_text; use crate::core::alignment; use crate::core::font::{self, Font}; -use crate::core::text::{LineHeight, Shaping}; +use crate::core::text::Shaping; use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation}; use once_cell::sync::OnceCell; @@ -50,7 +50,7 @@ pub enum Text { /// The size of the text in logical pixels. size: Pixels, /// The line height of the text. - line_height: LineHeight, + line_height: Pixels, /// The font of the text. font: Font, /// The horizontal alignment of the text. -- cgit From 5cd98f069dea8720bca7748d6c12fa410cbe79b5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 7 Apr 2024 12:42:12 +0200 Subject: Use built-in `[lints]` table in `Cargo.toml` --- graphics/src/lib.rs | 8 -------- graphics/src/text/cache.rs | 5 ++--- 2 files changed, 2 insertions(+), 11 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index d7f2f439..18bfd981 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -7,14 +7,6 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] -#![forbid(rust_2018_idioms)] -#![deny( - 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; diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs index b6473f85..822b61c4 100644 --- a/graphics/src/text/cache.rs +++ b/graphics/src/text/cache.rs @@ -7,8 +7,7 @@ use std::collections::hash_map; use std::hash::{Hash, Hasher}; /// A store of recently used sections of text. -#[allow(missing_debug_implementations)] -#[derive(Default)] +#[derive(Debug, Default)] pub struct Cache { entries: FxHashMap, aliases: FxHashMap, @@ -135,7 +134,7 @@ impl Key<'_> { pub type KeyHash = u64; /// A cache entry. -#[allow(missing_debug_implementations)] +#[derive(Debug)] pub struct Entry { /// The buffer of text, ready for drawing. pub buffer: cosmic_text::Buffer, -- cgit From 6ad5bb3597f640ac329801adf735d633bf0a512f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 9 Apr 2024 22:25:16 +0200 Subject: Port `iced_tiny_skia` to new layering architecture --- graphics/src/backend.rs | 36 ------- graphics/src/cached.rs | 18 ---- graphics/src/compositor.rs | 14 ++- graphics/src/damage.rs | 257 --------------------------------------------- graphics/src/layer.rs | 139 ++++++++++++++++++++++++ graphics/src/lib.rs | 10 +- graphics/src/primitive.rs | 160 ---------------------------- graphics/src/renderer.rs | 2 +- graphics/src/text.rs | 2 +- 9 files changed, 156 insertions(+), 482 deletions(-) delete mode 100644 graphics/src/backend.rs delete mode 100644 graphics/src/damage.rs create mode 100644 graphics/src/layer.rs delete mode 100644 graphics/src/primitive.rs (limited to 'graphics/src') diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs deleted file mode 100644 index 7abc42c5..00000000 --- a/graphics/src/backend.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Write a graphics backend. -use crate::core::image; -use crate::core::svg; -use crate::core::Size; -use crate::{Compositor, Mesh, Renderer}; - -use std::borrow::Cow; - -/// The graphics backend of a [`Renderer`]. -/// -/// [`Renderer`]: crate::Renderer -pub trait Backend: Sized { - /// The custom kind of primitives this [`Backend`] supports. - type Primitive: TryFrom; - - /// The default compositor of this [`Backend`]. - type Compositor: Compositor>; -} - -/// A graphics backend that supports text rendering. -pub trait Text { - /// Loads a font from its bytes. - fn load_font(&mut self, font: Cow<'static, [u8]>); -} - -/// A graphics backend that supports image rendering. -pub trait Image { - /// Returns the dimensions of the provided image. - fn dimensions(&self, handle: &image::Handle) -> Size; -} - -/// A graphics backend that supports SVG rendering. -pub trait Svg { - /// Returns the viewport dimensions of the provided SVG. - fn viewport_dimensions(&self, handle: &svg::Handle) -> Size; -} diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs index 1ba82f9f..c0e4e029 100644 --- a/graphics/src/cached.rs +++ b/graphics/src/cached.rs @@ -1,7 +1,3 @@ -use crate::Primitive; - -use std::sync::Arc; - /// A piece of data that can be cached. pub trait Cached: Sized { /// The type of cache produced. @@ -18,20 +14,6 @@ pub trait Cached: Sized { fn cache(self, previous: Option) -> Self::Cache; } -impl Cached for Primitive { - type Cache = Arc; - - fn load(cache: &Arc) -> Self { - Self::Cache { - content: cache.clone(), - } - } - - fn cache(self, _previous: Option>) -> Arc { - Arc::new(self) - } -} - #[cfg(debug_assertions)] impl Cached for () { type Cache = (); diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 86472a58..47521eb0 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -5,9 +5,11 @@ use crate::futures::{MaybeSend, MaybeSync}; use crate::{Error, Settings, Viewport}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; -use std::future::Future; use thiserror::Error; +use std::borrow::Cow; +use std::future::Future; + /// A graphics compositor that can draw to windows. pub trait Compositor: Sized { /// The iced renderer of the backend. @@ -60,6 +62,14 @@ pub trait Compositor: Sized { /// Returns [`Information`] used by this [`Compositor`]. fn fetch_information(&self) -> Information; + /// Loads a font from its bytes. + fn load_font(&mut self, font: Cow<'static, [u8]>) { + crate::text::font_system() + .write() + .expect("Write to font system") + .load_font(font); + } + /// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`]. /// /// [`Renderer`]: Self::Renderer @@ -168,6 +178,8 @@ impl Compositor for () { ) { } + fn load_font(&mut self, _font: Cow<'static, [u8]>) {} + fn fetch_information(&self) -> Information { Information { adapter: String::from("Null Renderer"), diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs deleted file mode 100644 index 8edf69d7..00000000 --- a/graphics/src/damage.rs +++ /dev/null @@ -1,257 +0,0 @@ -//! Track and compute the damage of graphical primitives. -use crate::core::alignment; -use crate::core::{Rectangle, Size}; -use crate::Primitive; - -use std::sync::Arc; - -/// A type that has some damage bounds. -pub trait Damage: PartialEq { - /// Returns the bounds of the [`Damage`]. - fn bounds(&self) -> Rectangle; -} - -impl Damage for Primitive { - fn bounds(&self) -> Rectangle { - match self { - Self::Text { - bounds, - horizontal_alignment, - vertical_alignment, - .. - } => { - let mut bounds = *bounds; - - bounds.x = match horizontal_alignment { - alignment::Horizontal::Left => bounds.x, - alignment::Horizontal::Center => { - bounds.x - bounds.width / 2.0 - } - alignment::Horizontal::Right => bounds.x - bounds.width, - }; - - bounds.y = match vertical_alignment { - alignment::Vertical::Top => bounds.y, - alignment::Vertical::Center => { - bounds.y - bounds.height / 2.0 - } - alignment::Vertical::Bottom => bounds.y - bounds.height, - }; - - bounds.expand(1.5) - } - Self::Paragraph { - paragraph, - position, - .. - } => { - let mut bounds = - Rectangle::new(*position, paragraph.min_bounds); - - bounds.x = match paragraph.horizontal_alignment { - alignment::Horizontal::Left => bounds.x, - alignment::Horizontal::Center => { - bounds.x - bounds.width / 2.0 - } - alignment::Horizontal::Right => bounds.x - bounds.width, - }; - - bounds.y = match paragraph.vertical_alignment { - alignment::Vertical::Top => bounds.y, - alignment::Vertical::Center => { - bounds.y - bounds.height / 2.0 - } - alignment::Vertical::Bottom => bounds.y - bounds.height, - }; - - bounds.expand(1.5) - } - Self::Editor { - editor, position, .. - } => { - let bounds = Rectangle::new(*position, editor.bounds); - - bounds.expand(1.5) - } - Self::RawText(raw) => { - // TODO: Add `size` field to `raw` to compute more accurate - // damage bounds (?) - raw.clip_bounds.expand(1.5) - } - Self::Quad { bounds, shadow, .. } if shadow.color.a > 0.0 => { - let bounds_with_shadow = Rectangle { - x: bounds.x + shadow.offset.x.min(0.0) - shadow.blur_radius, - y: bounds.y + shadow.offset.y.min(0.0) - shadow.blur_radius, - width: bounds.width - + shadow.offset.x.abs() - + shadow.blur_radius * 2.0, - height: bounds.height - + shadow.offset.y.abs() - + shadow.blur_radius * 2.0, - }; - - bounds_with_shadow.expand(1.0) - } - Self::Quad { bounds, .. } - | Self::Image { bounds, .. } - | Self::Svg { bounds, .. } => bounds.expand(1.0), - Self::Clip { bounds, .. } => bounds.expand(1.0), - Self::Group { primitives } => primitives - .iter() - .map(Self::bounds) - .fold(Rectangle::with_size(Size::ZERO), |a, b| { - Rectangle::union(&a, &b) - }), - Self::Transform { - transformation, - content, - } => content.bounds() * *transformation, - Self::Cache { content } => content.bounds(), - Self::Custom(custom) => custom.bounds(), - } - } -} - -fn regions(a: &Primitive, b: &Primitive) -> Vec { - match (a, b) { - ( - Primitive::Group { - primitives: primitives_a, - }, - Primitive::Group { - primitives: primitives_b, - }, - ) => return list(primitives_a, primitives_b), - ( - Primitive::Clip { - bounds: bounds_a, - content: content_a, - .. - }, - Primitive::Clip { - bounds: bounds_b, - content: content_b, - .. - }, - ) => { - if bounds_a == bounds_b { - return regions(content_a, content_b) - .into_iter() - .filter_map(|r| r.intersection(&bounds_a.expand(1.0))) - .collect(); - } else { - return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)]; - } - } - ( - Primitive::Transform { - transformation: transformation_a, - content: content_a, - }, - Primitive::Transform { - transformation: transformation_b, - content: content_b, - }, - ) => { - if transformation_a == transformation_b { - return regions(content_a, content_b) - .into_iter() - .map(|r| r * *transformation_a) - .collect(); - } - } - ( - Primitive::Cache { content: content_a }, - Primitive::Cache { content: content_b }, - ) => { - if Arc::ptr_eq(content_a, content_b) { - return vec![]; - } - } - _ if a == b => return vec![], - _ => {} - } - - let bounds_a = a.bounds(); - let bounds_b = b.bounds(); - - if bounds_a == bounds_b { - vec![bounds_a] - } else { - vec![bounds_a, bounds_b] - } -} - -/// Computes the damage regions between the two given lists of primitives. -pub fn list( - previous: &[Primitive], - current: &[Primitive], -) -> Vec { - let damage = previous - .iter() - .zip(current) - .flat_map(|(a, b)| regions(a, b)); - - if previous.len() == current.len() { - damage.collect() - } else { - let (smaller, bigger) = if previous.len() < current.len() { - (previous, current) - } else { - (current, previous) - }; - - // Extend damage by the added/removed primitives - damage - .chain(bigger[smaller.len()..].iter().map(Damage::bounds)) - .collect() - } -} - -/// Groups the given damage regions that are close together inside the given -/// bounds. -pub fn group( - mut damage: Vec, - scale_factor: f32, - bounds: Size, -) -> Vec { - use std::cmp::Ordering; - - const AREA_THRESHOLD: f32 = 20_000.0; - - let bounds = Rectangle { - x: 0.0, - y: 0.0, - width: bounds.width as f32, - height: bounds.height as f32, - }; - - damage.sort_by(|a, b| { - a.x.partial_cmp(&b.x) - .unwrap_or(Ordering::Equal) - .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal)) - }); - - let mut output = Vec::new(); - let mut scaled = damage - .into_iter() - .filter_map(|region| (region * scale_factor).intersection(&bounds)) - .filter(|region| region.width >= 1.0 && region.height >= 1.0); - - if let Some(mut current) = scaled.next() { - for region in scaled { - let union = current.union(®ion); - - if union.area() - current.area() - region.area() <= AREA_THRESHOLD { - current = union; - } else { - output.push(current); - current = region; - } - } - - output.push(current); - } - - output -} diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs new file mode 100644 index 00000000..0187cc59 --- /dev/null +++ b/graphics/src/layer.rs @@ -0,0 +1,139 @@ +//! Draw and stack layers of graphical primitives. +use crate::core::{Rectangle, Transformation}; + +/// A layer of graphical primitives. +/// +/// Layers normally dictate a set of primitives that are +/// rendered in a specific order. +pub trait Layer: Default { + /// Creates a new [`Layer`] with the given bounds. + fn with_bounds(bounds: Rectangle) -> Self; + + /// Flushes and settles any pending group of primitives in the [`Layer`]. + /// + /// This will be called when a [`Layer`] is finished. It allows layers to efficiently + /// record primitives together and defer grouping until the end. + fn flush(&mut self); + + /// Resizes the [`Layer`] to the given bounds. + fn resize(&mut self, bounds: Rectangle); + + /// Clears all the layers contents and resets its bounds. + fn reset(&mut self); +} + +/// A stack of layers used for drawing. +#[derive(Debug)] +pub struct Stack { + layers: Vec, + transformations: Vec, + previous: Vec, + current: usize, + active_count: usize, +} + +impl Stack { + /// Creates a new empty [`Stack`]. + pub fn new() -> Self { + Self { + layers: vec![T::default()], + transformations: vec![Transformation::IDENTITY], + previous: vec![], + current: 0, + active_count: 1, + } + } + + /// Returns a mutable reference to the current [`Layer`] of the [`Stack`], together with + /// the current [`Transformation`]. + #[inline] + pub fn current_mut(&mut self) -> (&mut T, Transformation) { + let transformation = self.transformation(); + + (&mut self.layers[self.current], transformation) + } + + /// Returns the current [`Transformation`] of the [`Stack`]. + #[inline] + pub fn transformation(&self) -> Transformation { + self.transformations.last().copied().unwrap() + } + + /// Pushes a new clipping region in the [`Stack`]; creating a new layer in the + /// process. + pub fn push_clip(&mut self, bounds: Rectangle) { + self.previous.push(self.current); + + self.current = self.active_count; + self.active_count += 1; + + let bounds = bounds * self.transformation(); + + if self.current == self.layers.len() { + self.layers.push(T::with_bounds(bounds)); + } else { + self.layers[self.current].resize(bounds); + } + } + + /// Pops the current clipping region from the [`Stack`] and restores the previous one. + /// + /// The current layer will be recorded for drawing. + pub fn pop_clip(&mut self) { + self.flush(); + + self.current = self.previous.pop().unwrap(); + } + + /// Pushes a new [`Transformation`] in the [`Stack`]. + /// + /// Future drawing operations will be affected by this new [`Transformation`] until + /// it is popped using [`pop_transformation`]. + /// + /// [`pop_transformation`]: Self::pop_transformation + pub fn push_transformation(&mut self, transformation: Transformation) { + self.transformations + .push(self.transformation() * transformation); + } + + /// Pops the current [`Transformation`] in the [`Stack`]. + pub fn pop_transformation(&mut self) { + let _ = self.transformations.pop(); + } + + /// Returns an iterator over mutable references to the layers in the [`Stack`]. + pub fn iter_mut(&mut self) -> impl Iterator { + self.flush(); + + self.layers[..self.active_count].iter_mut() + } + + /// Returns an iterator over immutable references to the layers in the [`Stack`]. + pub fn iter(&self) -> impl Iterator { + self.layers[..self.active_count].iter() + } + + /// Flushes and settles any primitives in the current layer of the [`Stack`]. + pub fn flush(&mut self) { + self.layers[self.current].flush(); + } + + /// Clears the layers of the [`Stack`], allowing reuse. + /// + /// This will normally keep layer allocations for future drawing operations. + pub fn clear(&mut self) { + for layer in self.layers[..self.active_count].iter_mut() { + layer.reset(); + } + + self.current = 0; + self.active_count = 1; + self.previous.clear(); + } +} + +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index b79ef70d..a9649c6e 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -10,35 +10,29 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; mod cached; -mod primitive; mod settings; mod viewport; -pub mod backend; pub mod color; pub mod compositor; -pub mod damage; pub mod error; pub mod gradient; pub mod image; +pub mod layer; pub mod mesh; -pub mod renderer; pub mod text; #[cfg(feature = "geometry")] pub mod geometry; pub use antialiasing::Antialiasing; -pub use backend::Backend; pub use cached::Cached; pub use compositor::Compositor; -pub use damage::Damage; pub use error::Error; pub use gradient::Gradient; pub use image::Image; +pub use layer::Layer; pub use mesh::Mesh; -pub use primitive::Primitive; -pub use renderer::Renderer; pub use settings::Settings; pub use text::Text; pub use viewport::Viewport; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs deleted file mode 100644 index 6929b0a1..00000000 --- a/graphics/src/primitive.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! Draw using different graphical primitives. -use crate::core::alignment; -use crate::core::image; -use crate::core::svg; -use crate::core::text; -use crate::core::{ - Background, Border, Color, Font, Pixels, Point, Rectangle, Shadow, - Transformation, Vector, -}; -use crate::text::editor; -use crate::text::paragraph; - -use std::sync::Arc; - -/// A rendering primitive. -#[derive(Debug, Clone, PartialEq)] -pub enum Primitive { - /// A text primitive - Text { - /// The contents of the text. - content: String, - /// The bounds of the text. - bounds: Rectangle, - /// The color of the text. - color: Color, - /// The size of the text in logical pixels. - size: Pixels, - /// The line height of the text. - line_height: text::LineHeight, - /// The font of the text. - font: Font, - /// The horizontal alignment of the text. - horizontal_alignment: alignment::Horizontal, - /// The vertical alignment of the text. - vertical_alignment: alignment::Vertical, - /// The shaping strategy of the text. - shaping: text::Shaping, - /// The clip bounds of the text. - clip_bounds: Rectangle, - }, - /// A paragraph primitive - Paragraph { - /// The [`paragraph::Weak`] reference. - paragraph: paragraph::Weak, - /// The position of the paragraph. - position: Point, - /// The color of the paragraph. - color: Color, - /// The clip bounds of the paragraph. - clip_bounds: Rectangle, - }, - /// An editor primitive - Editor { - /// The [`editor::Weak`] reference. - editor: editor::Weak, - /// The position of the editor. - position: Point, - /// The color of the editor. - color: Color, - /// The clip bounds of the editor. - clip_bounds: Rectangle, - }, - /// A raw `cosmic-text` primitive - RawText(crate::text::Raw), - /// A quad primitive - Quad { - /// The bounds of the quad - bounds: Rectangle, - /// The background of the quad - background: Background, - /// The [`Border`] of the quad - border: Border, - /// The [`Shadow`] of the quad - shadow: Shadow, - }, - /// An image primitive - Image { - /// The handle of the image - handle: image::Handle, - /// The filter method of the image - filter_method: image::FilterMethod, - /// The bounds of the image - bounds: Rectangle, - }, - /// An SVG primitive - Svg { - /// The path of the SVG file - handle: svg::Handle, - - /// The [`Color`] filter - color: Option, - - /// The bounds of the viewport - bounds: Rectangle, - }, - /// A group of primitives - Group { - /// The primitives of the group - primitives: Vec>, - }, - /// A clip primitive - Clip { - /// The bounds of the clip - bounds: Rectangle, - /// The content of the clip - content: Box>, - }, - /// A primitive that applies a [`Transformation`] - Transform { - /// The [`Transformation`] - transformation: Transformation, - - /// The primitive to transform - content: Box>, - }, - /// A cached primitive. - /// - /// This can be useful if you are implementing a widget where primitive - /// generation is expensive. - Cache { - /// The cached primitive - content: Arc>, - }, - /// A backend-specific primitive. - Custom(T), -} - -impl Primitive { - /// Groups the current [`Primitive`]. - pub fn group(primitives: Vec) -> Self { - Self::Group { primitives } - } - - /// Clips the current [`Primitive`]. - pub fn clip(self, bounds: Rectangle) -> Self { - Self::Clip { - bounds, - content: Box::new(self), - } - } - - /// Translates the current [`Primitive`]. - pub fn translate(self, translation: Vector) -> Self { - Self::Transform { - transformation: Transformation::translate( - translation.x, - translation.y, - ), - content: Box::new(self), - } - } - - /// Transforms the current [`Primitive`]. - pub fn transform(self, transformation: Transformation) -> Self { - Self::Transform { - transformation, - content: Box::new(self), - } - } -} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index d4f91dab..695759a4 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -66,7 +66,7 @@ impl iced_core::Renderer for Renderer { self.stack.push(std::mem::take(&mut self.primitives)); } - fn end_layer(&mut self, bounds: Rectangle) { + fn end_layer(&mut self) { let layer = std::mem::replace( &mut self.primitives, self.stack.pop().expect("a layer should be recording"), diff --git a/graphics/src/text.rs b/graphics/src/text.rs index f9fc1fec..c204c850 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -19,7 +19,7 @@ use std::borrow::Cow; use std::sync::{Arc, RwLock, Weak}; /// A text primitive. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Text { /// A paragraph. #[allow(missing_docs)] -- cgit From 14b9708f723f9fc730634e7ded3dec7dc912ce34 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 9 Apr 2024 22:29:03 +0200 Subject: Remove leftover `renderer` module in `iced_graphics` --- graphics/src/geometry.rs | 9 -- graphics/src/renderer.rs | 269 ----------------------------------------------- graphics/src/settings.rs | 2 +- 3 files changed, 1 insertion(+), 279 deletions(-) delete mode 100644 graphics/src/renderer.rs (limited to 'graphics/src') diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index d251efb8..dd07097f 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -36,15 +36,6 @@ pub trait Renderer: core::Renderer { 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; -} - #[cfg(debug_assertions)] impl Renderer for () { type Geometry = (); diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs deleted file mode 100644 index 695759a4..00000000 --- a/graphics/src/renderer.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! 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; -use crate::core::svg; -use crate::core::text::Text; -use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, -}; -use crate::mesh; -use crate::text; -use crate::{Mesh, Primitive}; - -use std::borrow::Cow; - -/// A backend-agnostic renderer that supports all the built-in widgets. -#[derive(Debug)] -pub struct Renderer { - backend: B, - default_font: Font, - default_text_size: Pixels, - primitives: Vec>, - stack: Vec>>, -} - -impl Renderer { - /// Creates a new [`Renderer`] from the given [`Backend`]. - pub fn new( - backend: B, - default_font: Font, - default_text_size: Pixels, - ) -> Self { - Self { - backend, - default_font, - default_text_size, - primitives: Vec::new(), - stack: Vec::new(), - } - } - - /// Returns a reference to the [`Backend`] of the [`Renderer`]. - pub fn backend(&self) -> &B { - &self.backend - } - - /// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing. - pub fn draw_primitive(&mut self, primitive: Primitive) { - self.primitives.push(primitive); - } - - /// Runs the given closure with the [`Backend`] and the recorded primitives - /// of the [`Renderer`]. - pub fn with_primitives( - &mut self, - f: impl FnOnce(&mut B, &[Primitive]) -> O, - ) -> O { - f(&mut self.backend, &self.primitives) - } -} - -impl iced_core::Renderer for Renderer { - fn start_layer(&mut self, _bounds: Rectangle) { - self.stack.push(std::mem::take(&mut self.primitives)); - } - - fn end_layer(&mut self) { - let layer = std::mem::replace( - &mut self.primitives, - self.stack.pop().expect("a layer should be recording"), - ); - - self.primitives.push(Primitive::group(layer).clip(bounds)); - } - - fn start_transformation(&mut self, _transformation: Transformation) { - self.stack.push(std::mem::take(&mut self.primitives)); - } - - 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.primitives - .push(Primitive::group(layer).transform(transformation)); - } - - fn fill_quad( - &mut self, - quad: renderer::Quad, - background: impl Into, - ) { - self.primitives.push(Primitive::Quad { - bounds: quad.bounds, - background: background.into(), - border: quad.border, - shadow: quad.shadow, - }); - } - - fn clear(&mut self) { - self.primitives.clear(); - } -} - -impl core::text::Renderer for Renderer -where - B: Backend + backend::Text, -{ - type Font = Font; - type Paragraph = text::Paragraph; - type Editor = text::Editor; - - const ICON_FONT: Font = Font::with_name("Iced-Icons"); - const CHECKMARK_ICON: char = '\u{f00c}'; - const ARROW_DOWN_ICON: char = '\u{e800}'; - - fn default_font(&self) -> Self::Font { - self.default_font - } - - fn default_size(&self) -> Pixels { - self.default_text_size - } - - fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - self.backend.load_font(bytes); - } - - fn fill_paragraph( - &mut self, - paragraph: &Self::Paragraph, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - self.primitives.push(Primitive::Paragraph { - paragraph: paragraph.downgrade(), - position, - color, - clip_bounds, - }); - } - - fn fill_editor( - &mut self, - editor: &Self::Editor, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - self.primitives.push(Primitive::Editor { - editor: editor.downgrade(), - position, - color, - clip_bounds, - }); - } - - fn fill_text( - &mut self, - text: Text, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - self.primitives.push(Primitive::Text { - content: text.content, - bounds: Rectangle::new(position, text.bounds), - size: text.size, - line_height: text.line_height, - color, - font: text.font, - horizontal_alignment: text.horizontal_alignment, - vertical_alignment: text.vertical_alignment, - shaping: text.shaping, - clip_bounds, - }); - } -} - -impl image::Renderer for Renderer -where - B: Backend + backend::Image, -{ - type Handle = image::Handle; - - fn measure_image(&self, handle: &image::Handle) -> Size { - self.backend().dimensions(handle) - } - - fn draw_image( - &mut self, - handle: image::Handle, - filter_method: image::FilterMethod, - bounds: Rectangle, - ) { - self.primitives.push(Primitive::Image { - handle, - filter_method, - bounds, - }); - } -} - -impl svg::Renderer for Renderer -where - B: Backend + backend::Svg, -{ - fn measure_svg(&self, handle: &svg::Handle) -> Size { - self.backend().viewport_dimensions(handle) - } - - fn draw_svg( - &mut self, - handle: svg::Handle, - color: Option, - bounds: Rectangle, - ) { - self.primitives.push(Primitive::Svg { - handle, - color, - bounds, - }); - } -} - -impl mesh::Renderer for Renderer { - fn draw_mesh(&mut self, mesh: Mesh) { - match B::Primitive::try_from(mesh) { - Ok(primitive) => { - self.draw_primitive(Primitive::Custom(primitive)); - } - Err(error) => { - log::warn!("mesh primitive could not be drawn: {error:?}"); - } - } - } -} - -#[cfg(feature = "geometry")] -impl crate::geometry::Renderer for Renderer -where - B: Backend + crate::geometry::Backend, - B::Frame: - crate::geometry::frame::Backend>, -{ - type Frame = B::Frame; - type Geometry = Primitive; - - fn new_frame(&self, size: Size) -> Self::Frame { - self.backend.new_frame(size) - } - - fn draw_geometry(&mut self, geometry: Self::Geometry) { - self.draw_primitive(geometry); - } -} - -impl compositor::Default for Renderer -where - B: Backend, -{ - type Compositor = B::Compositor; -} diff --git a/graphics/src/settings.rs b/graphics/src/settings.rs index 68673536..2e8275c6 100644 --- a/graphics/src/settings.rs +++ b/graphics/src/settings.rs @@ -1,7 +1,7 @@ use crate::core::{Font, Pixels}; use crate::Antialiasing; -/// The settings of a Backend. +/// The settings of a renderer. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Settings { /// The default [`Font`] to use. -- cgit From 1e802e776cb591f3860d1bfbaa1423d356fc8456 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Apr 2024 15:21:42 +0200 Subject: Reintroduce damage tracking for `iced_tiny_skia` --- graphics/src/damage.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ graphics/src/layer.rs | 5 ++++ graphics/src/lib.rs | 1 + graphics/src/text.rs | 70 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 graphics/src/damage.rs (limited to 'graphics/src') diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs new file mode 100644 index 00000000..ff8edaf5 --- /dev/null +++ b/graphics/src/damage.rs @@ -0,0 +1,80 @@ +//! Compute the damage between frames. +use crate::core::Rectangle; + +/// Diffs the damage regions given some previous and current primitives. +pub fn diff( + previous: &[T], + current: &[T], + bounds: impl Fn(&T) -> Vec, + diff: impl Fn(&T, &T) -> Vec, +) -> Vec { + let damage = previous.iter().zip(current).flat_map(|(a, b)| diff(a, b)); + + if previous.len() == current.len() { + damage.collect() + } else { + let (smaller, bigger) = if previous.len() < current.len() { + (previous, current) + } else { + (current, previous) + }; + + // Extend damage by the added/removed primitives + damage + .chain(bigger[smaller.len()..].iter().flat_map(bounds)) + .collect() + } +} + +/// Computes the damage regions given some previous and current primitives. +pub fn list( + previous: &[T], + current: &[T], + bounds: impl Fn(&T) -> Vec, + are_equal: impl Fn(&T, &T) -> bool, +) -> Vec { + diff(previous, current, &bounds, |a, b| { + if are_equal(a, b) { + vec![] + } else { + bounds(a).into_iter().chain(bounds(b)).collect() + } + }) +} + +/// Groups the given damage regions that are close together inside the given +/// bounds. +pub fn group(mut damage: Vec, bounds: Rectangle) -> Vec { + use std::cmp::Ordering; + + const AREA_THRESHOLD: f32 = 20_000.0; + + damage.sort_by(|a, b| { + a.x.partial_cmp(&b.x) + .unwrap_or(Ordering::Equal) + .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal)) + }); + + let mut output = Vec::new(); + let mut scaled = damage + .into_iter() + .filter_map(|region| region.intersection(&bounds)) + .filter(|region| region.width >= 1.0 && region.height >= 1.0); + + if let Some(mut current) = scaled.next() { + for region in scaled { + let union = current.union(®ion); + + if union.area() - current.area() - region.area() <= AREA_THRESHOLD { + current = union; + } else { + output.push(current); + current = region; + } + } + + output.push(current); + } + + output +} diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index 0187cc59..c9a818fb 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -113,6 +113,11 @@ impl Stack { self.layers[..self.active_count].iter() } + /// Returns the slice of layers in the [`Stack`]. + pub fn as_slice(&self) -> &[T] { + &self.layers[..self.active_count] + } + /// Flushes and settles any primitives in the current layer of the [`Stack`]. pub fn flush(&mut self) { self.layers[self.current].flush(); diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index a9649c6e..865ebd97 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -15,6 +15,7 @@ mod viewport; pub mod color; pub mod compositor; +pub mod damage; pub mod error; pub mod gradient; pub mod image; diff --git a/graphics/src/text.rs b/graphics/src/text.rs index c204c850..31b6de28 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -70,6 +70,76 @@ pub enum Text { }, } +impl Text { + /// Returns the visible bounds of the [`Text`]. + pub fn visible_bounds(&self) -> Option { + let (bounds, horizontal_alignment, vertical_alignment) = match self { + Text::Paragraph { + position, + paragraph, + clip_bounds, + .. + } => ( + Rectangle::new(*position, paragraph.min_bounds) + .intersection(clip_bounds), + Some(paragraph.horizontal_alignment), + Some(paragraph.vertical_alignment), + ), + Text::Editor { + editor, + position, + clip_bounds, + .. + } => ( + Rectangle::new(*position, editor.bounds) + .intersection(clip_bounds), + None, + None, + ), + Text::Cached { + bounds, + clip_bounds, + horizontal_alignment, + vertical_alignment, + .. + } => ( + bounds.intersection(clip_bounds), + Some(*horizontal_alignment), + Some(*vertical_alignment), + ), + Text::Raw { raw, .. } => (Some(raw.clip_bounds), None, None), + }; + + let mut bounds = bounds?; + + if let Some(alignment) = horizontal_alignment { + match alignment { + alignment::Horizontal::Left => {} + alignment::Horizontal::Center => { + bounds.x -= bounds.width / 2.0; + } + alignment::Horizontal::Right => { + bounds.x -= bounds.width; + } + } + } + + if let Some(alignment) = vertical_alignment { + match alignment { + alignment::Vertical::Top => {} + alignment::Vertical::Center => { + bounds.y -= bounds.height / 2.0; + } + alignment::Vertical::Bottom => { + bounds.y -= bounds.height; + } + } + } + + Some(bounds) + } +} + /// The regular variant of the [Fira Sans] font. /// /// It is loaded as part of the default fonts in Wasm builds. -- cgit From 32cd456fb936117307c178b4d47ae89124c8329a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Apr 2024 16:26:55 +0200 Subject: Account for `transformation` in `Text::visible_bounds` --- graphics/src/text.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/text.rs b/graphics/src/text.rs index 31b6de28..30269e69 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -78,10 +78,12 @@ impl Text { position, paragraph, clip_bounds, + transformation, .. } => ( Rectangle::new(*position, paragraph.min_bounds) - .intersection(clip_bounds), + .intersection(clip_bounds) + .map(|bounds| bounds * *transformation), Some(paragraph.horizontal_alignment), Some(paragraph.vertical_alignment), ), @@ -89,10 +91,12 @@ impl Text { editor, position, clip_bounds, + transformation, .. } => ( Rectangle::new(*position, editor.bounds) - .intersection(clip_bounds), + .intersection(clip_bounds) + .map(|bounds| bounds * *transformation), None, None, ), -- cgit From fdd9896dc5f727f4c659ad6252f1ab36cca77762 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Apr 2024 19:55:27 +0200 Subject: Track image damage in `iced_tiny_skia` --- graphics/src/image.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 47a7b30e..c6135e9e 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -7,7 +7,7 @@ use crate::core::svg; use crate::core::{Color, Rectangle}; /// A raster or vector image. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Image { /// A raster image. Raster { @@ -33,6 +33,17 @@ pub enum Image { }, } +impl Image { + /// Returns the bounds of the [`Image`]. + pub fn bounds(&self) -> Rectangle { + match self { + Image::Raster { bounds, .. } | Image::Vector { bounds, .. } => { + *bounds + } + } + } +} + #[cfg(feature = "image")] /// Tries to load an image by its [`Handle`]. /// -- cgit From 1e8554bf02f366b18b31b9ea1dfb150f7935ca06 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Apr 2024 20:23:07 +0200 Subject: Sort damage by distance from origin in `iced_graphics::damage` --- graphics/src/damage.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index ff8edaf5..17d60451 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -1,5 +1,5 @@ //! Compute the damage between frames. -use crate::core::Rectangle; +use crate::core::{Point, Rectangle}; /// Diffs the damage regions given some previous and current primitives. pub fn diff( @@ -50,9 +50,10 @@ pub fn group(mut damage: Vec, bounds: Rectangle) -> Vec { const AREA_THRESHOLD: f32 = 20_000.0; damage.sort_by(|a, b| { - a.x.partial_cmp(&b.x) + a.center() + .distance(Point::ORIGIN) + .partial_cmp(&b.center().distance(Point::ORIGIN)) .unwrap_or(Ordering::Equal) - .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal)) }); let mut output = Vec::new(); -- cgit From 3762c0590ceb0fc579a1e699702d7d5c2b204348 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 26 Apr 2024 15:17:10 +0200 Subject: Fix panic when scrolling a `TextEditor` inside a `scrollable` --- graphics/src/text/editor.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index c488a51c..4b8f0f2a 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -456,10 +456,14 @@ impl editor::Editor for Editor { } } Action::Scroll { lines } => { - editor.action( - font_system.raw(), - cosmic_text::Action::Scroll { lines }, - ); + let (_, height) = editor.buffer().size(); + + if height < i32::MAX as f32 { + editor.action( + font_system.raw(), + cosmic_text::Action::Scroll { lines }, + ); + } } } -- cgit From b5b78d505e22cafccb4ecbf57dc61f536ca558ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 07:57:54 +0200 Subject: Introduce `canvas::Cache` grouping Caches with the same `Group` will share their text atlas! --- graphics/src/cache.rs | 158 +++++++++++++++++++++++++++++++++++++++++ graphics/src/cached.rs | 24 ------- graphics/src/geometry.rs | 2 +- graphics/src/geometry/cache.rs | 83 ++++++++++------------ graphics/src/lib.rs | 4 +- 5 files changed, 200 insertions(+), 71 deletions(-) create mode 100644 graphics/src/cache.rs delete mode 100644 graphics/src/cached.rs (limited to 'graphics/src') diff --git a/graphics/src/cache.rs b/graphics/src/cache.rs new file mode 100644 index 00000000..7106c392 --- /dev/null +++ b/graphics/src/cache.rs @@ -0,0 +1,158 @@ +//! Cache computations and efficiently reuse them. +use std::cell::RefCell; +use std::fmt; +use std::sync::atomic::{self, AtomicU64}; + +/// A simple cache that stores generated values to avoid recomputation. +/// +/// Keeps track of the last generated value after clearing. +pub struct Cache { + group: Group, + state: RefCell>, +} + +impl Cache { + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + group: Group::unique(), + state: RefCell::new(State::Empty { previous: None }), + } + } + + /// Creates a new empty [`Cache`] with the given [`Group`]. + /// + /// Caches within the same group may reuse internal rendering storage. + /// + /// You should generally group caches that are likely to change + /// together. + pub fn with_group(group: Group) -> Self { + Cache { + group, + state: RefCell::new(State::Empty { previous: None }), + } + } + + /// Returns the [`Group`] of the [`Cache`]. + pub fn group(&self) -> Group { + self.group + } + + /// Puts the given value in the [`Cache`]. + /// + /// Notice that, given this is a cache, a mutable reference is not + /// necessary to call this method. You can safely update the cache in + /// rendering code. + pub fn put(&self, value: T) { + *self.state.borrow_mut() = State::Filled { current: value }; + } + + /// Returns a reference cell to the internal [`State`] of the [`Cache`]. + pub fn state(&self) -> &RefCell> { + &self.state + } + + /// Clears the [`Cache`]. + pub fn clear(&self) + where + T: Clone, + { + use std::ops::Deref; + + let previous = match self.state.borrow().deref() { + State::Empty { previous } => previous.clone(), + State::Filled { current } => Some(current.clone()), + }; + + *self.state.borrow_mut() = State::Empty { previous }; + } +} + +/// A cache group. +/// +/// Caches that share the same group generally change together. +/// +/// A cache group can be used to implement certain performance +/// optimizations during rendering, like batching or sharing atlases. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Group(u64); + +impl Group { + /// Generates a new unique cache [`Group`]. + pub fn unique() -> Self { + static NEXT: AtomicU64 = AtomicU64::new(0); + + Self(NEXT.fetch_add(1, atomic::Ordering::Relaxed)) + } +} + +impl fmt::Debug for Cache +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::ops::Deref; + + let state = self.state.borrow(); + + match state.deref() { + State::Empty { previous } => { + write!(f, "Cache::Empty {{ previous: {previous:?} }}") + } + State::Filled { current } => { + write!(f, "Cache::Filled {{ current: {current:?} }}") + } + } + } +} + +impl Default for Cache { + fn default() -> Self { + Self::new() + } +} + +/// The state of a [`Cache`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum State { + /// The [`Cache`] is empty. + Empty { + /// The previous value of the [`Cache`]. + previous: Option, + }, + /// The [`Cache`] is filled. + Filled { + /// The current value of the [`Cache`] + current: T, + }, +} + +/// A piece of data that can be cached. +pub trait Cached: Sized { + /// The type of cache produced. + type Cache: Clone; + + /// 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, group: Group, previous: Option) -> Self::Cache; +} + +#[cfg(debug_assertions)] +impl Cached for () { + type Cache = (); + + fn load(_cache: &Self::Cache) -> Self {} + + fn cache( + self, + _group: Group, + _previous: Option, + ) -> Self::Cache { + } +} diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs deleted file mode 100644 index c0e4e029..00000000 --- a/graphics/src/cached.rs +++ /dev/null @@ -1,24 +0,0 @@ -/// A piece of data that can be cached. -pub trait Cached: Sized { - /// The type of cache produced. - type Cache: Clone; - - /// 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, previous: Option) -> Self::Cache; -} - -#[cfg(debug_assertions)] -impl Cached for () { - type Cache = (); - - fn load(_cache: &Self::Cache) -> Self {} - - fn cache(self, _previous: Option) -> Self::Cache {} -} diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index dd07097f..ab4a7a36 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -18,8 +18,8 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; +use crate::cache::Cached; use crate::core::{self, Size}; -use crate::Cached; /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: core::Renderer { diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs index 665e996b..d70cee0b 100644 --- a/graphics/src/geometry/cache.rs +++ b/graphics/src/geometry/cache.rs @@ -1,8 +1,8 @@ +use crate::cache::{self, Cached}; use crate::core::Size; use crate::geometry::{self, Frame}; -use crate::Cached; -use std::cell::RefCell; +pub use cache::Group; /// A simple cache that stores generated geometry to avoid recomputation. /// @@ -12,7 +12,13 @@ pub struct Cache where Renderer: geometry::Renderer, { - state: RefCell>, + raw: crate::Cache::Cache>>, +} + +#[derive(Debug, Clone)] +struct Data { + bounds: Size, + geometry: T, } impl Cache @@ -22,20 +28,25 @@ where /// Creates a new empty [`Cache`]. pub fn new() -> Self { Cache { - state: RefCell::new(State::Empty { previous: None }), + raw: cache::Cache::new(), + } + } + + /// Creates a new empty [`Cache`] with the given [`Group`]. + /// + /// Caches within the same group may reuse internal rendering storage. + /// + /// You should generally group caches that are likely to change + /// together. + pub fn with_group(group: Group) -> Self { + Cache { + raw: crate::Cache::with_group(group), } } /// Clears the [`Cache`], forcing a redraw the next time it is used. pub fn clear(&self) { - use std::ops::Deref; - - let previous = match self.state.borrow().deref() { - State::Empty { previous } => previous.clone(), - State::Filled { geometry, .. } => Some(geometry.clone()), - }; - - *self.state.borrow_mut() = State::Empty { previous }; + self.raw.clear(); } /// Draws geometry using the provided closure and stores it in the @@ -56,27 +67,30 @@ where ) -> Renderer::Geometry { use std::ops::Deref; - let previous = match self.state.borrow().deref() { - State::Empty { previous } => previous.clone(), - State::Filled { - bounds: cached_bounds, - geometry, - } => { - if *cached_bounds == bounds { - return Cached::load(geometry); + let state = self.raw.state(); + + let previous = match state.borrow().deref() { + cache::State::Empty { previous } => { + previous.as_ref().map(|data| data.geometry.clone()) + } + cache::State::Filled { current } => { + if current.bounds == bounds { + return Cached::load(¤t.geometry); } - Some(geometry.clone()) + Some(current.geometry.clone()) } }; let mut frame = Frame::new(renderer, bounds); draw_fn(&mut frame); - let geometry = frame.into_geometry().cache(previous); + let geometry = frame.into_geometry().cache(self.raw.group(), previous); let result = Cached::load(&geometry); - *self.state.borrow_mut() = State::Filled { bounds, geometry }; + *state.borrow_mut() = cache::State::Filled { + current: Data { bounds, geometry }, + }; result } @@ -85,16 +99,10 @@ where impl std::fmt::Debug for Cache where Renderer: geometry::Renderer, + ::Cache: std::fmt::Debug, { 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:?} }}") - } - } + write!(f, "{:?}", &self.raw) } } @@ -106,16 +114,3 @@ where Self::new() } } - -enum State -where - Geometry: Cached, -{ - Empty { - previous: Option, - }, - Filled { - bounds: Size, - geometry: Geometry::Cache, - }, -} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 865ebd97..b5ef55e7 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,10 +9,10 @@ )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; -mod cached; mod settings; mod viewport; +pub mod cache; pub mod color; pub mod compositor; pub mod damage; @@ -27,7 +27,7 @@ pub mod text; pub mod geometry; pub use antialiasing::Antialiasing; -pub use cached::Cached; +pub use cache::Cache; pub use compositor::Compositor; pub use error::Error; pub use gradient::Gradient; -- cgit From b276a603a17eda219b32f207aa53e2b6a1321a9f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 23:15:04 +0200 Subject: Fix cache trimming loop in `iced_wgpu::text` --- graphics/src/cache.rs | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/cache.rs b/graphics/src/cache.rs index 7106c392..bbba79eb 100644 --- a/graphics/src/cache.rs +++ b/graphics/src/cache.rs @@ -15,7 +15,7 @@ impl Cache { /// Creates a new empty [`Cache`]. pub fn new() -> Self { Cache { - group: Group::unique(), + group: Group::singleton(), state: RefCell::new(State::Empty { previous: None }), } } @@ -27,6 +27,11 @@ impl Cache { /// You should generally group caches that are likely to change /// together. pub fn with_group(group: Group) -> Self { + assert!( + !group.is_singleton(), + "The group {group:?} cannot be shared!" + ); + Cache { group, state: RefCell::new(State::Empty { previous: None }), @@ -75,14 +80,40 @@ impl Cache { /// A cache group can be used to implement certain performance /// optimizations during rendering, like batching or sharing atlases. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Group(u64); +pub struct Group { + id: u64, + is_singleton: bool, +} impl Group { /// Generates a new unique cache [`Group`]. pub fn unique() -> Self { static NEXT: AtomicU64 = AtomicU64::new(0); - Self(NEXT.fetch_add(1, atomic::Ordering::Relaxed)) + Self { + id: NEXT.fetch_add(1, atomic::Ordering::Relaxed), + is_singleton: false, + } + } + + /// Returns `true` if the [`Group`] can only ever have a + /// single [`Cache`] in it. + /// + /// This is the default kind of [`Group`] assigned when using + /// [`Cache::new`]. + /// + /// Knowing that a [`Group`] will never be shared may be + /// useful for rendering backends to perform additional + /// optimizations. + pub fn is_singleton(self) -> bool { + self.is_singleton + } + + fn singleton() -> Self { + Self { + is_singleton: true, + ..Self::unique() + } } } -- cgit From 45254ab88c6ca76759523069c2fb8734de626f02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 May 2024 00:55:49 +0200 Subject: Use `Bytes` as the `Container` of `ImageBuffer` Since we don't need to mutate images once loaded, we avoid unnecessary extra allocations. --- graphics/src/image.rs | 51 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 20 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index c6135e9e..2a630530 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -50,7 +50,8 @@ impl Image { /// [`Handle`]: image::Handle pub fn load( handle: &image::Handle, -) -> ::image::ImageResult<::image::DynamicImage> { +) -> ::image::ImageResult<::image::ImageBuffer<::image::Rgba, image::Bytes>> +{ use bitflags::bitflags; bitflags! { @@ -100,8 +101,8 @@ pub fn load( } } - match handle.data() { - image::Data::Path(path) => { + let (width, height, pixels) = match handle { + image::Handle::Path(path) => { let image = ::image::open(path)?; let operation = std::fs::File::open(path) @@ -110,33 +111,43 @@ pub fn load( .and_then(|mut reader| Operation::from_exif(&mut reader).ok()) .unwrap_or_else(Operation::empty); - Ok(operation.perform(image)) + let rgba = operation.perform(image).into_rgba8(); + + ( + rgba.width(), + rgba.height(), + image::Bytes::from(rgba.into_raw()), + ) } - image::Data::Bytes(bytes) => { + image::Handle::Bytes(bytes) => { let image = ::image::load_from_memory(bytes)?; let operation = Operation::from_exif(&mut std::io::Cursor::new(bytes)) .ok() .unwrap_or_else(Operation::empty); - Ok(operation.perform(image)) + let rgba = operation.perform(image).into_rgba8(); + + ( + rgba.width(), + rgba.height(), + image::Bytes::from(rgba.into_raw()), + ) } - image::Data::Rgba { + image::Handle::Rgba { width, height, pixels, - } => { - if let Some(image) = - ::image::ImageBuffer::from_vec(*width, *height, pixels.to_vec()) - { - Ok(::image::DynamicImage::ImageRgba8(image)) - } else { - Err(::image::error::ImageError::Limits( - ::image::error::LimitError::from_kind( - ::image::error::LimitErrorKind::DimensionError, - ), - )) - } - } + } => (*width, *height, pixels.clone()), + }; + + if let Some(image) = ::image::ImageBuffer::from_raw(width, height, pixels) { + Ok(image) + } else { + Err(::image::error::ImageError::Limits( + ::image::error::LimitError::from_kind( + ::image::error::LimitErrorKind::DimensionError, + ), + )) } } -- cgit From b52c7bb610f593fffc624d461dca17ac50c81626 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 May 2024 01:39:43 +0200 Subject: Use an opaque `Id` type for `image::Handle` Hashing pointers is a terrible idea. --- graphics/src/image.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 2a630530..04c45057 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -102,7 +102,7 @@ pub fn load( } let (width, height, pixels) = match handle { - image::Handle::Path(path) => { + image::Handle::Path(_, path) => { let image = ::image::open(path)?; let operation = std::fs::File::open(path) @@ -119,7 +119,7 @@ pub fn load( image::Bytes::from(rgba.into_raw()), ) } - image::Handle::Bytes(bytes) => { + image::Handle::Bytes(_, bytes) => { let image = ::image::load_from_memory(bytes)?; let operation = Operation::from_exif(&mut std::io::Cursor::new(bytes)) @@ -138,6 +138,7 @@ pub fn load( width, height, pixels, + .. } => (*width, *height, pixels.clone()), }; -- cgit From 09a6bcfffc24f5abdc8709403bab7ae1e01563f1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 13:15:17 +0200 Subject: Add `Image` rotation support Co-authored-by: DKolter <68352124+DKolter@users.noreply.github.com> --- graphics/src/image.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 04c45057..083248bf 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -2,9 +2,7 @@ #[cfg(feature = "image")] pub use ::image as image_rs; -use crate::core::image; -use crate::core::svg; -use crate::core::{Color, Rectangle}; +use crate::core::{image, svg, Color, Rectangle, Size}; /// A raster or vector image. #[derive(Debug, Clone, PartialEq)] @@ -19,6 +17,12 @@ pub enum Image { /// The bounds of the image. bounds: Rectangle, + + /// The rotation of the image in radians + rotation: f32, + + /// The scale of the image after rotation + scale: Size, }, /// A vector image. Vector { @@ -30,6 +34,12 @@ pub enum Image { /// The bounds of the image. bounds: Rectangle, + + /// The rotation of the image in radians + rotation: f32, + + /// The scale of the image after rotation + scale: Size, }, } -- cgit From a57313b23ecb9843856ca0ea08635b6121fcb2cb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 15:21:22 +0200 Subject: Simplify image rotation API and its internals --- graphics/src/image.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 083248bf..4fd6998d 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -2,7 +2,7 @@ #[cfg(feature = "image")] pub use ::image as image_rs; -use crate::core::{image, svg, Color, Rectangle, Size}; +use crate::core::{image, svg, Color, Radians, Rectangle}; /// A raster or vector image. #[derive(Debug, Clone, PartialEq)] @@ -19,10 +19,7 @@ pub enum Image { bounds: Rectangle, /// The rotation of the image in radians - rotation: f32, - - /// The scale of the image after rotation - scale: Size, + rotation: Radians, }, /// A vector image. Vector { @@ -36,10 +33,7 @@ pub enum Image { bounds: Rectangle, /// The rotation of the image in radians - rotation: f32, - - /// The scale of the image after rotation - scale: Size, + rotation: Radians, }, } -- cgit From eac5bcb64f17dfbb52b64ea4f95693462986bb69 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 May 2024 07:04:57 +0200 Subject: Fix `Image::bounds` when rotation present in `iced_graphics` --- graphics/src/image.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 4fd6998d..9d09bf4c 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -41,9 +41,12 @@ impl Image { /// Returns the bounds of the [`Image`]. pub fn bounds(&self) -> Rectangle { match self { - Image::Raster { bounds, .. } | Image::Vector { bounds, .. } => { - *bounds + Image::Raster { + bounds, rotation, .. } + | Image::Vector { + bounds, rotation, .. + } => bounds.rotate(*rotation), } } } -- cgit From fa9e1d96ea1924b51749b775ea0e67e69bc8a305 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 May 2024 13:25:58 +0200 Subject: Introduce dynamic `opacity` support for `Image` and `Svg` --- graphics/src/image.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 9d09bf4c..318592be 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -18,8 +18,11 @@ pub enum Image { /// The bounds of the image. bounds: Rectangle, - /// The rotation of the image in radians + /// The rotation of the image. rotation: Radians, + + /// The opacity of the image. + opacity: f32, }, /// A vector image. Vector { @@ -32,8 +35,11 @@ pub enum Image { /// The bounds of the image. bounds: Rectangle, - /// The rotation of the image in radians + /// The rotation of the image. rotation: Radians, + + /// The opacity of the image. + opacity: f32, }, } -- cgit