Merge branch 'master' into beacon
This commit is contained in:
commit
aaf396256e
284 changed files with 18747 additions and 15450 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ®ion 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
856
tiny_skia/src/engine.rs
Normal 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(),
|
||||
);
|
||||
}
|
||||
|
|
@ -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
341
tiny_skia/src/layer.rs
Normal 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,
|
||||
¤t.quads,
|
||||
|(quad, _)| {
|
||||
quad.bounds
|
||||
.expand(1.0)
|
||||
.intersection(¤t.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,
|
||||
¤t.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,
|
||||
¤t.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,
|
||||
¤t.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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 ®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 * 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: ¶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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue