diff options
| author | 2024-04-09 22:25:16 +0200 | |
|---|---|---|
| committer | 2024-04-09 22:25:16 +0200 | |
| commit | 6ad5bb3597f640ac329801adf735d633bf0a512f (patch) | |
| tree | f0928edacd09d6537878d22b00ad7ed7829c9ac0 /graphics/src | |
| 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 '')
| -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)] | 
