Navigation: hold Space to pan (#1754)

Thanks @styles01.
This commit is contained in:
James Aita authored and GitHub committed 2026-10-17 04:09:48 -04:00
1 parent ff7f3204d5
commit a5003da9d7
3 files changed
+48 -3

No files matched your search

+13 -2
View File
@@ -387,8 +387,19 @@ pub(crate) fn handle(app: &mut SolveApp, ui: &mut egui::Ui, rect: Rect, resp: &e
} else if !primary_down && !primary_released { } else if !primary_down && !primary_released {
app.viewport.primary_nav = None; app.viewport.primary_nav = None;
} }
// The fixed navigation mode (navigation bar) or the preset's left-button binding. // Hold Space for a momentary pan: the hand tool of design apps. It overrides whatever the
let primary_nav = app.viewport.nav.or(app.viewport.primary_nav); // left button would otherwise do - selecting, dragging geometry, the preset's binding or the
// navigation bar's fixed mode - and lasts only while the key is held. Space still types in a
// text field, and while a dimension's text is being edited, so those keep the keyboard.
let space_pan = (inside || primary_down)
&& !ui.ctx().egui_wants_keyboard_input()
&& !crate::dim_view::editing()
&& ui.input(|i| i.key_down(egui::Key::Space));
if space_pan {
ui.ctx().set_cursor_icon(if primary_down { egui::CursorIcon::Grabbing } else { egui::CursorIcon::Grab });
}
// The fixed navigation mode (navigation bar), a held Space, or the preset's left-button binding.
let primary_nav = if space_pan { Some(NavMode::Pan) } else { app.viewport.nav.or(app.viewport.primary_nav) };
let button = if middle { let button = if middle {
Some(crate::prefs::NavButton::Middle) Some(crate::prefs::NavButton::Middle)
} else if secondary_down { } else if secondary_down {
+1 -1
View File
@@ -1139,7 +1139,7 @@ fn page(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
note(ui, crate::i18n::tr("nav.touchpad.help")); note(ui, crate::i18n::tr("nav.touchpad.help"));
} else { } else {
let snap_help = if app.preferences.nav == OPENSCAD_NAV { "" } else { "; hold Shift during orbit to snap" }; let snap_help = if app.preferences.nav == OPENSCAD_NAV { "" } else { "; hold Shift during orbit to snap" };
let raw_help = format!("{}; Ctrl+left drag on empty space orbits{snap_help}; the wheel zooms", nav_preset(&app.preferences.nav).help); let raw_help = format!("{}; Ctrl+left drag on empty space orbits{snap_help}; hold Space to pan; the wheel zooms", nav_preset(&app.preferences.nav).help);
note(ui, crate::i18n::tr_label(&raw_help)); note(ui, crate::i18n::tr_label(&raw_help));
} }
ui.label(crate::i18n::tr_label("Context menu")); ui.label(crate::i18n::tr_label("Context menu"));
+34
View File
@@ -4111,6 +4111,40 @@ mod tests {
app app
} }
fn space(pressed: bool) -> egui::Event {
egui::Event::Key { key: egui::Key::Space, physical_key: None, pressed, repeat: false, modifiers: egui::Modifiers::default() }
}
/// Holding Space is a momentary pan: the drag moves the view rather than the geometry under the
/// pointer, in every preset. The point is over the sketch's own point, so without Space this
/// drag would drag it (and the undo stack would grow).
#[test]
fn holding_space_pans_instead_of_dragging_geometry() {
for preset in crate::prefs::NAV_PRESETS.iter().map(|p| p.id) {
let ctx = egui::Context::default();
let mut app = hover_app(false);
app.preferences.nav = preset.into();
app.ui.show_origin = false;
hover_frame(&mut app, &ctx, vec![]);
let point = projection(&app, app.viewport.rect.unwrap()).to_screen(Vec3::ZERO).unwrap();
let before = app.cam;
let undo = app.session.undo.len();
let press = |pressed: bool, at: Pos2| egui::Event::PointerButton {
pos: at,
button: egui::PointerButton::Primary,
pressed,
modifiers: egui::Modifiers::default(),
};
hover_frame(&mut app, &ctx, vec![space(true)]);
hover_frame(&mut app, &ctx, vec![space(true), egui::Event::PointerMoved(point), press(true, point)]);
hover_frame(&mut app, &ctx, vec![space(true), egui::Event::PointerMoved(point + vec2(40.0, 25.0))]);
assert_ne!(app.cam, before, "{preset}: holding Space must pan the view");
assert_eq!(app.session.undo.len(), undo, "{preset}: holding Space must not edit the design");
assert!(app.viewport.point_drag.is_none(), "{preset}: holding Space must not drag geometry");
hover_frame(&mut app, &ctx, vec![press(false, point + vec2(40.0, 25.0)), space(false)]);
}
}
#[test] #[test]
fn ctrl_left_drag_from_empty_space_orbits_in_every_preset() { fn ctrl_left_drag_from_empty_space_orbits_in_every_preset() {
for preset in crate::prefs::NAV_PRESETS.iter().map(|p| p.id) { for preset in crate::prefs::NAV_PRESETS.iter().map(|p| p.id) {