Merge branch 'master' into beacon

This commit is contained in:
Héctor Ramón Jiménez 2024-05-09 12:32:25 +02:00
commit aaf396256e
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
284 changed files with 18747 additions and 15450 deletions

View file

@ -10,6 +10,9 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lints]
workspace = true
[features]
image = ["iced_graphics/image"]
svg = ["resvg"]
@ -25,7 +28,6 @@ log.workspace = true
rustc-hash.workspace = true
softbuffer.workspace = true
tiny-skia.workspace = true
xxhash-rust.workspace = true
resvg.workspace = true
resvg.optional = true

View file

@ -1,972 +0,0 @@
use crate::core::{
Background, Color, Gradient, Rectangle, Size, Transformation, Vector,
};
use crate::graphics::backend;
use crate::graphics::text;
use crate::graphics::{Damage, Viewport};
use crate::primitive::{self, Primitive};
use std::borrow::Cow;
pub struct Backend {
text_pipeline: crate::text::Pipeline,
#[cfg(feature = "image")]
raster_pipeline: crate::raster::Pipeline,
#[cfg(feature = "svg")]
vector_pipeline: crate::vector::Pipeline,
}
impl Backend {
pub fn new() -> Self {
Self {
text_pipeline: crate::text::Pipeline::new(),
#[cfg(feature = "image")]
raster_pipeline: crate::raster::Pipeline::new(),
#[cfg(feature = "svg")]
vector_pipeline: crate::vector::Pipeline::new(),
}
}
pub fn draw(
&mut self,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
primitives: &[Primitive],
viewport: &Viewport,
damage: &[Rectangle],
background_color: Color,
) {
let scale_factor = viewport.scale_factor() as f32;
for &region in damage {
let path = tiny_skia::PathBuilder::from_rect(
tiny_skia::Rect::from_xywh(
region.x,
region.y,
region.width,
region.height,
)
.expect("Create damage rectangle"),
);
pixels.fill_path(
&path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(into_color(
background_color,
)),
anti_alias: false,
blend_mode: tiny_skia::BlendMode::Source,
..Default::default()
},
tiny_skia::FillRule::default(),
tiny_skia::Transform::identity(),
None,
);
adjust_clip_mask(clip_mask, region);
for primitive in primitives {
self.draw_primitive(
primitive,
pixels,
clip_mask,
region,
scale_factor,
Transformation::IDENTITY,
);
}
}
self.text_pipeline.trim_cache();
#[cfg(feature = "image")]
self.raster_pipeline.trim_cache();
#[cfg(feature = "svg")]
self.vector_pipeline.trim_cache();
}
fn draw_primitive(
&mut self,
primitive: &Primitive,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
clip_bounds: Rectangle,
scale_factor: f32,
transformation: Transformation,
) {
match primitive {
Primitive::Quad {
bounds,
background,
border,
shadow,
} => {
debug_assert!(
bounds.width.is_normal(),
"Quad with non-normal width!"
);
debug_assert!(
bounds.height.is_normal(),
"Quad with non-normal height!"
);
let physical_bounds = (*bounds * transformation) * scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
let transform = into_transform(transformation)
.post_scale(scale_factor, scale_factor);
// Make sure the border radius is not larger than the bounds
let border_width = border
.width
.min(bounds.width / 2.0)
.min(bounds.height / 2.0);
let mut fill_border_radius = <[f32; 4]>::from(border.radius);
for radius in &mut fill_border_radius {
*radius = (*radius)
.min(bounds.width / 2.0)
.min(bounds.height / 2.0);
}
let path = rounded_rectangle(*bounds, fill_border_radius);
if shadow.color.a > 0.0 {
let shadow_bounds = (Rectangle {
x: bounds.x + shadow.offset.x - shadow.blur_radius,
y: bounds.y + shadow.offset.y - shadow.blur_radius,
width: bounds.width + shadow.blur_radius * 2.0,
height: bounds.height + shadow.blur_radius * 2.0,
} * transformation)
* scale_factor;
let radii = fill_border_radius
.into_iter()
.map(|radius| radius * scale_factor)
.collect::<Vec<_>>();
let (x, y, width, height) = (
shadow_bounds.x as u32,
shadow_bounds.y as u32,
shadow_bounds.width as u32,
shadow_bounds.height as u32,
);
let half_width = physical_bounds.width / 2.0;
let half_height = physical_bounds.height / 2.0;
let colors = (y..y + height)
.flat_map(|y| {
(x..x + width).map(move |x| (x as f32, y as f32))
})
.filter_map(|(x, y)| {
tiny_skia::Size::from_wh(half_width, half_height)
.map(|size| {
let shadow_distance = rounded_box_sdf(
Vector::new(
x - physical_bounds.position().x
- (shadow.offset.x
* scale_factor)
- half_width,
y - physical_bounds.position().y
- (shadow.offset.y
* scale_factor)
- half_height,
),
size,
&radii,
);
let shadow_alpha = 1.0
- smoothstep(
-shadow.blur_radius * scale_factor,
shadow.blur_radius * scale_factor,
shadow_distance,
);
let mut color = into_color(shadow.color);
color.apply_opacity(shadow_alpha);
color.to_color_u8().premultiply()
})
})
.collect();
if let Some(pixmap) = tiny_skia::IntSize::from_wh(
width, height,
)
.and_then(|size| {
tiny_skia::Pixmap::from_vec(
bytemuck::cast_vec(colors),
size,
)
}) {
pixels.draw_pixmap(
x as i32,
y as i32,
pixmap.as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::default(),
None,
);
}
}
pixels.fill_path(
&path,
&tiny_skia::Paint {
shader: match background {
Background::Color(color) => {
tiny_skia::Shader::SolidColor(into_color(
*color,
))
}
Background::Gradient(Gradient::Linear(linear)) => {
let (start, end) =
linear.angle.to_distance(bounds);
let stops: Vec<tiny_skia::GradientStop> =
linear
.stops
.into_iter()
.flatten()
.map(|stop| {
tiny_skia::GradientStop::new(
stop.offset,
tiny_skia::Color::from_rgba(
stop.color.b,
stop.color.g,
stop.color.r,
stop.color.a,
)
.expect("Create color"),
)
})
.collect();
tiny_skia::LinearGradient::new(
tiny_skia::Point {
x: start.x,
y: start.y,
},
tiny_skia::Point { x: end.x, y: end.y },
if stops.is_empty() {
vec![tiny_skia::GradientStop::new(
0.0,
tiny_skia::Color::BLACK,
)]
} else {
stops
},
tiny_skia::SpreadMode::Pad,
tiny_skia::Transform::identity(),
)
.expect("Create linear gradient")
}
},
anti_alias: true,
..tiny_skia::Paint::default()
},
tiny_skia::FillRule::EvenOdd,
transform,
clip_mask,
);
if border_width > 0.0 {
// Border path is offset by half the border width
let border_bounds = Rectangle {
x: bounds.x + border_width / 2.0,
y: bounds.y + border_width / 2.0,
width: bounds.width - border_width,
height: bounds.height - border_width,
};
// Make sure the border radius is correct
let mut border_radius = <[f32; 4]>::from(border.radius);
let mut is_simple_border = true;
for radius in &mut border_radius {
*radius = if *radius == 0.0 {
// Path should handle this fine
0.0
} else if *radius > border_width / 2.0 {
*radius - border_width / 2.0
} else {
is_simple_border = false;
0.0
}
.min(border_bounds.width / 2.0)
.min(border_bounds.height / 2.0);
}
// Stroking a path works well in this case
if is_simple_border {
let border_path =
rounded_rectangle(border_bounds, border_radius);
pixels.stroke_path(
&border_path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(
into_color(border.color),
),
anti_alias: true,
..tiny_skia::Paint::default()
},
&tiny_skia::Stroke {
width: border_width,
..tiny_skia::Stroke::default()
},
transform,
clip_mask,
);
} else {
// Draw corners that have too small border radii as having no border radius,
// but mask them with the rounded rectangle with the correct border radius.
let mut temp_pixmap = tiny_skia::Pixmap::new(
bounds.width as u32,
bounds.height as u32,
)
.unwrap();
let mut quad_mask = tiny_skia::Mask::new(
bounds.width as u32,
bounds.height as u32,
)
.unwrap();
let zero_bounds = Rectangle {
x: 0.0,
y: 0.0,
width: bounds.width,
height: bounds.height,
};
let path =
rounded_rectangle(zero_bounds, fill_border_radius);
quad_mask.fill_path(
&path,
tiny_skia::FillRule::EvenOdd,
true,
transform,
);
let path_bounds = Rectangle {
x: border_width / 2.0,
y: border_width / 2.0,
width: bounds.width - border_width,
height: bounds.height - border_width,
};
let border_radius_path =
rounded_rectangle(path_bounds, border_radius);
temp_pixmap.stroke_path(
&border_radius_path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(
into_color(border.color),
),
anti_alias: true,
..tiny_skia::Paint::default()
},
&tiny_skia::Stroke {
width: border_width,
..tiny_skia::Stroke::default()
},
transform,
Some(&quad_mask),
);
pixels.draw_pixmap(
bounds.x as i32,
bounds.y as i32,
temp_pixmap.as_ref(),
&tiny_skia::PixmapPaint::default(),
transform,
clip_mask,
);
}
}
}
Primitive::Paragraph {
paragraph,
position,
color,
clip_bounds: _, // TODO: Support text clip bounds
} => {
let physical_bounds =
Rectangle::new(*position, paragraph.min_bounds)
* transformation
* scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.text_pipeline.draw_paragraph(
paragraph,
*position,
*color,
scale_factor,
pixels,
clip_mask,
transformation,
);
}
Primitive::Editor {
editor,
position,
color,
clip_bounds: _, // TODO: Support text clip bounds
} => {
let physical_bounds = Rectangle::new(*position, editor.bounds)
* transformation
* scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.text_pipeline.draw_editor(
editor,
*position,
*color,
scale_factor,
pixels,
clip_mask,
transformation,
);
}
Primitive::Text {
content,
bounds,
color,
size,
line_height,
font,
horizontal_alignment,
vertical_alignment,
shaping,
clip_bounds: _, // TODO: Support text clip bounds
} => {
let physical_bounds =
primitive.bounds() * transformation * scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.text_pipeline.draw_cached(
content,
*bounds,
*color,
*size,
*line_height,
*font,
*horizontal_alignment,
*vertical_alignment,
*shaping,
scale_factor,
pixels,
clip_mask,
transformation,
);
}
Primitive::RawText(text::Raw {
buffer,
position,
color,
clip_bounds: _, // TODO: Support text clip bounds
}) => {
let Some(buffer) = buffer.upgrade() else {
return;
};
let (width, height) = buffer.size();
let physical_bounds =
Rectangle::new(*position, Size::new(width, height))
* transformation
* scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.text_pipeline.draw_raw(
&buffer,
*position,
*color,
scale_factor,
pixels,
clip_mask,
transformation,
);
}
#[cfg(feature = "image")]
Primitive::Image {
handle,
filter_method,
bounds,
} => {
let physical_bounds = (*bounds * transformation) * scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
let transform = into_transform(transformation)
.post_scale(scale_factor, scale_factor);
self.raster_pipeline.draw(
handle,
*filter_method,
*bounds,
pixels,
transform,
clip_mask,
);
}
#[cfg(not(feature = "image"))]
Primitive::Image { .. } => {
log::warn!(
"Unsupported primitive in `iced_tiny_skia`: {primitive:?}",
);
}
#[cfg(feature = "svg")]
Primitive::Svg {
handle,
bounds,
color,
} => {
let physical_bounds = (*bounds * transformation) * scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.vector_pipeline.draw(
handle,
*color,
(*bounds * transformation) * scale_factor,
pixels,
clip_mask,
);
}
#[cfg(not(feature = "svg"))]
Primitive::Svg { .. } => {
log::warn!(
"Unsupported primitive in `iced_tiny_skia`: {primitive:?}",
);
}
Primitive::Custom(primitive::Custom::Fill {
path,
paint,
rule,
}) => {
let bounds = path.bounds();
let physical_bounds = (Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
} * transformation)
* scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
pixels.fill_path(
path,
paint,
*rule,
into_transform(transformation)
.post_scale(scale_factor, scale_factor),
clip_mask,
);
}
Primitive::Custom(primitive::Custom::Stroke {
path,
paint,
stroke,
}) => {
let bounds = path.bounds();
let physical_bounds = (Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width().max(1.0),
height: bounds.height().max(1.0),
} * transformation)
* scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
pixels.stroke_path(
path,
paint,
stroke,
into_transform(transformation)
.post_scale(scale_factor, scale_factor),
clip_mask,
);
}
Primitive::Group { primitives } => {
for primitive in primitives {
self.draw_primitive(
primitive,
pixels,
clip_mask,
clip_bounds,
scale_factor,
transformation,
);
}
}
Primitive::Transform {
transformation: new_transformation,
content,
} => {
self.draw_primitive(
content,
pixels,
clip_mask,
clip_bounds,
scale_factor,
transformation * *new_transformation,
);
}
Primitive::Clip { bounds, content } => {
let bounds = (*bounds * transformation) * scale_factor;
if bounds == clip_bounds {
self.draw_primitive(
content,
pixels,
clip_mask,
bounds,
scale_factor,
transformation,
);
} else if let Some(bounds) = clip_bounds.intersection(&bounds) {
if bounds.x + bounds.width <= 0.0
|| bounds.y + bounds.height <= 0.0
|| bounds.x as u32 >= pixels.width()
|| bounds.y as u32 >= pixels.height()
|| bounds.width <= 1.0
|| bounds.height <= 1.0
{
return;
}
adjust_clip_mask(clip_mask, bounds);
self.draw_primitive(
content,
pixels,
clip_mask,
bounds,
scale_factor,
transformation,
);
adjust_clip_mask(clip_mask, clip_bounds);
}
}
Primitive::Cache { content } => {
self.draw_primitive(
content,
pixels,
clip_mask,
clip_bounds,
scale_factor,
transformation,
);
}
}
}
}
impl Default for Backend {
fn default() -> Self {
Self::new()
}
}
fn into_color(color: Color) -> tiny_skia::Color {
tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
.expect("Convert color from iced to tiny_skia")
}
fn into_transform(transformation: Transformation) -> tiny_skia::Transform {
let translation = transformation.translation();
tiny_skia::Transform {
sx: transformation.scale_factor(),
kx: 0.0,
ky: 0.0,
sy: transformation.scale_factor(),
tx: translation.x,
ty: translation.y,
}
}
fn rounded_rectangle(
bounds: Rectangle,
border_radius: [f32; 4],
) -> tiny_skia::Path {
let [top_left, top_right, bottom_right, bottom_left] = border_radius;
if top_left == 0.0
&& top_right == 0.0
&& bottom_right == 0.0
&& bottom_left == 0.0
{
return tiny_skia::PathBuilder::from_rect(
tiny_skia::Rect::from_xywh(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
)
.expect("Build quad rectangle"),
);
}
if top_left == top_right
&& top_left == bottom_right
&& top_left == bottom_left
&& top_left == bounds.width / 2.0
&& top_left == bounds.height / 2.0
{
return tiny_skia::PathBuilder::from_circle(
bounds.x + bounds.width / 2.0,
bounds.y + bounds.height / 2.0,
top_left,
)
.expect("Build circle path");
}
let mut builder = tiny_skia::PathBuilder::new();
builder.move_to(bounds.x + top_left, bounds.y);
builder.line_to(bounds.x + bounds.width - top_right, bounds.y);
if top_right > 0.0 {
arc_to(
&mut builder,
bounds.x + bounds.width - top_right,
bounds.y,
bounds.x + bounds.width,
bounds.y + top_right,
top_right,
);
}
maybe_line_to(
&mut builder,
bounds.x + bounds.width,
bounds.y + bounds.height - bottom_right,
);
if bottom_right > 0.0 {
arc_to(
&mut builder,
bounds.x + bounds.width,
bounds.y + bounds.height - bottom_right,
bounds.x + bounds.width - bottom_right,
bounds.y + bounds.height,
bottom_right,
);
}
maybe_line_to(
&mut builder,
bounds.x + bottom_left,
bounds.y + bounds.height,
);
if bottom_left > 0.0 {
arc_to(
&mut builder,
bounds.x + bottom_left,
bounds.y + bounds.height,
bounds.x,
bounds.y + bounds.height - bottom_left,
bottom_left,
);
}
maybe_line_to(&mut builder, bounds.x, bounds.y + top_left);
if top_left > 0.0 {
arc_to(
&mut builder,
bounds.x,
bounds.y + top_left,
bounds.x + top_left,
bounds.y,
top_left,
);
}
builder.finish().expect("Build rounded rectangle path")
}
fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) {
if path.last_point() != Some(tiny_skia::Point { x, y }) {
path.line_to(x, y);
}
}
fn arc_to(
path: &mut tiny_skia::PathBuilder,
x_from: f32,
y_from: f32,
x_to: f32,
y_to: f32,
radius: f32,
) {
let svg_arc = kurbo::SvgArc {
from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)),
to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)),
radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)),
x_rotation: 0.0,
large_arc: false,
sweep: true,
};
match kurbo::Arc::from_svg_arc(&svg_arc) {
Some(arc) => {
arc.to_cubic_beziers(0.1, |p1, p2, p| {
path.cubic_to(
p1.x as f32,
p1.y as f32,
p2.x as f32,
p2.y as f32,
p.x as f32,
p.y as f32,
);
});
}
None => {
path.line_to(x_to, y_to);
}
}
}
fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) {
clip_mask.clear();
let path = {
let mut builder = tiny_skia::PathBuilder::new();
builder.push_rect(
tiny_skia::Rect::from_xywh(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
)
.unwrap(),
);
builder.finish().unwrap()
};
clip_mask.fill_path(
&path,
tiny_skia::FillRule::EvenOdd,
false,
tiny_skia::Transform::default(),
);
}
fn smoothstep(a: f32, b: f32, x: f32) -> f32 {
let x = ((x - a) / (b - a)).clamp(0.0, 1.0);
x * x * (3.0 - 2.0 * x)
}
fn rounded_box_sdf(
to_center: Vector,
size: tiny_skia::Size,
radii: &[f32],
) -> f32 {
let radius = match (to_center.x > 0.0, to_center.y > 0.0) {
(true, true) => radii[2],
(true, false) => radii[1],
(false, true) => radii[3],
(false, false) => radii[0],
};
let x = (to_center.x.abs() - size.width() + radius).max(0.0);
let y = (to_center.y.abs() - size.height() + radius).max(0.0);
(x.powf(2.0) + y.powf(2.0)).sqrt() - radius
}
impl iced_graphics::Backend for Backend {
type Primitive = primitive::Custom;
}
impl backend::Text for Backend {
fn load_font(&mut self, font: Cow<'static, [u8]>) {
self.text_pipeline.load_font(font);
}
}
#[cfg(feature = "image")]
impl backend::Image for Backend {
fn dimensions(
&self,
handle: &crate::core::image::Handle,
) -> crate::core::Size<u32> {
self.raster_pipeline.dimensions(handle)
}
}
#[cfg(feature = "svg")]
impl backend::Svg for Backend {
fn viewport_dimensions(
&self,
handle: &crate::core::svg::Handle,
) -> crate::core::Size<u32> {
self.vector_pipeline.viewport_dimensions(handle)
}
}

856
tiny_skia/src/engine.rs Normal file
View file

@ -0,0 +1,856 @@
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: text_bounds, // TODO
} => {
let physical_bounds = *text_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,
rotation,
opacity,
} => {
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 &_);
let center = physical_bounds.center();
let radians = f32::from(*rotation);
let transform = into_transform(_transformation).post_rotate_at(
radians.to_degrees(),
center.x,
center.y,
);
self.raster_pipeline.draw(
handle,
*filter_method,
*bounds,
*opacity,
_pixels,
transform,
clip_mask,
);
}
#[cfg(feature = "svg")]
Image::Vector {
handle,
color,
bounds,
rotation,
opacity,
} => {
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 &_);
let center = physical_bounds.center();
let radians = f32::from(*rotation);
let transform = into_transform(_transformation).post_rotate_at(
radians.to_degrees(),
center.x,
center.y,
);
self.vector_pipeline.draw(
handle,
*color,
physical_bounds,
*opacity,
_pixels,
transform,
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(),
);
}

View file

@ -1,45 +1,102 @@
use crate::core::text::LineHeight;
use crate::core::{Pixels, Point, Rectangle, Size, Transformation, Vector};
use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector};
use crate::graphics::cache::{self, Cached};
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke};
use crate::graphics::geometry::{Path, Style, Text};
use crate::graphics::Gradient;
use crate::primitive::{self, Primitive};
use crate::graphics::geometry::{self, Path, Style};
use crate::graphics::{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, _group: cache::Group, _previous: Option<Cache>) -> Cache {
match self {
Self::Live {
primitives,
text,
clip_bounds,
} => Cache {
primitives: Rc::from(primitives),
text: Rc::from(text),
clip_bounds,
},
Self::Cache(cache) => cache,
}
}
}
#[derive(Debug)]
pub struct Frame {
size: Size,
clip_bounds: Rectangle,
transform: tiny_skia::Transform,
stack: Vec<tiny_skia::Transform>,
primitives: Vec<Primitive>,
text: Vec<Text>,
}
impl Frame {
pub fn new(size: Size) -> Self {
Self::with_clip(Rectangle::with_size(size))
}
pub fn with_clip(clip_bounds: Rectangle) -> Self {
Self {
size,
transform: tiny_skia::Transform::identity(),
clip_bounds,
stack: Vec::new(),
primitives: Vec::new(),
text: Vec::new(),
transform: tiny_skia::Transform::from_translate(
clip_bounds.x,
clip_bounds.y,
),
}
}
}
pub fn width(&self) -> f32 {
self.size.width
impl geometry::frame::Backend for Frame {
type Geometry = Geometry;
fn width(&self) -> f32 {
self.clip_bounds.width
}
pub fn height(&self) -> f32 {
self.size.height
fn height(&self) -> f32 {
self.clip_bounds.height
}
pub fn size(&self) -> Size {
self.size
fn size(&self) -> Size {
self.clip_bounds.size()
}
pub fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
fn center(&self) -> Point {
Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
}
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
let Some(path) =
convert_path(path).and_then(|path| path.transform(self.transform))
else {
@ -51,15 +108,14 @@ impl Frame {
let mut paint = into_paint(fill.style);
paint.shader.transform(self.transform);
self.primitives
.push(Primitive::Custom(primitive::Custom::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
}));
self.primitives.push(Primitive::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
});
}
pub fn fill_rectangle(
fn fill_rectangle(
&mut self,
top_left: Point,
size: Size,
@ -79,15 +135,14 @@ impl Frame {
};
paint.shader.transform(self.transform);
self.primitives
.push(Primitive::Custom(primitive::Custom::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
}));
self.primitives.push(Primitive::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
});
}
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
let Some(path) =
convert_path(path).and_then(|path| path.transform(self.transform))
else {
@ -100,20 +155,19 @@ impl Frame {
let mut paint = into_paint(stroke.style);
paint.shader.transform(self.transform);
self.primitives
.push(Primitive::Custom(primitive::Custom::Stroke {
path,
paint,
stroke: skia_stroke,
}));
self.primitives.push(Primitive::Stroke {
path,
paint,
stroke: skia_stroke,
});
}
pub 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();
if self.transform.is_scale_translate()
if !self.transform.has_skew()
&& scale_x == scale_y
&& scale_x > 0.0
&& scale_y > 0.0
@ -155,12 +209,12 @@ impl 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,
@ -172,50 +226,51 @@ impl Frame {
}
}
pub fn push_transform(&mut self) {
fn push_transform(&mut self) {
self.stack.push(self.transform);
}
pub fn pop_transform(&mut self) {
fn pop_transform(&mut self) {
self.transform = self.stack.pop().expect("Pop transform");
}
pub fn clip(&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 draft(&mut self, clip_bounds: Rectangle) -> Self {
Self::with_clip(clip_bounds)
}
pub fn translate(&mut self, translation: Vector) {
fn paste(&mut self, frame: Self, _at: Point) {
self.primitives.extend(frame.primitives);
self.text.extend(frame.text);
}
fn translate(&mut self, translation: Vector) {
self.transform =
self.transform.pre_translate(translation.x, translation.y);
}
pub fn rotate(&mut self, angle: f32) {
self.transform = self
.transform
.pre_concat(tiny_skia::Transform::from_rotate(angle.to_degrees()));
fn rotate(&mut self, angle: impl Into<Radians>) {
self.transform = self.transform.pre_concat(
tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()),
);
}
pub fn scale(&mut self, scale: impl Into<f32>) {
fn scale(&mut self, scale: impl Into<f32>) {
let scale = scale.into();
self.scale_nonuniform(Vector { x: scale, y: scale });
}
pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
let scale = scale.into();
self.transform = self.transform.pre_scale(scale.x, scale.y);
}
pub fn into_primitive(self) -> Primitive {
Primitive::Clip {
bounds: Rectangle::new(Point::ORIGIN, self.size),
content: Box::new(Primitive::Group {
primitives: self.primitives,
}),
fn into_geometry(self) -> Geometry {
Geometry::Live {
primitives: self.primitives,
text: self.text,
clip_bounds: self.clip_bounds,
}
}
}

341
tiny_skia/src/layer.rs Normal file
View file

@ -0,0 +1,341 @@
use crate::core::{
image, renderer::Quad, svg, Background, Color, Point, Radians, Rectangle,
Transformation,
};
use crate::graphics::damage;
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>,
}
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,
rotation: Radians,
opacity: f32,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
rotation,
opacity,
};
self.images.push(image);
}
pub fn draw_svg(
&mut self,
handle: svg::Handle,
color: Option<Color>,
bounds: Rectangle,
transformation: Transformation,
rotation: Radians,
opacity: f32,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
rotation,
opacity,
};
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,
));
}
pub fn damage(previous: &Self, current: &Self) -> Vec<Rectangle> {
if previous.bounds != current.bounds {
return vec![previous.bounds, current.bounds];
}
let mut damage = damage::list(
&previous.quads,
&current.quads,
|(quad, _)| {
quad.bounds
.expand(1.0)
.intersection(&current.bounds)
.into_iter()
.collect()
},
|(quad_a, background_a), (quad_b, background_b)| {
quad_a == quad_b && background_a == background_b
},
);
let text = damage::diff(
&previous.text,
&current.text,
|item| {
item.as_slice()
.iter()
.filter_map(Text::visible_bounds)
.map(|bounds| bounds * item.transformation())
.collect()
},
|text_a, text_b| {
damage::list(
text_a.as_slice(),
text_b.as_slice(),
|text| {
text.visible_bounds()
.into_iter()
.map(|bounds| bounds * text_a.transformation())
.collect()
},
|text_a, text_b| text_a == text_b,
)
},
);
let primitives = damage::list(
&previous.primitives,
&current.primitives,
|item| match item {
Item::Live(primitive) => vec![primitive.visible_bounds()],
Item::Group(primitives, group_bounds, transformation) => {
primitives
.as_slice()
.iter()
.map(Primitive::visible_bounds)
.map(|bounds| bounds * *transformation)
.filter_map(|bounds| bounds.intersection(group_bounds))
.collect()
}
Item::Cached(_, bounds, _) => {
vec![*bounds]
}
},
|primitive_a, primitive_b| match (primitive_a, primitive_b) {
(
Item::Cached(cache_a, bounds_a, transformation_a),
Item::Cached(cache_b, bounds_b, transformation_b),
) => {
Rc::ptr_eq(cache_a, cache_b)
&& bounds_a == bounds_b
&& transformation_a == transformation_b
}
_ => false,
},
);
let images = damage::list(
&previous.images,
&current.images,
|image| vec![image.bounds().expand(1.0)],
Image::eq,
);
damage.extend(text);
damage.extend(primitives);
damage.extend(images);
damage
}
}
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.primitives.clear();
self.text.clear();
self.images.clear();
}
}
#[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,
}
}
}

View file

@ -1,9 +1,9 @@
#![forbid(rust_2018_idioms)]
#![deny(unsafe_code, unused_results, rustdoc::broken_intra_doc_links)]
#![allow(missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod window;
mod backend;
mod engine;
mod layer;
mod primitive;
mod settings;
mod text;
@ -20,12 +20,357 @@ 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, 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) -> &[Layer] {
self.layers.flush();
self.layers.as_slice()
}
pub fn draw(
&mut self,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
viewport: &Viewport,
damage: &[Rectangle],
background_color: Color,
) {
let scale_factor = viewport.scale_factor() as f32;
self.layers.flush();
for &region in damage {
let region = region * scale_factor;
let path = tiny_skia::PathBuilder::from_rect(
tiny_skia::Rect::from_xywh(
region.x,
region.y,
region.width,
region.height,
)
.expect("Create damage rectangle"),
);
pixels.fill_path(
&path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(engine::into_color(
background_color,
)),
anti_alias: false,
blend_mode: tiny_skia::BlendMode::Source,
..Default::default()
},
tiny_skia::FillRule::default(),
tiny_skia::Transform::identity(),
None,
);
for layer in self.layers.iter() {
let Some(clip_bounds) =
region.intersection(&(layer.bounds * scale_factor))
else {
continue;
};
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.primitives {
let Some(new_clip_bounds) = (group.clip_bounds()
* scale_factor)
.intersection(&clip_bounds)
else {
continue;
};
engine::adjust_clip_mask(clip_mask, new_clip_bounds);
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 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 image in &layer.images {
self.engine.draw_image(
image,
Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
}
}
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) -> crate::core::Size<u32> {
self.engine.raster_pipeline.dimensions(handle)
}
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
rotation: core::Radians,
opacity: f32,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_image(
handle,
filter_method,
bounds,
transformation,
rotation,
opacity,
);
}
}
#[cfg(feature = "svg")]
impl core::svg::Renderer for Renderer {
fn measure_svg(
&self,
handle: &core::svg::Handle,
) -> crate::core::Size<u32> {
self.engine.vector_pipeline.viewport_dimensions(handle)
}
fn draw_svg(
&mut self,
handle: core::svg::Handle,
color: Option<Color>,
bounds: Rectangle,
rotation: core::Radians,
opacity: f32,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_svg(
handle,
color,
bounds,
transformation,
rotation,
opacity,
);
}
}
impl compositor::Default for Renderer {
type Compositor = window::Compositor;
}

View file

@ -1,10 +1,7 @@
use crate::core::Rectangle;
use crate::graphics::Damage;
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.
@ -25,20 +22,19 @@ pub enum Custom {
},
}
impl Damage for Custom {
fn bounds(&self) -> Rectangle {
match self {
Self::Fill { path, .. } | Self::Stroke { path, .. } => {
let bounds = path.bounds();
impl Primitive {
/// Returns the visible bounds of the [`Primitive`].
pub fn visible_bounds(&self) -> Rectangle {
let bounds = match self {
Primitive::Fill { path, .. } => path.bounds(),
Primitive::Stroke { path, .. } => path.bounds(),
};
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
}
.expand(1.0)
}
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
}
}
}

View file

@ -6,6 +6,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
use std::cell::RefCell;
use std::collections::hash_map;
#[derive(Debug)]
pub struct Pipeline {
cache: RefCell<Cache>,
}
@ -30,6 +31,7 @@ impl Pipeline {
handle: &raster::Handle,
filter_method: raster::FilterMethod,
bounds: Rectangle,
opacity: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
transform: tiny_skia::Transform,
clip_mask: Option<&tiny_skia::Mask>,
@ -55,6 +57,7 @@ impl Pipeline {
image,
&tiny_skia::PixmapPaint {
quality,
opacity,
..Default::default()
},
transform,
@ -68,10 +71,10 @@ impl Pipeline {
}
}
#[derive(Default)]
#[derive(Debug, Default)]
struct Cache {
entries: FxHashMap<u64, Option<Entry>>,
hits: FxHashSet<u64>,
entries: FxHashMap<raster::Id, Option<Entry>>,
hits: FxHashSet<raster::Id>,
}
impl Cache {
@ -82,7 +85,7 @@ impl Cache {
let id = handle.id();
if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) {
let image = graphics::image::load(handle).ok()?.into_rgba8();
let image = graphics::image::load(handle).ok()?;
let mut buffer =
vec![0u32; image.width() as usize * image.height() as usize];
@ -119,6 +122,7 @@ impl Cache {
}
}
#[derive(Debug)]
struct Entry {
width: u32,
height: u32,

View file

@ -1,8 +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.
@ -22,3 +23,12 @@ impl Default for Settings {
}
}
}
impl From<graphics::Settings> for Settings {
fn from(settings: graphics::Settings) -> Self {
Self {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
}
}
}

View file

@ -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,
};
@ -13,7 +13,7 @@ use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::hash_map;
#[allow(missing_debug_implementations)]
#[derive(Debug)]
pub struct Pipeline {
glyph_cache: GlyphCache,
cache: RefCell<Cache>,
@ -27,6 +27,8 @@ impl Pipeline {
}
}
// TODO: Shared engine
#[allow(dead_code)]
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
font_system()
.write()
@ -41,7 +43,6 @@ impl Pipeline {
paragraph: &paragraph::Weak,
position: Point,
color: Color,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@ -62,7 +63,6 @@ impl Pipeline {
color,
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
scale_factor,
pixels,
clip_mask,
transformation,
@ -74,7 +74,6 @@ impl Pipeline {
editor: &editor::Weak,
position: Point,
color: Color,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@ -95,7 +94,6 @@ impl Pipeline {
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,
scale_factor,
pixels,
clip_mask,
transformation,
@ -108,17 +106,16 @@ impl Pipeline {
bounds: Rectangle,
color: Color,
size: Pixels,
line_height: LineHeight,
line_height: Pixels,
font: Font,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
) {
let line_height = f32::from(line_height.to_absolute(size));
let line_height = f32::from(line_height);
let mut font_system = font_system().write().expect("Write font system");
let font_system = font_system.raw();
@ -149,7 +146,6 @@ impl Pipeline {
color,
horizontal_alignment,
vertical_alignment,
scale_factor,
pixels,
clip_mask,
transformation,
@ -161,7 +157,6 @@ impl Pipeline {
buffer: &cosmic_text::Buffer,
position: Point,
color: Color,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@ -178,7 +173,6 @@ impl Pipeline {
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,
scale_factor,
pixels,
clip_mask,
transformation,
@ -199,12 +193,11 @@ fn draw(
color: Color,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
) {
let bounds = bounds * transformation * scale_factor;
let bounds = bounds * transformation;
let x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
@ -222,8 +215,8 @@ fn draw(
for run in buffer.layout_runs() {
for glyph in run.glyphs {
let physical_glyph = glyph
.physical((x, y), scale_factor * transformation.scale_factor());
let physical_glyph =
glyph.physical((x, y), transformation.scale_factor());
if let Some((buffer, placement)) = glyph_cache.allocate(
physical_glyph.cache_key,
@ -247,10 +240,8 @@ fn draw(
pixels.draw_pixmap(
physical_glyph.x + placement.left,
physical_glyph.y - placement.top
+ (run.line_y
* scale_factor
* transformation.scale_factor())
.round() as i32,
+ (run.line_y * transformation.scale_factor()).round()
as i32,
pixmap,
&tiny_skia::PixmapPaint {
opacity,

View file

@ -4,11 +4,13 @@ use crate::graphics::text;
use resvg::usvg::{self, TreeTextToPath};
use rustc_hash::{FxHashMap, FxHashSet};
use tiny_skia::Transform;
use std::cell::RefCell;
use std::collections::hash_map;
use std::fs;
#[derive(Debug)]
pub struct Pipeline {
cache: RefCell<Cache>,
}
@ -32,7 +34,9 @@ impl Pipeline {
handle: &Handle,
color: Option<Color>,
bounds: Rectangle,
opacity: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
transform: Transform,
clip_mask: Option<&tiny_skia::Mask>,
) {
if let Some(image) = self.cache.borrow_mut().draw(
@ -44,8 +48,11 @@ impl Pipeline {
bounds.x as i32,
bounds.y as i32,
image,
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::identity(),
&tiny_skia::PixmapPaint {
opacity,
..tiny_skia::PixmapPaint::default()
},
transform,
clip_mask,
);
}
@ -203,3 +210,13 @@ impl Cache {
self.raster_hits.clear();
}
}
impl std::fmt::Debug for Cache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Cache")
.field("tree_hits", &self.tree_hits)
.field("rasters", &self.rasters)
.field("raster_hits", &self.raster_hits)
.finish_non_exhaustive()
}
}

View file

@ -1,43 +1,55 @@
use crate::core::{Color, Rectangle, Size};
use crate::graphics::compositor::{self, Information};
use crate::graphics::damage;
use crate::graphics::{Error, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
use crate::graphics::error::{self, Error};
use crate::graphics::{self, Viewport};
use crate::{Layer, Renderer, Settings};
use std::collections::VecDeque;
use std::num::NonZeroU32;
#[allow(missing_debug_implementations)]
pub struct Compositor {
context: softbuffer::Context<Box<dyn compositor::Window>>,
settings: Settings,
}
#[allow(missing_debug_implementations)]
pub struct Surface {
window: softbuffer::Surface<
Box<dyn compositor::Window>,
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,
}
impl crate::graphics::Compositor for Compositor {
type Settings = Settings;
type Renderer = Renderer;
type Surface = Surface;
fn new<W: compositor::Window>(
settings: Self::Settings,
async fn with_backend<W: compositor::Window>(
settings: graphics::Settings,
compatible_window: W,
backend: Option<&str>,
) -> Result<Self, Error> {
Ok(new(settings, compatible_window))
match backend {
None | Some("tiny-skia") | Some("tiny_skia") => {
Ok(new(settings.into(), compatible_window))
}
Some(backend) => Err(Error::GraphicsAdapterNotFound {
backend: "tiny-skia",
reason: error::Reason::DidNotMatch {
preferred_backend: backend.to_owned(),
},
}),
}
}
fn create_renderer(&self) -> Self::Renderer {
Renderer::new(
Backend::new(),
self.settings.default_font,
self.settings.default_text_size,
)
@ -59,7 +71,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,
};
@ -85,7 +97,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 {
@ -102,9 +114,7 @@ impl crate::graphics::Compositor for Compositor {
viewport: &Viewport,
background_color: Color,
) -> Result<(), compositor::SurfaceError> {
renderer.with_primitives(|backend, primitives| {
present(backend, surface, primitives, viewport, background_color)
})
present(renderer, surface, viewport, background_color)
}
fn screenshot(
@ -114,9 +124,7 @@ impl crate::graphics::Compositor for Compositor {
viewport: &Viewport,
background_color: Color,
) -> Vec<u8> {
renderer.with_primitives(|backend, primitives| {
screenshot(surface, backend, primitives, viewport, background_color)
})
screenshot(renderer, surface, viewport, background_color)
}
}
@ -132,37 +140,41 @@ pub fn new<W: compositor::Window>(
}
pub fn present(
backend: &mut Backend,
renderer: &mut Renderer,
surface: &mut Surface,
primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
) -> 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))
let damage = last_layers
.and_then(|last_layers| {
(surface.background_color == background_color).then(|| {
damage::diff(
last_layers,
renderer.layers(),
|layer| vec![layer.bounds],
Layer::damage,
)
})
})
.unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
@ -170,10 +182,11 @@ pub fn present(
return Ok(());
}
surface.primitive_stack.push_front(primitives.to_vec());
surface.layer_stack.push_front(renderer.layers().to_vec());
surface.background_color = background_color;
let damage = damage::group(damage, scale_factor, physical_size);
let damage =
damage::group(damage, Rectangle::with_size(viewport.logical_size()));
let mut pixels = tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut buffer),
@ -182,10 +195,9 @@ pub fn present(
)
.expect("Create pixel map");
backend.draw(
renderer.draw(
&mut pixels,
&mut surface.clip_mask,
primitives,
viewport,
&damage,
background_color,
@ -195,9 +207,8 @@ pub fn present(
}
pub fn screenshot(
renderer: &mut Renderer,
surface: &mut Surface,
backend: &mut Backend,
primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
) -> Vec<u8> {
@ -206,7 +217,7 @@ pub fn screenshot(
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,
@ -214,7 +225,6 @@ pub fn screenshot(
)
.expect("Create offscreen pixel map"),
&mut surface.clip_mask,
primitives,
viewport,
&[Rectangle::with_size(Size::new(
size.width as f32,