From da05d6b67dcefdcc10d99d4902c645849643d40f Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 9 Sep 2025 18:51:07 +0200 Subject: [PATCH] :bug: Fix boolean and group shadows --- render-wasm/src/render.rs | 127 +++++++++++++++++++++----------------- render-wasm/src/shapes.rs | 13 ++-- 2 files changed, 74 insertions(+), 66 deletions(-) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 3f86983e98..076b6c0dde 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -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 = 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 = 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(); + } } } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 4e39473d2b..a92dcda94b 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -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();