diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 3875da7f00..ad4c742e33 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -38,12 +38,14 @@ const VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 1; const MAX_BLOCKING_TIME_MS: i32 = 32; const NODE_BATCH_THRESHOLD: i32 = 10; +type ClipStack = Vec<(Rect, Option, Matrix)>; + pub struct NodeRenderState { pub id: Uuid, // We use this bool to keep that we've traversed all the children inside this node. visited_children: bool, // This is used to clip the content of frames. - clip_bounds: Option<(Rect, Option, Matrix)>, + clip_bounds: Option, // This is a flag to indicate that we've already drawn the mask of a masked group. visited_mask: bool, // This bool indicates that we're drawing the mask shape. @@ -68,13 +70,26 @@ impl NodeRenderState { /// the clipping region to compensate for coordinate system transformations. /// This is useful for nested coordinate systems or when elements are grouped /// and need relative positioning adjustments. + fn append_clip( + clip_stack: Option, + clip: (Rect, Option, Matrix), + ) -> Option { + match clip_stack { + Some(mut stack) => { + stack.push(clip); + Some(stack) + } + None => Some(vec![clip]), + } + } + pub fn get_children_clip_bounds( &self, element: &Shape, offset: Option<(f32, f32)>, - ) -> Option<(Rect, Option, Matrix)> { + ) -> Option { if self.id.is_nil() || !element.clip() { - return self.clip_bounds; + return self.clip_bounds.clone(); } let mut bounds = element.selrect(); @@ -95,7 +110,7 @@ impl NodeRenderState { _ => None, }; - Some((bounds, corners, transform)) + Self::append_clip(self.clip_bounds.clone(), (bounds, corners, transform)) } /// Calculates the clip bounds for shadow rendering of a given shape. @@ -113,9 +128,9 @@ impl NodeRenderState { &self, element: &Shape, shadow: &Shadow, - ) -> Option<(Rect, Option, Matrix)> { + ) -> Option { if self.id.is_nil() { - return self.clip_bounds; + return self.clip_bounds.clone(); } // Assert that the shape is either a Frame or Group @@ -136,9 +151,9 @@ impl NodeRenderState { _ => None, }; - Some((bounds, corners, transform)) + Self::append_clip(self.clip_bounds.clone(), (bounds, corners, transform)) } - _ => self.clip_bounds, + _ => self.clip_bounds.clone(), } } } @@ -554,7 +569,7 @@ impl RenderState { pub fn render_shape( &mut self, shape: &Shape, - clip_bounds: Option<(Rect, Option, Matrix)>, + clip_bounds: Option, fills_surface_id: SurfaceId, strokes_surface_id: SurfaceId, innershadows_surface_id: SurfaceId, @@ -574,40 +589,42 @@ impl RenderState { let antialias = shape.should_use_antialias(self.get_scale()); // set clipping - if let Some((bounds, corners, transform)) = clip_bounds { - self.surfaces.apply_mut(surface_ids, |s| { - s.canvas().concat(&transform); - }); + if let Some(clips) = clip_bounds.as_ref() { + for (bounds, corners, transform) in clips.iter() { + self.surfaces.apply_mut(surface_ids, |s| { + s.canvas().concat(transform); + }); + + if let Some(corners) = corners { + let rrect = RRect::new_rect_radii(*bounds, corners); + self.surfaces.apply_mut(surface_ids, |s| { + s.canvas() + .clip_rrect(rrect, skia::ClipOp::Intersect, antialias); + }); + } else { + self.surfaces.apply_mut(surface_ids, |s| { + s.canvas() + .clip_rect(*bounds, skia::ClipOp::Intersect, antialias); + }); + } + + // This renders a red line around clipped + // shapes (frames). + if self.options.is_debug_visible() { + let mut paint = skia::Paint::default(); + paint.set_style(skia::PaintStyle::Stroke); + paint.set_color(skia::Color::from_argb(255, 255, 0, 0)); + paint.set_stroke_width(4.); + self.surfaces + .canvas(fills_surface_id) + .draw_rect(*bounds, &paint); + } - if let Some(corners) = corners { - let rrect = RRect::new_rect_radii(bounds, &corners); self.surfaces.apply_mut(surface_ids, |s| { s.canvas() - .clip_rrect(rrect, skia::ClipOp::Intersect, antialias); - }); - } else { - self.surfaces.apply_mut(surface_ids, |s| { - s.canvas() - .clip_rect(bounds, skia::ClipOp::Intersect, antialias); + .concat(&transform.invert().unwrap_or(Matrix::default())); }); } - - // This renders a red line around clipped - // shapes (frames). - if self.options.is_debug_visible() { - let mut paint = skia::Paint::default(); - paint.set_style(skia::PaintStyle::Stroke); - paint.set_color(skia::Color::from_argb(255, 255, 0, 0)); - paint.set_stroke_width(4.); - self.surfaces - .canvas(fills_surface_id) - .draw_rect(bounds, &paint); - } - - self.surfaces.apply_mut(surface_ids, |s| { - s.canvas() - .concat(&transform.invert().unwrap_or(Matrix::default())); - }); } // We don't want to change the value in the global state @@ -1228,7 +1245,7 @@ impl RenderState { shape: &Shape, shape_bounds: &Rect, shadow: &Shadow, - clip_bounds: Option<(Rect, Option, Matrix)>, + clip_bounds: Option, scale: f32, translation: (f32, f32), extra_layer_blur: Option, @@ -1372,14 +1389,12 @@ impl RenderState { let mut iteration = 0; let mut is_empty = true; - while let Some(node_render_state) = self.pending_nodes.pop() { - let NodeRenderState { - id: node_id, - visited_children, - clip_bounds, - visited_mask, - mask, - } = node_render_state; + while let Some(mut node_render_state) = self.pending_nodes.pop() { + let node_id = node_render_state.id; + let visited_children = node_render_state.visited_children; + let visited_mask = node_render_state.visited_mask; + let mask = node_render_state.mask; + let clip_bounds = node_render_state.clip_bounds.clone(); is_empty = false; @@ -1462,7 +1477,7 @@ impl RenderState { element, &element.extrect(tree, scale), shadow, - clip_bounds, + clip_bounds.clone(), scale, translation, None, @@ -1550,37 +1565,40 @@ impl RenderState { } } - if let Some((bounds, corners, transform)) = clip_bounds.as_ref() { + if let Some(clips) = clip_bounds.as_ref() { let antialias = element.should_use_antialias(scale); - let mut total_matrix = Matrix::new_identity(); - total_matrix.pre_scale((scale, scale), None); - total_matrix.pre_translate((translation.0, translation.1)); - total_matrix.pre_concat(transform); self.surfaces.canvas(SurfaceId::Current).save(); - self.surfaces - .canvas(SurfaceId::Current) - .concat(&total_matrix); + for (bounds, corners, transform) in clips.iter() { + let mut total_matrix = Matrix::new_identity(); + total_matrix.pre_scale((scale, scale), None); + total_matrix.pre_translate((translation.0, translation.1)); + total_matrix.pre_concat(transform); - if let Some(corners) = corners { - let rrect = RRect::new_rect_radii(*bounds, corners); - self.surfaces.canvas(SurfaceId::Current).clip_rrect( - rrect, - skia::ClipOp::Intersect, - antialias, - ); - } else { - self.surfaces.canvas(SurfaceId::Current).clip_rect( - *bounds, - skia::ClipOp::Intersect, - antialias, - ); + self.surfaces + .canvas(SurfaceId::Current) + .concat(&total_matrix); + + if let Some(corners) = corners { + let rrect = RRect::new_rect_radii(*bounds, corners); + self.surfaces.canvas(SurfaceId::Current).clip_rrect( + rrect, + skia::ClipOp::Intersect, + antialias, + ); + } else { + self.surfaces.canvas(SurfaceId::Current).clip_rect( + *bounds, + skia::ClipOp::Intersect, + antialias, + ); + } + + self.surfaces + .canvas(SurfaceId::Current) + .concat(&total_matrix.invert().unwrap_or_default()); } - self.surfaces - .canvas(SurfaceId::Current) - .concat(&total_matrix.invert().unwrap_or_default()); - self.surfaces .draw_into(SurfaceId::DropShadows, SurfaceId::Current, None); @@ -1596,7 +1614,7 @@ impl RenderState { self.render_shape( element, - clip_bounds, + clip_bounds.clone(), SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::InnerShadows, @@ -1624,7 +1642,7 @@ impl RenderState { self.pending_nodes.push(NodeRenderState { id: node_id, visited_children: true, - clip_bounds, + clip_bounds: clip_bounds.clone(), visited_mask: false, mask, }); @@ -1651,7 +1669,7 @@ impl RenderState { self.pending_nodes.push(NodeRenderState { id: **child_id, visited_children: false, - clip_bounds: children_clip_bounds, + clip_bounds: children_clip_bounds.clone(), visited_mask: false, mask: false, });