feat(ui): feature-parity context menu — Snap Overrides, transparent Pan/Zoom, ARC keyword flow

The command menu now follows the commercial solutions' layout row for row: Enter, Cancel,
Recent Input ▸, the command's keywords, Snap Overrides ▸ (every object snap
with its glyph, Mid Between 2 Points, None, Osnap Settings…), then Pan and
Zoom. Pan / Zoom run transparently while a command is active ('PAN,
'ZOOM …): navigation modes leave the command untouched and an interactive
ZOOM Window parks it and resumes it afterwards. Rows carry an icon gutter
so labels align; the Enter row shows a default value ("<0.5000>") as its
hint instead of a second finish row.

ARC gets the commercial solutions' prompt flow with all keywords: start [Center], second
point [Center/End], end; after Center: end [Angle/chord Length]; after
End: center [Angle/Direction/Radius]; Enter continues tangent. The
previous default (Center/Start/End with method codes) stays as ARC_CSE.

Object snaps gain a one-shot "None" override. New strings in all locales.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-09-17 12:05:15 +03:00
1 parent 96df4928c7
commit 02cd98e790
34 files changed
+1305 -67

No files matched your search

+4 -4
View File
@@ -311,8 +311,8 @@ impl OpenCADStudio {
}
"ARC" => {
use crate::modules::draw::draw::arc::Arc3PCommand;
let new_cmd = Arc3PCommand::new();
use crate::modules::draw::draw::arc::ArcCommand;
let new_cmd = ArcCommand::new();
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
@@ -323,8 +323,8 @@ impl OpenCADStudio {
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
"ARC_CSE" => {
use crate::modules::draw::draw::arc::ArcCommand;
let new_cmd = ArcCommand::new();
use crate::modules::draw::draw::arc::ArcCSECommand;
let new_cmd = ArcCSECommand::new();
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
+40 -5
View File
@@ -63,6 +63,12 @@ impl OpenCADStudio {
// routing, so every path below (Start-tab gate, plugins, all dispatch
// families, the Repeat menu) sees the canonical command. Arguments after
// the first space are left untouched. A non-alias passes through as-is.
// AutoCAD's transparent prefix: `'PAN` / `'ZOOM` run in the middle
// of another command and hand control back to it afterwards.
let (cmd, quoted) = match cmd.trim().strip_prefix('\'') {
Some(rest) => (rest.trim(), true),
None => (cmd, false),
};
let resolved = self.resolve_alias(cmd);
let cmd = resolved.as_deref().unwrap_or(cmd);
if is_spacemouse_command(cmd) {
@@ -72,11 +78,22 @@ impl OpenCADStudio {
// already running: pressing F8 partway through a LINE means "constrain
// the rest of this line", not "abandon it". Everything below tears the
// running command down, so a transparent one skips straight past it.
// (#677)
if is_transparent(cmd) {
return self
.dispatch_families(cmd, i)
.unwrap_or_else(Task::none);
// (#677) A quoted navigation command does the same; an interactive
// one (ZOOM Window) parks the running command and resumes it when the
// zoom ends, as does a ZOOM the zoom prompt itself hands off to.
let transparent = is_transparent(cmd)
|| (quoted && is_transparent_capable(cmd))
|| (self.tabs[i].transparent_resume && cmd.starts_with("ZOOM"));
if transparent {
if zoom_prompts(cmd) && self.tabs[i].active_cmd.is_some() {
self.tabs[i].suspended_cmd = self.tabs[i].active_cmd.take();
self.tabs[i].transparent_resume = true;
self.tabs[i].scene.clear_preview_wire();
}
let task = self.dispatch_families(cmd, i).unwrap_or_else(Task::none);
// A one-shot zoom is over already — resume straight away.
self.resume_transparent_parent(i);
return task;
}
// A new command abandons any grip edit and its numeric input.
let had_pending_grip_input = self.grip_pending.take().is_some();
@@ -115,6 +132,9 @@ impl OpenCADStudio {
// template-property override too (#239).
self.restore_add_selected_defaults();
}
// A command parked behind a transparent zoom / MTP goes with it.
self.tabs[i].suspended_cmd = None;
self.tabs[i].transparent_resume = false;
// Starting any command leaves interactive navigation modes (their own
// command arms below re-enable the selected one).
self.tabs[i].pan_mode = false;
@@ -275,6 +295,21 @@ impl OpenCADStudio {
/// point, so F8 during a MOVE both ended the move and made the dragged ghost
/// vanish. Nothing here starts a command, opens a document or reads geometry,
/// so there is nothing for the teardown to protect. (#677)
/// Commands that may run transparently (`'PAN`, `'ZOOM …`): the navigation
/// set, which never edits the drawing.
fn is_transparent_capable(cmd: &str) -> bool {
cmd == "PAN" || cmd == "ZOOM" || cmd.starts_with("ZOOM ") || matches!(cmd, "ZW" | "ZE" | "ZA" | "ZP" | "ZI" | "ZO" | "ZD" | "ZEA" | "ZOBJ")
}
/// ZOOM forms that install an interactive prompt (window corners, object
/// pick) and so need the running command parked while they last.
fn zoom_prompts(cmd: &str) -> bool {
matches!(
cmd,
"ZOOM" | "ZOOM WINDOW" | "ZOOM W" | "ZW" | "ZOOM OBJECT" | "ZOOM O" | "ZOBJ"
)
}
pub fn is_transparent(cmd: &str) -> bool {
is_spacemouse_command(cmd)
|| matches!(