🐛 Fix nested clipping

This commit is contained in:
Alejandro Alonso
2025-11-26 09:27:40 +01:00
committed by Belén Albeza
parent 9183dbbc43
commit d9ab28e6ed

View File

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