🐛 Fix boolean and group shadows

This commit is contained in:
Alejandro Alonso
2025-09-09 18:51:07 +02:00
parent 99a100ad63
commit da05d6b67d
2 changed files with 74 additions and 66 deletions

View File

@@ -124,7 +124,7 @@ impl NodeRenderState {
/// * `element` - The shape element for which to calculate shadow clip bounds
/// * `modifiers` - Optional transformation matrix to apply to the bounds
/// * `shadow` - The shadow configuration containing blur, offset, and other properties
pub fn get_shadow_clip_bounds(
pub fn get_nested_shadow_clip_bounds(
&self,
element: &Shape,
modifiers: Option<&Matrix>,
@@ -134,7 +134,13 @@ impl NodeRenderState {
return self.clip_bounds;
}
let bounds = element.get_frame_shadow_bounds(shadow);
// Assert that the shape is either a Frame or Group
assert!(
matches!(element.shape_type, Type::Frame(_) | Type::Group(_)),
"Shape must be a Frame or Group for nested shadow clip bounds calculation"
);
let bounds = element.get_selrect_shadow_bounds(shadow);
let mut transform = element.transform;
transform.post_translate(element.center());
transform.pre_translate(-element.center());
@@ -753,6 +759,8 @@ impl RenderState {
s.canvas().concat(&matrix);
});
// For boolean shapes, there's no need to calculate children because
// when painting the shape, the necessary path is already calculated
let shape = if let Type::Bool(_) = &shape.shape_type {
// If any child transform doesn't match the parent transform means
// that the children is transformed and we need to recalculate the
@@ -1326,67 +1334,72 @@ impl RenderState {
translation,
);
// Nested shapes shadowing - apply black shadow to child shapes too
for shadow_shape_id in element.children.iter() {
let shadow_shape = tree.get(shadow_shape_id).unwrap();
let clip_bounds = node_render_state.get_shadow_clip_bounds(
element,
modifiers.get(&element.id),
shadow,
);
if !matches!(shadow_shape.shape_type, Type::Text(_)) {
self.render_drop_black_shadow(
tree,
modifiers,
structure,
shadow_shape,
if !matches!(element.shape_type, Type::Bool(_)) {
// Nested shapes shadowing - apply black shadow to child shapes too
for shadow_shape_id in element.children.iter() {
let shadow_shape = tree.get(shadow_shape_id).unwrap();
let clip_bounds = node_render_state.get_nested_shadow_clip_bounds(
element,
modifiers.get(&element.id),
shadow,
scale_content.get(&element.id),
clip_bounds,
scale,
translation,
);
} else {
let paint = skia::Paint::default();
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
self.surfaces
.canvas(SurfaceId::DropShadows)
.save_layer(&layer_rec);
self.surfaces
.canvas(SurfaceId::DropShadows)
.scale((scale, scale));
self.surfaces
.canvas(SurfaceId::DropShadows)
.translate(translation);
if !matches!(shadow_shape.shape_type, Type::Text(_)) {
self.render_drop_black_shadow(
tree,
modifiers,
structure,
shadow_shape,
shadow,
scale_content.get(&element.id),
clip_bounds,
scale,
translation,
);
} else {
let paint = skia::Paint::default();
let layer_rec =
skia::canvas::SaveLayerRec::default().paint(&paint);
let mut transformed_shadow: Cow<Shadow> = Cow::Borrowed(shadow);
// transformed_shadow.to_mut().offset = (0., 0.);
transformed_shadow.to_mut().color = skia::Color::BLACK;
transformed_shadow.to_mut().blur = transformed_shadow.blur * scale;
self.surfaces
.canvas(SurfaceId::DropShadows)
.save_layer(&layer_rec);
self.surfaces
.canvas(SurfaceId::DropShadows)
.scale((scale, scale));
self.surfaces
.canvas(SurfaceId::DropShadows)
.translate(translation);
let mut new_shadow_paint = skia::Paint::default();
new_shadow_paint
.set_image_filter(transformed_shadow.get_drop_shadow_filter());
new_shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
let mut transformed_shadow: Cow<Shadow> = Cow::Borrowed(shadow);
// transformed_shadow.to_mut().offset = (0., 0.);
transformed_shadow.to_mut().color = skia::Color::BLACK;
transformed_shadow.to_mut().blur =
transformed_shadow.blur * scale;
self.render_shape(
tree,
modifiers,
structure,
shadow_shape,
scale_content.get(&element.id),
clip_bounds,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
true,
None,
Some(vec![new_shadow_paint.clone()]),
);
self.surfaces.canvas(SurfaceId::DropShadows).restore();
let mut new_shadow_paint = skia::Paint::default();
new_shadow_paint.set_image_filter(
transformed_shadow.get_drop_shadow_filter(),
);
new_shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
self.render_shape(
tree,
modifiers,
structure,
shadow_shape,
scale_content.get(&element.id),
clip_bounds,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
true,
None,
Some(vec![new_shadow_paint.clone()]),
);
self.surfaces.canvas(SurfaceId::DropShadows).restore();
}
}
}

View File

@@ -725,12 +725,12 @@ impl Shape {
.get_or_init(|| self.calculate_extrect(shapes_pool, modifiers))
}
/// Calculates the bounding rectangle for a frame shape's shadow, taking into account
/// Calculates the bounding rectangle for a selrect shape's shadow, taking into account
/// stroke widths and shadow properties.
///
/// This method computes the expanded bounds that would be needed to fully render
/// the shadow effect for a frame shape. It considers:
/// - The base frame bounds (selection rectangle)
/// the shadow effect for a shape. It considers:
/// - The base bounds (selection rectangle)
/// - Maximum stroke width across all strokes, accounting for stroke rendering kind
/// - Shadow offset (x, y displacement)
/// - Shadow blur radius (expands bounds outward)
@@ -742,12 +742,7 @@ impl Shape {
/// # Returns
/// A `math::Rect` representing the bounding rectangle that encompasses the shadow.
/// Returns an empty rectangle if the shadow is hidden.
pub fn get_frame_shadow_bounds(&self, shadow: &Shadow) -> math::Rect {
assert!(
self.is_frame(),
"This method can only be called on frame shapes"
);
pub fn get_selrect_shadow_bounds(&self, shadow: &Shadow) -> math::Rect {
let base_bounds = self.selrect();
let mut rect = skia::Rect::new_empty();