fix(ctx-menu): anchor right-click menu under cursor, share overlay infra

The right-click context menu was being placed via window-relative
coordinates while the cursor position it anchored to was canvas-relative,
so the menu drifted away from the cursor by the size of the surrounding
ribbon / sidebar / status bar.

Move the menu inside `viewport_stack` so its anchor resolves in the same
coordinate space the cursor was captured in. Introduce
`position_canvas_overlay(anchor, panel)` — wraps `panel` in a column+row
of fixed-width spacers and `iced::widget::opaque(panel)` so events on the
menu itself don't fall through to the viewport mouse area underneath.
The multi-functional grip popup is rewritten to use the same helper.

Outside-click dismiss now mirrors the grip popup: `ViewportLeftPress`
checks `selection.context_menu.take()` at the top and returns early
when it was set, so a press that reaches the viewport mouse area
(i.e. outside the opaque panel) closes the menu without running the
rest of the left-click handler. The redundant `context_menu = None`
later in the handler and the now-unused `Message::ViewportContextMenuClose`
are removed.

Two related issues fixed in the same pass:

* The right-drag threshold for orbit was 3 px squared, small enough that
  normal hand jitter between a right-button press and release was
  promoting a click to a drag and suppressing the menu on release.
  Bumped to 8 px squared.
* `Message::ViewportExit` was clearing `context_menu`. With the menu now
  living above the viewport mouse area, the opaque panel capturing the
  cursor fires the underlying area's exit handler — which closed the
  menu the instant it opened. Stop clearing the menu there; outside-click
  dismiss is enough.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.7 committed 2026-05-29 15:58:23 +03:00
1 parent 0954d6d123
commit cf925be861
3 files changed
+77 -60

No files matched your search

+19 -9
View File
@@ -1888,7 +1888,11 @@ impl OpenCADStudio {
if let Some(press) = sel.right_press_pos {
let dx = p.x - press.x;
let dy = p.y - press.y;
if !sel.right_dragging && (dx * dx + dy * dy) > 9.0 {
// 8 px (64 squared) threshold so normal hand jitter
// between a right-button press and release doesn't
// promote a click-and-release to an orbit drag, which
// would suppress the context menu on release.
if !sel.right_dragging && (dx * dx + dy * dy) > 64.0 {
sel.right_dragging = true;
sel.context_menu = None;
}
@@ -2238,7 +2242,11 @@ impl OpenCADStudio {
sel.poly_active = false;
sel.poly_points.clear();
sel.poly_crossing = false;
sel.context_menu = None;
// Don't touch `context_menu` here. ViewportExit also fires
// when an upper overlay (the right-click menu panel) takes
// the cursor, so clearing the menu state on every exit
// would close the menu the moment it opens. Outside-click
// dismiss is handled in `ViewportLeftPress`.
Task::none()
}
@@ -2252,6 +2260,15 @@ impl OpenCADStudio {
self.grip_hover = None;
return Task::none();
}
// Same dismiss-on-outside-click for the right-click
// context menu: its panel is opaque, so a press that
// reaches here is outside the menu.
{
let mut sel = self.tabs[i].scene.selection.borrow_mut();
if sel.context_menu.take().is_some() {
return Task::none();
}
}
let (p, vp_size) = {
let sel = self.tabs[i].scene.selection.borrow();
let p = match sel.last_move_pos {
@@ -2368,7 +2385,6 @@ impl OpenCADStudio {
}
let mut sel = self.tabs[i].scene.selection.borrow_mut();
sel.context_menu = None;
sel.left_down = true;
// Stored in full-canvas space (like ViewportMove's cursor and
// the overlay box / lasso drawing); release maps it into the
@@ -4150,12 +4166,6 @@ impl OpenCADStudio {
iced::clipboard::write(info)
}
Message::ViewportContextMenuClose => {
let i = self.active_tab;
self.tabs[i].scene.selection.borrow_mut().context_menu = None;
Task::none()
}
Message::EnterViewport(handle) => {
let i = self.active_tab;
// Clear paper-space selection before entering model space.