From 8601e38d4b8f9fe0808d7390a8a0ba699ee12f2d Mon Sep 17 00:00:00 2001 From: lillith <35337584+0xlillith@users.noreply.github.com> Date: Mon, 12 Oct 2026 00:30:05 +0200 Subject: [PATCH] fix(ui): restore free Shift-middle orbit --- crates/ui-egui/src/viewport.rs | 30 ++++++++++++++++++++++++++++-- docs/book/src/guide/ui-tour.md | 5 +++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/viewport.rs b/crates/ui-egui/src/viewport.rs index 2dd71180..62948458 100644 --- a/crates/ui-egui/src/viewport.rs +++ b/crates/ui-egui/src/viewport.rs @@ -1534,7 +1534,11 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) { if mode == Some(NavMode::Orbit) { let press = ui.input(|i| i.pointer.press_origin()); app.cancel_view_animation(); - orbit_drag(app, rect, f64::from(delta.x), f64::from(delta.y), press, mods.shift, true); + // Shift can be required to enter orbit (Fusion/Inventor middle drag), rather + // than an additional request for snapping. Only an extra modifier snaps. + let without_shift = egui::Modifiers { shift: false, ..mods }; + let snap = mods.shift && (!middle || crate::prefs::nav_mode(app, middle, secondary_down, without_shift) == Some(NavMode::Orbit)); + orbit_drag(app, rect, f64::from(delta.x), f64::from(delta.y), press, snap, true); } else { if matches!(mode, Some(NavMode::Pan | NavMode::Zoom)) { app.viewport.orbit_snap = None; @@ -3408,7 +3412,8 @@ mod tests { h.call("ui.view", json!({"view":"front"})); let before = h.app.cam; h.call("ui.drag", json!({"x0":800,"y0":500,"x1":1020,"y1":500,"button":"middle","shift":true,"steps":40})); - assert!((h.app.cam.yaw - std::f64::consts::FRAC_PI_2).abs() < 1e-6, "before={before:?}, after={:?}", h.app.cam); + assert!((h.app.cam.yaw - 1.76).abs() < 1e-6, "before={before:?}, after={:?}", h.app.cam); + assert!(h.app.viewport.orbit_snap.is_none()); h.call("ui.view", json!({"view":"front"})); h.call("ui.drag", json!({"x0":800,"y0":500,"x1":850,"y1":520,"button":"middle"})); assert_eq!(h.app.cam.yaw, before.yaw); @@ -3416,6 +3421,27 @@ mod tests { assert_ne!(h.app.cam.target, before.target); } + #[test] + fn preset_shift_middle_orbit_is_free_but_shift_right_orbit_snaps() { + for preset in ["fusion", "inventor"] { + let mut h = crate::scenario::Harness::new(); + h.app.preferences.nav = preset.into(); + h.call("ui.view", json!({"view":"front"})); + let before = h.app.cam; + h.call("ui.drag", json!({"x0":800,"y0":500,"x1":850,"y1":520,"button":"middle","shift":true,"steps":20})); + let mut expected = before; + expected.orbit(50.0, 20.0); + assert!((h.app.cam.yaw - expected.yaw).abs() < 1e-6, "{preset}: {:?}", h.app.cam); + assert!((h.app.cam.pitch - expected.pitch).abs() < 1e-6, "{preset}: {:?}", h.app.cam); + assert_eq!(h.app.cam.target, before.target); + assert_eq!(h.app.cam.distance, before.distance); + assert!(h.app.viewport.orbit_snap.is_none()); + h.call("ui.view", json!({"view":"front"})); + h.call("ui.drag", json!({"x0":800,"y0":500,"x1":1020,"y1":500,"button":"right","shift":true,"steps":40})); + assert!((h.app.cam.yaw - std::f64::consts::FRAC_PI_2).abs() < 1e-6, "{preset}: {:?}", h.app.cam); + } + } + #[test] fn axis_view_click_again_flips_and_drag_snaps() { let mut h = crate::scenario::Harness::new(); diff --git a/docs/book/src/guide/ui-tour.md b/docs/book/src/guide/ui-tour.md index 0718a3e1..d17fc99d 100644 --- a/docs/book/src/guide/ui-tour.md +++ b/docs/book/src/guide/ui-tour.md @@ -14,8 +14,9 @@ row gives that item's menu. Rows drag into groups and other components. - **Viewport.** Click to select, Shift or Ctrl to add, drag for a window or crossing box. The wheel zooms at the cursor, the middle button pans and right-drag orbits freely (the mouse - preset in Preferences switches to SolidWorks or Inventor style). Hold Shift during an orbit - to snap to the nearest standard or diagonal view; the default Shift+middle gesture also snaps. + preset in Preferences switches to SolidWorks or Inventor style). Shift+middle orbits freely + in the default and Inventor presets. Shift+right-drag snaps to the nearest standard or + diagonal view; Shift does not request snapping when the preset already requires it to orbit. The axis view shows the world's X/Y/Z directions in SolidCraft's colors. Click a signed axis tip to look along that axis; click the aligned tip again to look from the opposite side. Drag the axis view to orbit around the view centre, with Shift for snapping. Top and bottom