🐛 Fix board clip on drag (#11620)

This commit is contained in:
Elena Torró 2026-09-11 08:27:11 +02:00 committed by GitHub
parent 0913545b41
commit 32ed9b5a08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 109 additions and 4 deletions

View File

@ -626,3 +626,92 @@ test("Renders background blur clipped by a board with clip content", async ({
await expect(workspace.canvas).toHaveScreenshot();
});
test("Clips a group dragged into a board with clip content", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-shapes-groups-boards.json");
await workspace.goToWorkspace({
id: "53a7ff09-2228-81d3-8006-4b5eac177245",
pageId: "53a7ff09-2228-81d3-8006-4b5eac177246",
});
await workspace.waitForFirstRenderWithoutUI();
// Select the group, then drag it so it straddles the right edge of the
// board. The overflow must not be painted while the pointer is still down.
await workspace.viewport.hover({ position: { x: 1028, y: 548 } });
await page.mouse.down();
await page.mouse.up();
await page.waitForTimeout(200);
await page.mouse.down();
for (const [x, y] of [
[1000, 540],
[920, 530],
[830, 520],
]) {
await workspace.viewport.hover({ position: { x, y } });
await page.waitForTimeout(100);
}
await page.waitForTimeout(600);
await expect(workspace.canvas).toHaveScreenshot();
await page.mouse.up();
await page.waitForTimeout(800);
await expect(workspace.canvas).toHaveScreenshot();
});
test("Clips a group dragged inside a board with clip content", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-shapes-groups-boards.json");
await workspace.goToWorkspace({
id: "53a7ff09-2228-81d3-8006-4b5eac177245",
pageId: "53a7ff09-2228-81d3-8006-4b5eac177246",
});
await workspace.waitForFirstRenderWithoutUI();
await workspace.viewport.hover({ position: { x: 1028, y: 548 } });
await page.mouse.down();
await page.mouse.up();
await page.waitForTimeout(200);
// Drop the group inside the board so it becomes one of its children.
await page.mouse.down();
for (const [x, y] of [
[900, 540],
[700, 520],
]) {
await workspace.viewport.hover({ position: { x, y } });
await page.waitForTimeout(100);
}
await page.waitForTimeout(600);
await page.mouse.up();
await page.waitForTimeout(800);
// Drag it towards the right edge, now as a board child.
await page.mouse.down();
for (const [x, y] of [
[750, 520],
[830, 520],
]) {
await workspace.viewport.hover({ position: { x, y } });
await page.waitForTimeout(100);
}
await page.waitForTimeout(600);
await expect(workspace.canvas).toHaveScreenshot();
await page.mouse.up();
await page.waitForTimeout(800);
await expect(workspace.canvas).toHaveScreenshot();
});

View File

@ -438,6 +438,7 @@ pub(crate) struct RenderState {
pub viewport_presented: bool,
}
#[derive(Clone)]
pub struct InteractiveDragCrop {
pub src_doc_bounds: Rect,
pub src_selrect: Rect,
@ -3725,7 +3726,21 @@ impl RenderState {
);
if use_cached {
if let Some(crop) = self.backbuffer_crop_cache.get(&node_id) {
if let Some(crop) = self.backbuffer_crop_cache.get(&node_id).cloned() {
self.surfaces.canvas(target_surface).save();
self.surfaces.canvas(target_surface).reset_matrix();
if let Some(clips) = clip_bounds.as_ref() {
let antialias = element
.should_use_antialias(scale, self.options.antialias_threshold);
self.clip_target_surface_to_stack(
clips,
target_surface,
scale,
antialias,
);
}
let crop_image = &crop.image;
let crop_src_selrect = crop.src_selrect;
@ -3737,14 +3752,11 @@ impl RenderState {
),
None => (0.0, 0.0),
};
let scale = self.get_scale();
let translation = self
.surfaces
.get_render_context_translation(self.render_area, scale);
let canvas = self.surfaces.canvas(target_surface);
canvas.save();
canvas.reset_matrix();
// If the crop includes shadows/blur (extrect pixels outside the fill/stroke
// silhouette), do NOT apply the silhouette clip or we'd cut those pixels.
let should_clip_crop = element.shadows.is_empty() && element.blur.is_none();

View File

@ -1535,6 +1535,10 @@ impl Shape {
return false;
}
if matches!(self.shape_type, Type::Group(_)) {
return false;
}
// If a frame shows overflow (clip_content=false) and its visible content exceeds the
// frame bounds, a cached crop anchored to the frame can easily become incorrect while
// moving (children can extend beyond selrect). Be conservative and render live.