diff options
author | 2024-04-09 22:25:16 +0200 | |
---|---|---|
committer | 2024-04-09 22:25:16 +0200 | |
commit | 6ad5bb3597f640ac329801adf735d633bf0a512f (patch) | |
tree | f0928edacd09d6537878d22b00ad7ed7829c9ac0 /graphics | |
parent | 2c6fd9ac14c5d270e05b97b7a7fab811d25834c4 (diff) | |
download | iced-6ad5bb3597f640ac329801adf735d633bf0a512f.tar.gz iced-6ad5bb3597f640ac329801adf735d633bf0a512f.tar.bz2 iced-6ad5bb3597f640ac329801adf735d633bf0a512f.zip |
Port `iced_tiny_skia` to new layering architecture
Diffstat (limited to 'graphics')
-rw-r--r-- | graphics/src/backend.rs | 36 | ||||
-rw-r--r-- | graphics/src/cached.rs | 18 | ||||
-rw-r--r-- | graphics/src/compositor.rs | 14 | ||||
-rw-r--r-- | graphics/src/damage.rs | 257 | ||||
-rw-r--r-- | graphics/src/layer.rs | 139 | ||||
-rw-r--r-- | graphics/src/lib.rs | 10 | ||||
-rw-r--r-- | graphics/src/primitive.rs | 160 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 2 | ||||
-rw-r--r-- | graphics/src/text.rs | 2 |
9 files changed, 156 insertions, 482 deletions
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<Mesh, Error = &'static str>; - - /// The default compositor of this [`Backend`]. - type Compositor: Compositor<Renderer = Renderer<Self>>; -} - -/// 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<u32>; -} - -/// 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<u32>; -} 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>) -> Self::Cache; } -impl<T> Cached for Primitive<T> { - type Cache = Arc<Self>; - - fn load(cache: &Arc<Self>) -> Self { - Self::Cache { - content: cache.clone(), - } - } - - fn cache(self, _previous: Option<Arc<Self>>) -> Arc<Self> { - 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<T: Damage> Damage for Primitive<T> { - 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<T: Damage>(a: &Primitive<T>, b: &Primitive<T>) -> Vec<Rectangle> { - 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<T: Damage>( - previous: &[Primitive<T>], - current: &[Primitive<T>], -) -> Vec<Rectangle> { - 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<Rectangle>, - scale_factor: f32, - bounds: Size<u32>, -) -> Vec<Rectangle> { - 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<T: Layer> { + layers: Vec<T>, + transformations: Vec<Transformation>, + previous: Vec<usize>, + current: usize, + active_count: usize, +} + +impl<T: Layer> Stack<T> { + /// 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<Item = &mut T> { + 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<Item = &T> { + 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<T: Layer> Default for Stack<T> { + 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<T> { - /// 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<Color>, - - /// The bounds of the viewport - bounds: Rectangle, - }, - /// A group of primitives - Group { - /// The primitives of the group - primitives: Vec<Primitive<T>>, - }, - /// A clip primitive - Clip { - /// The bounds of the clip - bounds: Rectangle, - /// The content of the clip - content: Box<Primitive<T>>, - }, - /// A primitive that applies a [`Transformation`] - Transform { - /// The [`Transformation`] - transformation: Transformation, - - /// The primitive to transform - content: Box<Primitive<T>>, - }, - /// A cached primitive. - /// - /// This can be useful if you are implementing a widget where primitive - /// generation is expensive. - Cache { - /// The cached primitive - content: Arc<Primitive<T>>, - }, - /// A backend-specific primitive. - Custom(T), -} - -impl<T> Primitive<T> { - /// Groups the current [`Primitive`]. - pub fn group(primitives: Vec<Self>) -> 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<B: Backend> iced_core::Renderer for Renderer<B> { 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)] |