feat: add COMMANDLINEFADETIME sysvar for overlay history fade

Controls how long command-line history lines stay visible, in ms (0-60000, default 3000). Bare form stays active until a value or Esc.
This commit is contained in:
Karim Jerbi committed 2026-09-06 02:13:09 +01:00
1 parent b5eabae712
commit 0dab3ec54a
6 files changed
+150 -20

No files matched your search

+1
View File
@@ -534,6 +534,7 @@ inventory::submit!(crate::command::CommandRegistration {
"SKPOLY",
"SKTOLERANCE",
"CLIPROMPTLINES",
"COMMANDLINEFADETIME",
"COLORTHEME",
"SELECTIONAREA",
"SELECTIONAREAOPACITY",
+54 -1
View File
@@ -801,6 +801,20 @@ impl OpenCADStudio {
}
}
// ── COMMANDLINEFADETIME — command-line history fade time ───
// How long history lines stay visible above the command window,
// in ms (0–60000, default 3000). Bare form prompts for a value
// (Enter reports current); 0 hides transient overlay lines.
"COMMANDLINEFADETIME" => {
use crate::command::ValuePromptCommand;
let c = ValuePromptCommand::new(
"COMMANDLINEFADETIME",
"COMMANDLINEFADETIME overlay fade time in ms (0-60000, 0 = hide) <Enter reports>:",
);
self.command_line.push_info(&c.prompt());
self.tabs[i].active_cmd = Some(Box::new(c));
}
// ── SETVAR — read / write system variables ───────────────────
// SETVAR <name> → report the value
// SETVAR <name> <value> → set it
@@ -821,6 +835,7 @@ impl OpenCADStudio {
| "SNAPANG"
| "TEXTFILL"
| "CLIPROMPTLINES"
| "COMMANDLINEFADETIME"
| "ATTREQ"
| "ATTDIA"
| "DIMASSOC"
@@ -925,7 +940,7 @@ impl OpenCADStudio {
let value = it.next().map(|s| s.trim().to_string());
if name.is_empty() || name == "?" {
self.command_line.push_info(
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE COLORTHEME SELECTIONAREA SELECTIONAREAOPACITY SELECTIONEFFECT SELECTIONEFFECTCOLOR WINDOWSAREACOLOR CROSSINGAREACOLOR SELECTIONPREVIEW GRIPSIZE GRIPCOLOR GRIPHOT GRIPHOVER | CLAYER CELTYPE TEXTSTYLE (read-only)",
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES COMMANDLINEFADETIME ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE COLORTHEME SELECTIONAREA SELECTIONAREAOPACITY SELECTIONEFFECT SELECTIONEFFECTCOLOR WINDOWSAREACOLOR CROSSINGAREACOLOR SELECTIONPREVIEW GRIPSIZE GRIPCOLOR GRIPHOT GRIPHOVER | CLAYER CELTYPE TEXTSTYLE (read-only)",
);
} else {
if matches!(name.as_str(), "SHOWHIST" | "SOLIDHIST") {
@@ -1503,6 +1518,20 @@ impl OpenCADStudio {
false,
)),
},
"COMMANDLINEFADETIME" => match &value {
Some(v) => match v.parse::<i32>() {
Ok(n) if (0..=60000).contains(&n) => {
self.commandline_fade_ms = n;
self.command_line.set_commandline_fade_ms(n as u32);
Ok((format!("COMMANDLINEFADETIME = {n}"), true))
}
_ => Err("SETVAR: integer from 0 to 60000 required.".into()),
},
None => Ok((
format!("COMMANDLINEFADETIME = {}", self.commandline_fade_ms),
false,
)),
},
"ATTREQ" => match &value {
Some(v) => parse_bool(v)
.map(|b| {
@@ -2083,6 +2112,7 @@ impl OpenCADStudio {
| "CURSORTYPE"
| "SNAPANG"
| "CLIPROMPTLINES"
| "COMMANDLINEFADETIME"
| "COLORTHEME"
| "SELECTIONAREA"
| "SELECTIONAREAOPACITY"
@@ -3121,4 +3151,27 @@ mod tests {
assert_eq!(app.model_space.grip_hot, 0);
assert_eq!(app.model_space.grip_hover, 0);
}
#[test]
fn bare_commandlinefadetime_stays_active_until_value_or_cancel() {
let mut app = fresh_app();
let _ = app.run_command_line("COMMANDLINEFADETIME");
// Like RECTANGLE and PICKADD: bare form installs an active command
// whose pinned prompt stays until a value, Enter, or Esc.
assert!(
app.tabs[app.active_tab].active_cmd.is_some(),
"bare COMMANDLINEFADETIME must stay active"
);
// A value sets the variable and ends the command.
let _ = app.run_command_line("COMMANDLINEFADETIME 5000");
assert_eq!(app.commandline_fade_ms, 5000);
assert_eq!(app.command_line.commandline_fade_ms(), 5000);
assert!(app.tabs[app.active_tab].active_cmd.is_none());
// Bare again, then Esc cancels without changing the value.
let _ = app.run_command_line("COMMANDLINEFADETIME");
assert!(app.tabs[app.active_tab].active_cmd.is_some());
let _ = app.update(crate::app::Message::CommandEscape);
assert!(app.tabs[app.active_tab].active_cmd.is_none());
assert_eq!(app.commandline_fade_ms, 5000);
}
}
+3
View File
@@ -534,6 +534,8 @@ pub(super) struct OpenCADStudio {
/// CLIPROMPTLINES: how many temporary prompt lines for a single command
/// are displayed above the command window (0–50, Registry, default 3).
cliprompt_lines: i32,
/// COMMANDLINEFADETIME: overlay history visible time in ms (0–60000, default 3000).
commandline_fade_ms: i32,
/// MRU list of block names inserted via INSERT, most recent first, capped to 20.
block_mru: Vec<String>,
/// Insertion frequency per block name (uppercase key → count), capped.
@@ -3363,6 +3365,7 @@ impl OpenCADStudio {
default_bg_color: None,
default_paper_bg_color: None,
cliprompt_lines: 3,
commandline_fade_ms: 3000,
block_mru: Vec::new(),
block_freq: std::collections::HashMap::new(),
#[cfg(not(target_arch = "wasm32"))]
+24
View File
@@ -243,6 +243,13 @@ pub struct UserSettings {
/// are displayed above the command window (0–50, Registry, default 3).
#[serde(default = "default_clipromptlines", deserialize_with = "deserialize_clipromptlines")]
pub cliprompt_lines: i32,
/// COMMANDLINEFADETIME: how long command-line overlay history lines stay
/// visible, in milliseconds (0–60000, default 3000). 0 skips transient lines.
#[serde(
default = "default_commandline_fade_ms",
deserialize_with = "deserialize_commandline_fade_ms"
)]
pub commandline_fade_ms: i32,
/// Most-recently-inserted block names, most recent first, capped to 20.
/// Used to rank INSERT suggestions without touching the drawing file.
#[serde(default)]
@@ -256,6 +263,22 @@ fn default_clipromptlines() -> i32 {
3
}
fn default_commandline_fade_ms() -> i32 {
3000
}
fn deserialize_commandline_fade_ms<'de, D>(de: D) -> Result<i32, D::Error>
where
D: serde::Deserializer<'de>,
{
let v = i32::deserialize(de).unwrap_or(3000);
Ok(v.clamp(0, 60000))
}
pub fn clamp_commandline_fade_ms(v: i32) -> i32 {
v.clamp(0, 60000)
}
fn default_dimension_continue_mode() -> i16 {
1
}
@@ -312,6 +335,7 @@ impl Default for UserSettings {
paper_bg_color: None,
language: crate::i18n::Language::default(),
cliprompt_lines: 3,
commandline_fade_ms: 3000,
block_mru: Vec::new(),
block_freq: std::collections::HashMap::new(),
}
+8
View File
@@ -397,6 +397,9 @@ impl OpenCADStudio {
paper_bg_color: None,
language: self.language,
cliprompt_lines: crate::app::settings::clamp_clipromptlines(self.cliprompt_lines),
commandline_fade_ms: crate::app::settings::clamp_commandline_fade_ms(
self.commandline_fade_ms,
),
block_mru: self.block_mru.clone(),
block_freq: self.block_freq.clone(),
}
@@ -462,6 +465,11 @@ impl OpenCADStudio {
self.cliprompt_lines = crate::app::settings::clamp_clipromptlines(s.cliprompt_lines);
self.command_line
.set_cliprompt_lines(self.cliprompt_lines.clamp(0, 50) as u8);
self.commandline_fade_ms =
crate::app::settings::clamp_commandline_fade_ms(s.commandline_fade_ms);
self.command_line.set_commandline_fade_ms(
self.commandline_fade_ms.clamp(0, 60000) as u32,
);
// Block usage: clone but cap to sane sizes (MRU 20, freq map 200)
self.block_mru = s.block_mru.iter().take(20).cloned().collect();
self.block_freq = s