Port iced_tiny_skia to new layering architecture
This commit is contained in:
parent
2c6fd9ac14
commit
6ad5bb3597
28 changed files with 1948 additions and 1935 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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 = ();
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -1,257 +0,0 @@
|
|||
//! Track and compute the damage of graphical primitives.
|
||||
use crate::core::alignment;
|
||||
use crate::core::{Rectangle, Size};
|
||||
use crate::Primitive;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A type that has some damage bounds.
|
||||
pub trait Damage: PartialEq {
|
||||
/// Returns the bounds of the [`Damage`].
|
||||
fn bounds(&self) -> Rectangle;
|
||||
}
|
||||
|
||||
impl<T: Damage> Damage for Primitive<T> {
|
||||
fn bounds(&self) -> Rectangle {
|
||||
match self {
|
||||
Self::Text {
|
||||
bounds,
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
..
|
||||
} => {
|
||||
let mut bounds = *bounds;
|
||||
|
||||
bounds.x = match horizontal_alignment {
|
||||
alignment::Horizontal::Left => bounds.x,
|
||||
alignment::Horizontal::Center => {
|
||||
bounds.x - bounds.width / 2.0
|
||||
}
|
||||
alignment::Horizontal::Right => bounds.x - bounds.width,
|
||||
};
|
||||
|
||||
bounds.y = match vertical_alignment {
|
||||
alignment::Vertical::Top => bounds.y,
|
||||
alignment::Vertical::Center => {
|
||||
bounds.y - bounds.height / 2.0
|
||||
}
|
||||
alignment::Vertical::Bottom => bounds.y - bounds.height,
|
||||
};
|
||||
|
||||
bounds.expand(1.5)
|
||||
}
|
||||
Self::Paragraph {
|
||||
paragraph,
|
||||
position,
|
||||
..
|
||||
} => {
|
||||
let mut bounds =
|
||||
Rectangle::new(*position, paragraph.min_bounds);
|
||||
|
||||
bounds.x = match paragraph.horizontal_alignment {
|
||||
alignment::Horizontal::Left => bounds.x,
|
||||
alignment::Horizontal::Center => {
|
||||
bounds.x - bounds.width / 2.0
|
||||
}
|
||||
alignment::Horizontal::Right => bounds.x - bounds.width,
|
||||
};
|
||||
|
||||
bounds.y = match paragraph.vertical_alignment {
|
||||
alignment::Vertical::Top => bounds.y,
|
||||
alignment::Vertical::Center => {
|
||||
bounds.y - bounds.height / 2.0
|
||||
}
|
||||
alignment::Vertical::Bottom => bounds.y - bounds.height,
|
||||
};
|
||||
|
||||
bounds.expand(1.5)
|
||||
}
|
||||
Self::Editor {
|
||||
editor, position, ..
|
||||
} => {
|
||||
let bounds = Rectangle::new(*position, editor.bounds);
|
||||
|
||||
bounds.expand(1.5)
|
||||
}
|
||||
Self::RawText(raw) => {
|
||||
// TODO: Add `size` field to `raw` to compute more accurate
|
||||
// damage bounds (?)
|
||||
raw.clip_bounds.expand(1.5)
|
||||
}
|
||||
Self::Quad { bounds, shadow, .. } if shadow.color.a > 0.0 => {
|
||||
let bounds_with_shadow = Rectangle {
|
||||
x: bounds.x + shadow.offset.x.min(0.0) - shadow.blur_radius,
|
||||
y: bounds.y + shadow.offset.y.min(0.0) - shadow.blur_radius,
|
||||
width: bounds.width
|
||||
+ shadow.offset.x.abs()
|
||||
+ shadow.blur_radius * 2.0,
|
||||
height: bounds.height
|
||||
+ shadow.offset.y.abs()
|
||||
+ shadow.blur_radius * 2.0,
|
||||
};
|
||||
|
||||
bounds_with_shadow.expand(1.0)
|
||||
}
|
||||
Self::Quad { bounds, .. }
|
||||
| Self::Image { bounds, .. }
|
||||
| Self::Svg { bounds, .. } => bounds.expand(1.0),
|
||||
Self::Clip { bounds, .. } => bounds.expand(1.0),
|
||||
Self::Group { primitives } => primitives
|
||||
.iter()
|
||||
.map(Self::bounds)
|
||||
.fold(Rectangle::with_size(Size::ZERO), |a, b| {
|
||||
Rectangle::union(&a, &b)
|
||||
}),
|
||||
Self::Transform {
|
||||
transformation,
|
||||
content,
|
||||
} => content.bounds() * *transformation,
|
||||
Self::Cache { content } => content.bounds(),
|
||||
Self::Custom(custom) => custom.bounds(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn regions<T: Damage>(a: &Primitive<T>, b: &Primitive<T>) -> Vec<Rectangle> {
|
||||
match (a, b) {
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: primitives_a,
|
||||
},
|
||||
Primitive::Group {
|
||||
primitives: primitives_b,
|
||||
},
|
||||
) => return list(primitives_a, primitives_b),
|
||||
(
|
||||
Primitive::Clip {
|
||||
bounds: bounds_a,
|
||||
content: content_a,
|
||||
..
|
||||
},
|
||||
Primitive::Clip {
|
||||
bounds: bounds_b,
|
||||
content: content_b,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
if bounds_a == bounds_b {
|
||||
return regions(content_a, content_b)
|
||||
.into_iter()
|
||||
.filter_map(|r| r.intersection(&bounds_a.expand(1.0)))
|
||||
.collect();
|
||||
} else {
|
||||
return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)];
|
||||
}
|
||||
}
|
||||
(
|
||||
Primitive::Transform {
|
||||
transformation: transformation_a,
|
||||
content: content_a,
|
||||
},
|
||||
Primitive::Transform {
|
||||
transformation: transformation_b,
|
||||
content: content_b,
|
||||
},
|
||||
) => {
|
||||
if transformation_a == transformation_b {
|
||||
return regions(content_a, content_b)
|
||||
.into_iter()
|
||||
.map(|r| r * *transformation_a)
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
(
|
||||
Primitive::Cache { content: content_a },
|
||||
Primitive::Cache { content: content_b },
|
||||
) => {
|
||||
if Arc::ptr_eq(content_a, content_b) {
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
_ if a == b => return vec![],
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let bounds_a = a.bounds();
|
||||
let bounds_b = b.bounds();
|
||||
|
||||
if bounds_a == bounds_b {
|
||||
vec![bounds_a]
|
||||
} else {
|
||||
vec![bounds_a, bounds_b]
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the damage regions between the two given lists of primitives.
|
||||
pub fn list<T: Damage>(
|
||||
previous: &[Primitive<T>],
|
||||
current: &[Primitive<T>],
|
||||
) -> Vec<Rectangle> {
|
||||
let damage = previous
|
||||
.iter()
|
||||
.zip(current)
|
||||
.flat_map(|(a, b)| regions(a, b));
|
||||
|
||||
if previous.len() == current.len() {
|
||||
damage.collect()
|
||||
} else {
|
||||
let (smaller, bigger) = if previous.len() < current.len() {
|
||||
(previous, current)
|
||||
} else {
|
||||
(current, previous)
|
||||
};
|
||||
|
||||
// Extend damage by the added/removed primitives
|
||||
damage
|
||||
.chain(bigger[smaller.len()..].iter().map(Damage::bounds))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Groups the given damage regions that are close together inside the given
|
||||
/// bounds.
|
||||
pub fn group(
|
||||
mut damage: Vec<Rectangle>,
|
||||
scale_factor: f32,
|
||||
bounds: Size<u32>,
|
||||
) -> Vec<Rectangle> {
|
||||
use std::cmp::Ordering;
|
||||
|
||||
const AREA_THRESHOLD: f32 = 20_000.0;
|
||||
|
||||
let bounds = Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: bounds.width as f32,
|
||||
height: bounds.height as f32,
|
||||
};
|
||||
|
||||
damage.sort_by(|a, b| {
|
||||
a.x.partial_cmp(&b.x)
|
||||
.unwrap_or(Ordering::Equal)
|
||||
.then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal))
|
||||
});
|
||||
|
||||
let mut output = Vec::new();
|
||||
let mut scaled = damage
|
||||
.into_iter()
|
||||
.filter_map(|region| (region * scale_factor).intersection(&bounds))
|
||||
.filter(|region| region.width >= 1.0 && region.height >= 1.0);
|
||||
|
||||
if let Some(mut current) = scaled.next() {
|
||||
for region in scaled {
|
||||
let union = current.union(®ion);
|
||||
|
||||
if union.area() - current.area() - region.area() <= AREA_THRESHOLD {
|
||||
current = union;
|
||||
} else {
|
||||
output.push(current);
|
||||
current = region;
|
||||
}
|
||||
}
|
||||
|
||||
output.push(current);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
139
graphics/src/layer.rs
Normal file
139
graphics/src/layer.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
831
tiny_skia/src/engine.rs
Normal file
831
tiny_skia/src/engine.rs
Normal file
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
|
@ -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 {
|
||||
size,
|
||||
transform: tiny_skia::Transform::identity(),
|
||||
stack: Vec::new(),
|
||||
primitives: Vec::new(),
|
||||
}
|
||||
Self::with_clip(Rectangle::with_size(size))
|
||||
}
|
||||
|
||||
pub fn into_primitive(self) -> Primitive {
|
||||
Primitive::Clip {
|
||||
bounds: Rectangle::new(Point::ORIGIN, self.size),
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: self.primitives,
|
||||
}),
|
||||
pub fn with_clip(clip_bounds: Rectangle) -> Self {
|
||||
Self {
|
||||
clip_bounds,
|
||||
stack: Vec::new(),
|
||||
primitives: Vec::new(),
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
243
tiny_skia/src/layer.rs
Normal file
243
tiny_skia/src/layer.rs
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 ®ion 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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: ¶graph::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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
pub fn draw_mesh_group(
|
||||
&mut self,
|
||||
meshes: Vec<Mesh>,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
self.flush_meshes();
|
||||
|
||||
let transformation = self.transformation();
|
||||
|
||||
self.layers[self.current]
|
||||
.triangles
|
||||
.push(triangle::Item::Group {
|
||||
transformation,
|
||||
self.triangles.push(triangle::Item::Group {
|
||||
meshes,
|
||||
transformation,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw_mesh_cache(&mut self, cache: triangle::Cache) {
|
||||
self.flush_pending();
|
||||
pub fn draw_mesh_cache(
|
||||
&mut self,
|
||||
cache: triangle::Cache,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
self.flush_meshes();
|
||||
|
||||
let transformation = self.transformation();
|
||||
|
||||
self.layers[self.current]
|
||||
.triangles
|
||||
.push(triangle::Item::Cached {
|
||||
transformation,
|
||||
self.triangles.push(triangle::Item::Cached {
|
||||
cache,
|
||||
transformation,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw_text_group(&mut self, text: Vec<Text>) {
|
||||
self.flush_pending();
|
||||
pub fn draw_text_group(
|
||||
&mut self,
|
||||
text: Vec<Text>,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
self.flush_text();
|
||||
|
||||
let transformation = self.transformation();
|
||||
|
||||
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();
|
||||
pub fn draw_text_cache(
|
||||
&mut self,
|
||||
cache: text::Cache,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
self.flush_text();
|
||||
|
||||
let transformation = self.transformation();
|
||||
|
||||
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);
|
||||
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.current = self.active_count;
|
||||
self.active_count += 1;
|
||||
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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let bounds = bounds * self.transformation();
|
||||
|
||||
if self.current == self.layers.len() {
|
||||
self.layers.push(Layer {
|
||||
impl graphics::Layer for Layer {
|
||||
fn with_bounds(bounds: Rectangle) -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
..Layer::default()
|
||||
});
|
||||
self.pending_meshes.push(Vec::new());
|
||||
self.pending_text.push(Vec::new());
|
||||
} else {
|
||||
self.layers[self.current].bounds = bounds;
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_clip(&mut self) {
|
||||
self.flush_pending();
|
||||
|
||||
self.current = self.previous.pop().unwrap();
|
||||
fn flush(&mut self) {
|
||||
self.flush_meshes();
|
||||
self.flush_text();
|
||||
}
|
||||
|
||||
pub fn push_transformation(&mut self, transformation: Transformation) {
|
||||
self.flush_pending();
|
||||
|
||||
self.transformations
|
||||
.push(self.transformation() * transformation);
|
||||
fn resize(&mut self, bounds: Rectangle) {
|
||||
self.bounds = bounds;
|
||||
}
|
||||
|
||||
pub fn pop_transformation(&mut self) {
|
||||
let _ = self.transformations.pop();
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.bounds = Rectangle::INFINITE;
|
||||
|
||||
fn transformation(&self) -> Transformation {
|
||||
self.transformations.last().copied().unwrap()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Layer> {
|
||||
self.flush_pending();
|
||||
|
||||
self.layers[..self.active_count].iter_mut()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Layer> {
|
||||
self.layers[..self.active_count].iter()
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// 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(),
|
||||
});
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())))
|
||||
|
|
|
|||
|
|
@ -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(())))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue