use crate::{prelude::*, wrap_render::WrapRender};
#[derive(Clone, Copy, PartialEq, Lerp)]
pub struct BoxShadow {
pub offset: Point,
pub blur_radius: f32,
pub spread_radius: f32,
pub color: Color,
}
impl Default for BoxShadow {
fn default() -> Self {
Self {
color: Color::TRANSPARENT,
blur_radius: 0.,
offset: Point::new(0., 0.),
spread_radius: 0.0,
}
}
}
impl BoxShadow {
pub fn new(offset: Point, blur_radius: f32, spread_radius: f32, color: Color) -> Self {
Self { color, blur_radius, offset, spread_radius }
}
pub fn is_empty(&self) -> bool {
self.color.alpha == 0
|| (self.blur_radius == 0. && self.offset == Point::new(0., 0.) && self.spread_radius == 0.)
}
pub fn shadow_rect(&self, size: Size, blur_radius: f32) -> Rect {
let spread_radius = self.spread_radius;
let offset = self.offset;
let x = offset.x - blur_radius - spread_radius;
let y = offset.y - blur_radius - spread_radius;
let width = size.width + (spread_radius + blur_radius) * 2.0;
let height = size.height + (spread_radius + blur_radius) * 2.0;
Rect::new(Point::new(x, y), Size::new(width, height))
}
}
#[derive(Default, Clone)]
pub struct BoxShadowWidget {
pub box_shadow: BoxShadow,
}
impl Declare for BoxShadowWidget {
type Builder = FatObj<()>;
#[inline]
fn declarer() -> Self::Builder { FatObj::new(()) }
}
impl WrapRender for BoxShadowWidget {
fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) {
let size = ctx.box_size().unwrap();
if size.is_empty() || self.box_shadow.is_empty() {
host.paint(ctx);
return;
}
let box_shadow = self.box_shadow;
let blur_radius = box_shadow.blur_radius.round().max(0.);
let shadow_rect = box_shadow.shadow_rect(size, blur_radius);
let shadow_size = shadow_rect.size;
let radius: Option<Radius> = {
let (provider_ctx, _) = ctx.provider_ctx_and_box_painter();
Provider::of::<Radius>(provider_ctx).map(|r| *r)
};
let mut builder = path_builder::PathBuilder::default();
builder.rect(&shadow_rect, true); if let Some(radius) = &radius {
builder.rect_round(&Rect::from_size(size), radius, false); } else {
builder.rect(&Rect::from_size(size), false); }
ctx.painter().save();
let mut painter = ctx.painter().fork();
ctx.painter().clip(builder.build().into());
let actual_shadow_size =
Size::new(shadow_size.width - blur_radius * 2.0, shadow_size.height - blur_radius * 2.0);
let scale_x = actual_shadow_size.width / size.width;
let scale_y = actual_shadow_size.height / size.height;
painter.translate(
box_shadow.offset.x - box_shadow.spread_radius,
box_shadow.offset.y - box_shadow.spread_radius,
);
if blur_radius > 0. {
painter.filter(Filter::blur(blur_radius));
}
painter.scale(scale_x, scale_y);
if let Some(radius) = radius {
painter.rect_round(&Rect::from_size(size), &radius, true);
} else {
painter.rect(&Rect::from_size(size), true);
}
painter.set_fill_brush(box_shadow.color).fill();
ctx.painter().merge(&mut painter);
ctx.painter().restore();
host.paint(ctx);
}
fn visual_box(&self, host: &dyn Render, ctx: &mut VisualCtx) -> Option<Rect> {
let size = ctx.box_size().unwrap();
let box_shadow = self.box_shadow;
let blur_radius = box_shadow.blur_radius.round();
let shadow_rect = box_shadow.shadow_rect(size, blur_radius);
Some(
host
.visual_box(ctx)
.map(|rect| rect.union(&shadow_rect))
.unwrap_or(shadow_rect),
)
}
#[inline]
fn wrapper_dirty_phase(&self) -> DirtyPhase { DirtyPhase::Paint }
}
impl_compose_child_for_wrap_render!(BoxShadowWidget);
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests {
use ribir::{core::test_helper::*, material as ribir_material, prelude::*};
use ribir_dev_helper::*;
#[cfg(feature = "png")]
widget_image_tests!(
box_shadow_basic,
WidgetTester::new(fn_widget! {
@Container {
size: Size::new(100., 100.),
radius: Radius::all(50.),
box_shadow: BoxShadow::new(Point::new(45., 15.), 20., 12., Color::RED.with_alpha(0.6)),
}
})
.with_comparison(0.00005)
.with_wnd_size(Size::new(200., 200.))
);
}