From bb973e2c76c9c9538b5366514cad747fff304f93 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sun, 4 Oct 2026 00:30:29 +0700 Subject: [PATCH] =?UTF-8?q?M13.17:=20the=20viewer=20keeps=20its=20pan=20af?= =?UTF-8?q?ter=20a=20middle-button=20or=20Hand=20drag=20=E2=80=94=20egui?= =?UTF-8?q?=20reports=20no=20total=20drag=20on=20the=20release=20frame,=20?= =?UTF-8?q?so=20the=20pan=20was=20recomputed=20as=20the=20drag's=20start?= =?UTF-8?q?=20and=20snapped=20back?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/ui-egui/src/panels/viewer.rs | 7 +++++-- crates/ui-egui/tests/ui_viewer.rs | 32 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/panels/viewer.rs b/crates/ui-egui/src/panels/viewer.rs index ff798b0e..bfcdde2d 100644 --- a/crates/ui-egui/src/panels/viewer.rs +++ b/crates/ui-egui/src/panels/viewer.rs @@ -1097,8 +1097,11 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let merge = format!("viewer-drag-{}", ui.data(|d| d.get_temp::(egui::Id::new("viewer-drag-n")).unwrap_or(0))); match g { Gesture::Pan { start_pan } => { - let d = resp.total_drag_delta().unwrap_or_default(); - app.ui.viewer.pan = [start_pan[0] + d.x, start_pan[1] + d.y]; + // No total drag on the release frame: keep the pan reached so far (it used to + // snap back to where the drag began). + if let Some(d) = resp.total_drag_delta() { + app.ui.viewer.pan = [start_pan[0] + d.x, start_pan[1] + d.y]; + } } Gesture::Move { layers, start, snap_src } => { let mut d = [cpt[0] - start[0], cpt[1] - start[1]]; diff --git a/crates/ui-egui/tests/ui_viewer.rs b/crates/ui-egui/tests/ui_viewer.rs index 9cde7bf0..2ba8b2ad 100644 --- a/crates/ui-egui/tests/ui_viewer.rs +++ b/crates/ui-egui/tests/ui_viewer.rs @@ -502,3 +502,35 @@ fn puppet_marquee_selects_pins_and_alt_drag_works_over_the_art() { let sel: Vec = h.state().session.state.selected_props.iter().map(|(_, u)| *u).collect(); assert_eq!(sel, vec![bend]); } + +/// Drag with `button` from `from` to `to` in steps, then release. +fn drag_with(h: &mut Harness<'_, EffectcraftApp>, button: egui::PointerButton, from: Pos2, to: Pos2) { + h.input_mut().events.push(Event::PointerMoved(from)); + h.input_mut().events.push(Event::PointerButton { pos: from, button, pressed: true, modifiers: Default::default() }); + h.step(); + for i in 1..=8 { + h.input_mut().events.push(Event::PointerMoved(from + (to - from) * (i as f32 / 8.0))); + h.step(); + } + h.input_mut().events.push(Event::PointerButton { pos: to, button, pressed: false, modifiers: Default::default() }); + h.run_steps(3); +} + +#[test] +fn middle_and_hand_drags_pan_the_viewer_and_the_pan_stays_after_release() { + let mut h = harness(); + let c = rect(&h, "viewer.comp").center(); + let before = h.state().ui.viewer.pan; + // Middle-button drag with the Selection tool: pans, and stays panned after the release. + drag_with(&mut h, egui::PointerButton::Middle, c, c + vec2(120.0, 60.0)); + let after = h.state().ui.viewer.pan; + assert!((after[0] - before[0] - 120.0).abs() < 1.0 && (after[1] - before[1] - 60.0).abs() < 1.0, "{before:?} → {after:?}"); + assert_eq!(h.state().ui.tool, Tool::Selection, "the tool is unchanged"); + // The Hand tool's primary drag keeps its pan too. + h.state_mut().ui.tool = Tool::Hand; + h.run_steps(2); + let c = rect(&h, "viewer.comp").center(); + drag_with(&mut h, egui::PointerButton::Primary, c, c - vec2(50.0, 30.0)); + let end = h.state().ui.viewer.pan; + assert!((end[0] - after[0] + 50.0).abs() < 1.0 && (end[1] - after[1] + 30.0).abs() < 1.0, "{after:?} → {end:?}"); +}