diff options
Diffstat (limited to 'tiny_skia/src/backend.rs')
-rw-r--r-- | tiny_skia/src/backend.rs | 113 |
1 files changed, 105 insertions, 8 deletions
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index d1393b4d..ea4a3ec6 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,3 +1,5 @@ +use tiny_skia::Size; + use crate::core::{Background, Color, Gradient, Rectangle, Vector}; use crate::graphics::backend; use crate::graphics::text; @@ -150,9 +152,8 @@ impl Backend { Primitive::Quad { bounds, background, - border_radius, - border_width, - border_color, + border, + shadow, } => { let physical_bounds = (*bounds + translation) * scale_factor; @@ -170,11 +171,12 @@ impl Backend { .post_scale(scale_factor, scale_factor); // Make sure the border radius is not larger than the bounds - let border_width = border_width + let border_width = border + .width .min(bounds.width / 2.0) .min(bounds.height / 2.0); - let mut fill_border_radius = *border_radius; + let mut fill_border_radius = <[f32; 4]>::from(border.radius); for radius in &mut fill_border_radius { *radius = (*radius) .min(bounds.width / 2.0) @@ -182,6 +184,81 @@ impl Backend { } 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, + } + translation) + * 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)| { + 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 { @@ -252,7 +329,7 @@ impl Backend { }; // Make sure the border radius is correct - let mut border_radius = *border_radius; + let mut border_radius = <[f32; 4]>::from(border.radius); let mut is_simple_border = true; for radius in &mut border_radius { @@ -278,7 +355,7 @@ impl Backend { &border_path, &tiny_skia::Paint { shader: tiny_skia::Shader::SolidColor( - into_color(*border_color), + into_color(border.color), ), anti_alias: true, ..tiny_skia::Paint::default() @@ -334,7 +411,7 @@ impl Backend { &border_radius_path, &tiny_skia::Paint { shader: tiny_skia::Shader::SolidColor( - into_color(*border_color), + into_color(border.color), ), anti_alias: true, ..tiny_skia::Paint::default() @@ -864,6 +941,26 @@ fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) { ); } +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: 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; } |