Ctrl+left drag on empty space orbits in every navigation preset (#471)
A left press with Ctrl (or Cmd) held, no Shift or Alt, on empty space orbits for that press, unless the preset binds Ctrl+left itself. It only orbits once the pointer moves: a Ctrl+click still adds to the selection, and Ctrl+drag on sketch geometry still drags it without snapping. Works with a tool active, without adding geometry. Ctrl+box selection from empty space becomes the orbit; Shift+box selection still adds. Preferences › Navigation and the UI tour mention it. Test drives every preset through real pointer events. Claude-Session: https://claude.ai/code/session_01YDDPmNdRoBgkHcLeUDKVfb Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
fc1d70cc3b
commit
1ae6e9beb4
3 files changed
+74
-12
No files matched your search
@@ -816,7 +816,7 @@ fn page(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
});
|
||||
ui.end_row();
|
||||
let snap_help = if app.preferences.nav == OPENSCAD_NAV { "" } else { "; hold Shift during orbit to snap" };
|
||||
note(ui, &format!("{}{snap_help}; the wheel zooms", nav_preset(&app.preferences.nav).help));
|
||||
note(ui, &format!("{}; Ctrl+left drag on empty space orbits{snap_help}; the wheel zooms", nav_preset(&app.preferences.nav).help));
|
||||
ui.label("Context menu");
|
||||
ui.checkbox(&mut app.preferences.radial_context_menu, "Radial context menu");
|
||||
ui.end_row();
|
||||
|
||||
@@ -1726,20 +1726,24 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
// A left press with the preset's keys (Alias: Shift+Alt) navigates until the button is let
|
||||
// go (kept through the release frame, so the end of the drag doesn't select either).
|
||||
if primary_pressed && inside {
|
||||
let openscad_orbit = app.preferences.nav == crate::prefs::OPENSCAD_NAV
|
||||
&& app.tool.is_none()
|
||||
&& app.dialog.is_none()
|
||||
&& app.viewport.context_menu.is_none()
|
||||
&& !mods.shift
|
||||
&& hover.is_some_and(|p| {
|
||||
let proj = projection(app, rect);
|
||||
pick(app, &proj, p).is_empty() && crate::dim_view::hit(p).is_none() && crate::sketch_tools::glyph_at(p).is_none()
|
||||
});
|
||||
app.viewport.primary_nav = if app.preferences.nav != crate::prefs::OPENSCAD_NAV || openscad_orbit {
|
||||
let empty_space = |app: &SolveApp| {
|
||||
app.viewport.context_menu.is_none()
|
||||
&& hover.is_some_and(|p| {
|
||||
let proj = projection(app, rect);
|
||||
pick(app, &proj, p).is_empty() && crate::dim_view::hit(p).is_none() && crate::sketch_tools::glyph_at(p).is_none()
|
||||
})
|
||||
};
|
||||
let openscad_orbit =
|
||||
app.preferences.nav == crate::prefs::OPENSCAD_NAV && app.tool.is_none() && app.dialog.is_none() && !mods.shift && empty_space(app);
|
||||
let preset = if app.preferences.nav != crate::prefs::OPENSCAD_NAV || openscad_orbit {
|
||||
crate::prefs::nav_mode(app, crate::prefs::NavButton::Left, mods)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Ctrl+left drag from empty space orbits in every preset (Cmd on macOS). A Ctrl+click
|
||||
// still selects, and Ctrl on geometry still drags it without snapping.
|
||||
let ctrl_orbit = (mods.ctrl || mods.command) && !mods.shift && !mods.alt && empty_space(app);
|
||||
app.viewport.primary_nav = preset.or(ctrl_orbit.then_some(NavMode::Orbit));
|
||||
} else if !primary_down && !primary_released {
|
||||
app.viewport.primary_nav = None;
|
||||
}
|
||||
@@ -3094,6 +3098,63 @@ mod tests {
|
||||
app
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctrl_left_drag_from_empty_space_orbits_in_every_preset() {
|
||||
for preset in crate::prefs::NAV_PRESETS.iter().map(|p| p.id) {
|
||||
for action in ["orbit", "tool", "point", "click"] {
|
||||
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 (start, end) = match action {
|
||||
"point" => (point, point + vec2(30.0, 20.0)),
|
||||
"click" => (point, point),
|
||||
_ => (point - vec2(60.0, 60.0), point - vec2(20.0, 30.0)),
|
||||
};
|
||||
if action == "tool" {
|
||||
app.start("sketch.line");
|
||||
}
|
||||
let before = app.cam;
|
||||
let undo = app.session.undo.len();
|
||||
let modifiers = egui::Modifiers { ctrl: true, command: !cfg!(target_os = "macos"), ..Default::default() };
|
||||
let frame = |app: &mut SolveApp, mut events: Vec<egui::Event>| {
|
||||
events.insert(0, egui::Event::ModifiersChanged(modifiers));
|
||||
hover_frame(app, &ctx, events);
|
||||
};
|
||||
frame(
|
||||
&mut app,
|
||||
vec![
|
||||
egui::Event::PointerMoved(start),
|
||||
egui::Event::PointerButton { pos: start, button: egui::PointerButton::Primary, pressed: true, modifiers },
|
||||
],
|
||||
);
|
||||
frame(&mut app, vec![egui::Event::PointerMoved(end)]);
|
||||
frame(&mut app, vec![]);
|
||||
match action {
|
||||
"orbit" | "tool" => {
|
||||
assert_ne!(app.cam, before, "{preset}: Ctrl+left drag on empty space orbits ({action})");
|
||||
assert!(app.viewport.boxsel.is_none(), "{preset}: no box selection while orbiting");
|
||||
}
|
||||
"point" => {
|
||||
assert_eq!(app.cam, before, "{preset}: Ctrl+drag on sketch geometry is not an orbit");
|
||||
assert!(app.viewport.point_drag.is_some(), "{preset}: Ctrl+drag still drags the point");
|
||||
}
|
||||
_ => assert_eq!(app.cam, before, "{preset}: a Ctrl+click does not orbit"),
|
||||
}
|
||||
frame(&mut app, vec![egui::Event::PointerButton { pos: end, button: egui::PointerButton::Primary, pressed: false, modifiers }]);
|
||||
frame(&mut app, vec![]);
|
||||
match action {
|
||||
"click" => assert!(!app.session.selection.is_empty(), "{preset}: Ctrl+click still selects"),
|
||||
"tool" => assert_eq!(app.session.undo.len(), undo, "{preset}: the orbit drag adds no geometry"),
|
||||
"orbit" => assert!(app.session.selection.is_empty(), "{preset}: the end of the orbit selects nothing"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openscad_left_drag_preserves_editing_and_selection() {
|
||||
for action in ["point", "box", "tool"] {
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
trackpad, pinch zooms and two-finger scroll pans (Navigation › Two-finger scroll pans;
|
||||
off: scroll zooms like the wheel); mouse wheels zoom and the thumb wheel pans sideways.
|
||||
Shift+middle orbits freely
|
||||
in the default and Inventor presets. Shift+right-drag snaps to the nearest standard or
|
||||
in the default and Inventor presets. In every preset, Ctrl+left drag (Cmd on macOS) that starts
|
||||
on empty space orbits; a Ctrl+click still adds to the selection. 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.
|
||||
|
||||
Reference in new issue
Block a user