diff --git a/src/app/automation.rs b/src/app/automation.rs index 2fddea27c..6598a0652 100644 --- a/src/app/automation.rs +++ b/src/app/automation.rs @@ -497,6 +497,27 @@ mod tests { ); } + #[test] + fn value_prompt_commands_inline_args() { + // A single-value setting command entered with its value on one line + // drives the interactive front-end (start + value step) and applies via + // the inline handler. (F4) + let mut app = OpenCADStudio::new_for_test(); + app.automation_op(r#"{"op":"new"}"#); + app.automation_op(r#"{"op":"run","cmd":"PDMODE 3"}"#); + app.automation_op(r#"{"op":"run","cmd":"LTSCALE 2.5"}"#); + let i = app.active_tab; + let h = &app.tabs[i].scene.document.header; + assert_eq!(h.point_display_mode, 3, "PDMODE 3 should set point mode"); + assert!( + (h.linetype_scale - 2.5).abs() < 1e-9, + "LTSCALE 2.5 should set scale, got {}", + h.linetype_scale + ); + // No command should be left dangling. + assert!(app.tabs[i].active_cmd.is_none(), "command must have finished"); + } + #[test] fn save_then_open_round_trips() { let mut app = OpenCADStudio::new_for_test(); diff --git a/src/app/commands.rs b/src/app/commands.rs index 1a0ac49c0..aa09970f9 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -4944,7 +4944,13 @@ impl OpenCADStudio { } } } - cmd if cmd == "LTSCALE" || cmd.starts_with("LTSCALE ") => { + "LTSCALE" => { + use crate::command::ValuePromptCommand; + let c = ValuePromptCommand::new("LTSCALE", "LTSCALE new global line-type scale:"); + self.command_line.push_info(&c.prompt()); + self.tabs[i].active_cmd = Some(Box::new(c)); + } + cmd if cmd.starts_with("LTSCALE ") => { let val_str = cmd.trim_start_matches("LTSCALE").trim(); if val_str.is_empty() { let v = self.tabs[i].scene.document.header.linetype_scale; @@ -4964,7 +4970,16 @@ impl OpenCADStudio { self.command_line.push_error("Usage: LTSCALE [value]"); } } - cmd if cmd == "PDMODE" || cmd.starts_with("PDMODE ") => { + "PDMODE" => { + use crate::command::ValuePromptCommand; + let c = ValuePromptCommand::new( + "PDMODE", + "PDMODE new value [0=dot 1=none 2=+ 3=x 4=tick; +32 circle +64 square]:", + ); + self.command_line.push_info(&c.prompt()); + self.tabs[i].active_cmd = Some(Box::new(c)); + } + cmd if cmd.starts_with("PDMODE ") => { let val_str = cmd.trim_start_matches("PDMODE").trim(); if val_str.is_empty() { let v = self.tabs[i].scene.document.header.point_display_mode; @@ -4999,7 +5014,16 @@ impl OpenCADStudio { .push_error("Requires 0 OR 1 OR MULTIPLE OR SINGLE"); } } - cmd if cmd == "PDSIZE" || cmd.starts_with("PDSIZE ") => { + "PDSIZE" => { + use crate::command::ValuePromptCommand; + let c = ValuePromptCommand::new( + "PDSIZE", + "PDSIZE new point size (0 = 5% of viewport, <0 = absolute):", + ); + self.command_line.push_info(&c.prompt()); + self.tabs[i].active_cmd = Some(Box::new(c)); + } + cmd if cmd.starts_with("PDSIZE ") => { let val_str = cmd.trim_start_matches("PDSIZE").trim(); if val_str.is_empty() { let v = self.tabs[i].scene.document.header.point_display_size; @@ -5057,7 +5081,16 @@ impl OpenCADStudio { } } } - cmd if cmd == "CELTSCALE" || cmd.starts_with("CELTSCALE ") => { + "CELTSCALE" => { + use crate::command::ValuePromptCommand; + let c = ValuePromptCommand::new( + "CELTSCALE", + "CELTSCALE new current-object line-type scale:", + ); + self.command_line.push_info(&c.prompt()); + self.tabs[i].active_cmd = Some(Box::new(c)); + } + cmd if cmd.starts_with("CELTSCALE ") => { let val_str = cmd.trim_start_matches("CELTSCALE").trim(); if val_str.is_empty() { let v = self.tabs[i] diff --git a/src/command/mod.rs b/src/command/mod.rs index 99f91ed85..1ae6eedfb 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -79,6 +79,57 @@ pub enum StepInput { Escape, } +/// Generic interactive front-end for a single-value setting command (PDMODE, +/// PDSIZE, LTSCALE, CELTSCALE, …). Bare `` enters this command, which +/// prompts for one value and then delegates to the existing inline +/// ` ` handler via [`CmdResult::Dispatch`]. A bare Enter delegates +/// to ` ` (trailing space) so the inline handler reports the current +/// value. Keeps the value math + persistence in the one inline place while +/// making the command prompt for its argument step-by-step. +pub struct ValuePromptCommand { + name: &'static str, + prompt: &'static str, +} + +impl ValuePromptCommand { + pub fn new(name: &'static str, prompt: &'static str) -> Self { + Self { name, prompt } + } +} + +impl CadCommand for ValuePromptCommand { + fn name(&self) -> &'static str { + self.name + } + + fn prompt(&self) -> String { + self.prompt.to_string() + } + + fn wants_text_input(&self) -> bool { + true + } + + fn on_text_input(&mut self, text: &str) -> Option { + let t = text.trim(); + if t.is_empty() { + Some(CmdResult::Dispatch(format!("{} ", self.name))) + } else { + Some(CmdResult::Dispatch(format!("{} {t}", self.name))) + } + } + + fn on_point(&mut self, _pt: DVec3) -> CmdResult { + // A value command takes no point; ignore stray clicks, keep prompting. + CmdResult::NeedPoint + } + + fn on_enter(&mut self) -> CmdResult { + // Bare Enter → report the current value via the inline handler. + CmdResult::Dispatch(format!("{} ", self.name)) + } +} + // ── Result token ────────────────────────────────────────────────────────── /// Returned by every `CadCommand` method to tell main.rs what to do.