forked from 1b-agent/opencadstudio-upstream
feat(command): clickable option buttons in the command line (#304)
Commands now expose their step options as clickable buttons rendered inline beside the command-line prompt, so options need not be typed. Adds a CmdOption/options() API to CadCommand: each command returns the options for its current step; the command line renders them as uppercase buttons and a click feeds the keyword to the command exactly as if typed (an empty keyword submits like Enter). Construction commands become "central" — CIRCLE offers [3P/2P/TTR/TTT/ DIAMETER], ELLIPSE [ARC/AXIS], ARC [SCE/SCA/SEA/SER/CSA/3P], RECTANGLE [ROTATION/CENTER], POLYGON [CIRCUMSCRIBED/EDGE] — each keyword hands off to the existing dedicated variant command via CmdResult::Dispatch, instead of the variants only being reachable as isolated commands. Migrates the existing bracketed-option prompts (PLINE, PEDIT, SPLINE, SCALE, FILLET, CHAMFER, HATCH, GRADIENT, SOLID, POINT, RAY, XLINE, TRIM, EXTEND) to the same button API and strips the option text from the prompt strings, keeping value/coordinate hints. PEDIT's Width option now enters a value sub-step so the button acts on click. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
efa4bdd5e1
commit
31ac5372d6
18 files changed
+457
-28
No files matched your search
@@ -1249,7 +1249,7 @@ impl CadCommand for FilletCommand {
|
||||
fn prompt(&self) -> String {
|
||||
match &self.step {
|
||||
FilletStep::First => format!(
|
||||
"FILLET Select first object (Line/Arc/LwPolyline) [R={:.4} | type R to change]:",
|
||||
"FILLET Select first object (Line/Arc/LwPolyline) [R={:.4}]:",
|
||||
self.radius
|
||||
),
|
||||
FilletStep::WaitingForRadius => {
|
||||
@@ -1264,6 +1264,16 @@ impl CadCommand for FilletCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||
use crate::command::CmdOption;
|
||||
match self.step {
|
||||
// Offer the radius keyword while selecting objects; the "R" token is
|
||||
// already handled by on_text_input.
|
||||
FilletStep::First | FilletStep::Second { .. } => vec![CmdOption::new("Radius", "R")],
|
||||
FilletStep::WaitingForRadius => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
matches!(self.step, FilletStep::WaitingForRadius)
|
||||
}
|
||||
@@ -1623,7 +1633,7 @@ impl CadCommand for ChamferCommand {
|
||||
fn prompt(&self) -> String {
|
||||
match &self.step {
|
||||
ChamferStep::First => format!(
|
||||
"CHAMFER Select first line [D1={:.4} D2={:.4} | type D to change]:",
|
||||
"CHAMFER Select first line [D1={:.4} D2={:.4}]:",
|
||||
self.dist1, self.dist2
|
||||
),
|
||||
ChamferStep::WaitingForDist1 => {
|
||||
@@ -1644,6 +1654,18 @@ impl CadCommand for ChamferCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||
use crate::command::CmdOption;
|
||||
match self.step {
|
||||
// Offer the distance keyword while selecting objects; the "D" token
|
||||
// is already handled by on_text_input.
|
||||
ChamferStep::First
|
||||
| ChamferStep::Second { .. }
|
||||
| ChamferStep::SecondPoly { .. } => vec![CmdOption::new("Distance", "D")],
|
||||
ChamferStep::WaitingForDist1 | ChamferStep::WaitingForDist2 => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
matches!(
|
||||
self.step,
|
||||
|
||||
@@ -16,11 +16,17 @@ use crate::command::{CadCommand, CmdResult};
|
||||
|
||||
pub struct PeditCommand {
|
||||
target: Option<Handle>,
|
||||
/// Set once the bare `W` keyword (or the Width button) is chosen: the next
|
||||
/// text input is parsed as the new width instead of an option. (#304)
|
||||
awaiting_width: bool,
|
||||
}
|
||||
|
||||
impl PeditCommand {
|
||||
pub fn new() -> Self {
|
||||
Self { target: None }
|
||||
Self {
|
||||
target: None,
|
||||
awaiting_width: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +38,24 @@ impl CadCommand for PeditCommand {
|
||||
fn prompt(&self) -> String {
|
||||
if self.target.is_none() {
|
||||
"PEDIT Select polyline:".into()
|
||||
} else if self.awaiting_width {
|
||||
"PEDIT Specify new width:".into()
|
||||
} else {
|
||||
"PEDIT Enter option [C=Close O=Open W=Width X=Exit]:".into()
|
||||
"PEDIT Enter option:".into()
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||
use crate::command::CmdOption;
|
||||
if self.target.is_none() || self.awaiting_width {
|
||||
vec![]
|
||||
} else {
|
||||
vec![
|
||||
CmdOption::new("Close", "C"),
|
||||
CmdOption::new("Open", "O"),
|
||||
CmdOption::new("Width", "W"),
|
||||
CmdOption::new("eXit", "X"),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +79,21 @@ impl CadCommand for PeditCommand {
|
||||
let handle = self.target?;
|
||||
let up = text.trim().to_uppercase();
|
||||
|
||||
// Width sub-step: the previous input was a bare `W` (typed or via the
|
||||
// Width button), so this input is the width value. (#304)
|
||||
if self.awaiting_width {
|
||||
let w: f64 = up
|
||||
.replace(',', ".")
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|&v: &f64| v >= 0.0)?;
|
||||
self.awaiting_width = false;
|
||||
return Some(CmdResult::PeditOp {
|
||||
handle,
|
||||
op: PeditOp::SetWidth(w),
|
||||
});
|
||||
}
|
||||
|
||||
match up.as_str() {
|
||||
"X" | "EXIT" => return Some(CmdResult::Cancel),
|
||||
"C" | "CLOSE" => {
|
||||
@@ -71,10 +108,17 @@ impl CadCommand for PeditCommand {
|
||||
op: PeditOp::SetClosed(false),
|
||||
})
|
||||
}
|
||||
"W" | "WIDTH" => {
|
||||
// Bare width keyword (the Width button): prompt for the value
|
||||
// rather than doing nothing.
|
||||
self.awaiting_width = true;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(rest) = up.strip_prefix("W ").or_else(|| up.strip_prefix("W")) {
|
||||
// Inline shorthand `W <value>`.
|
||||
if let Some(rest) = up.strip_prefix("W ") {
|
||||
let w: f64 = rest
|
||||
.trim()
|
||||
.replace(',', ".")
|
||||
|
||||
@@ -82,7 +82,7 @@ impl CadCommand for ScaleCommand {
|
||||
self.handles.len()
|
||||
),
|
||||
Step::Factor { .. } => format!(
|
||||
"SCALE Specify scale factor or [Reference] <{:.4}>:",
|
||||
"SCALE Specify scale factor <{:.4}>:",
|
||||
self.default_factor
|
||||
),
|
||||
Step::RefLen { .. } => {
|
||||
@@ -95,6 +95,15 @@ impl CadCommand for ScaleCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||
use crate::command::CmdOption;
|
||||
match &self.step {
|
||||
// The reference-scaling keyword is only offered at the factor step.
|
||||
Step::Factor { .. } => vec![CmdOption::new("Reference", "R")],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match &self.step {
|
||||
Step::Base => {
|
||||
|
||||
@@ -2027,7 +2027,11 @@ impl CadCommand for TrimCommand {
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
"TRIM Click segment to remove [Enter=done]:".into()
|
||||
"TRIM Click segment to remove:".into()
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||
vec![crate::command::CmdOption::enter("Done")]
|
||||
}
|
||||
|
||||
fn needs_entity_pick(&self) -> bool {
|
||||
@@ -2537,7 +2541,11 @@ impl CadCommand for ExtendCommand {
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
"EXTEND Click near end of object to extend [Enter=done]:".into()
|
||||
"EXTEND Click near end of object to extend:".into()
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||
vec![crate::command::CmdOption::enter("Done")]
|
||||
}
|
||||
|
||||
fn needs_entity_pick(&self) -> bool {
|
||||
|
||||
Reference in new issue
Block a user