feat(snap): Shift+RMB one-shot snap override menu (#337)

Shift+right-click in the viewport opens a cursor-anchored grid of snap
icons (names as hover tooltips). Picking one engages a one-shot
override: only that snap applies — even with running osnap off — until
the next point pick commits, which restores the saved configuration.
Esc drops an unconsumed override; an outside click closes the menu.
The status-bar snap toggles are untouched — this is a separate,
temporary mechanism.

Covers items 1 and 2 of #337 (mid-between-two-points still pending).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Fable 5 committed 2026-07-22 12:27:27 +03:00
1 parent d2be0c9bea
commit dd40e1f632
7 files changed
+166

No files matched your search

+4
View File
@@ -431,6 +431,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
}
pub(super) fn on_command_escape(&mut self) -> Task<Message> {
// Esc drops an unconsumed one-shot snap override and closes
// its menu (#337). Falls through — Esc keeps its usual effect.
self.snap_override_popup = None;
self.snapper.clear_override();
// Open MText editor swallows Escape (cancel without committing).
if self.mtext_editor.is_some() {
self.mtext_cancel();
+27
View File
@@ -292,6 +292,24 @@ impl OpenCADStudio {
}
}
Message::SnapOverridePick(t) => {
self.snap_override_popup = None;
self.snapper.set_override(t);
let label = crate::snap::ALL_SNAP_MODES
.iter()
.find(|(m, _, _)| *m == t)
.map(|(_, _, l)| *l)
.unwrap_or("Snap");
self.command_line
.push_info(&format!("Snap override: {label} (next pick only)."));
Task::none()
}
Message::SnapOverrideClose => {
self.snap_override_popup = None;
Task::none()
}
Message::FileDropped(path) => {
// Desktop drag & drop (#344): accept the formats the Open
// dialog accepts — a drop has no picker filter, so anything
@@ -1367,6 +1385,15 @@ impl OpenCADStudio {
Message::ViewportRightPress => {
let i = self.active_tab;
self.ribbon.close_dropdown();
// Shift+RMB: the one-shot snap override menu at the cursor —
// pick a snap for just the next point, then it expires (#337).
if self.shift_down {
let pos = self.tabs[i].scene.selection.borrow().last_move_pos;
if let Some(p) = pos {
self.snap_override_popup = Some(p);
}
return Task::none();
}
let mut sel = self.tabs[i].scene.selection.borrow_mut();
let Some(p) = sel.last_move_pos else {
return Task::none();
+3
View File
@@ -2175,6 +2175,9 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
}
}
self.last_point = Some(world_pt);
// The one-shot snap override is spent by this pick —
// restore the running osnap configuration (#337).
self.snapper.clear_override();
self.dyn_user_reshaped = false;
self.sync_dyn_fields();
self.reset_tracking_after_point();