From 1fceec92ea6076aa6c2e0117230ca800124c12e1 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 8 Apr 2026 18:13:21 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20MLEADERSTYLE=20command=20=E2=80=94=20cr?= =?UTF-8?q?eate=20and=20manage=20MultiLeader=20styles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stores MultiLeaderStyle objects in document.objects. Supports: MLEADERSTYLE LIST — list all styles MLEADERSTYLE NEW — create a new style MLEADERSTYLE SET — edit text_height, arrowhead_size, landing_distance, landing_gap MLEADERSTYLE CURRENT [] — get/set active style Active style name tracked per-tab in active_mleader_style. Co-Authored-By: Claude Sonnet 4.6 --- src/app/commands.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++ src/app/document.rs | 3 ++ 2 files changed, 97 insertions(+) diff --git a/src/app/commands.rs b/src/app/commands.rs index e6b4cb8cd..7fb00840e 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -2781,6 +2781,100 @@ impl H7CAD { } } + // ── MLeader Style management ────────────────────────────────── + cmd if cmd == "MLEADERSTYLE" || cmd.starts_with("MLEADERSTYLE ") => { + use acadrust::objects::{ObjectType, MultiLeaderStyle}; + let raw_rest = cmd.trim_start_matches("MLEADERSTYLE").trim(); + let parts: Vec<&str> = raw_rest.split_whitespace().collect(); + let sub = parts.first().map(|s| s.to_uppercase()).unwrap_or_default(); + match sub.as_str() { + "" | "LIST" | "?" => { + let styles: Vec = self.tabs[i].scene.document + .objects.values() + .filter_map(|o| if let ObjectType::MultiLeaderStyle(s) = o { Some(format!("{}(txt:{:.2} asz:{:.2})", s.name, s.text_height, s.arrowhead_size)) } else { None }) + .collect(); + let current = &self.tabs[i].active_mleader_style; + if styles.is_empty() { + self.command_line.push_output(&format!("MLeader styles: (none) active: {current}")); + } else { + self.command_line.push_output(&format!("MLeader styles: {} active: {current}", styles.join(", "))); + } + } + "NEW" | "N" => { + let name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string(); + if name.is_empty() { + self.command_line.push_error("Usage: MLEADERSTYLE NEW "); + } else { + let already_exists = self.tabs[i].scene.document.objects.values() + .any(|o| matches!(o, ObjectType::MultiLeaderStyle(s) if s.name == name)); + if already_exists { + self.command_line.push_error(&format!("MLEADERSTYLE: '{}' already exists.", name)); + } else { + let handle = self.tabs[i].scene.document.allocate_handle(); + let mut style = MultiLeaderStyle::new(&name); + style.handle = handle; + self.tabs[i].scene.document.objects.insert(handle, ObjectType::MultiLeaderStyle(style)); + self.push_undo_snapshot(i, "MLEADERSTYLE NEW"); + self.tabs[i].dirty = true; + self.command_line.push_output(&format!("MLEADERSTYLE: '{}' created.", name)); + } + } + } + "SET" | "S" => { + // MLEADERSTYLE SET + // Properties: text_height arrowhead_size landing_distance landing_gap + let style_name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string(); + let prop = parts.get(2).map(|s| s.to_lowercase()).unwrap_or_default(); + let val_str = parts.get(3).map(|s| s.trim()).unwrap_or(""); + if let Ok(val) = val_str.parse::() { + let style_entry = self.tabs[i].scene.document.objects.values_mut() + .find_map(|o| if let ObjectType::MultiLeaderStyle(s) = o { if s.name == style_name { Some(s) } else { None } } else { None }); + if let Some(s) = style_entry { + match prop.as_str() { + "text_height" | "textheight" | "txth" => { s.text_height = val; } + "arrowhead_size" | "arrowsize" | "asz" => { s.arrowhead_size = val; } + "landing_distance" | "landing" | "dogleg" => { s.landing_distance = val; } + "landing_gap" | "gap" => { s.landing_gap = val; } + _ => { + self.command_line.push_error(&format!( + "MLEADERSTYLE: unknown property '{}'. Try: text_height arrowhead_size landing_distance landing_gap", prop + )); + return Task::none(); + } + } + self.push_undo_snapshot(i, "MLEADERSTYLE SET"); + self.tabs[i].dirty = true; + self.command_line.push_output(&format!("MLEADERSTYLE: '{style_name}'.{prop} = {val:.3}")); + } else { + self.command_line.push_error(&format!("MLEADERSTYLE: '{}' not found.", style_name)); + } + } else { + self.command_line.push_error("Usage: MLEADERSTYLE SET "); + } + } + "CURRENT" | "C" | "ACTIVE" => { + let name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string(); + if name.is_empty() { + self.command_line.push_output(&format!("Current MLeader style: {}", self.tabs[i].active_mleader_style)); + } else { + let exists = name == "Standard" || self.tabs[i].scene.document.objects.values() + .any(|o| matches!(o, ObjectType::MultiLeaderStyle(s) if s.name == name)); + if exists { + self.tabs[i].active_mleader_style = name.clone(); + self.command_line.push_output(&format!("MLEADERSTYLE: current style set to '{name}'.")); + } else { + self.command_line.push_error(&format!("MLEADERSTYLE: '{}' not found.", name)); + } + } + } + _ => { + self.command_line.push_info( + "Usage: MLEADERSTYLE LIST | NEW | SET | CURRENT []" + ); + } + } + } + // ── TextStyle / Style management ────────────────────────────── cmd if cmd == "STYLE" || cmd == "TEXTSTYLE" || cmd.starts_with("STYLE ") || cmd.starts_with("TEXTSTYLE ") => { let (prefix, rest) = if cmd.starts_with("TEXTSTYLE") { diff --git a/src/app/document.rs b/src/app/document.rs index 78dd8886d..f980d8bc1 100644 --- a/src/app/document.rs +++ b/src/app/document.rs @@ -40,6 +40,8 @@ pub(super) struct DocumentTab { pub(super) paper_bg_color: Option<[f32; 4]>, /// Active REFEDIT session, if any. pub(super) refedit_session: Option, + /// Currently active MLeader style name. + pub(super) active_mleader_style: String, } impl DocumentTab { @@ -69,6 +71,7 @@ impl DocumentTab { bg_color: None, paper_bg_color: None, refedit_session: None, + active_mleader_style: "Standard".to_string(), } }