Minimize: a held press keeps its page point when the canvas moves, not only when it narrows (#2051)

While a press that went down on the canvas is held, the view centre now follows the canvas rect's own move as well as its size change, so a still pointer keeps its page point when the left sidebar or rail appears mid-press.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Trevor BrownandClaude Fable 5.1 committed 2026-11-06 13:43:32 -05:00
1 parent 5d21ca8791
commit 36b728304b
2 files changed
+54 -18

No files matched your search

+23 -18
View File
@@ -195,32 +195,37 @@ fn pointer_event(app: &FigApp, kind: PointerKind, pos: Pos2, m: &egui::Modifiers
/// labels, handles, comments, versions/branch/dev/live UI and the cursor.
pub fn show(app: &mut FigApp, ui: &mut Ui, worker: &mut Worker) {
let rect = ui.max_rect();
app.canvas.rect = rect;
let old_rect = std::mem::replace(&mut app.canvas.rect, rect);
let ctx = ui.ctx().clone();
let ppp = ctx.pixels_per_point();
let resp = ui.interact(rect, ui.scope_id().with("canvas"), Sense::click_and_drag());
let key_box = app.ui.key_box;
crate::a11y::canvas(&mut app.ui.nav, &resp, || key_box.map_or_else(|| describe(&app.session), describe_box));
if let Ok(ds) = app.session.ds_mut()
&& ((ds.view.vw - f64::from(rect.width())).abs() > 0.5 || (ds.view.vh - f64::from(rect.height())).abs() > 0.5)
{
// Keep the centre; the first sizing after opening re-fits.
if let Ok(ds) = app.session.ds_mut() {
let resized = (ds.view.vw - f64::from(rect.width())).abs() > 0.5 || (ds.view.vh - f64::from(rect.height())).abs() > 0.5;
// The first sizing after opening re-fits.
let first = ds.view.vw == 1200.0 && ds.view.vh == 800.0;
// While a button is held that went down on the canvas (a click that selects shows the right panel, minimized, and a
// click that deselects hides it) the page point under the pointer stays where it is: the
// gesture's own pointer events would otherwise move by half the panel's width over the
// zoom, and the move handling would take that for a drag. Keep the top-left instead.
let held = resp.is_pointer_button_down_on() && !first;
// While a button is held that went down on the canvas (a click that selects shows the
// right panel, minimized, and a click that deselects hides it; the left panel and rail
// can appear too) the page point under a still pointer stays where it is: the gesture's
// own pointer events would otherwise map to another page point and the move handling
// would take that for a drag. `to_page` is `origin + (p - rect.min) / z` with
// `origin = c - vw / 2 / z`, so the centre moves by half the size change plus the
// rect's own move, over the zoom.
let held = resp.is_pointer_button_down_on() && !first && old_rect.is_finite();
let z = ds.view.zoom.max(1e-9);
let (dw, dh) = (f64::from(rect.width()) - ds.view.vw, f64::from(rect.height()) - ds.view.vh);
ds.view.vw = f64::from(rect.width());
ds.view.vh = f64::from(rect.height());
if held {
ds.view.cx += dw / 2.0 / z;
ds.view.cy += dh / 2.0 / z;
let (dw, dh) = if resized { (f64::from(rect.width()) - ds.view.vw, f64::from(rect.height()) - ds.view.vh) } else { (0.0, 0.0) };
if resized {
// Otherwise keep the centre.
ds.view.vw = f64::from(rect.width());
ds.view.vh = f64::from(rect.height());
if first {
ds.zoom_to_fit();
}
}
if first {
ds.zoom_to_fit();
if held {
ds.view.cx += (dw / 2.0 + f64::from(rect.min.x - old_rect.min.x)) / z;
ds.view.cy += (dh / 2.0 + f64::from(rect.min.y - old_rect.min.y)) / z;
}
}
let own_cursor = input(app, ui, &resp);
+31
View File
@@ -442,3 +442,34 @@ fn a_click_that_hides_the_right_panel_does_not_move_the_frame() {
assert_eq!(frame_x(&u, ids[4]), 1880.0);
assert_eq!(u.app.session.ds().unwrap().history.undo.len(), steps);
}
/// The left sidebar and rail appearing while a press is held (Minimize toggled off mid-press)
/// moves the canvas's left edge: a still pointer keeps its page point, so the release moves nothing.
#[test]
fn a_held_press_keeps_its_page_point_when_the_canvas_moves() {
let (mut u, ids) = minimized_row();
u.app.run("select.set", json!({"ids": [ids[3]]}));
for _ in 0..3 {
u.frame(vec![]);
}
let steps = u.app.session.ds().unwrap().history.undo.len();
let left = u.app.canvas.rect.min.x;
let p = u.app.to_screen(1880.0 + 200.0, 300.0);
u.frame(vec![Event::PointerMoved(p)]);
u.frame(vec![Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: true, modifiers: Default::default() }]);
u.frame(vec![]);
let before = u.app.to_page(p);
u.app.run("view.minimize", json!({"on": false}));
u.frame(vec![]);
u.frame(vec![]);
assert!(u.app.canvas.rect.min.x > left + 100.0, "the left sidebar and rail moved the canvas's edge");
let after = u.app.to_page(p);
assert!((after.x - before.x).abs() < 0.5 && (after.y - before.y).abs() < 0.5, "the pointer's page point moved: {before:?} to {after:?}");
u.frame(vec![Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() }]);
for _ in 0..3 {
u.frame(vec![]);
}
assert_eq!(frame_x(&u, ids[4]), 1880.0, "the frame did not move");
assert_eq!(frame_x(&u, ids[3]), 1410.0);
assert_eq!(u.app.session.ds().unwrap().history.undo.len(), steps, "no history step");
}