Port iced_tiny_skia to new layering architecture

This commit is contained in:
Héctor Ramón Jiménez 2024-04-09 22:25:16 +02:00
parent 2c6fd9ac14
commit 6ad5bb3597
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
28 changed files with 1948 additions and 1935 deletions

View file

@ -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>;
}

View file

@ -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 = ();

View file

@ -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"),

View file

@ -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
}

139
graphics/src/layer.rs Normal file
View file

@ -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()
}
}

View file

@ -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;

View file

@ -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),
}
}
}

View file

@ -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"),

View file

@ -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)]