summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-04-09 22:25:16 +0200
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-04-09 22:25:16 +0200
commit6ad5bb3597f640ac329801adf735d633bf0a512f (patch)
treef0928edacd09d6537878d22b00ad7ed7829c9ac0
parent2c6fd9ac14c5d270e05b97b7a7fab811d25834c4 (diff)
downloadiced-6ad5bb3597f640ac329801adf735d633bf0a512f.tar.gz
iced-6ad5bb3597f640ac329801adf735d633bf0a512f.tar.bz2
iced-6ad5bb3597f640ac329801adf735d633bf0a512f.zip
Port `iced_tiny_skia` to new layering architecture
-rw-r--r--core/src/renderer.rs8
-rw-r--r--core/src/renderer/null.rs8
-rw-r--r--core/src/text.rs4
-rw-r--r--graphics/src/backend.rs36
-rw-r--r--graphics/src/cached.rs18
-rw-r--r--graphics/src/compositor.rs14
-rw-r--r--graphics/src/damage.rs257
-rw-r--r--graphics/src/layer.rs139
-rw-r--r--graphics/src/lib.rs10
-rw-r--r--graphics/src/primitive.rs160
-rw-r--r--graphics/src/renderer.rs2
-rw-r--r--graphics/src/text.rs2
-rw-r--r--renderer/src/fallback.rs18
-rw-r--r--tiny_skia/src/backend.rs1033
-rw-r--r--tiny_skia/src/engine.rs831
-rw-r--r--tiny_skia/src/geometry.rs143
-rw-r--r--tiny_skia/src/layer.rs243
-rw-r--r--tiny_skia/src/lib.rs383
-rw-r--r--tiny_skia/src/primitive.rs33
-rw-r--r--tiny_skia/src/settings.rs4
-rw-r--r--tiny_skia/src/text.rs29
-rw-r--r--tiny_skia/src/window/compositor.rs72
-rw-r--r--wgpu/src/geometry.rs24
-rw-r--r--wgpu/src/layer.rs301
-rw-r--r--wgpu/src/lib.rs58
-rw-r--r--wgpu/src/primitive.rs6
-rw-r--r--winit/src/application.rs12
-rw-r--r--winit/src/multi_window.rs7
28 files changed, 1934 insertions, 1921 deletions
diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index f5ef8f68..a2785ae8 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -14,7 +14,7 @@ pub trait Renderer {
/// Ends recording a new layer.
///
/// The new layer will clip its contents to the provided `bounds`.
- fn end_layer(&mut self, bounds: Rectangle);
+ fn end_layer(&mut self);
/// Draws the primitives recorded in the given closure in a new layer.
///
@@ -22,7 +22,7 @@ pub trait Renderer {
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
self.start_layer(bounds);
f(self);
- self.end_layer(bounds);
+ self.end_layer();
}
/// Starts recording with a new [`Transformation`].
@@ -31,7 +31,7 @@ pub trait Renderer {
/// Ends recording a new layer.
///
/// The new layer will clip its contents to the provided `bounds`.
- fn end_transformation(&mut self, transformation: Transformation);
+ fn end_transformation(&mut self);
/// Applies a [`Transformation`] to the primitives recorded in the given closure.
fn with_transformation(
@@ -41,7 +41,7 @@ pub trait Renderer {
) {
self.start_transformation(transformation);
f(self);
- self.end_transformation(transformation);
+ self.end_transformation();
}
/// Applies a translation to the primitives recorded in the given closure.
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index f36d19aa..fe38ec87 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -7,16 +7,14 @@ use crate::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
-use std::borrow::Cow;
-
impl Renderer for () {
fn start_layer(&mut self, _bounds: Rectangle) {}
- fn end_layer(&mut self, _bounds: Rectangle) {}
+ fn end_layer(&mut self) {}
fn start_transformation(&mut self, _transformation: Transformation) {}
- fn end_transformation(&mut self, _transformation: Transformation) {}
+ fn end_transformation(&mut self) {}
fn clear(&mut self) {}
@@ -45,8 +43,6 @@ impl text::Renderer for () {
Pixels(16.0)
}
- fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
-
fn fill_paragraph(
&mut self,
_paragraph: &Self::Paragraph,
diff --git a/core/src/text.rs b/core/src/text.rs
index 3f1d2c77..b30feae0 100644
--- a/core/src/text.rs
+++ b/core/src/text.rs
@@ -11,7 +11,6 @@ pub use paragraph::Paragraph;
use crate::alignment;
use crate::{Color, Pixels, Point, Rectangle, Size};
-use std::borrow::Cow;
use std::hash::{Hash, Hasher};
/// A paragraph.
@@ -192,9 +191,6 @@ pub trait Renderer: crate::Renderer {
/// Returns the default size of [`Text`].
fn default_size(&self) -> Pixels;
- /// Loads a [`Self::Font`] from its bytes.
- fn load_font(&mut self, font: Cow<'static, [u8]>);
-
/// Draws the given [`Paragraph`] at the given position and with the given
/// [`Color`].
fn fill_paragraph(
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(&region);
-
- 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)]
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index 975f4866..c932de00 100644
--- a/renderer/src/fallback.rs
+++ b/renderer/src/fallback.rs
@@ -9,6 +9,8 @@ use crate::graphics;
use crate::graphics::compositor;
use crate::graphics::mesh;
+use std::borrow::Cow;
+
/// A renderer `A` with a fallback strategy `B`.
///
/// This type can be used to easily compose existing renderers and
@@ -51,8 +53,8 @@ where
delegate!(self, renderer, renderer.start_layer(bounds));
}
- fn end_layer(&mut self, bounds: Rectangle) {
- delegate!(self, renderer, renderer.end_layer(bounds));
+ fn end_layer(&mut self) {
+ delegate!(self, renderer, renderer.end_layer());
}
fn start_transformation(&mut self, transformation: Transformation) {
@@ -63,8 +65,8 @@ where
);
}
- fn end_transformation(&mut self, transformation: Transformation) {
- delegate!(self, renderer, renderer.end_transformation(transformation));
+ fn end_transformation(&mut self) {
+ delegate!(self, renderer, renderer.end_transformation());
}
}
@@ -93,10 +95,6 @@ where
delegate!(self, renderer, renderer.default_size())
}
- fn load_font(&mut self, font: std::borrow::Cow<'static, [u8]>) {
- delegate!(self, renderer, renderer.load_font(font));
- }
-
fn fill_paragraph(
&mut self,
text: &Self::Paragraph,
@@ -323,6 +321,10 @@ where
}
}
+ fn load_font(&mut self, font: Cow<'static, [u8]>) {
+ delegate!(self, compositor, compositor.load_font(font));
+ }
+
fn fetch_information(&self) -> compositor::Information {
delegate!(self, compositor, compositor.fetch_information())
}
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
deleted file mode 100644
index d0f28876..00000000
--- a/tiny_skia/src/backend.rs
+++ /dev/null
@@ -1,1033 +0,0 @@
-use crate::core::{
- Background, Color, Gradient, Rectangle, Size, Transformation, Vector,
-};
-use crate::graphics::backend;
-use crate::graphics::text;
-use crate::graphics::{Damage, Viewport};
-use crate::primitive::{self, Primitive};
-use crate::window;
-
-use std::borrow::Cow;
-
-#[derive(Debug)]
-pub struct Backend {
- text_pipeline: crate::text::Pipeline,
-
- #[cfg(feature = "image")]
- raster_pipeline: crate::raster::Pipeline,
-
- #[cfg(feature = "svg")]
- vector_pipeline: crate::vector::Pipeline,
-}
-
-impl Backend {
- pub fn new() -> Self {
- Self {
- text_pipeline: crate::text::Pipeline::new(),
-
- #[cfg(feature = "image")]
- raster_pipeline: crate::raster::Pipeline::new(),
-
- #[cfg(feature = "svg")]
- vector_pipeline: crate::vector::Pipeline::new(),
- }
- }
-
- pub fn draw<T: AsRef<str>>(
- &mut self,
- pixels: &mut tiny_skia::PixmapMut<'_>,
- clip_mask: &mut tiny_skia::Mask,
- primitives: &[Primitive],
- viewport: &Viewport,
- damage: &[Rectangle],
- background_color: Color,
- overlay: &[T],
- ) {
- let physical_size = viewport.physical_size();
- let scale_factor = viewport.scale_factor() as f32;
-
- if !overlay.is_empty() {
- let path = tiny_skia::PathBuilder::from_rect(
- tiny_skia::Rect::from_xywh(
- 0.0,
- 0.0,
- physical_size.width as f32,
- physical_size.height as f32,
- )
- .expect("Create damage rectangle"),
- );
-
- pixels.fill_path(
- &path,
- &tiny_skia::Paint {
- shader: tiny_skia::Shader::SolidColor(into_color(Color {
- a: 0.1,
- ..background_color
- })),
- anti_alias: false,
- ..Default::default()
- },
- tiny_skia::FillRule::default(),
- tiny_skia::Transform::identity(),
- None,
- );
- }
-
- for &region in damage {
- let path = tiny_skia::PathBuilder::from_rect(
- tiny_skia::Rect::from_xywh(
- region.x,
- region.y,
- region.width,
- region.height,
- )
- .expect("Create damage rectangle"),
- );
-
- pixels.fill_path(
- &path,
- &tiny_skia::Paint {
- shader: tiny_skia::Shader::SolidColor(into_color(
- background_color,
- )),
- anti_alias: false,
- blend_mode: tiny_skia::BlendMode::Source,
- ..Default::default()
- },
- tiny_skia::FillRule::default(),
- tiny_skia::Transform::identity(),
- None,
- );
-
- adjust_clip_mask(clip_mask, region);
-
- for primitive in primitives {
- self.draw_primitive(
- primitive,
- pixels,
- clip_mask,
- region,
- scale_factor,
- Transformation::IDENTITY,
- );
- }
-
- if !overlay.is_empty() {
- pixels.stroke_path(
- &path,
- &tiny_skia::Paint {
- shader: tiny_skia::Shader::SolidColor(into_color(
- Color::from_rgb(1.0, 0.0, 0.0),
- )),
- anti_alias: false,
- ..tiny_skia::Paint::default()
- },
- &tiny_skia::Stroke {
- width: 1.0,
- ..tiny_skia::Stroke::default()
- },
- tiny_skia::Transform::identity(),
- None,
- );
- }
- }
-
- self.text_pipeline.trim_cache();
-
- #[cfg(feature = "image")]
- self.raster_pipeline.trim_cache();
-
- #[cfg(feature = "svg")]
- self.vector_pipeline.trim_cache();
- }
-
- fn draw_primitive(
- &mut self,
- primitive: &Primitive,
- pixels: &mut tiny_skia::PixmapMut<'_>,
- clip_mask: &mut tiny_skia::Mask,
- clip_bounds: Rectangle,
- scale_factor: f32,
- transformation: Transformation,
- ) {
- match primitive {
- Primitive::Quad {
- bounds,
- background,
- border,
- shadow,
- } => {
- debug_assert!(
- bounds.width.is_normal(),
- "Quad with non-normal width!"
- );
- debug_assert!(
- bounds.height.is_normal(),
- "Quad with non-normal height!"
- );
-
- let physical_bounds = (*bounds * transformation) * scale_factor;
-
- if !clip_bounds.intersects(&physical_bounds) {
- return;
- }
-
- let clip_mask = (!physical_bounds.is_within(&clip_bounds))
- .then_some(clip_mask as &_);
-
- let transform = into_transform(transformation)
- .post_scale(scale_factor, scale_factor);
-
- // Make sure the border radius is not larger than the bounds
- let border_width = border
- .width
- .min(bounds.width / 2.0)
- .min(bounds.height / 2.0);
-
- let mut fill_border_radius = <[f32; 4]>::from(border.radius);
- for radius in &mut fill_border_radius {
- *radius = (*radius)
- .min(bounds.width / 2.0)
- .min(bounds.height / 2.0);
- }
- let path = rounded_rectangle(*bounds, fill_border_radius);
-
- if shadow.color.a > 0.0 {
- let shadow_bounds = (Rectangle {
- x: bounds.x + shadow.offset.x - shadow.blur_radius,
- y: bounds.y + shadow.offset.y - shadow.blur_radius,
- width: bounds.width + shadow.blur_radius * 2.0,
- height: bounds.height + shadow.blur_radius * 2.0,
- } * transformation)
- * scale_factor;
-
- let radii = fill_border_radius
- .into_iter()
- .map(|radius| radius * scale_factor)
- .collect::<Vec<_>>();
- let (x, y, width, height) = (
- shadow_bounds.x as u32,
- shadow_bounds.y as u32,
- shadow_bounds.width as u32,
- shadow_bounds.height as u32,
- );
- let half_width = physical_bounds.width / 2.0;
- let half_height = physical_bounds.height / 2.0;
-
- let colors = (y..y + height)
- .flat_map(|y| {
- (x..x + width).map(move |x| (x as f32, y as f32))
- })
- .filter_map(|(x, y)| {
- tiny_skia::Size::from_wh(half_width, half_height)
- .map(|size| {
- let shadow_distance = rounded_box_sdf(
- Vector::new(
- x - physical_bounds.position().x
- - (shadow.offset.x
- * scale_factor)
- - half_width,
- y - physical_bounds.position().y
- - (shadow.offset.y
- * scale_factor)
- - half_height,
- ),
- size,
- &radii,
- )
- .max(0.0);
- let shadow_alpha = 1.0
- - smoothstep(
- -shadow.blur_radius * scale_factor,
- shadow.blur_radius * scale_factor,
- shadow_distance,
- );
-
- let mut color = into_color(shadow.color);
- color.apply_opacity(shadow_alpha);
-
- color.to_color_u8().premultiply()
- })
- })
- .collect();
-
- if let Some(pixmap) = tiny_skia::IntSize::from_wh(
- width, height,
- )
- .and_then(|size| {
- tiny_skia::Pixmap::from_vec(
- bytemuck::cast_vec(colors),
- size,
- )
- }) {
- pixels.draw_pixmap(
- x as i32,
- y as i32,
- pixmap.as_ref(),
- &tiny_skia::PixmapPaint::default(),
- tiny_skia::Transform::default(),
- None,
- );
- }
- }
-
- pixels.fill_path(
- &path,
- &tiny_skia::Paint {
- shader: match background {
- Background::Color(color) => {
- tiny_skia::Shader::SolidColor(into_color(
- *color,
- ))
- }
- Background::Gradient(Gradient::Linear(linear)) => {
- let (start, end) =
- linear.angle.to_distance(bounds);
-
- let stops: Vec<tiny_skia::GradientStop> =
- linear
- .stops
- .into_iter()
- .flatten()
- .map(|stop| {
- tiny_skia::GradientStop::new(
- stop.offset,
- tiny_skia::Color::from_rgba(
- stop.color.b,
- stop.color.g,
- stop.color.r,
- stop.color.a,
- )
- .expect("Create color"),
- )
- })
- .collect();
-
- tiny_skia::LinearGradient::new(
- tiny_skia::Point {
- x: start.x,
- y: start.y,
- },
- tiny_skia::Point { x: end.x, y: end.y },
- if stops.is_empty() {
- vec![tiny_skia::GradientStop::new(
- 0.0,
- tiny_skia::Color::BLACK,
- )]
- } else {
- stops
- },
- tiny_skia::SpreadMode::Pad,
- tiny_skia::Transform::identity(),
- )
- .expect("Create linear gradient")
- }
- },
- anti_alias: true,
- ..tiny_skia::Paint::default()
- },
- tiny_skia::FillRule::EvenOdd,
- transform,
- clip_mask,
- );
-
- if border_width > 0.0 {
- // Border path is offset by half the border width
- let border_bounds = Rectangle {
- x: bounds.x + border_width / 2.0,
- y: bounds.y + border_width / 2.0,
- width: bounds.width - border_width,
- height: bounds.height - border_width,
- };
-
- // Make sure the border radius is correct
- let mut border_radius = <[f32; 4]>::from(border.radius);
- let mut is_simple_border = true;
-
- for radius in &mut border_radius {
- *radius = if *radius == 0.0 {
- // Path should handle this fine
- 0.0
- } else if *radius > border_width / 2.0 {
- *radius - border_width / 2.0
- } else {
- is_simple_border = false;
- 0.0
- }
- .min(border_bounds.width / 2.0)
- .min(border_bounds.height / 2.0);
- }
-
- // Stroking a path works well in this case
- if is_simple_border {
- let border_path =
- rounded_rectangle(border_bounds, border_radius);
-
- pixels.stroke_path(
- &border_path,
- &tiny_skia::Paint {
- shader: tiny_skia::Shader::SolidColor(
- into_color(border.color),
- ),
- anti_alias: true,
- ..tiny_skia::Paint::default()
- },
- &tiny_skia::Stroke {
- width: border_width,
- ..tiny_skia::Stroke::default()
- },
- transform,
- clip_mask,
- );
- } else {
- // Draw corners that have too small border radii as having no border radius,
- // but mask them with the rounded rectangle with the correct border radius.
- let mut temp_pixmap = tiny_skia::Pixmap::new(
- bounds.width as u32,
- bounds.height as u32,
- )
- .unwrap();
-
- let mut quad_mask = tiny_skia::Mask::new(
- bounds.width as u32,
- bounds.height as u32,
- )
- .unwrap();
-
- let zero_bounds = Rectangle {
- x: 0.0,
- y: 0.0,
- width: bounds.width,
- height: bounds.height,
- };
- let path =
- rounded_rectangle(zero_bounds, fill_border_radius);
-
- quad_mask.fill_path(
- &path,
- tiny_skia::FillRule::EvenOdd,
- true,
- transform,
- );
- let path_bounds = Rectangle {
- x: border_width / 2.0,
- y: border_width / 2.0,
- width: bounds.width - border_width,
- height: bounds.height - border_width,
- };
-
- let border_radius_path =
- rounded_rectangle(path_bounds, border_radius);
-
- temp_pixmap.stroke_path(
- &border_radius_path,
- &tiny_skia::Paint {
- shader: tiny_skia::Shader::SolidColor(
- into_color(border.color),
- ),
- anti_alias: true,
- ..tiny_skia::Paint::default()
- },
- &tiny_skia::Stroke {
- width: border_width,
- ..tiny_skia::Stroke::default()
- },
- transform,
- Some(&quad_mask),
- );
-
- pixels.draw_pixmap(
- bounds.x as i32,
- bounds.y as i32,
- temp_pixmap.as_ref(),
- &tiny_skia::PixmapPaint::default(),
- transform,
- clip_mask,
- );
- }
- }
- }
- Primitive::Paragraph {
- paragraph,
- position,
- color,
- clip_bounds: _, // TODO: Support text clip bounds
- } => {
- let physical_bounds =
- Rectangle::new(*position, paragraph.min_bounds)
- * transformation
- * scale_factor;
-
- if !clip_bounds.intersects(&physical_bounds) {
- return;
- }
-
- let clip_mask = (!physical_bounds.is_within(&clip_bounds))
- .then_some(clip_mask as &_);
-
- self.text_pipeline.draw_paragraph(
- paragraph,
- *position,
- *color,
- scale_factor,
- pixels,
- clip_mask,
- transformation,
- );
- }
- Primitive::Editor {
- editor,
- position,
- color,
- clip_bounds: _, // TODO: Support text clip bounds
- } => {
- let physical_bounds = Rectangle::new(*position, editor.bounds)
- * transformation
- * scale_factor;
-
- if !clip_bounds.intersects(&physical_bounds) {
- return;
- }
-
- let clip_mask = (!physical_bounds.is_within(&clip_bounds))
- .then_some(clip_mask as &_);
-
- self.text_pipeline.draw_editor(
- editor,
- *position,
- *color,
- scale_factor,
- pixels,
- clip_mask,
- transformation,
- );
- }
- Primitive::Text {
- content,
- bounds,
- color,
- size,
- line_height,
- font,
- horizontal_alignment,
- vertical_alignment,
- shaping,
- clip_bounds: _, // TODO: Support text clip bounds
- } => {
- let physical_bounds =
- primitive.bounds() * transformation * scale_factor;
-
- if !clip_bounds.intersects(&physical_bounds) {
- return;
- }
-
- let clip_mask = (!physical_bounds.is_within(&clip_bounds))
- .then_some(clip_mask as &_);
-
- self.text_pipeline.draw_cached(
- content,
- *bounds,
- *color,
- *size,
- *line_height,
- *font,
- *horizontal_alignment,
- *vertical_alignment,
- *shaping,
- scale_factor,
- pixels,
- clip_mask,
- transformation,
- );
- }
- Primitive::RawText(text::Raw {
- buffer,
- position,
- color,
- clip_bounds: _, // TODO: Support text clip bounds
- }) => {
- let Some(buffer) = buffer.upgrade() else {
- return;
- };
-
- let (width, height) = buffer.size();
-
- let physical_bounds =
- Rectangle::new(*position, Size::new(width, height))
- * transformation
- * scale_factor;
-
- if !clip_bounds.intersects(&physical_bounds) {
- return;
- }
-
- let clip_mask = (!physical_bounds.is_within(&clip_bounds))
- .then_some(clip_mask as &_);
-
- self.text_pipeline.draw_raw(
- &buffer,
- *position,
- *color,
- scale_factor,
- pixels,
- clip_mask,
- transformation,
- );
- }
- #[cfg(feature = "image")]
- Primitive::Image {
- handle,
- filter_method,
- bounds,
- } => {
- let physical_bounds = (*bounds * transformation) * scale_factor;
-
- if !clip_bounds.intersects(&physical_bounds) {
- return;
- }
-
- let clip_mask = (!physical_bounds.is_within(&clip_bounds))
- .then_some(clip_mask as &_);
-
- let transform = into_transform(transformation)
- .post_scale(scale_factor, scale_factor);
-
- self.raster_pipeline.draw(
- handle,
- *filter_method,
- *bounds,
- pixels,
- transform,
- clip_mask,
- );
- }
- #[cfg(not(feature = "image"))]
- Primitive::Image { .. } => {
- log::warn!(
- "Unsupported primitive in `iced_tiny_skia`: {primitive:?}",
- );
- }
- #[cfg(feature = "svg")]
- Primitive::Svg {
- handle,
- bounds,
- color,
- } => {
- let physical_bounds = (*bounds * transformation) * scale_factor;
-
- if !clip_bounds.intersects(&physical_bounds) {
- return;
- }
-
- let clip_mask = (!physical_bounds.is_within(&clip_bounds))
- .then_some(clip_mask as &_);
-
- self.vector_pipeline.draw(
- handle,
- *color,
- (*bounds * transformation) * scale_factor,
- pixels,
- clip_mask,
- );
- }
- #[cfg(not(feature = "svg"))]
- Primitive::Svg { .. } => {
- log::warn!(
- "Unsupported primitive in `iced_tiny_skia`: {primitive:?}",
- );
- }
- Primitive::Custom(primitive::Custom::Fill {
- path,
- paint,
- rule,
- }) => {
- let bounds = path.bounds();
-
- let physical_bounds = (Rectangle {
- x: bounds.x(),
- y: bounds.y(),
- width: bounds.width(),
- height: bounds.height(),
- } * transformation)
- * scale_factor;
-
- if !clip_bounds.intersects(&physical_bounds) {
- return;
- }
-
- let clip_mask = (!physical_bounds.is_within(&clip_bounds))
- .then_some(clip_mask as &_);
-
- pixels.fill_path(
- path,
- paint,
- *rule,
- into_transform(transformation)
- .post_scale(scale_factor, scale_factor),
- clip_mask,
- );
- }
- Primitive::Custom(primitive::Custom::Stroke {
- path,
- paint,
- stroke,
- }) => {
- let bounds = path.bounds();
-
- let physical_bounds = (Rectangle {
- x: bounds.x(),
- y: bounds.y(),
- width: bounds.width().max(1.0),
- height: bounds.height().max(1.0),
- } * transformation)
- * scale_factor;
-
- if !clip_bounds.intersects(&physical_bounds) {
- return;
- }
-
- let clip_mask = (!physical_bounds.is_within(&clip_bounds))
- .then_some(clip_mask as &_);
-
- pixels.stroke_path(
- path,
- paint,
- stroke,
- into_transform(transformation)
- .post_scale(scale_factor, scale_factor),
- clip_mask,
- );
- }
- Primitive::Group { primitives } => {
- for primitive in primitives {
- self.draw_primitive(
- primitive,
- pixels,
- clip_mask,
- clip_bounds,
- scale_factor,
- transformation,
- );
- }
- }
- Primitive::Transform {
- transformation: new_transformation,
- content,
- } => {
- self.draw_primitive(
- content,
- pixels,
- clip_mask,
- clip_bounds,
- scale_factor,
- transformation * *new_transformation,
- );
- }
- Primitive::Clip { bounds, content } => {
- let bounds = (*bounds * transformation) * scale_factor;
-
- if bounds == clip_bounds {
- self.draw_primitive(
- content,
- pixels,
- clip_mask,
- bounds,
- scale_factor,
- transformation,
- );
- } else if let Some(bounds) = clip_bounds.intersection(&bounds) {
- if bounds.x + bounds.width <= 0.0
- || bounds.y + bounds.height <= 0.0
- || bounds.x as u32 >= pixels.width()
- || bounds.y as u32 >= pixels.height()
- || bounds.width <= 1.0
- || bounds.height <= 1.0
- {
- return;
- }
-
- adjust_clip_mask(clip_mask, bounds);
-
- self.draw_primitive(
- content,
- pixels,
- clip_mask,
- bounds,
- scale_factor,
- transformation,
- );
-
- adjust_clip_mask(clip_mask, clip_bounds);
- }
- }
- Primitive::Cache { content } => {
- self.draw_primitive(
- content,
- pixels,
- clip_mask,
- clip_bounds,
- scale_factor,
- transformation,
- );
- }
- }
- }
-}
-
-impl Default for Backend {
- fn default() -> Self {
- Self::new()
- }
-}
-
-fn into_color(color: Color) -> tiny_skia::Color {
- tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
- .expect("Convert color from iced to tiny_skia")
-}
-
-fn into_transform(transformation: Transformation) -> tiny_skia::Transform {
- let translation = transformation.translation();
-
- tiny_skia::Transform {
- sx: transformation.scale_factor(),
- kx: 0.0,
- ky: 0.0,
- sy: transformation.scale_factor(),
- tx: translation.x,
- ty: translation.y,
- }
-}
-
-fn rounded_rectangle(
- bounds: Rectangle,
- border_radius: [f32; 4],
-) -> tiny_skia::Path {
- let [top_left, top_right, bottom_right, bottom_left] = border_radius;
-
- if top_left == 0.0
- && top_right == 0.0
- && bottom_right == 0.0
- && bottom_left == 0.0
- {
- return tiny_skia::PathBuilder::from_rect(
- tiny_skia::Rect::from_xywh(
- bounds.x,
- bounds.y,
- bounds.width,
- bounds.height,
- )
- .expect("Build quad rectangle"),
- );
- }
-
- if top_left == top_right
- && top_left == bottom_right
- && top_left == bottom_left
- && top_left == bounds.width / 2.0
- && top_left == bounds.height / 2.0
- {
- return tiny_skia::PathBuilder::from_circle(
- bounds.x + bounds.width / 2.0,
- bounds.y + bounds.height / 2.0,
- top_left,
- )
- .expect("Build circle path");
- }
-
- let mut builder = tiny_skia::PathBuilder::new();
-
- builder.move_to(bounds.x + top_left, bounds.y);
- builder.line_to(bounds.x + bounds.width - top_right, bounds.y);
-
- if top_right > 0.0 {
- arc_to(
- &mut builder,
- bounds.x + bounds.width - top_right,
- bounds.y,
- bounds.x + bounds.width,
- bounds.y + top_right,
- top_right,
- );
- }
-
- maybe_line_to(
- &mut builder,
- bounds.x + bounds.width,
- bounds.y + bounds.height - bottom_right,
- );
-
- if bottom_right > 0.0 {
- arc_to(
- &mut builder,
- bounds.x + bounds.width,
- bounds.y + bounds.height - bottom_right,
- bounds.x + bounds.width - bottom_right,
- bounds.y + bounds.height,
- bottom_right,
- );
- }
-
- maybe_line_to(
- &mut builder,
- bounds.x + bottom_left,
- bounds.y + bounds.height,
- );
-
- if bottom_left > 0.0 {
- arc_to(
- &mut builder,
- bounds.x + bottom_left,
- bounds.y + bounds.height,
- bounds.x,
- bounds.y + bounds.height - bottom_left,
- bottom_left,
- );
- }
-
- maybe_line_to(&mut builder, bounds.x, bounds.y + top_left);
-
- if top_left > 0.0 {
- arc_to(
- &mut builder,
- bounds.x,
- bounds.y + top_left,
- bounds.x + top_left,
- bounds.y,
- top_left,
- );
- }
-
- builder.finish().expect("Build rounded rectangle path")
-}
-
-fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) {
- if path.last_point() != Some(tiny_skia::Point { x, y }) {
- path.line_to(x, y);
- }
-}
-
-fn arc_to(
- path: &mut tiny_skia::PathBuilder,
- x_from: f32,
- y_from: f32,
- x_to: f32,
- y_to: f32,
- radius: f32,
-) {
- let svg_arc = kurbo::SvgArc {
- from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)),
- to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)),
- radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)),
- x_rotation: 0.0,
- large_arc: false,
- sweep: true,
- };
-
- match kurbo::Arc::from_svg_arc(&svg_arc) {
- Some(arc) => {
- arc.to_cubic_beziers(0.1, |p1, p2, p| {
- path.cubic_to(
- p1.x as f32,
- p1.y as f32,
- p2.x as f32,
- p2.y as f32,
- p.x as f32,
- p.y as f32,
- );
- });
- }
- None => {
- path.line_to(x_to, y_to);
- }
- }
-}
-
-fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) {
- clip_mask.clear();
-
- let path = {
- let mut builder = tiny_skia::PathBuilder::new();
- builder.push_rect(
- tiny_skia::Rect::from_xywh(
- bounds.x,
- bounds.y,
- bounds.width,
- bounds.height,
- )
- .unwrap(),
- );
-
- builder.finish().unwrap()
- };
-
- clip_mask.fill_path(
- &path,
- tiny_skia::FillRule::EvenOdd,
- false,
- tiny_skia::Transform::default(),
- );
-}
-
-fn smoothstep(a: f32, b: f32, x: f32) -> f32 {
- let x = ((x - a) / (b - a)).clamp(0.0, 1.0);
-
- x * x * (3.0 - 2.0 * x)
-}
-
-fn rounded_box_sdf(
- to_center: Vector,
- size: tiny_skia::Size,
- radii: &[f32],
-) -> f32 {
- let radius = match (to_center.x > 0.0, to_center.y > 0.0) {
- (true, true) => radii[2],
- (true, false) => radii[1],
- (false, true) => radii[3],
- (false, false) => radii[0],
- };
-
- let x = (to_center.x.abs() - size.width() + radius).max(0.0);
- let y = (to_center.y.abs() - size.height() + radius).max(0.0);
-
- (x.powf(2.0) + y.powf(2.0)).sqrt() - radius
-}
-
-impl backend::Backend for Backend {
- type Primitive = primitive::Custom;
- type Compositor = window::Compositor;
-}
-
-impl backend::Text for Backend {
- fn load_font(&mut self, font: Cow<'static, [u8]>) {
- self.text_pipeline.load_font(font);
- }
-}
-
-#[cfg(feature = "image")]
-impl backend::Image for Backend {
- fn dimensions(
- &self,
- handle: &crate::core::image::Handle,
- ) -> crate::core::Size<u32> {
- self.raster_pipeline.dimensions(handle)
- }
-}
-
-#[cfg(feature = "svg")]
-impl backend::Svg for Backend {
- fn viewport_dimensions(
- &self,
- handle: &crate::core::svg::Handle,
- ) -> crate::core::Size<u32> {
- self.vector_pipeline.viewport_dimensions(handle)
- }
-}
-
-#[cfg(feature = "geometry")]
-impl crate::graphics::geometry::Backend for Backend {
- type Frame = crate::geometry::Frame;
-
- fn new_frame(&self, size: Size) -> Self::Frame {
- crate::geometry::Frame::new(size)
- }
-}
diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs
new file mode 100644
index 00000000..3930f46a
--- /dev/null
+++ b/tiny_skia/src/engine.rs
@@ -0,0 +1,831 @@
+use crate::core::renderer::Quad;
+use crate::core::{
+ Background, Color, Gradient, Rectangle, Size, Transformation, Vector,
+};
+use crate::graphics::{Image, Text};
+use crate::text;
+use crate::Primitive;
+
+#[derive(Debug)]
+pub struct Engine {
+ text_pipeline: text::Pipeline,
+
+ #[cfg(feature = "image")]
+ pub(crate) raster_pipeline: crate::raster::Pipeline,
+ #[cfg(feature = "svg")]
+ pub(crate) vector_pipeline: crate::vector::Pipeline,
+}
+
+impl Engine {
+ pub fn new() -> Self {
+ Self {
+ text_pipeline: text::Pipeline::new(),
+ #[cfg(feature = "image")]
+ raster_pipeline: crate::raster::Pipeline::new(),
+ #[cfg(feature = "svg")]
+ vector_pipeline: crate::vector::Pipeline::new(),
+ }
+ }
+
+ pub fn draw_quad(
+ &mut self,
+ quad: &Quad,
+ background: &Background,
+ transformation: Transformation,
+ pixels: &mut tiny_skia::PixmapMut<'_>,
+ clip_mask: &mut tiny_skia::Mask,
+ clip_bounds: Rectangle,
+ ) {
+ debug_assert!(
+ quad.bounds.width.is_normal(),
+ "Quad with non-normal width!"
+ );
+ debug_assert!(
+ quad.bounds.height.is_normal(),
+ "Quad with non-normal height!"
+ );
+
+ let physical_bounds = quad.bounds * transformation;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ let transform = into_transform(transformation);
+
+ // Make sure the border radius is not larger than the bounds
+ let border_width = quad
+ .border
+ .width
+ .min(quad.bounds.width / 2.0)
+ .min(quad.bounds.height / 2.0);
+
+ let mut fill_border_radius = <[f32; 4]>::from(quad.border.radius);
+
+ for radius in &mut fill_border_radius {
+ *radius = (*radius)
+ .min(quad.bounds.width / 2.0)
+ .min(quad.bounds.height / 2.0);
+ }
+
+ let path = rounded_rectangle(quad.bounds, fill_border_radius);
+
+ let shadow = quad.shadow;
+
+ if shadow.color.a > 0.0 {
+ let shadow_bounds = Rectangle {
+ x: quad.bounds.x + shadow.offset.x - shadow.blur_radius,
+ y: quad.bounds.y + shadow.offset.y - shadow.blur_radius,
+ width: quad.bounds.width + shadow.blur_radius * 2.0,
+ height: quad.bounds.height + shadow.blur_radius * 2.0,
+ } * transformation;
+
+ let radii = fill_border_radius
+ .into_iter()
+ .map(|radius| radius * transformation.scale_factor())
+ .collect::<Vec<_>>();
+ let (x, y, width, height) = (
+ shadow_bounds.x as u32,
+ shadow_bounds.y as u32,
+ shadow_bounds.width as u32,
+ shadow_bounds.height as u32,
+ );
+ let half_width = physical_bounds.width / 2.0;
+ let half_height = physical_bounds.height / 2.0;
+
+ let colors = (y..y + height)
+ .flat_map(|y| (x..x + width).map(move |x| (x as f32, y as f32)))
+ .filter_map(|(x, y)| {
+ tiny_skia::Size::from_wh(half_width, half_height).map(
+ |size| {
+ let shadow_distance = rounded_box_sdf(
+ Vector::new(
+ x - physical_bounds.position().x
+ - (shadow.offset.x
+ * transformation.scale_factor())
+ - half_width,
+ y - physical_bounds.position().y
+ - (shadow.offset.y
+ * transformation.scale_factor())
+ - half_height,
+ ),
+ size,
+ &radii,
+ )
+ .max(0.0);
+ let shadow_alpha = 1.0
+ - smoothstep(
+ -shadow.blur_radius
+ * transformation.scale_factor(),
+ shadow.blur_radius
+ * transformation.scale_factor(),
+ shadow_distance,
+ );
+
+ let mut color = into_color(shadow.color);
+ color.apply_opacity(shadow_alpha);
+
+ color.to_color_u8().premultiply()
+ },
+ )
+ })
+ .collect();
+
+ if let Some(pixmap) = tiny_skia::IntSize::from_wh(width, height)
+ .and_then(|size| {
+ tiny_skia::Pixmap::from_vec(
+ bytemuck::cast_vec(colors),
+ size,
+ )
+ })
+ {
+ pixels.draw_pixmap(
+ x as i32,
+ y as i32,
+ pixmap.as_ref(),
+ &tiny_skia::PixmapPaint::default(),
+ tiny_skia::Transform::default(),
+ None,
+ );
+ }
+ }
+
+ pixels.fill_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: match background {
+ Background::Color(color) => {
+ tiny_skia::Shader::SolidColor(into_color(*color))
+ }
+ Background::Gradient(Gradient::Linear(linear)) => {
+ let (start, end) =
+ linear.angle.to_distance(&quad.bounds);
+
+ let stops: Vec<tiny_skia::GradientStop> = linear
+ .stops
+ .into_iter()
+ .flatten()
+ .map(|stop| {
+ tiny_skia::GradientStop::new(
+ stop.offset,
+ tiny_skia::Color::from_rgba(
+ stop.color.b,
+ stop.color.g,
+ stop.color.r,
+ stop.color.a,
+ )
+ .expect("Create color"),
+ )
+ })
+ .collect();
+
+ tiny_skia::LinearGradient::new(
+ tiny_skia::Point {
+ x: start.x,
+ y: start.y,
+ },
+ tiny_skia::Point { x: end.x, y: end.y },
+ if stops.is_empty() {
+ vec![tiny_skia::GradientStop::new(
+ 0.0,
+ tiny_skia::Color::BLACK,
+ )]
+ } else {
+ stops
+ },
+ tiny_skia::SpreadMode::Pad,
+ tiny_skia::Transform::identity(),
+ )
+ .expect("Create linear gradient")
+ }
+ },
+ anti_alias: true,
+ ..tiny_skia::Paint::default()
+ },
+ tiny_skia::FillRule::EvenOdd,
+ transform,
+ clip_mask,
+ );
+
+ if border_width > 0.0 {
+ // Border path is offset by half the border width
+ let border_bounds = Rectangle {
+ x: quad.bounds.x + border_width / 2.0,
+ y: quad.bounds.y + border_width / 2.0,
+ width: quad.bounds.width - border_width,
+ height: quad.bounds.height - border_width,
+ };
+
+ // Make sure the border radius is correct
+ let mut border_radius = <[f32; 4]>::from(quad.border.radius);
+ let mut is_simple_border = true;
+
+ for radius in &mut border_radius {
+ *radius = if *radius == 0.0 {
+ // Path should handle this fine
+ 0.0
+ } else if *radius > border_width / 2.0 {
+ *radius - border_width / 2.0
+ } else {
+ is_simple_border = false;
+ 0.0
+ }
+ .min(border_bounds.width / 2.0)
+ .min(border_bounds.height / 2.0);
+ }
+
+ // Stroking a path works well in this case
+ if is_simple_border {
+ let border_path =
+ rounded_rectangle(border_bounds, border_radius);
+
+ pixels.stroke_path(
+ &border_path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(into_color(
+ quad.border.color,
+ )),
+ anti_alias: true,
+ ..tiny_skia::Paint::default()
+ },
+ &tiny_skia::Stroke {
+ width: border_width,
+ ..tiny_skia::Stroke::default()
+ },
+ transform,
+ clip_mask,
+ );
+ } else {
+ // Draw corners that have too small border radii as having no border radius,
+ // but mask them with the rounded rectangle with the correct border radius.
+ let mut temp_pixmap = tiny_skia::Pixmap::new(
+ quad.bounds.width as u32,
+ quad.bounds.height as u32,
+ )
+ .unwrap();
+
+ let mut quad_mask = tiny_skia::Mask::new(
+ quad.bounds.width as u32,
+ quad.bounds.height as u32,
+ )
+ .unwrap();
+
+ let zero_bounds = Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: quad.bounds.width,
+ height: quad.bounds.height,
+ };
+ let path = rounded_rectangle(zero_bounds, fill_border_radius);
+
+ quad_mask.fill_path(
+ &path,
+ tiny_skia::FillRule::EvenOdd,
+ true,
+ transform,
+ );
+ let path_bounds = Rectangle {
+ x: border_width / 2.0,
+ y: border_width / 2.0,
+ width: quad.bounds.width - border_width,
+ height: quad.bounds.height - border_width,
+ };
+
+ let border_radius_path =
+ rounded_rectangle(path_bounds, border_radius);
+
+ temp_pixmap.stroke_path(
+ &border_radius_path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(into_color(
+ quad.border.color,
+ )),
+ anti_alias: true,
+ ..tiny_skia::Paint::default()
+ },
+ &tiny_skia::Stroke {
+ width: border_width,
+ ..tiny_skia::Stroke::default()
+ },
+ transform,
+ Some(&quad_mask),
+ );
+
+ pixels.draw_pixmap(
+ quad.bounds.x as i32,
+ quad.bounds.y as i32,
+ temp_pixmap.as_ref(),
+ &tiny_skia::PixmapPaint::default(),
+ transform,
+ clip_mask,
+ );
+ }
+ }
+ }
+
+ pub fn draw_text(
+ &mut self,
+ text: &Text,
+ transformation: Transformation,
+ pixels: &mut tiny_skia::PixmapMut<'_>,
+ clip_mask: &mut tiny_skia::Mask,
+ clip_bounds: Rectangle,
+ ) {
+ match text {
+ Text::Paragraph {
+ paragraph,
+ position,
+ color,
+ clip_bounds: _, // TODO
+ transformation: local_transformation,
+ } => {
+ let transformation = transformation * *local_transformation;
+
+ let physical_bounds =
+ Rectangle::new(*position, paragraph.min_bounds)
+ * transformation;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ self.text_pipeline.draw_paragraph(
+ paragraph,
+ *position,
+ *color,
+ pixels,
+ clip_mask,
+ transformation,
+ );
+ }
+ Text::Editor {
+ editor,
+ position,
+ color,
+ clip_bounds: _, // TODO
+ transformation: local_transformation,
+ } => {
+ let transformation = transformation * *local_transformation;
+
+ let physical_bounds =
+ Rectangle::new(*position, editor.bounds) * transformation;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ self.text_pipeline.draw_editor(
+ editor,
+ *position,
+ *color,
+ pixels,
+ clip_mask,
+ transformation,
+ );
+ }
+ Text::Cached {
+ content,
+ bounds,
+ color,
+ size,
+ line_height,
+ font,
+ horizontal_alignment,
+ vertical_alignment,
+ shaping,
+ clip_bounds: _, // TODO
+ } => {
+ let physical_bounds = *bounds * transformation;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ self.text_pipeline.draw_cached(
+ content,
+ *bounds,
+ *color,
+ *size,
+ *line_height,
+ *font,
+ *horizontal_alignment,
+ *vertical_alignment,
+ *shaping,
+ pixels,
+ clip_mask,
+ transformation,
+ );
+ }
+ Text::Raw {
+ raw,
+ transformation: local_transformation,
+ } => {
+ let Some(buffer) = raw.buffer.upgrade() else {
+ return;
+ };
+
+ let transformation = transformation * *local_transformation;
+ let (width, height) = buffer.size();
+
+ let physical_bounds =
+ Rectangle::new(raw.position, Size::new(width, height))
+ * transformation;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ self.text_pipeline.draw_raw(
+ &buffer,
+ raw.position,
+ raw.color,
+ pixels,
+ clip_mask,
+ transformation,
+ );
+ }
+ }
+ }
+
+ pub fn draw_primitive(
+ &mut self,
+ primitive: &Primitive,
+ transformation: Transformation,
+ pixels: &mut tiny_skia::PixmapMut<'_>,
+ clip_mask: &mut tiny_skia::Mask,
+ layer_bounds: Rectangle,
+ ) {
+ match primitive {
+ Primitive::Fill { path, paint, rule } => {
+ let physical_bounds = {
+ let bounds = path.bounds();
+
+ Rectangle {
+ x: bounds.x(),
+ y: bounds.y(),
+ width: bounds.width(),
+ height: bounds.height(),
+ } * transformation
+ };
+
+ let Some(clip_bounds) =
+ layer_bounds.intersection(&physical_bounds)
+ else {
+ return;
+ };
+
+ let clip_mask =
+ (physical_bounds != clip_bounds).then_some(clip_mask as &_);
+
+ pixels.fill_path(
+ path,
+ paint,
+ *rule,
+ into_transform(transformation),
+ clip_mask,
+ );
+ }
+ Primitive::Stroke {
+ path,
+ paint,
+ stroke,
+ } => {
+ let physical_bounds = {
+ let bounds = path.bounds();
+
+ Rectangle {
+ x: bounds.x(),
+ y: bounds.y(),
+ width: bounds.width(),
+ height: bounds.height(),
+ } * transformation
+ };
+
+ let Some(clip_bounds) =
+ layer_bounds.intersection(&physical_bounds)
+ else {
+ return;
+ };
+
+ let clip_mask =
+ (physical_bounds != clip_bounds).then_some(clip_mask as &_);
+
+ pixels.stroke_path(
+ path,
+ paint,
+ stroke,
+ into_transform(transformation),
+ clip_mask,
+ );
+ }
+ }
+ }
+
+ pub fn draw_image(
+ &mut self,
+ image: &Image,
+ transformation: Transformation,
+ pixels: &mut tiny_skia::PixmapMut<'_>,
+ clip_mask: &mut tiny_skia::Mask,
+ clip_bounds: Rectangle,
+ ) {
+ match image {
+ #[cfg(feature = "image")]
+ Image::Raster {
+ handle,
+ filter_method,
+ bounds,
+ } => {
+ let physical_bounds = *bounds * transformation;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ self.raster_pipeline.draw(
+ handle,
+ *filter_method,
+ *bounds,
+ pixels,
+ into_transform(transformation),
+ clip_mask,
+ );
+ }
+ #[cfg(feature = "svg")]
+ Image::Vector {
+ handle,
+ color,
+ bounds,
+ } => {
+ let physical_bounds = *bounds * transformation;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ self.vector_pipeline.draw(
+ handle,
+ *color,
+ physical_bounds,
+ pixels,
+ clip_mask,
+ );
+ }
+ #[cfg(not(feature = "image"))]
+ Image::Raster { .. } => {
+ log::warn!(
+ "Unsupported primitive in `iced_tiny_skia`: {image:?}",
+ );
+ }
+ #[cfg(not(feature = "svg"))]
+ Image::Vector { .. } => {
+ log::warn!(
+ "Unsupported primitive in `iced_tiny_skia`: {image:?}",
+ );
+ }
+ }
+ }
+
+ pub fn trim(&mut self) {
+ self.text_pipeline.trim_cache();
+
+ #[cfg(feature = "image")]
+ self.raster_pipeline.trim_cache();
+
+ #[cfg(feature = "svg")]
+ self.vector_pipeline.trim_cache();
+ }
+}
+
+pub fn into_color(color: Color) -> tiny_skia::Color {
+ tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
+ .expect("Convert color from iced to tiny_skia")
+}
+
+fn into_transform(transformation: Transformation) -> tiny_skia::Transform {
+ let translation = transformation.translation();
+
+ tiny_skia::Transform {
+ sx: transformation.scale_factor(),
+ kx: 0.0,
+ ky: 0.0,
+ sy: transformation.scale_factor(),
+ tx: translation.x,
+ ty: translation.y,
+ }
+}
+
+fn rounded_rectangle(
+ bounds: Rectangle,
+ border_radius: [f32; 4],
+) -> tiny_skia::Path {
+ let [top_left, top_right, bottom_right, bottom_left] = border_radius;
+
+ if top_left == 0.0
+ && top_right == 0.0
+ && bottom_right == 0.0
+ && bottom_left == 0.0
+ {
+ return tiny_skia::PathBuilder::from_rect(
+ tiny_skia::Rect::from_xywh(
+ bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height,
+ )
+ .expect("Build quad rectangle"),
+ );
+ }
+
+ if top_left == top_right
+ && top_left == bottom_right
+ && top_left == bottom_left
+ && top_left == bounds.width / 2.0
+ && top_left == bounds.height / 2.0
+ {
+ return tiny_skia::PathBuilder::from_circle(
+ bounds.x + bounds.width / 2.0,
+ bounds.y + bounds.height / 2.0,
+ top_left,
+ )
+ .expect("Build circle path");
+ }
+
+ let mut builder = tiny_skia::PathBuilder::new();
+
+ builder.move_to(bounds.x + top_left, bounds.y);
+ builder.line_to(bounds.x + bounds.width - top_right, bounds.y);
+
+ if top_right > 0.0 {
+ arc_to(
+ &mut builder,
+ bounds.x + bounds.width - top_right,
+ bounds.y,
+ bounds.x + bounds.width,
+ bounds.y + top_right,
+ top_right,
+ );
+ }
+
+ maybe_line_to(
+ &mut builder,
+ bounds.x + bounds.width,
+ bounds.y + bounds.height - bottom_right,
+ );
+
+ if bottom_right > 0.0 {
+ arc_to(
+ &mut builder,
+ bounds.x + bounds.width,
+ bounds.y + bounds.height - bottom_right,
+ bounds.x + bounds.width - bottom_right,
+ bounds.y + bounds.height,
+ bottom_right,
+ );
+ }
+
+ maybe_line_to(
+ &mut builder,
+ bounds.x + bottom_left,
+ bounds.y + bounds.height,
+ );
+
+ if bottom_left > 0.0 {
+ arc_to(
+ &mut builder,
+ bounds.x + bottom_left,
+ bounds.y + bounds.height,
+ bounds.x,
+ bounds.y + bounds.height - bottom_left,
+ bottom_left,
+ );
+ }
+
+ maybe_line_to(&mut builder, bounds.x, bounds.y + top_left);
+
+ if top_left > 0.0 {
+ arc_to(
+ &mut builder,
+ bounds.x,
+ bounds.y + top_left,
+ bounds.x + top_left,
+ bounds.y,
+ top_left,
+ );
+ }
+
+ builder.finish().expect("Build rounded rectangle path")
+}
+
+fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) {
+ if path.last_point() != Some(tiny_skia::Point { x, y }) {
+ path.line_to(x, y);
+ }
+}
+
+fn arc_to(
+ path: &mut tiny_skia::PathBuilder,
+ x_from: f32,
+ y_from: f32,
+ x_to: f32,
+ y_to: f32,
+ radius: f32,
+) {
+ let svg_arc = kurbo::SvgArc {
+ from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)),
+ to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)),
+ radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)),
+ x_rotation: 0.0,
+ large_arc: false,
+ sweep: true,
+ };
+
+ match kurbo::Arc::from_svg_arc(&svg_arc) {
+ Some(arc) => {
+ arc.to_cubic_beziers(0.1, |p1, p2, p| {
+ path.cubic_to(
+ p1.x as f32,
+ p1.y as f32,
+ p2.x as f32,
+ p2.y as f32,
+ p.x as f32,
+ p.y as f32,
+ );
+ });
+ }
+ None => {
+ path.line_to(x_to, y_to);
+ }
+ }
+}
+
+fn smoothstep(a: f32, b: f32, x: f32) -> f32 {
+ let x = ((x - a) / (b - a)).clamp(0.0, 1.0);
+
+ x * x * (3.0 - 2.0 * x)
+}
+
+fn rounded_box_sdf(
+ to_center: Vector,
+ size: tiny_skia::Size,
+ radii: &[f32],
+) -> f32 {
+ let radius = match (to_center.x > 0.0, to_center.y > 0.0) {
+ (true, true) => radii[2],
+ (true, false) => radii[1],
+ (false, true) => radii[3],
+ (false, false) => radii[0],
+ };
+
+ let x = (to_center.x.abs() - size.width() + radius).max(0.0);
+ let y = (to_center.y.abs() - size.height() + radius).max(0.0);
+
+ (x.powf(2.0) + y.powf(2.0)).sqrt() - radius
+}
+
+pub fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) {
+ clip_mask.clear();
+
+ let path = {
+ let mut builder = tiny_skia::PathBuilder::new();
+ builder.push_rect(
+ tiny_skia::Rect::from_xywh(
+ bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height,
+ )
+ .unwrap(),
+ );
+
+ builder.finish().unwrap()
+ };
+
+ clip_mask.fill_path(
+ &path,
+ tiny_skia::FillRule::EvenOdd,
+ false,
+ tiny_skia::Transform::default(),
+ );
+}
diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs
index a9dcb610..04aabf0d 100644
--- a/tiny_skia/src/geometry.rs
+++ b/tiny_skia/src/geometry.rs
@@ -1,58 +1,98 @@
use crate::core::text::LineHeight;
-use crate::core::{
- Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,
-};
+use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector};
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke};
-use crate::graphics::geometry::{self, Path, Style, Text};
-use crate::graphics::Gradient;
-use crate::primitive::{self, Primitive};
+use crate::graphics::geometry::{self, Path, Style};
+use crate::graphics::{Cached, Gradient, Text};
+use crate::Primitive;
+
+use std::rc::Rc;
+
+#[derive(Debug)]
+pub enum Geometry {
+ Live {
+ text: Vec<Text>,
+ primitives: Vec<Primitive>,
+ clip_bounds: Rectangle,
+ },
+ Cache(Cache),
+}
+
+#[derive(Debug, Clone)]
+pub struct Cache {
+ pub text: Rc<[Text]>,
+ pub primitives: Rc<[Primitive]>,
+ pub clip_bounds: Rectangle,
+}
+
+impl Cached for Geometry {
+ type Cache = Cache;
+
+ fn load(cache: &Cache) -> Self {
+ Self::Cache(cache.clone())
+ }
+
+ fn cache(self, _previous: Option<Cache>) -> Cache {
+ match self {
+ Self::Live {
+ primitives,
+ text,
+ clip_bounds,
+ } => Cache {
+ primitives: Rc::from(primitives),
+ text: Rc::from(text),
+ clip_bounds,
+ },
+ Self::Cache(cache) => cache,
+ }
+ }
+}
#[derive(Debug)]
pub struct Frame {
- size: Size,
+ clip_bounds: Rectangle,
transform: tiny_skia::Transform,
stack: Vec<tiny_skia::Transform>,
primitives: Vec<Primitive>,
+ text: Vec<Text>,
}
impl Frame {
pub fn new(size: Size) -> Self {
+ Self::with_clip(Rectangle::with_size(size))
+ }
+
+ pub fn with_clip(clip_bounds: Rectangle) -> Self {
Self {
- size,
- transform: tiny_skia::Transform::identity(),
+ clip_bounds,
stack: Vec::new(),
primitives: Vec::new(),
- }
- }
-
- pub fn into_primitive(self) -> Primitive {
- Primitive::Clip {
- bounds: Rectangle::new(Point::ORIGIN, self.size),
- content: Box::new(Primitive::Group {
- primitives: self.primitives,
- }),
+ text: Vec::new(),
+ transform: tiny_skia::Transform::from_translate(
+ clip_bounds.x,
+ clip_bounds.y,
+ ),
}
}
}
impl geometry::frame::Backend for Frame {
- type Geometry = Primitive;
+ type Geometry = Geometry;
fn width(&self) -> f32 {
- self.size.width
+ self.clip_bounds.width
}
fn height(&self) -> f32 {
- self.size.height
+ self.clip_bounds.height
}
fn size(&self) -> Size {
- self.size
+ self.clip_bounds.size()
}
fn center(&self) -> Point {
- Point::new(self.size.width / 2.0, self.size.height / 2.0)
+ Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
}
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
@@ -67,12 +107,11 @@ impl geometry::frame::Backend for Frame {
let mut paint = into_paint(fill.style);
paint.shader.transform(self.transform);
- self.primitives
- .push(Primitive::Custom(primitive::Custom::Fill {
- path,
- paint,
- rule: into_fill_rule(fill.rule),
- }));
+ self.primitives.push(Primitive::Fill {
+ path,
+ paint,
+ rule: into_fill_rule(fill.rule),
+ });
}
fn fill_rectangle(
@@ -95,12 +134,11 @@ impl geometry::frame::Backend for Frame {
};
paint.shader.transform(self.transform);
- self.primitives
- .push(Primitive::Custom(primitive::Custom::Fill {
- path,
- paint,
- rule: into_fill_rule(fill.rule),
- }));
+ self.primitives.push(Primitive::Fill {
+ path,
+ paint,
+ rule: into_fill_rule(fill.rule),
+ });
}
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
@@ -116,15 +154,14 @@ impl geometry::frame::Backend for Frame {
let mut paint = into_paint(stroke.style);
paint.shader.transform(self.transform);
- self.primitives
- .push(Primitive::Custom(primitive::Custom::Stroke {
- path,
- paint,
- stroke: skia_stroke,
- }));
+ self.primitives.push(Primitive::Stroke {
+ path,
+ paint,
+ stroke: skia_stroke,
+ });
}
- fn fill_text(&mut self, text: impl Into<Text>) {
+ fn fill_text(&mut self, text: impl Into<geometry::Text>) {
let text = text.into();
let (scale_x, scale_y) = self.transform.get_scale();
@@ -171,12 +208,12 @@ impl geometry::frame::Backend for Frame {
};
// TODO: Honor layering!
- self.primitives.push(Primitive::Text {
+ self.text.push(Text::Cached {
content: text.content,
bounds,
color: text.color,
size,
- line_height,
+ line_height: line_height.to_absolute(size),
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
@@ -197,14 +234,12 @@ impl geometry::frame::Backend for Frame {
}
fn draft(&mut self, clip_bounds: Rectangle) -> Self {
- Self::new(clip_bounds.size())
+ Self::with_clip(clip_bounds)
}
- fn paste(&mut self, frame: Self, at: Point) {
- self.primitives.push(Primitive::Transform {
- transformation: Transformation::translate(at.x, at.y),
- content: Box::new(frame.into_primitive()),
- });
+ fn paste(&mut self, frame: Self, _at: Point) {
+ self.primitives.extend(frame.primitives);
+ self.text.extend(frame.text);
}
fn translate(&mut self, translation: Vector) {
@@ -230,8 +265,12 @@ impl geometry::frame::Backend for Frame {
self.transform = self.transform.pre_scale(scale.x, scale.y);
}
- fn into_geometry(self) -> Self::Geometry {
- self.into_primitive()
+ fn into_geometry(self) -> Geometry {
+ Geometry::Live {
+ primitives: self.primitives,
+ text: self.text,
+ clip_bounds: self.clip_bounds,
+ }
}
}
diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs
new file mode 100644
index 00000000..1f5c8b46
--- /dev/null
+++ b/tiny_skia/src/layer.rs
@@ -0,0 +1,243 @@
+use crate::core::image;
+use crate::core::renderer::Quad;
+use crate::core::svg;
+use crate::core::{Background, Color, Point, Rectangle, Transformation};
+use crate::graphics::layer;
+use crate::graphics::text::{Editor, Paragraph, Text};
+use crate::graphics::{self, Image};
+use crate::Primitive;
+
+use std::rc::Rc;
+
+pub type Stack = layer::Stack<Layer>;
+
+#[derive(Debug, Clone)]
+pub struct Layer {
+ pub bounds: Rectangle,
+ pub quads: Vec<(Quad, Background)>,
+ pub primitives: Vec<Item<Primitive>>,
+ pub text: Vec<Item<Text>>,
+ pub images: Vec<Image>,
+}
+
+#[derive(Debug, Clone)]
+pub enum Item<T> {
+ Live(T),
+ Group(Vec<T>, Rectangle, Transformation),
+ Cached(Rc<[T]>, Rectangle, Transformation),
+}
+
+impl<T> Item<T> {
+ pub fn transformation(&self) -> Transformation {
+ match self {
+ Item::Live(_) => Transformation::IDENTITY,
+ Item::Group(_, _, transformation)
+ | Item::Cached(_, _, transformation) => *transformation,
+ }
+ }
+
+ pub fn clip_bounds(&self) -> Rectangle {
+ match self {
+ Item::Live(_) => Rectangle::INFINITE,
+ Item::Group(_, clip_bounds, _)
+ | Item::Cached(_, clip_bounds, _) => *clip_bounds,
+ }
+ }
+
+ pub fn as_slice(&self) -> &[T] {
+ match self {
+ Item::Live(item) => std::slice::from_ref(item),
+ Item::Group(group, _, _) => group.as_slice(),
+ Item::Cached(cache, _, _) => cache,
+ }
+ }
+}
+
+impl Layer {
+ pub fn draw_quad(
+ &mut self,
+ mut quad: Quad,
+ background: Background,
+ transformation: Transformation,
+ ) {
+ quad.bounds = quad.bounds * transformation;
+ self.quads.push((quad, background));
+ }
+
+ pub fn draw_paragraph(
+ &mut self,
+ paragraph: &Paragraph,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ transformation: Transformation,
+ ) {
+ let paragraph = Text::Paragraph {
+ paragraph: paragraph.downgrade(),
+ position,
+ color,
+ clip_bounds,
+ transformation,
+ };
+
+ self.text.push(Item::Live(paragraph));
+ }
+
+ pub fn draw_editor(
+ &mut self,
+ editor: &Editor,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ transformation: Transformation,
+ ) {
+ let editor = Text::Editor {
+ editor: editor.downgrade(),
+ position,
+ color,
+ clip_bounds,
+ transformation,
+ };
+
+ self.text.push(Item::Live(editor));
+ }
+
+ pub fn draw_text(
+ &mut self,
+ text: crate::core::Text,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ transformation: Transformation,
+ ) {
+ let text = Text::Cached {
+ content: text.content,
+ bounds: Rectangle::new(position, text.bounds) * transformation,
+ color,
+ size: text.size * transformation.scale_factor(),
+ line_height: text.line_height.to_absolute(text.size)
+ * transformation.scale_factor(),
+ font: text.font,
+ horizontal_alignment: text.horizontal_alignment,
+ vertical_alignment: text.vertical_alignment,
+ shaping: text.shaping,
+ clip_bounds: clip_bounds * transformation,
+ };
+
+ self.text.push(Item::Live(text));
+ }
+
+ pub fn draw_text_group(
+ &mut self,
+ text: Vec<Text>,
+ clip_bounds: Rectangle,
+ transformation: Transformation,
+ ) {
+ self.text
+ .push(Item::Group(text, clip_bounds, transformation));
+ }
+
+ pub fn draw_text_cache(
+ &mut self,
+ text: Rc<[Text]>,
+ clip_bounds: Rectangle,
+ transformation: Transformation,
+ ) {
+ self.text
+ .push(Item::Cached(text, clip_bounds, transformation));
+ }
+
+ pub fn draw_image(
+ &mut self,
+ handle: image::Handle,
+ filter_method: image::FilterMethod,
+ bounds: Rectangle,
+ transformation: Transformation,
+ ) {
+ let image = Image::Raster {
+ handle,
+ filter_method,
+ bounds: bounds * transformation,
+ };
+
+ self.images.push(image);
+ }
+
+ pub fn draw_svg(
+ &mut self,
+ handle: svg::Handle,
+ color: Option<Color>,
+ bounds: Rectangle,
+ transformation: Transformation,
+ ) {
+ let svg = Image::Vector {
+ handle,
+ color,
+ bounds: bounds * transformation,
+ };
+
+ self.images.push(svg);
+ }
+
+ pub fn draw_primitive_group(
+ &mut self,
+ primitives: Vec<Primitive>,
+ clip_bounds: Rectangle,
+ transformation: Transformation,
+ ) {
+ self.primitives.push(Item::Group(
+ primitives,
+ clip_bounds,
+ transformation,
+ ));
+ }
+
+ pub fn draw_primitive_cache(
+ &mut self,
+ primitives: Rc<[Primitive]>,
+ clip_bounds: Rectangle,
+ transformation: Transformation,
+ ) {
+ self.primitives.push(Item::Cached(
+ primitives,
+ clip_bounds,
+ transformation,
+ ));
+ }
+}
+
+impl Default for Layer {
+ fn default() -> Self {
+ Self {
+ bounds: Rectangle::INFINITE,
+ quads: Vec::new(),
+ primitives: Vec::new(),
+ text: Vec::new(),
+ images: Vec::new(),
+ }
+ }
+}
+
+impl graphics::Layer for Layer {
+ fn with_bounds(bounds: Rectangle) -> Self {
+ Self {
+ bounds,
+ ..Self::default()
+ }
+ }
+
+ fn flush(&mut self) {}
+
+ fn resize(&mut self, bounds: graphics::core::Rectangle) {
+ self.bounds = bounds;
+ }
+
+ fn reset(&mut self) {
+ self.bounds = Rectangle::INFINITE;
+
+ self.quads.clear();
+ self.text.clear();
+ self.primitives.clear();
+ self.images.clear();
+ }
+}
diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs
index d1f68daa..fed2f32c 100644
--- a/tiny_skia/src/lib.rs
+++ b/tiny_skia/src/lib.rs
@@ -2,7 +2,8 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod window;
-mod backend;
+mod engine;
+mod layer;
mod primitive;
mod settings;
mod text;
@@ -19,12 +20,388 @@ pub mod geometry;
pub use iced_graphics as graphics;
pub use iced_graphics::core;
-pub use backend::Backend;
+pub use layer::Layer;
pub use primitive::Primitive;
pub use settings::Settings;
+#[cfg(feature = "geometry")]
+pub use geometry::Geometry;
+
+use crate::core::renderer;
+use crate::core::{
+ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
+};
+use crate::engine::Engine;
+use crate::graphics::compositor;
+use crate::graphics::text::{Editor, Paragraph};
+use crate::graphics::Viewport;
+
/// A [`tiny-skia`] graphics renderer for [`iced`].
///
/// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
/// [`iced`]: https://github.com/iced-rs/iced
-pub type Renderer = iced_graphics::Renderer<Backend>;
+#[derive(Debug)]
+pub struct Renderer {
+ default_font: Font,
+ default_text_size: Pixels,
+ layers: layer::Stack,
+ engine: Engine, // TODO: Shared engine
+}
+
+impl Renderer {
+ pub fn new(default_font: Font, default_text_size: Pixels) -> Self {
+ Self {
+ default_font,
+ default_text_size,
+ layers: layer::Stack::new(),
+ engine: Engine::new(),
+ }
+ }
+
+ pub fn layers(&mut self) -> impl Iterator<Item = &Layer> {
+ self.layers.flush();
+ self.layers.iter()
+ }
+
+ pub fn draw<T: AsRef<str>>(
+ &mut self,
+ pixels: &mut tiny_skia::PixmapMut<'_>,
+ clip_mask: &mut tiny_skia::Mask,
+ viewport: &Viewport,
+ damage: &[Rectangle],
+ background_color: Color,
+ overlay: &[T],
+ ) {
+ let physical_size = viewport.physical_size();
+ let scale_factor = viewport.scale_factor() as f32;
+
+ if !overlay.is_empty() {
+ let path = tiny_skia::PathBuilder::from_rect(
+ tiny_skia::Rect::from_xywh(
+ 0.0,
+ 0.0,
+ physical_size.width as f32,
+ physical_size.height as f32,
+ )
+ .expect("Create damage rectangle"),
+ );
+
+ pixels.fill_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(engine::into_color(
+ Color {
+ a: 0.1,
+ ..background_color
+ },
+ )),
+ anti_alias: false,
+ ..Default::default()
+ },
+ tiny_skia::FillRule::default(),
+ tiny_skia::Transform::identity(),
+ None,
+ );
+ }
+
+ self.layers.flush();
+
+ for &region in damage {
+ let region = region * scale_factor;
+
+ let path = tiny_skia::PathBuilder::from_rect(
+ tiny_skia::Rect::from_xywh(
+ region.x,
+ region.y,
+ region.width,
+ region.height,
+ )
+ .expect("Create damage rectangle"),
+ );
+
+ pixels.fill_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(engine::into_color(
+ background_color,
+ )),
+ anti_alias: false,
+ blend_mode: tiny_skia::BlendMode::Source,
+ ..Default::default()
+ },
+ tiny_skia::FillRule::default(),
+ tiny_skia::Transform::identity(),
+ None,
+ );
+
+ for layer in self.layers.iter() {
+ let Some(clip_bounds) = region.intersection(&layer.bounds)
+ else {
+ continue;
+ };
+
+ let clip_bounds = clip_bounds * scale_factor;
+ engine::adjust_clip_mask(clip_mask, clip_bounds);
+
+ for (quad, background) in &layer.quads {
+ self.engine.draw_quad(
+ quad,
+ background,
+ Transformation::scale(scale_factor),
+ pixels,
+ clip_mask,
+ clip_bounds,
+ );
+ }
+
+ for group in &layer.text {
+ for text in group.as_slice() {
+ self.engine.draw_text(
+ text,
+ group.transformation()
+ * Transformation::scale(scale_factor),
+ pixels,
+ clip_mask,
+ clip_bounds,
+ );
+ }
+ }
+
+ for group in &layer.primitives {
+ let Some(new_clip_bounds) =
+ group.clip_bounds().intersection(&layer.bounds)
+ else {
+ continue;
+ };
+
+ engine::adjust_clip_mask(
+ clip_mask,
+ new_clip_bounds * scale_factor,
+ );
+
+ for primitive in group.as_slice() {
+ self.engine.draw_primitive(
+ primitive,
+ group.transformation()
+ * Transformation::scale(scale_factor),
+ pixels,
+ clip_mask,
+ clip_bounds,
+ );
+ }
+
+ engine::adjust_clip_mask(clip_mask, clip_bounds);
+ }
+
+ for image in &layer.images {
+ self.engine.draw_image(
+ image,
+ Transformation::scale(scale_factor),
+ pixels,
+ clip_mask,
+ clip_bounds,
+ );
+ }
+ }
+
+ if !overlay.is_empty() {
+ pixels.stroke_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(
+ engine::into_color(Color::from_rgb(1.0, 0.0, 0.0)),
+ ),
+ anti_alias: false,
+ ..tiny_skia::Paint::default()
+ },
+ &tiny_skia::Stroke {
+ width: 1.0,
+ ..tiny_skia::Stroke::default()
+ },
+ tiny_skia::Transform::identity(),
+ None,
+ );
+ }
+ }
+
+ self.engine.trim();
+ }
+}
+
+impl core::Renderer for Renderer {
+ fn start_layer(&mut self, bounds: Rectangle) {
+ self.layers.push_clip(bounds);
+ }
+
+ fn end_layer(&mut self) {
+ self.layers.pop_clip();
+ }
+
+ fn start_transformation(&mut self, transformation: Transformation) {
+ self.layers.push_transformation(transformation);
+ }
+
+ fn end_transformation(&mut self) {
+ self.layers.pop_transformation();
+ }
+
+ fn fill_quad(
+ &mut self,
+ quad: renderer::Quad,
+ background: impl Into<Background>,
+ ) {
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_quad(quad, background.into(), transformation);
+ }
+
+ fn clear(&mut self) {
+ self.layers.clear();
+ }
+}
+
+impl core::text::Renderer for Renderer {
+ type Font = Font;
+ type Paragraph = Paragraph;
+ type Editor = 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 fill_paragraph(
+ &mut self,
+ text: &Self::Paragraph,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ let (layer, transformation) = self.layers.current_mut();
+
+ layer.draw_paragraph(
+ text,
+ position,
+ color,
+ clip_bounds,
+ transformation,
+ );
+ }
+
+ fn fill_editor(
+ &mut self,
+ editor: &Self::Editor,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_editor(editor, position, color, clip_bounds, transformation);
+ }
+
+ fn fill_text(
+ &mut self,
+ text: core::Text,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_text(text, position, color, clip_bounds, transformation);
+ }
+}
+
+#[cfg(feature = "geometry")]
+impl graphics::geometry::Renderer for Renderer {
+ type Geometry = Geometry;
+ type Frame = geometry::Frame;
+
+ fn new_frame(&self, size: core::Size) -> Self::Frame {
+ geometry::Frame::new(size)
+ }
+
+ fn draw_geometry(&mut self, geometry: Self::Geometry) {
+ let (layer, transformation) = self.layers.current_mut();
+
+ match geometry {
+ Geometry::Live {
+ primitives,
+ text,
+ clip_bounds,
+ } => {
+ layer.draw_primitive_group(
+ primitives,
+ clip_bounds,
+ transformation,
+ );
+
+ layer.draw_text_group(text, clip_bounds, transformation);
+ }
+ Geometry::Cache(cache) => {
+ layer.draw_primitive_cache(
+ cache.primitives,
+ cache.clip_bounds,
+ transformation,
+ );
+
+ layer.draw_text_cache(
+ cache.text,
+ cache.clip_bounds,
+ transformation,
+ );
+ }
+ }
+ }
+}
+
+impl graphics::mesh::Renderer for Renderer {
+ fn draw_mesh(&mut self, _mesh: graphics::Mesh) {
+ log::warn!("iced_tiny_skia does not support drawing meshes");
+ }
+}
+
+#[cfg(feature = "image")]
+impl core::image::Renderer for Renderer {
+ type Handle = core::image::Handle;
+
+ fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
+ self.engine.raster_pipeline.dimensions(handle)
+ }
+
+ fn draw_image(
+ &mut self,
+ handle: Self::Handle,
+ filter_method: core::image::FilterMethod,
+ bounds: Rectangle,
+ ) {
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_image(handle, filter_method, bounds, transformation);
+ }
+}
+
+#[cfg(feature = "svg")]
+impl core::svg::Renderer for Renderer {
+ fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
+ self.engine.vector_pipeline.viewport_dimensions(handle)
+ }
+
+ fn draw_svg(
+ &mut self,
+ handle: core::svg::Handle,
+ color: Option<Color>,
+ bounds: Rectangle,
+ ) {
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_svg(handle, color, bounds, transformation);
+ }
+}
+
+impl compositor::Default for Renderer {
+ type Compositor = window::Compositor;
+}
diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs
index b7c428e4..5305788d 100644
--- a/tiny_skia/src/primitive.rs
+++ b/tiny_skia/src/primitive.rs
@@ -1,10 +1,5 @@
-use crate::core::Rectangle;
-use crate::graphics::{Damage, Mesh};
-
-pub type Primitive = crate::graphics::Primitive<Custom>;
-
#[derive(Debug, Clone, PartialEq)]
-pub enum Custom {
+pub enum Primitive {
/// A path filled with some paint.
Fill {
/// The path to fill.
@@ -24,29 +19,3 @@ pub enum Custom {
stroke: tiny_skia::Stroke,
},
}
-
-impl Damage for Custom {
- fn bounds(&self) -> Rectangle {
- match self {
- Self::Fill { path, .. } | Self::Stroke { path, .. } => {
- let bounds = path.bounds();
-
- Rectangle {
- x: bounds.x(),
- y: bounds.y(),
- width: bounds.width(),
- height: bounds.height(),
- }
- .expand(1.0)
- }
- }
- }
-}
-
-impl TryFrom<Mesh> for Custom {
- type Error = &'static str;
-
- fn try_from(_mesh: Mesh) -> Result<Self, Self::Error> {
- Err("unsupported")
- }
-}
diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs
index 01d015b4..672c49f3 100644
--- a/tiny_skia/src/settings.rs
+++ b/tiny_skia/src/settings.rs
@@ -1,9 +1,9 @@
use crate::core::{Font, Pixels};
use crate::graphics;
-/// The settings of a [`Backend`].
+/// The settings of a [`Compositor`].
///
-/// [`Backend`]: crate::Backend
+/// [`Compositor`]: crate::window::Compositor
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
/// The default [`Font`] to use.
diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs
index 66ee88da..c71deb10 100644
--- a/tiny_skia/src/text.rs
+++ b/tiny_skia/src/text.rs
@@ -1,5 +1,5 @@
use crate::core::alignment;
-use crate::core::text::{LineHeight, Shaping};
+use crate::core::text::Shaping;
use crate::core::{
Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
@@ -27,6 +27,8 @@ impl Pipeline {
}
}
+ // TODO: Shared engine
+ #[allow(dead_code)]
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
font_system()
.write()
@@ -41,7 +43,6 @@ impl Pipeline {
paragraph: &paragraph::Weak,
position: Point,
color: Color,
- scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@@ -62,7 +63,6 @@ impl Pipeline {
color,
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
- scale_factor,
pixels,
clip_mask,
transformation,
@@ -74,7 +74,6 @@ impl Pipeline {
editor: &editor::Weak,
position: Point,
color: Color,
- scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@@ -95,7 +94,6 @@ impl Pipeline {
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,
- scale_factor,
pixels,
clip_mask,
transformation,
@@ -108,17 +106,16 @@ impl Pipeline {
bounds: Rectangle,
color: Color,
size: Pixels,
- line_height: LineHeight,
+ line_height: Pixels,
font: Font,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
- scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
) {
- let line_height = f32::from(line_height.to_absolute(size));
+ let line_height = f32::from(line_height);
let mut font_system = font_system().write().expect("Write font system");
let font_system = font_system.raw();
@@ -149,7 +146,6 @@ impl Pipeline {
color,
horizontal_alignment,
vertical_alignment,
- scale_factor,
pixels,
clip_mask,
transformation,
@@ -161,7 +157,6 @@ impl Pipeline {
buffer: &cosmic_text::Buffer,
position: Point,
color: Color,
- scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@@ -178,7 +173,6 @@ impl Pipeline {
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,
- scale_factor,
pixels,
clip_mask,
transformation,
@@ -199,12 +193,11 @@ fn draw(
color: Color,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
- scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
) {
- let bounds = bounds * transformation * scale_factor;
+ let bounds = bounds * transformation;
let x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
@@ -222,8 +215,8 @@ fn draw(
for run in buffer.layout_runs() {
for glyph in run.glyphs {
- let physical_glyph = glyph
- .physical((x, y), scale_factor * transformation.scale_factor());
+ let physical_glyph =
+ glyph.physical((x, y), transformation.scale_factor());
if let Some((buffer, placement)) = glyph_cache.allocate(
physical_glyph.cache_key,
@@ -247,10 +240,8 @@ fn draw(
pixels.draw_pixmap(
physical_glyph.x + placement.left,
physical_glyph.y - placement.top
- + (run.line_y
- * scale_factor
- * transformation.scale_factor())
- .round() as i32,
+ + (run.line_y * transformation.scale_factor()).round()
+ as i32,
pixmap,
&tiny_skia::PixmapPaint {
opacity,
diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs
index 2350adb9..e1a9dfec 100644
--- a/tiny_skia/src/window/compositor.rs
+++ b/tiny_skia/src/window/compositor.rs
@@ -1,9 +1,8 @@
use crate::core::{Color, Rectangle, Size};
use crate::graphics::compositor::{self, Information};
-use crate::graphics::damage;
use crate::graphics::error::{self, Error};
use crate::graphics::{self, Viewport};
-use crate::{Backend, Primitive, Renderer, Settings};
+use crate::{Layer, Renderer, Settings};
use std::collections::VecDeque;
use std::num::NonZeroU32;
@@ -21,7 +20,7 @@ pub struct Surface {
Box<dyn compositor::Window>,
>,
clip_mask: tiny_skia::Mask,
- primitive_stack: VecDeque<Vec<Primitive>>,
+ layer_stack: VecDeque<Vec<Layer>>,
background_color: Color,
max_age: u8,
}
@@ -50,7 +49,6 @@ impl crate::graphics::Compositor for Compositor {
fn create_renderer(&self) -> Self::Renderer {
Renderer::new(
- Backend::new(),
self.settings.default_font,
self.settings.default_text_size,
)
@@ -72,7 +70,7 @@ impl crate::graphics::Compositor for Compositor {
window,
clip_mask: tiny_skia::Mask::new(width, height)
.expect("Create clip mask"),
- primitive_stack: VecDeque::new(),
+ layer_stack: VecDeque::new(),
background_color: Color::BLACK,
max_age: 0,
};
@@ -98,7 +96,7 @@ impl crate::graphics::Compositor for Compositor {
surface.clip_mask =
tiny_skia::Mask::new(width, height).expect("Create clip mask");
- surface.primitive_stack.clear();
+ surface.layer_stack.clear();
}
fn fetch_information(&self) -> Information {
@@ -116,16 +114,7 @@ impl crate::graphics::Compositor for Compositor {
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
- renderer.with_primitives(|backend, primitives| {
- present(
- backend,
- surface,
- primitives,
- viewport,
- background_color,
- overlay,
- )
- })
+ present(renderer, surface, viewport, background_color, overlay)
}
fn screenshot<T: AsRef<str>>(
@@ -136,16 +125,7 @@ impl crate::graphics::Compositor for Compositor {
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
- renderer.with_primitives(|backend, primitives| {
- screenshot(
- surface,
- backend,
- primitives,
- viewport,
- background_color,
- overlay,
- )
- })
+ screenshot(renderer, surface, viewport, background_color, overlay)
}
}
@@ -161,49 +141,52 @@ pub fn new<W: compositor::Window>(
}
pub fn present<T: AsRef<str>>(
- backend: &mut Backend,
+ renderer: &mut Renderer,
surface: &mut Surface,
- primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
let physical_size = viewport.physical_size();
- let scale_factor = viewport.scale_factor() as f32;
let mut buffer = surface
.window
.buffer_mut()
.map_err(|_| compositor::SurfaceError::Lost)?;
- let last_primitives = {
+ let _last_layers = {
let age = buffer.age();
surface.max_age = surface.max_age.max(age);
- surface.primitive_stack.truncate(surface.max_age as usize);
+ surface.layer_stack.truncate(surface.max_age as usize);
if age > 0 {
- surface.primitive_stack.get(age as usize - 1)
+ surface.layer_stack.get(age as usize - 1)
} else {
None
}
};
- let damage = last_primitives
- .and_then(|last_primitives| {
- (surface.background_color == background_color)
- .then(|| damage::list(last_primitives, primitives))
- })
- .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
+ // TODO
+ // let damage = last_layers
+ // .and_then(|last_layers| {
+ // (surface.background_color == background_color)
+ // .then(|| damage::layers(last_layers, renderer.layers()))
+ // })
+ // .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
+
+ let damage = vec![Rectangle::with_size(viewport.logical_size())];
if damage.is_empty() {
return Ok(());
}
- surface.primitive_stack.push_front(primitives.to_vec());
+ surface
+ .layer_stack
+ .push_front(renderer.layers().cloned().collect());
surface.background_color = background_color;
- let damage = damage::group(damage, scale_factor, physical_size);
+ // let damage = damage::group(damage, viewport.logical_size());
let mut pixels = tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut buffer),
@@ -212,10 +195,9 @@ pub fn present<T: AsRef<str>>(
)
.expect("Create pixel map");
- backend.draw(
+ renderer.draw(
&mut pixels,
&mut surface.clip_mask,
- primitives,
viewport,
&damage,
background_color,
@@ -226,9 +208,8 @@ pub fn present<T: AsRef<str>>(
}
pub fn screenshot<T: AsRef<str>>(
+ renderer: &mut Renderer,
surface: &mut Surface,
- backend: &mut Backend,
- primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
overlay: &[T],
@@ -238,7 +219,7 @@ pub fn screenshot<T: AsRef<str>>(
let mut offscreen_buffer: Vec<u32> =
vec![0; size.width as usize * size.height as usize];
- backend.draw(
+ renderer.draw(
&mut tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut offscreen_buffer),
size.width,
@@ -246,7 +227,6 @@ pub fn screenshot<T: AsRef<str>>(
)
.expect("Create offscreen pixel map"),
&mut surface.clip_mask,
- primitives,
viewport,
&[Rectangle::with_size(Size::new(
size.width as f32,
diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs
index 985650e2..60967082 100644
--- a/wgpu/src/geometry.rs
+++ b/wgpu/src/geometry.rs
@@ -19,18 +19,6 @@ use lyon::tessellation;
use std::borrow::Cow;
-/// A frame for drawing some geometry.
-#[allow(missing_debug_implementations)]
-pub struct Frame {
- clip_bounds: Rectangle,
- buffers: BufferStack,
- meshes: Vec<Mesh>,
- text: Vec<Text>,
- transforms: Transforms,
- fill_tessellator: tessellation::FillTessellator,
- stroke_tessellator: tessellation::StrokeTessellator,
-}
-
#[derive(Debug)]
pub enum Geometry {
Live { meshes: Vec<Mesh>, text: Vec<Text> },
@@ -79,6 +67,18 @@ impl Cached for Geometry {
}
}
+/// A frame for drawing some geometry.
+#[allow(missing_debug_implementations)]
+pub struct Frame {
+ clip_bounds: Rectangle,
+ buffers: BufferStack,
+ meshes: Vec<Mesh>,
+ text: Vec<Text>,
+ transforms: Transforms,
+ fill_tessellator: tessellation::FillTessellator,
+ stroke_tessellator: tessellation::StrokeTessellator,
+}
+
impl Frame {
/// Creates a new [`Frame`] with the given [`Size`].
pub fn new(size: Size) -> Frame {
diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs
index 7a18e322..9526c5a8 100644
--- a/wgpu/src/layer.rs
+++ b/wgpu/src/layer.rs
@@ -1,6 +1,8 @@
use crate::core::renderer;
use crate::core::{Background, Color, Point, Rectangle, Transformation};
+use crate::graphics;
use crate::graphics::color;
+use crate::graphics::layer;
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Mesh;
use crate::image::{self, Image};
@@ -9,6 +11,8 @@ use crate::quad::{self, Quad};
use crate::text::{self, Text};
use crate::triangle;
+pub type Stack = layer::Stack<Layer>;
+
#[derive(Debug)]
pub struct Layer {
pub bounds: Rectangle,
@@ -17,48 +21,18 @@ pub struct Layer {
pub primitives: primitive::Batch,
pub text: text::Batch,
pub images: image::Batch,
+ pending_meshes: Vec<Mesh>,
+ pending_text: Vec<Text>,
}
-impl Default for Layer {
- fn default() -> Self {
- Self {
- bounds: Rectangle::INFINITE,
- quads: quad::Batch::default(),
- triangles: triangle::Batch::default(),
- primitives: primitive::Batch::default(),
- text: text::Batch::default(),
- images: image::Batch::default(),
- }
- }
-}
-
-#[derive(Debug)]
-pub struct Stack {
- layers: Vec<Layer>,
- transformations: Vec<Transformation>,
- previous: Vec<usize>,
- pending_meshes: Vec<Vec<Mesh>>,
- pending_text: Vec<Vec<Text>>,
- current: usize,
- active_count: usize,
-}
-
-impl Stack {
- pub fn new() -> Self {
- Self {
- layers: vec![Layer::default()],
- transformations: vec![Transformation::IDENTITY],
- previous: vec![],
- pending_meshes: vec![Vec::new()],
- pending_text: vec![Vec::new()],
- current: 0,
- active_count: 1,
- }
- }
-
- pub fn draw_quad(&mut self, quad: renderer::Quad, background: Background) {
- let transformation = self.transformations.last().unwrap();
- let bounds = quad.bounds * *transformation;
+impl Layer {
+ pub fn draw_quad(
+ &mut self,
+ quad: renderer::Quad,
+ background: Background,
+ transformation: Transformation,
+ ) {
+ let bounds = quad.bounds * transformation;
let quad = Quad {
position: [bounds.x, bounds.y],
@@ -71,7 +45,7 @@ impl Stack {
shadow_blur_radius: quad.shadow.blur_radius,
};
- self.layers[self.current].quads.add(quad, &background);
+ self.quads.add(quad, &background);
}
pub fn draw_paragraph(
@@ -80,16 +54,17 @@ impl Stack {
position: Point,
color: Color,
clip_bounds: Rectangle,
+ transformation: Transformation,
) {
let paragraph = Text::Paragraph {
paragraph: paragraph.downgrade(),
position,
color,
clip_bounds,
- transformation: self.transformations.last().copied().unwrap(),
+ transformation,
};
- self.pending_text[self.current].push(paragraph);
+ self.pending_text.push(paragraph);
}
pub fn draw_editor(
@@ -98,16 +73,17 @@ impl Stack {
position: Point,
color: Color,
clip_bounds: Rectangle,
+ transformation: Transformation,
) {
let editor = Text::Editor {
editor: editor.downgrade(),
position,
color,
clip_bounds,
- transformation: self.transformation(),
+ transformation,
};
- self.pending_text[self.current].push(editor);
+ self.pending_text.push(editor);
}
pub fn draw_text(
@@ -116,9 +92,8 @@ impl Stack {
position: Point,
color: Color,
clip_bounds: Rectangle,
+ transformation: Transformation,
) {
- let transformation = self.transformation();
-
let text = Text::Cached {
content: text.content,
bounds: Rectangle::new(position, text.bounds) * transformation,
@@ -133,7 +108,7 @@ impl Stack {
clip_bounds: clip_bounds * transformation,
};
- self.pending_text[self.current].push(text);
+ self.pending_text.push(text);
}
pub fn draw_image(
@@ -141,14 +116,15 @@ impl Stack {
handle: crate::core::image::Handle,
filter_method: crate::core::image::FilterMethod,
bounds: Rectangle,
+ transformation: Transformation,
) {
let image = Image::Raster {
handle,
filter_method,
- bounds: bounds * self.transformation(),
+ bounds: bounds * transformation,
};
- self.layers[self.current].images.push(image);
+ self.images.push(image);
}
pub fn draw_svg(
@@ -156,72 +132,87 @@ impl Stack {
handle: crate::core::svg::Handle,
color: Option<Color>,
bounds: Rectangle,
+ transformation: Transformation,
) {
let svg = Image::Vector {
handle,
color,
- bounds: bounds * self.transformation(),
+ bounds: bounds * transformation,
};
- self.layers[self.current].images.push(svg);
+ self.images.push(svg);
}
- pub fn draw_mesh(&mut self, mut mesh: Mesh) {
+ pub fn draw_mesh(
+ &mut self,
+ mut mesh: Mesh,
+ transformation: Transformation,
+ ) {
match &mut mesh {
- Mesh::Solid { transformation, .. }
- | Mesh::Gradient { transformation, .. } => {
- *transformation = *transformation * self.transformation();
+ Mesh::Solid {
+ transformation: local_transformation,
+ ..
+ }
+ | Mesh::Gradient {
+ transformation: local_transformation,
+ ..
+ } => {
+ *local_transformation = *local_transformation * transformation;
}
}
- self.pending_meshes[self.current].push(mesh);
+ self.pending_meshes.push(mesh);
}
- pub fn draw_mesh_group(&mut self, meshes: Vec<Mesh>) {
- self.flush_pending();
-
- let transformation = self.transformation();
+ pub fn draw_mesh_group(
+ &mut self,
+ meshes: Vec<Mesh>,
+ transformation: Transformation,
+ ) {
+ self.flush_meshes();
- self.layers[self.current]
- .triangles
- .push(triangle::Item::Group {
- transformation,
- meshes,
- });
+ self.triangles.push(triangle::Item::Group {
+ meshes,
+ transformation,
+ });
}
- pub fn draw_mesh_cache(&mut self, cache: triangle::Cache) {
- self.flush_pending();
-
- let transformation = self.transformation();
+ pub fn draw_mesh_cache(
+ &mut self,
+ cache: triangle::Cache,
+ transformation: Transformation,
+ ) {
+ self.flush_meshes();
- self.layers[self.current]
- .triangles
- .push(triangle::Item::Cached {
- transformation,
- cache,
- });
+ self.triangles.push(triangle::Item::Cached {
+ cache,
+ transformation,
+ });
}
- pub fn draw_text_group(&mut self, text: Vec<Text>) {
- self.flush_pending();
-
- let transformation = self.transformation();
+ pub fn draw_text_group(
+ &mut self,
+ text: Vec<Text>,
+ transformation: Transformation,
+ ) {
+ self.flush_text();
- self.layers[self.current].text.push(text::Item::Group {
- transformation,
+ self.text.push(text::Item::Group {
text,
+ transformation,
});
}
- pub fn draw_text_cache(&mut self, cache: text::Cache) {
- self.flush_pending();
-
- let transformation = self.transformation();
+ pub fn draw_text_cache(
+ &mut self,
+ cache: text::Cache,
+ transformation: Transformation,
+ ) {
+ self.flush_text();
- self.layers[self.current].text.push(text::Item::Cached {
- transformation,
+ self.text.push(text::Item::Cached {
cache,
+ transformation,
});
}
@@ -229,112 +220,74 @@ impl Stack {
&mut self,
bounds: Rectangle,
primitive: Box<dyn Primitive>,
+ transformation: Transformation,
) {
- let bounds = bounds * self.transformation();
+ let bounds = bounds * transformation;
- self.layers[self.current]
- .primitives
+ self.primitives
.push(primitive::Instance { bounds, primitive });
}
- 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(Layer {
- bounds,
- ..Layer::default()
+ fn flush_meshes(&mut self) {
+ if !self.pending_meshes.is_empty() {
+ self.triangles.push(triangle::Item::Group {
+ transformation: Transformation::IDENTITY,
+ meshes: self.pending_meshes.drain(..).collect(),
});
- self.pending_meshes.push(Vec::new());
- self.pending_text.push(Vec::new());
- } else {
- self.layers[self.current].bounds = bounds;
}
}
- pub fn pop_clip(&mut self) {
- self.flush_pending();
-
- self.current = self.previous.pop().unwrap();
- }
-
- pub fn push_transformation(&mut self, transformation: Transformation) {
- self.flush_pending();
-
- self.transformations
- .push(self.transformation() * transformation);
- }
-
- pub fn pop_transformation(&mut self) {
- let _ = self.transformations.pop();
- }
-
- fn transformation(&self) -> Transformation {
- self.transformations.last().copied().unwrap()
+ fn flush_text(&mut self) {
+ if !self.pending_text.is_empty() {
+ self.text.push(text::Item::Group {
+ transformation: Transformation::IDENTITY,
+ text: self.pending_text.drain(..).collect(),
+ });
+ }
}
+}
- pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Layer> {
- self.flush_pending();
-
- self.layers[..self.active_count].iter_mut()
+impl graphics::Layer for Layer {
+ fn with_bounds(bounds: Rectangle) -> Self {
+ Self {
+ bounds,
+ ..Self::default()
+ }
}
- pub fn iter(&self) -> impl Iterator<Item = &Layer> {
- self.layers[..self.active_count].iter()
+ fn flush(&mut self) {
+ self.flush_meshes();
+ self.flush_text();
}
- pub fn clear(&mut self) {
- for (live, pending_meshes) in self.layers[..self.active_count]
- .iter_mut()
- .zip(self.pending_meshes.iter_mut())
- {
- live.bounds = Rectangle::INFINITE;
-
- live.quads.clear();
- live.triangles.clear();
- live.primitives.clear();
- live.text.clear();
- live.images.clear();
- pending_meshes.clear();
- }
-
- self.current = 0;
- self.active_count = 1;
- self.previous.clear();
+ fn resize(&mut self, bounds: Rectangle) {
+ self.bounds = bounds;
}
- // We want to keep the allocated memory
- #[allow(clippy::drain_collect)]
- fn flush_pending(&mut self) {
- let transformation = self.transformation();
-
- let pending_meshes = &mut self.pending_meshes[self.current];
- if !pending_meshes.is_empty() {
- self.layers[self.current]
- .triangles
- .push(triangle::Item::Group {
- transformation,
- meshes: pending_meshes.drain(..).collect(),
- });
- }
+ fn reset(&mut self) {
+ self.bounds = Rectangle::INFINITE;
- let pending_text = &mut self.pending_text[self.current];
- if !pending_text.is_empty() {
- self.layers[self.current].text.push(text::Item::Group {
- transformation,
- text: pending_text.drain(..).collect(),
- });
- }
+ self.quads.clear();
+ self.triangles.clear();
+ self.primitives.clear();
+ self.text.clear();
+ self.images.clear();
+ self.pending_meshes.clear();
+ self.pending_text.clear();
}
}
-impl Default for Stack {
+impl Default for Layer {
fn default() -> Self {
- Self::new()
+ Self {
+ bounds: Rectangle::INFINITE,
+ quads: quad::Batch::default(),
+ triangles: triangle::Batch::default(),
+ primitives: primitive::Batch::default(),
+ text: text::Batch::default(),
+ images: image::Batch::default(),
+ pending_meshes: Vec::new(),
+ pending_text: Vec::new(),
+ }
}
}
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index b1ae4e89..178522de 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -66,8 +66,6 @@ use crate::core::{
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport;
-use std::borrow::Cow;
-
/// A [`wgpu`] graphics renderer for [`iced`].
///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
@@ -422,7 +420,7 @@ impl core::Renderer for Renderer {
self.layers.push_clip(bounds);
}
- fn end_layer(&mut self, _bounds: Rectangle) {
+ fn end_layer(&mut self) {
self.layers.pop_clip();
}
@@ -430,7 +428,7 @@ impl core::Renderer for Renderer {
self.layers.push_transformation(transformation);
}
- fn end_transformation(&mut self, _transformation: Transformation) {
+ fn end_transformation(&mut self) {
self.layers.pop_transformation();
}
@@ -439,7 +437,8 @@ impl core::Renderer for Renderer {
quad: core::renderer::Quad,
background: impl Into<Background>,
) {
- self.layers.draw_quad(quad, background.into());
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_quad(quad, background.into(), transformation);
}
fn clear(&mut self) {
@@ -464,15 +463,6 @@ impl core::text::Renderer for Renderer {
self.default_text_size
}
- fn load_font(&mut self, font: Cow<'static, [u8]>) {
- graphics::text::font_system()
- .write()
- .expect("Write font system")
- .load_font(font);
-
- // TODO: Invalidate buffer cache
- }
-
fn fill_paragraph(
&mut self,
text: &Self::Paragraph,
@@ -480,8 +470,15 @@ impl core::text::Renderer for Renderer {
color: Color,
clip_bounds: Rectangle,
) {
- self.layers
- .draw_paragraph(text, position, color, clip_bounds);
+ let (layer, transformation) = self.layers.current_mut();
+
+ layer.draw_paragraph(
+ text,
+ position,
+ color,
+ clip_bounds,
+ transformation,
+ );
}
fn fill_editor(
@@ -491,8 +488,8 @@ impl core::text::Renderer for Renderer {
color: Color,
clip_bounds: Rectangle,
) {
- self.layers
- .draw_editor(editor, position, color, clip_bounds);
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_editor(editor, position, color, clip_bounds, transformation);
}
fn fill_text(
@@ -502,7 +499,8 @@ impl core::text::Renderer for Renderer {
color: Color,
clip_bounds: Rectangle,
) {
- self.layers.draw_text(text, position, color, clip_bounds);
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_text(text, position, color, clip_bounds, transformation);
}
}
@@ -520,7 +518,8 @@ impl core::image::Renderer for Renderer {
filter_method: core::image::FilterMethod,
bounds: Rectangle,
) {
- self.layers.draw_image(handle, filter_method, bounds);
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_image(handle, filter_method, bounds, transformation);
}
}
@@ -536,13 +535,15 @@ impl core::svg::Renderer for Renderer {
color_filter: Option<Color>,
bounds: Rectangle,
) {
- self.layers.draw_svg(handle, color_filter, bounds);
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_svg(handle, color_filter, bounds, transformation);
}
}
impl graphics::mesh::Renderer for Renderer {
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
- self.layers.draw_mesh(mesh);
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_mesh(mesh, transformation);
}
}
@@ -556,18 +557,20 @@ impl graphics::geometry::Renderer for Renderer {
}
fn draw_geometry(&mut self, geometry: Self::Geometry) {
+ let (layer, transformation) = self.layers.current_mut();
+
match geometry {
Geometry::Live { meshes, text } => {
- self.layers.draw_mesh_group(meshes);
- self.layers.draw_text_group(text);
+ layer.draw_mesh_group(meshes, transformation);
+ layer.draw_text_group(text, transformation);
}
Geometry::Cached(cache) => {
if let Some(meshes) = cache.meshes {
- self.layers.draw_mesh_cache(meshes);
+ layer.draw_mesh_cache(meshes, transformation);
}
if let Some(text) = cache.text {
- self.layers.draw_text_cache(text);
+ layer.draw_text_cache(text, transformation);
}
}
}
@@ -576,7 +579,8 @@ impl graphics::geometry::Renderer for Renderer {
impl primitive::Renderer for Renderer {
fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) {
- self.layers.draw_primitive(bounds, Box::new(primitive));
+ let (layer, transformation) = self.layers.current_mut();
+ layer.draw_primitive(bounds, Box::new(primitive), transformation);
}
}
diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs
index 4ba1ed9a..1313e752 100644
--- a/wgpu/src/primitive.rs
+++ b/wgpu/src/primitive.rs
@@ -43,7 +43,7 @@ pub struct Instance {
}
impl Instance {
- /// Creates a new [`Pipeline`] with the given [`Primitive`].
+ /// Creates a new [`Instance`] with the given [`Primitive`].
pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self {
Instance {
bounds,
@@ -80,7 +80,7 @@ impl Storage {
self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| {
pipeline
.downcast_ref::<T>()
- .expect("Pipeline with this type does not exist in Storage.")
+ .expect("Value with this type does not exist in Storage.")
})
}
@@ -89,7 +89,7 @@ impl Storage {
self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| {
pipeline
.downcast_mut::<T>()
- .expect("Pipeline with this type does not exist in Storage.")
+ .expect("Value with this type does not exist in Storage.")
})
}
}
diff --git a/winit/src/application.rs b/winit/src/application.rs
index d68523fa..1ca80609 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -220,13 +220,11 @@ where
};
}
- let compositor = C::new(graphics_settings, window.clone()).await?;
- let mut renderer = compositor.create_renderer();
+ let mut compositor = C::new(graphics_settings, window.clone()).await?;
+ let renderer = compositor.create_renderer();
for font in settings.fonts {
- use crate::core::text::Renderer;
-
- renderer.load_font(font);
+ compositor.load_font(font);
}
let (mut event_sender, event_receiver) = mpsc::unbounded();
@@ -950,10 +948,8 @@ pub fn run_command<A, C, E>(
*cache = current_cache;
}
command::Action::LoadFont { bytes, tagger } => {
- use crate::core::text::Renderer;
-
// TODO: Error handling (?)
- renderer.load_font(bytes);
+ compositor.load_font(bytes);
proxy
.send_event(tagger(Ok(())))
diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs
index e17cc180..3537ac18 100644
--- a/winit/src/multi_window.rs
+++ b/winit/src/multi_window.rs
@@ -1194,13 +1194,8 @@ fn run_command<A, C, E>(
uis.drain().map(|(id, ui)| (id, ui.into_cache())).collect();
}
command::Action::LoadFont { bytes, tagger } => {
- use crate::core::text::Renderer;
-
- // TODO change this once we change each renderer to having a single backend reference.. :pain:
// TODO: Error handling (?)
- for (_, window) in window_manager.iter_mut() {
- window.renderer.load_font(bytes.clone());
- }
+ compositor.load_font(bytes.clone());
proxy
.send_event(tagger(Ok(())))