use std::any::TypeId;
use vello::Scene;
use vello::kurbo::{Affine, BezPath, Insets, Point, RoundedRect, Shape as _, Size};
use vello::peniko::color::{AlphaColor, Srgb};
use crate::core::{Property, UpdateCtx};
use crate::properties::CornerRadius;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BoxShadow {
pub color: AlphaColor<Srgb>,
pub offset: Point,
pub blur_radius: f64,
}
impl Property for BoxShadow {
fn static_default() -> &'static Self {
static DEFAULT: BoxShadow = BoxShadow {
color: AlphaColor::TRANSPARENT,
offset: Point::ZERO,
blur_radius: 0.,
};
&DEFAULT
}
}
impl Default for BoxShadow {
fn default() -> Self {
*Self::static_default()
}
}
impl BoxShadow {
pub fn new(color: AlphaColor<Srgb>, offset: impl Into<Point>) -> Self {
Self {
color,
offset: offset.into(),
blur_radius: 0.,
}
}
pub const fn blur(self, blur_radius: f64) -> Self {
Self {
blur_radius,
..self
}
}
pub fn prop_changed(ctx: &mut UpdateCtx<'_>, property_type: TypeId) {
if property_type != TypeId::of::<Self>() {
return;
}
ctx.request_layout();
}
pub const fn is_visible(&self) -> bool {
let alpha = self.color.components[3];
alpha != 0.0
}
pub fn shadow_rect(&self, size: Size, border_radius: &CornerRadius) -> RoundedRect {
size.to_rect().to_rounded_rect(border_radius.radius)
}
pub fn paint(&self, scene: &mut Scene, transform: Affine, rect: RoundedRect) {
if !self.is_visible() {
return;
}
let transform = transform.pre_translate(self.offset.to_vec2());
let blur_radius = self.blur_radius.max(0.);
let radius = (rect.radii().bottom_left
+ rect.radii().bottom_right
+ rect.radii().top_left
+ rect.radii().top_right)
/ 4.;
let std_dev = blur_radius;
let kernel_size = 2.5 * std_dev;
let carve_out_rect = rect - self.offset.to_vec2();
let big_rect = rect.rect().inflate(kernel_size, kernel_size);
let clip_shape = BezPath::from_iter(
big_rect
.path_elements(0.1)
.chain(carve_out_rect.to_path(0.1).reverse_subpaths()),
);
scene.push_clip_layer(transform, &clip_shape);
scene.draw_blurred_rounded_rect_in(
&big_rect,
transform,
rect.rect(),
self.color,
radius,
blur_radius,
);
scene.pop_layer();
}
pub fn get_insets(&self) -> Insets {
let blur_radius = self.blur_radius.max(0.);
Insets {
x0: (blur_radius - self.offset.x).max(0.),
y0: (blur_radius - self.offset.y).max(0.),
x1: (blur_radius + self.offset.x).max(0.),
y1: (blur_radius + self.offset.y).max(0.),
}
}
}