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 <[email protected]>
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

+32
View File
@@ -137,6 +137,12 @@ pub struct Snapper {
/// direction + point under the cursor, when it was first hovered, and
/// whether this dwell has already fired (so it acquires/toggles once).
parallel_dwell: Option<(Vec3, Vec3, Instant, bool)>,
/// One-shot snap override (Shift+RMB menu): the (enabled set, snap on)
/// pair saved when the override engaged, restored when it is consumed by
/// the next point pick or cancelled. While `Some`, `enabled` holds only
/// the override mode and `snap_enabled` is forced on — the override works
/// even with running osnap off (#337).
override_saved: Option<(HashSet<SnapType>, bool)>,
}
impl Default for Snapper {
@@ -164,6 +170,7 @@ impl Default for Snapper {
from_point: None,
parallel_ref: None,
parallel_dwell: None,
override_saved: None,
}
}
}
@@ -178,6 +185,30 @@ impl Snapper {
self.enabled.contains(&t)
}
/// Engage a one-shot snap override (Shift+RMB menu): only `t` snaps, even
/// with running osnap off, until `clear_override` restores the saved
/// configuration. Re-picking while active replaces the mode but keeps the
/// original saved state.
pub fn set_override(&mut self, t: SnapType) {
if self.override_saved.is_none() {
self.override_saved = Some((self.enabled.clone(), self.snap_enabled));
}
self.enabled = std::iter::once(t).collect();
self.snap_enabled = true;
}
/// Restore the pre-override snap configuration. No-op when inactive.
pub fn clear_override(&mut self) {
if let Some((enabled, on)) = self.override_saved.take() {
self.enabled = enabled;
self.snap_enabled = on;
}
}
pub fn override_active(&self) -> bool {
self.override_saved.is_some()
}
/// Whether temporary tracking points are being acquired and drawn: OTRACK
/// on, or the Extension object snap on. Extension tracks a segment's line
/// only from an acquired endpoint, and works independently of OTRACK's
@@ -748,6 +779,7 @@ impl Snapper {
from_point: None,
parallel_ref: None,
parallel_dwell: None,
override_saved: None,
};
// Tangent-only: Grid is disabled here, so the grid basis is irrelevant.
tmp.snap(