🐛 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 NODE_BATCH_THRESHOLD: i32 = 10;
type ClipStack = Vec<(Rect, Option<Corners>, 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<Corners>, Matrix)>,
clip_bounds: Option<ClipStack>,
// 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<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(
&self,
element: &Shape,
offset: Option<(f32, f32)>,
) -> Option<(Rect, Option<Corners>, Matrix)> {
) -> Option<ClipStack> {
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<Corners>, Matrix)> {
) -> Option<ClipStack> {
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<Corners>, Matrix)>,
clip_bounds: Option<ClipStack>,
fills_surface_id: SurfaceId,
strokes_surface_id: SurfaceId,
innershadows_surface_id: SurfaceId,
@@ -574,13 +589,14 @@ impl RenderState {
let antialias = shape.should_use_antialias(self.get_scale());
// set clipping
if let Some((bounds, corners, transform)) = clip_bounds {
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);
s.canvas().concat(transform);
});
if let Some(corners) = corners {
let rrect = RRect::new_rect_radii(bounds, &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);
@@ -588,7 +604,7 @@ impl RenderState {
} else {
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas()
.clip_rect(bounds, skia::ClipOp::Intersect, antialias);
.clip_rect(*bounds, skia::ClipOp::Intersect, antialias);
});
}
@@ -601,7 +617,7 @@ impl RenderState {
paint.set_stroke_width(4.);
self.surfaces
.canvas(fills_surface_id)
.draw_rect(bounds, &paint);
.draw_rect(*bounds, &paint);
}
self.surfaces.apply_mut(surface_ids, |s| {
@@ -609,6 +625,7 @@ impl RenderState {
.concat(&transform.invert().unwrap_or(Matrix::default()));
});
}
}
// We don't want to change the value in the global state
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
@@ -1228,7 +1245,7 @@ impl RenderState {
shape: &Shape,
shape_bounds: &Rect,
shadow: &Shadow,
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
clip_bounds: Option<ClipStack>,
scale: f32,
translation: (f32, f32),
extra_layer_blur: Option<Blur>,
@@ -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,14 +1565,16 @@ 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);
self.surfaces.canvas(SurfaceId::Current).save();
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);
self.surfaces.canvas(SurfaceId::Current).save();
self.surfaces
.canvas(SurfaceId::Current)
.concat(&total_matrix);
@@ -1580,6 +1597,7 @@ impl RenderState {
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,
});