feat(viewport): RMB=Enter option + timed right-click

Adds a configurable right-mouse-button behavior without disturbing orbit
or the context menu.

- New persisted setting `rmb_enter` (command `RMBENTER [ON|OFF]`, default
  off): when on, a right-click while a command is active acts as Enter
  (commit/close); when idle it still opens the context menu. Right-drag
  always orbits, so nothing is lost.
- Right-click vs orbit is now time-based: orbit engages only after the
  button is held >=150ms and the pointer moves, so a quick right-click is
  no longer swallowed by slight pointer jitter (was an 8px distance gate).
- The right-click Repeat menu now lists commands from every source
  (command line, ribbon, context menu, shortcuts) via a unified
  recents list recorded at the dispatch choke point, instead of only
  typed commands.

Closes #185

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-27 16:16:08 +03:00
commit 098585bb2d
10 changed files with 157 additions and 53 deletions

View file

@ -103,6 +103,10 @@ pub struct UserSettings {
pub plugin_repos: Vec<String>,
/// 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
@ -133,6 +137,7 @@ impl Default for UserSettings {
disabled_plugins: Vec::new(),
plugin_repos: Vec::new(),
texteditmode: false,
rmb_enter: false,
bg_color: None,
paper_bg_color: None,
}
@ -170,6 +175,7 @@ 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)
@ -218,7 +224,7 @@ impl UserSettings {
.collect::<Vec<_>>()
.join(",");
let body = format!(
"dyn={}\northo={}\npolar={}\npolar_increment_deg={}\nosnap={}\notrack={}\ndefault_assoc_prompted={}\nsnap_modes={}\ndisabled_plugins={}\nplugin_repos={}\ntexteditmode={}\nbg_color={}\npaper_bg_color={}\n",
"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",
b(self.dyn_input),
b(self.ortho),
b(self.polar),
@ -230,6 +236,7 @@ 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),
);