From df0e283ebaeee82d2d75edc75ffbc4a7065c90d5 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 25 Jun 2026 13:35:25 +0300 Subject: [PATCH] feat(commands): interactive value prompts for PDMODE/PDSIZE/LTSCALE/CELTSCALE (F4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bare PDMODE / PDSIZE / LTSCALE / CELTSCALE now enter a generic ValuePromptCommand front-end that prompts for the value and delegates to the existing inline ` ` handler via CmdResult::Dispatch, so they prompt step-by-step like UCS. A bare Enter reports the current value; `PDMODE 3` still works as one typed / headless line. Only pure single-value setting commands are converted. Dialog-opening commands (LAYER, PURGE, DIMSTYLE, STYLE …) intentionally keep their bare-name behaviour (open the panel), and subcommand-tree commands keep their inline handlers — all already typable with arguments since F1. Adds headless test value_prompt_commands_inline_args. Fourth phase of the unified step driver. Co-Authored-By: Claude Opus 4.8 --- src/app/automation.rs | 21 ++++++++++++++++++ src/app/commands.rs | 41 ++++++++++++++++++++++++++++++---- src/command/mod.rs | 51 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 4 deletions(-) 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.