forked from 1b-agent/opencadstudio-upstream
feat(commands): step the selection-first text commands (bucket C)
Add SelectThenValueCommand (gather selection, then one value) and convert: - SelectThenValueCommand: HYPERLINK (url), ARCTEXT (text), TEXTFIT (width), SCALETEXT (factor), TCOUNT (start), TEXTMASK (Enter to apply). - SelectThenKeywordCommand: JUSTIFYTEXT (Left/Center/Right/Middle/Aligned/Fit), TCASE (Upper/Lower/Sentence/Title). Each bare form gathers a selection when none is set, prompts, and delegates to the inline handler. OBJECTSCALE already opens its dialog; TABLE CELL takes three values (row/col/text) and stays typeable — TABLE bare already builds a table interactively.
This commit is contained in:
1 parent
cf5362aeab
commit
208e617868
4 files changed
+185
-8
No files matched your search
+83
-6
@@ -56,7 +56,26 @@ impl OpenCADStudio {
|
||||
|
||||
// JUSTIFYTEXT <option> — change the justification of selected text and
|
||||
// multiline-text objects.
|
||||
cmd if cmd == "JUSTIFYTEXT" || cmd.starts_with("JUSTIFYTEXT ") => {
|
||||
"JUSTIFYTEXT" => {
|
||||
use crate::command::SelectThenKeywordCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c = SelectThenKeywordCommand::new(
|
||||
"JUSTIFYTEXT",
|
||||
"JUSTIFYTEXT [Left / Center / Right / Middle / Aligned / Fit]:",
|
||||
vec![
|
||||
("Left", "LEFT", None),
|
||||
("Center", "CENTER", None),
|
||||
("Right", "RIGHT", None),
|
||||
("Middle", "MIDDLE", None),
|
||||
("Aligned", "ALIGN", None),
|
||||
("Fit", "FIT", None),
|
||||
],
|
||||
has_sel,
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("JUSTIFYTEXT ") => {
|
||||
let opt = cmd.split_whitespace().nth(1).unwrap_or("").to_uppercase();
|
||||
let handles: Vec<acadrust::Handle> = self.tabs[i]
|
||||
.scene
|
||||
@@ -157,7 +176,24 @@ impl OpenCADStudio {
|
||||
|
||||
// TCASE <Upper|Lower|Sentence|Title> — change the case of the text in
|
||||
// selected text and multiline-text objects.
|
||||
cmd if cmd == "TCASE" || cmd.starts_with("TCASE ") => {
|
||||
"TCASE" => {
|
||||
use crate::command::SelectThenKeywordCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c = SelectThenKeywordCommand::new(
|
||||
"TCASE",
|
||||
"TCASE [Upper / Lower / Sentence / Title]:",
|
||||
vec![
|
||||
("Upper", "UPPER", None),
|
||||
("Lower", "LOWER", None),
|
||||
("Sentence", "SENTENCE", None),
|
||||
("Title", "TITLE", None),
|
||||
],
|
||||
has_sel,
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("TCASE ") => {
|
||||
let opt = cmd.split_whitespace().nth(1).unwrap_or("").to_uppercase();
|
||||
let handles: Vec<acadrust::Handle> = self.tabs[i]
|
||||
.scene
|
||||
@@ -226,7 +262,18 @@ impl OpenCADStudio {
|
||||
// extent and bring the text in front, so the mask hides whatever is
|
||||
// underneath while the text stays readable. (Same world-XY coordinate
|
||||
// space the WIPEOUT command uses.)
|
||||
cmd if cmd == "TEXTMASK" || cmd.starts_with("TEXTMASK ") => {
|
||||
"TEXTMASK" => {
|
||||
use crate::command::SelectThenValueCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c = SelectThenValueCommand::new(
|
||||
"TEXTMASK",
|
||||
"TEXTMASK press Enter to mask the selected text:",
|
||||
has_sel,
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("TEXTMASK ") => {
|
||||
let handles: Vec<acadrust::Handle> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
@@ -289,7 +336,15 @@ impl OpenCADStudio {
|
||||
// (text width fit)
|
||||
// TEXTFIT <width> — adjust the width factor of selected single-line
|
||||
// text so its rendered width matches the target.
|
||||
cmd if cmd == "TEXTFIT" || cmd.starts_with("TEXTFIT ") => {
|
||||
"TEXTFIT" => {
|
||||
use crate::command::SelectThenValueCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c =
|
||||
SelectThenValueCommand::new("TEXTFIT", "TEXTFIT target width:", has_sel);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("TEXTFIT ") => {
|
||||
let target: f64 = match cmd.split_whitespace().nth(1).and_then(|s| s.parse().ok()) {
|
||||
Some(v) if v > 0.0 => v,
|
||||
_ => {
|
||||
@@ -351,7 +406,18 @@ impl OpenCADStudio {
|
||||
// (text sequential numbering)
|
||||
// TCOUNT [start] — prefix selected single-line text with sequential
|
||||
// numbers in reading order (top-to-bottom, then left-to-right).
|
||||
cmd if cmd == "TCOUNT" || cmd.starts_with("TCOUNT ") => {
|
||||
"TCOUNT" => {
|
||||
use crate::command::SelectThenValueCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c = SelectThenValueCommand::new(
|
||||
"TCOUNT",
|
||||
"TCOUNT starting number (Enter for 1):",
|
||||
has_sel,
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("TCOUNT ") => {
|
||||
let start: i64 = cmd
|
||||
.split_whitespace()
|
||||
.nth(1)
|
||||
@@ -997,7 +1063,18 @@ impl OpenCADStudio {
|
||||
|
||||
// ARCTEXT <text> — lay the text out as one Text entity per character
|
||||
// along the selected arc, each rotated to follow the tangent.
|
||||
cmd if cmd == "ARCTEXT" || cmd.starts_with("ARCTEXT ") => {
|
||||
"ARCTEXT" => {
|
||||
use crate::command::SelectThenValueCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c = SelectThenValueCommand::new(
|
||||
"ARCTEXT",
|
||||
"ARCTEXT text to place along the arc:",
|
||||
has_sel,
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("ARCTEXT ") => {
|
||||
use acadrust::entities::Text;
|
||||
use acadrust::types::Vector3;
|
||||
let text = cmd.strip_prefix("ARCTEXT").unwrap_or("").trim().to_string();
|
||||
|
||||
@@ -640,7 +640,18 @@ impl OpenCADStudio {
|
||||
|
||||
// HYPERLINK <url> — attach a hyperlink to the selected objects, stored
|
||||
// in the standard PE_URL XData record so it round-trips in the file.
|
||||
cmd if cmd == "HYPERLINK" || cmd.starts_with("HYPERLINK ") => {
|
||||
"HYPERLINK" => {
|
||||
use crate::command::SelectThenValueCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c = SelectThenValueCommand::new(
|
||||
"HYPERLINK",
|
||||
"HYPERLINK URL to attach:",
|
||||
has_sel,
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("HYPERLINK ") => {
|
||||
use acadrust::xdata::{ExtendedDataRecord, XDataValue};
|
||||
let url = cmd.strip_prefix("HYPERLINK").unwrap_or("").trim().to_string();
|
||||
if url.is_empty() {
|
||||
|
||||
@@ -1848,7 +1848,18 @@ impl OpenCADStudio {
|
||||
// ── SCALETEXT — rescale selected Text/MText entities ─────────────────
|
||||
// Usage: SCALETEXT <factor> e.g. SCALETEXT 2
|
||||
// SCALETEXT H <height> set absolute height
|
||||
cmd if cmd == "SCALETEXT" || cmd.starts_with("SCALETEXT ") => {
|
||||
"SCALETEXT" => {
|
||||
use crate::command::SelectThenValueCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c = SelectThenValueCommand::new(
|
||||
"SCALETEXT",
|
||||
"SCALETEXT scale factor (or 'H <height>' for an absolute height):",
|
||||
has_sel,
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("SCALETEXT ") => {
|
||||
let rest = cmd.trim_start_matches("SCALETEXT").trim();
|
||||
let parts: Vec<&str> = rest.split_whitespace().collect();
|
||||
let selected_handles: Vec<acadrust::Handle> = self.tabs[i]
|
||||
|
||||
@@ -595,6 +595,84 @@ impl CadCommand for SelectThenKeywordCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic interactive front-end for a single-value command that operates on
|
||||
/// the current selection (HYPERLINK url, ARCTEXT text, TEXTFIT width, TCASE
|
||||
/// aside…). Gathers a selection first when none is set (Enter confirms), then
|
||||
/// prompts for one value and dispatches `<name> <value>` to the inline handler,
|
||||
/// which reads the still-selected set. A bare Enter on the value step dispatches
|
||||
/// `<name>` alone — for commands whose value is optional (TCOUNT, TEXTMASK).
|
||||
pub struct SelectThenValueCommand {
|
||||
name: &'static str,
|
||||
value_prompt: &'static str,
|
||||
gathering: bool,
|
||||
selected: Vec<Handle>,
|
||||
}
|
||||
|
||||
impl SelectThenValueCommand {
|
||||
pub fn new(name: &'static str, value_prompt: &'static str, has_selection: bool) -> Self {
|
||||
Self {
|
||||
name,
|
||||
value_prompt,
|
||||
gathering: !has_selection,
|
||||
selected: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for SelectThenValueCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
if self.gathering {
|
||||
format!("{} select objects, then press Enter:", self.name)
|
||||
} else {
|
||||
self.value_prompt.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
!self.gathering
|
||||
}
|
||||
|
||||
fn is_selection_gathering(&self) -> bool {
|
||||
self.gathering
|
||||
}
|
||||
|
||||
fn on_selection_complete(&mut self, handles: Vec<Handle>) -> CmdResult {
|
||||
self.selected = handles;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
if self.gathering {
|
||||
return None;
|
||||
}
|
||||
let t = text.trim();
|
||||
if t.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(CmdResult::Dispatch(format!("{} {t}", self.name)))
|
||||
}
|
||||
|
||||
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
if self.gathering {
|
||||
if self.selected.is_empty() {
|
||||
return CmdResult::Cancel;
|
||||
}
|
||||
self.gathering = false;
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
// Value step, nothing typed: the no-argument form (optional value).
|
||||
CmdResult::Dispatch(format!("{} ", self.name))
|
||||
}
|
||||
}
|
||||
|
||||
// ── Result token ──────────────────────────────────────────────────────────
|
||||
|
||||
/// Returned by every `CadCommand` method to tell main.rs what to do.
|
||||
|
||||
Reference in new issue
Block a user