mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
🐛 Fix nested clipping
This commit is contained in:
committed by
Belén Albeza
parent
9183dbbc43
commit
d9ab28e6ed
@@ -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,13 +589,14 @@ 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() {
|
||||||
|
for (bounds, corners, transform) in clips.iter() {
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
self.surfaces.apply_mut(surface_ids, |s| {
|
||||||
s.canvas().concat(&transform);
|
s.canvas().concat(transform);
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(corners) = corners {
|
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| {
|
self.surfaces.apply_mut(surface_ids, |s| {
|
||||||
s.canvas()
|
s.canvas()
|
||||||
.clip_rrect(rrect, skia::ClipOp::Intersect, antialias);
|
.clip_rrect(rrect, skia::ClipOp::Intersect, antialias);
|
||||||
@@ -588,7 +604,7 @@ impl RenderState {
|
|||||||
} else {
|
} else {
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
self.surfaces.apply_mut(surface_ids, |s| {
|
||||||
s.canvas()
|
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.);
|
paint.set_stroke_width(4.);
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(fills_surface_id)
|
.canvas(fills_surface_id)
|
||||||
.draw_rect(bounds, &paint);
|
.draw_rect(*bounds, &paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
self.surfaces.apply_mut(surface_ids, |s| {
|
||||||
@@ -609,6 +625,7 @@ impl RenderState {
|
|||||||
.concat(&transform.invert().unwrap_or(Matrix::default()));
|
.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
|
||||||
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||||
@@ -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,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);
|
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();
|
let mut total_matrix = Matrix::new_identity();
|
||||||
total_matrix.pre_scale((scale, scale), None);
|
total_matrix.pre_scale((scale, scale), None);
|
||||||
total_matrix.pre_translate((translation.0, translation.1));
|
total_matrix.pre_translate((translation.0, translation.1));
|
||||||
total_matrix.pre_concat(transform);
|
total_matrix.pre_concat(transform);
|
||||||
|
|
||||||
self.surfaces.canvas(SurfaceId::Current).save();
|
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(SurfaceId::Current)
|
.canvas(SurfaceId::Current)
|
||||||
.concat(&total_matrix);
|
.concat(&total_matrix);
|
||||||
@@ -1580,6 +1597,7 @@ impl RenderState {
|
|||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(SurfaceId::Current)
|
.canvas(SurfaceId::Current)
|
||||||
.concat(&total_matrix.invert().unwrap_or_default());
|
.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,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user