From b691854e7a1868278491f842a99f15394d2dae57 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 27 Jun 2026 16:32:40 +0300 Subject: [PATCH] feat(viewport): RMB 1st=Enter, 2nd=menu (no setting) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the opt-in `rmb_enter` setting (and `RMBENTER` command) with always-on behavior. While a command is active, the first viewport right-click acts as Enter (commit / close) and a second consecutive right-click opens the context menu instead. Idle right-click still opens the menu; right-drag still orbits. The cycle is tracked per tab (`right_click_entered`) and restarts on any other interaction — a left-click pick, a new command, an orbit, or leaving the viewport — so the next right-click is Enter again. Refines #185. Co-Authored-By: Claude Opus 4.8 --- src/app/commands/display.rs | 21 --------------------- src/app/commands/mod.rs | 5 ++++- src/app/mod.rs | 4 ---- src/app/settings.rs | 9 +-------- src/app/update/file.rs | 2 -- src/app/update/mod.rs | 14 +++++++++----- src/app/update/viewport.rs | 7 +++++++ src/scene/pick/selection_state.rs | 5 +++++ 8 files changed, 26 insertions(+), 41 deletions(-) diff --git a/src/app/commands/display.rs b/src/app/commands/display.rs index 345e2b0c4..867a7b207 100644 --- a/src/app/commands/display.rs +++ b/src/app/commands/display.rs @@ -17,27 +17,6 @@ impl OpenCADStudio { .push_output("PAN: drag with the left mouse button. Press Esc to exit."); } - // RMBENTER [ON|OFF] — toggle whether a viewport right-click acts as - // Enter (commit / close) while a command is active. Idle right-click - // still opens the context menu and right-drag still orbits. The - // preference is persisted across runs. - cmd if cmd == "RMBENTER" || cmd.starts_with("RMBENTER ") => { - let arg = cmd - .split_once(' ') - .map(|(_, r)| r.trim().to_uppercase()) - .unwrap_or_default(); - self.rmb_enter = match arg.as_str() { - "ON" | "1" => true, - "OFF" | "0" => false, - _ => !self.rmb_enter, - }; - self.command_line.push_output(if self.rmb_enter { - "RMBENTER on: right-click acts as Enter while a command is active." - } else { - "RMBENTER off: right-click opens the context menu." - }); - } - // ── TABLE cell editing ───────────────────────────────────────────── // TABLE CELL — set text for a cell in the selected Table cmd if cmd.starts_with("TABLE ") => { diff --git a/src/app/commands/mod.rs b/src/app/commands/mod.rs index dc92913d2..e3ec2e159 100644 --- a/src/app/commands/mod.rs +++ b/src/app/commands/mod.rs @@ -67,6 +67,9 @@ impl OpenCADStudio { // Reset the last committed point so the first click of the new command // is not constrained by ortho/polar relative to a previous command's endpoint. self.last_point = None; + // Starting a command restarts the right-click cycle, so its first + // right-click acts as Enter rather than opening the context menu. + self.tabs[i].scene.selection.borrow_mut().right_click_entered = false; // A fresh command starts at the polar/cartesian default — clear // any `,`-driven reshape from a previous command (#35). self.dyn_user_reshaped = false; @@ -208,7 +211,7 @@ inventory::submit!(crate::command::CommandRegistration { "P", "PAN", "PAGESETUP", "PERF", "PERSP", "PLOT", "PLOTSTYLE", "PLOTSTYLEEDITOR", "PLOTSTYLEPANEL", "PR", "PRINT", "PROPERTIES", "PROPS", "PSPACE", "PURGE", "QS", "QSAVE", "QSELECT", - "QUIT", "REDO", "REDRAW", "REDRWALL", "REGEN", "REGENALL", "RENAME", "REPORT", "RMBENTER", + "QUIT", "REDO", "REDRAW", "REDRWALL", "REGEN", "REGENALL", "RENAME", "REPORT", "SA", "SAVE", "SAVEAS", "SCALETEXT", "SELECTALL", "SELECTSIMILAR", "SELSIM", "SHEETSET", "SHORTCUTS", "SOLID", "SSM", "STEPOUT", "STLOUT", "STPOUT", "STYLE", "STYLESMANAGER", "TABLESTYLE", "TOOLPALETTES", "TP", "TS", "U", "UCS", "UCSICON", "UNDERLAY", diff --git a/src/app/mod.rs b/src/app/mod.rs index 5a7ef3c85..a137c0584 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -247,9 +247,6 @@ pub(super) struct OpenCADStudio { dyn_input: bool, /// Controls whether the TEXTEDIT command repeats automatically (0 = Multiple, 1 = Single). pub texteditmode: bool, - /// When true, a viewport right-click acts as Enter while a command is active - /// (idle right-click still opens the context menu). Toggled by `RMBENTER`. - pub rmb_enter: bool, /// Persisted default viewport background, restored from settings and applied /// to every drawing tab (new and opened) so a chosen background survives /// restarts (#188). `None` = the built-in dark-grey / off-white defaults. @@ -1826,7 +1823,6 @@ impl OpenCADStudio { show_grid: false, dyn_input: true, texteditmode: false, - rmb_enter: false, default_bg_color: None, default_paper_bg_color: None, awaiting_vports: false, diff --git a/src/app/settings.rs b/src/app/settings.rs index d9ea5c2c6..e5f834fe2 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -103,10 +103,6 @@ pub struct UserSettings { pub plugin_repos: Vec, /// Controls whether the TEXTEDIT command repeats automatically (0 = Multiple, 1 = Single). pub texteditmode: bool, - /// When true, a right-click in the viewport acts as Enter (commit / close) - /// while a command is active; when idle it still opens the context menu. - /// Toggled by the `RMBENTER` command. Right-drag always orbits. - pub rmb_enter: bool, /// Persisted viewport background colours (0–255 RGB); `None` = app default /// (dark grey model / off-white paper). Applied to every drawing tab on /// launch and to tabs opened later, so a chosen background survives restarts @@ -137,7 +133,6 @@ impl Default for UserSettings { disabled_plugins: Vec::new(), plugin_repos: Vec::new(), texteditmode: false, - rmb_enter: false, bg_color: None, paper_bg_color: None, } @@ -175,7 +170,6 @@ impl UserSettings { "bg_color" => s.bg_color = parse_rgb(val), "paper_bg_color" => s.paper_bg_color = parse_rgb(val), "default_assoc_prompted" => s.default_assoc_prompted = val == "1", - "rmb_enter" => s.rmb_enter = val == "1", "texteditmode" => { if let Some(v) = crate::modules::annotate::textedit::parse_texteditmode(val) @@ -224,7 +218,7 @@ impl UserSettings { .collect::>() .join(","); let body = format!( - "dyn={}\northo={}\npolar={}\npolar_increment_deg={}\nosnap={}\notrack={}\ndefault_assoc_prompted={}\nsnap_modes={}\ndisabled_plugins={}\nplugin_repos={}\ntexteditmode={}\nrmb_enter={}\nbg_color={}\npaper_bg_color={}\n", + "dyn={}\northo={}\npolar={}\npolar_increment_deg={}\nosnap={}\notrack={}\ndefault_assoc_prompted={}\nsnap_modes={}\ndisabled_plugins={}\nplugin_repos={}\ntexteditmode={}\nbg_color={}\npaper_bg_color={}\n", b(self.dyn_input), b(self.ortho), b(self.polar), @@ -236,7 +230,6 @@ impl UserSettings { self.disabled_plugins.join(","), self.plugin_repos.join(","), self.texteditmode, - b(self.rmb_enter), rgb_to_str(self.bg_color), rgb_to_str(self.paper_bg_color), ); diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 0c3f659fb..a6d42d556 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -40,7 +40,6 @@ impl OpenCADStudio { }, plugin_repos: self.plugin_repos.clone(), texteditmode: self.texteditmode, - rmb_enter: self.rmb_enter, bg_color: self.default_bg_color.map(f4_to_u3), paper_bg_color: self.default_paper_bg_color.map(f4_to_u3), } @@ -59,7 +58,6 @@ impl OpenCADStudio { self.disabled_plugins = s.disabled_plugins.iter().cloned().collect(); self.plugin_repos = s.plugin_repos.clone(); self.texteditmode = s.texteditmode; - self.rmb_enter = s.rmb_enter; self.default_bg_color = s.bg_color.map(u3_to_f4); self.default_paper_bg_color = s.paper_bg_color.map(u3_to_f4); // Push the restored background onto every drawing tab that exists now diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 2268394c7..e9b1a1a8c 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -1119,14 +1119,18 @@ impl OpenCADStudio { if !was_click { return Task::none(); } - // A right-click (no orbit). With "RMB = Enter" enabled, a click - // while a command is active fires Enter (commit/close) instead - // of the menu; when idle — or the option is off — it opens the - // context menu as before, so the menu is never lost. - if self.rmb_enter && self.tabs[i].active_cmd.is_some() { + // A right-click (no orbit). While a command is active the first + // right-click acts as Enter (commit / close); a second + // consecutive right-click opens the context menu instead. When + // idle it always opens the menu. (Right-drag, handled above, + // always orbits.) Any other interaction — a left-click pick or a + // new command — resets the cycle so the next right-click is Enter. + if self.tabs[i].active_cmd.is_some() && !sel.right_click_entered { + sel.right_click_entered = true; drop(sel); return self.update(Message::CommandFinalize); } + sel.right_click_entered = false; sel.context_menu = Some(click_pos); sel.draworder_submenu = false; Task::none() diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index e854b8e7e..0608d36cf 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -585,6 +585,8 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { if held && moved { sel.right_dragging = true; sel.context_menu = None; + // An orbit restarts the right-click cycle. + sel.right_click_entered = false; // Start the orbit from the current position so the // view doesn't jump by the pre-threshold movement. sel.right_last_pos = Some(p); @@ -1226,6 +1228,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { sel.right_press_time = None; sel.right_last_pos = None; sel.right_dragging = false; + sel.right_click_entered = false; sel.middle_down = false; sel.middle_last_pos = None; sel.box_anchor = None; @@ -1440,6 +1443,10 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { pub(super) fn on_viewport_left_press(&mut self) -> Task { let i = self.active_tab; + // A left-click during a command resets the right-click cycle, so + // the next right-click acts as Enter again rather than opening + // the context menu. + self.tabs[i].scene.selection.borrow_mut().right_click_entered = false; // A click in the viewport dismisses any open ribbon dropdown // (e.g. the annotation style combo), which has no backdrop of // its own to catch outside clicks. diff --git a/src/scene/pick/selection_state.rs b/src/scene/pick/selection_state.rs index 9c993b545..dcd031d6b 100644 --- a/src/scene/pick/selection_state.rs +++ b/src/scene/pick/selection_state.rs @@ -27,6 +27,11 @@ pub struct SelectionState { pub right_press_time: Option, pub right_dragging: bool, pub right_last_pos: Option, + /// While a command is active, a right-click acts as Enter; the *next* + /// consecutive right-click opens the context menu instead. This tracks + /// whether the previous right-click already fired Enter. Reset by any + /// other interaction (left-click pick, a new command) and on viewport exit. + pub right_click_entered: bool, pub middle_down: bool, pub middle_last_pos: Option, pub middle_last_press_time: Option,