feat(mleaderstyle): add Multileader Style editor window
New dedicated style-manager window for MultiLeaderStyle objects (the existing mlstyle window edits MLineStyle, not multileader). Opened via the MLEADERSTYLE command / ribbon manager entry. Editable across Leader Format / Structure / Content sections: - path/content/text-angle/text-alignment enums (cycle buttons) - line & text color (ACI), arrowhead & break-gap sizes - landing distance/gap, max points, segment angles, scale factor - default text, text height - enable landing/dogleg, text frame, text-always-left, annotative New/Delete/Set-Current style management. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0dd86eb658
commit
e0c5db9e0c
6 changed files with 796 additions and 1 deletions
|
|
@ -3983,7 +3983,10 @@ impl OpenCADStudio {
|
||||||
let parts: Vec<&str> = raw_rest.split_whitespace().collect();
|
let parts: Vec<&str> = raw_rest.split_whitespace().collect();
|
||||||
let sub = parts.first().map(|s| s.to_uppercase()).unwrap_or_default();
|
let sub = parts.first().map(|s| s.to_uppercase()).unwrap_or_default();
|
||||||
match sub.as_str() {
|
match sub.as_str() {
|
||||||
"" | "LIST" | "?" => {
|
"" | "DIALOG" | "UI" => {
|
||||||
|
return Task::done(Message::MLeaderStyleDialogOpen);
|
||||||
|
}
|
||||||
|
"LIST" | "?" => {
|
||||||
let styles: Vec<String> = self.tabs[i]
|
let styles: Vec<String> = self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.document
|
.document
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,7 @@ pub(super) struct OpenCADStudio {
|
||||||
textstyle_window: Option<window::Id>,
|
textstyle_window: Option<window::Id>,
|
||||||
tablestyle_window: Option<window::Id>,
|
tablestyle_window: Option<window::Id>,
|
||||||
mlstyle_window: Option<window::Id>,
|
mlstyle_window: Option<window::Id>,
|
||||||
|
mleaderstyle_window: Option<window::Id>,
|
||||||
layout_manager_window: Option<window::Id>,
|
layout_manager_window: Option<window::Id>,
|
||||||
plotstyle_window: Option<window::Id>,
|
plotstyle_window: Option<window::Id>,
|
||||||
dimstyle_window: Option<window::Id>,
|
dimstyle_window: Option<window::Id>,
|
||||||
|
|
@ -294,6 +295,21 @@ pub(super) struct OpenCADStudio {
|
||||||
// ── MLineStyle Dialog ─────────────────────────────────────────────────
|
// ── MLineStyle Dialog ─────────────────────────────────────────────────
|
||||||
mlstyle_selected: String,
|
mlstyle_selected: String,
|
||||||
|
|
||||||
|
// ── MLeaderStyle Dialog ───────────────────────────────────────────────
|
||||||
|
mleaderstyle_selected: String,
|
||||||
|
mls_landing_distance: String,
|
||||||
|
mls_landing_gap: String,
|
||||||
|
mls_arrowhead_size: String,
|
||||||
|
mls_text_height: String,
|
||||||
|
mls_scale_factor: String,
|
||||||
|
mls_break_gap: String,
|
||||||
|
mls_first_seg_angle: String,
|
||||||
|
mls_second_seg_angle: String,
|
||||||
|
mls_max_points: String,
|
||||||
|
mls_default_text: String,
|
||||||
|
mls_line_color: String,
|
||||||
|
mls_text_color: String,
|
||||||
|
|
||||||
// ── TableStyle Dialog ─────────────────────────────────────────────────
|
// ── TableStyle Dialog ─────────────────────────────────────────────────
|
||||||
tablestyle_selected: String,
|
tablestyle_selected: String,
|
||||||
/// Edit buffers for the table style's general margins.
|
/// Edit buffers for the table style's general margins.
|
||||||
|
|
@ -1005,6 +1021,21 @@ pub enum Message {
|
||||||
MlStyleDialogSetCurrent,
|
MlStyleDialogSetCurrent,
|
||||||
MlStyleDialogNew,
|
MlStyleDialogNew,
|
||||||
MlStyleDialogDelete,
|
MlStyleDialogDelete,
|
||||||
|
// ── MLeaderStyle Dialog ───────────────────────────────────────────────
|
||||||
|
MLeaderStyleDialogOpen,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
MLeaderStyleDialogClose,
|
||||||
|
MLeaderStyleDialogSelect(String),
|
||||||
|
MLeaderStyleDialogSetCurrent,
|
||||||
|
MLeaderStyleDialogNew,
|
||||||
|
MLeaderStyleDialogDelete,
|
||||||
|
MLeaderStyleEdit {
|
||||||
|
field: &'static str,
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
MLeaderStyleToggle(&'static str),
|
||||||
|
MLeaderStyleCycle(&'static str),
|
||||||
|
MLeaderStyleApply,
|
||||||
// ── DimStyle Dialog ───────────────────────────────────────────────────
|
// ── DimStyle Dialog ───────────────────────────────────────────────────
|
||||||
DimStyleDialogOpen,
|
DimStyleDialogOpen,
|
||||||
DimStyleDialogClose,
|
DimStyleDialogClose,
|
||||||
|
|
@ -1108,6 +1139,7 @@ impl OpenCADStudio {
|
||||||
textstyle_window: None,
|
textstyle_window: None,
|
||||||
tablestyle_window: None,
|
tablestyle_window: None,
|
||||||
mlstyle_window: None,
|
mlstyle_window: None,
|
||||||
|
mleaderstyle_window: None,
|
||||||
layout_manager_window: None,
|
layout_manager_window: None,
|
||||||
plotstyle_window: None,
|
plotstyle_window: None,
|
||||||
dimstyle_window: None,
|
dimstyle_window: None,
|
||||||
|
|
@ -1172,6 +1204,20 @@ impl OpenCADStudio {
|
||||||
ts_vmargin: "1.5".to_string(),
|
ts_vmargin: "1.5".to_string(),
|
||||||
// MLineStyle dialog
|
// MLineStyle dialog
|
||||||
mlstyle_selected: "Standard".to_string(),
|
mlstyle_selected: "Standard".to_string(),
|
||||||
|
// MLeaderStyle dialog
|
||||||
|
mleaderstyle_selected: "Standard".to_string(),
|
||||||
|
mls_landing_distance: String::new(),
|
||||||
|
mls_landing_gap: String::new(),
|
||||||
|
mls_arrowhead_size: String::new(),
|
||||||
|
mls_text_height: String::new(),
|
||||||
|
mls_scale_factor: String::new(),
|
||||||
|
mls_break_gap: String::new(),
|
||||||
|
mls_first_seg_angle: String::new(),
|
||||||
|
mls_second_seg_angle: String::new(),
|
||||||
|
mls_max_points: String::new(),
|
||||||
|
mls_default_text: String::new(),
|
||||||
|
mls_line_color: String::new(),
|
||||||
|
mls_text_color: String::new(),
|
||||||
// DimStyle dialog
|
// DimStyle dialog
|
||||||
dimstyle_selected: "Standard".to_string(),
|
dimstyle_selected: "Standard".to_string(),
|
||||||
dimstyle_tab: 0,
|
dimstyle_tab: 0,
|
||||||
|
|
@ -1308,6 +1354,9 @@ pub fn run() -> iced::Result {
|
||||||
if Some(window_id) == state.mlstyle_window {
|
if Some(window_id) == state.mlstyle_window {
|
||||||
return "Multiline Style".into();
|
return "Multiline Style".into();
|
||||||
}
|
}
|
||||||
|
if Some(window_id) == state.mleaderstyle_window {
|
||||||
|
return "Multileader Style".into();
|
||||||
|
}
|
||||||
if Some(window_id) == state.layout_manager_window {
|
if Some(window_id) == state.layout_manager_window {
|
||||||
return "Layout Manager".into();
|
return "Layout Manager".into();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1428,6 +1428,10 @@ impl OpenCADStudio {
|
||||||
self.mlstyle_window = None;
|
self.mlstyle_window = None;
|
||||||
self.ribbon.deactivate_tool_if("MLSTYLE");
|
self.ribbon.deactivate_tool_if("MLSTYLE");
|
||||||
}
|
}
|
||||||
|
if self.mleaderstyle_window == Some(id) {
|
||||||
|
self.mleaderstyle_window = None;
|
||||||
|
self.ribbon.deactivate_tool_if("MLEADERSTYLE");
|
||||||
|
}
|
||||||
if self.layout_manager_window == Some(id) {
|
if self.layout_manager_window == Some(id) {
|
||||||
self.layout_manager_window = None;
|
self.layout_manager_window = None;
|
||||||
self.ribbon.deactivate_tool_if("LAYOUTMANAGER");
|
self.ribbon.deactivate_tool_if("LAYOUTMANAGER");
|
||||||
|
|
@ -6045,6 +6049,302 @@ impl OpenCADStudio {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── MLeaderStyle Dialog ───────────────────────────────────────────
|
||||||
|
Message::MLeaderStyleDialogOpen => {
|
||||||
|
use acadrust::objects::ObjectType;
|
||||||
|
let i = self.active_tab;
|
||||||
|
let cur = self.tabs[i].active_mleader_style.clone();
|
||||||
|
let exists = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.values()
|
||||||
|
.any(|o| matches!(o, ObjectType::MultiLeaderStyle(s) if s.name == cur));
|
||||||
|
self.mleaderstyle_selected = if exists {
|
||||||
|
cur
|
||||||
|
} else {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.values()
|
||||||
|
.find_map(|o| {
|
||||||
|
if let ObjectType::MultiLeaderStyle(s) = o {
|
||||||
|
Some(s.name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "Standard".to_string())
|
||||||
|
};
|
||||||
|
self.load_mleaderstyle_bufs(i);
|
||||||
|
if let Some(id) = self.mleaderstyle_window {
|
||||||
|
return window::gain_focus(id);
|
||||||
|
}
|
||||||
|
let (id, task) = window::open(window::Settings {
|
||||||
|
size: iced::Size::new(560.0, 560.0),
|
||||||
|
resizable: true,
|
||||||
|
level: window::Level::AlwaysOnTop,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
self.mleaderstyle_window = Some(id);
|
||||||
|
task.map(|_| Message::Noop)
|
||||||
|
}
|
||||||
|
Message::MLeaderStyleDialogClose => {
|
||||||
|
if let Some(id) = self.mleaderstyle_window.take() {
|
||||||
|
window::close(id)
|
||||||
|
} else {
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::MLeaderStyleDialogSelect(name) => {
|
||||||
|
self.mleaderstyle_selected = name;
|
||||||
|
let i = self.active_tab;
|
||||||
|
self.load_mleaderstyle_bufs(i);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MLeaderStyleDialogSetCurrent => {
|
||||||
|
use acadrust::objects::ObjectType;
|
||||||
|
let i = self.active_tab;
|
||||||
|
let name = self.mleaderstyle_selected.clone();
|
||||||
|
let exists = 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.ribbon.active_mleader_style = name.clone();
|
||||||
|
self.command_line
|
||||||
|
.push_output(&format!("Current multileader style: {}", name));
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MLeaderStyleDialogNew => {
|
||||||
|
use acadrust::objects::ObjectType;
|
||||||
|
let i = self.active_tab;
|
||||||
|
let doc = &self.tabs[i].scene.document;
|
||||||
|
let mut n = 1u32;
|
||||||
|
let new_name = loop {
|
||||||
|
let candidate = format!("MLeader{}", n);
|
||||||
|
let taken = doc.objects.values().any(|o| {
|
||||||
|
matches!(o, ObjectType::MultiLeaderStyle(s) if s.name.eq_ignore_ascii_case(&candidate))
|
||||||
|
});
|
||||||
|
if !taken {
|
||||||
|
break candidate;
|
||||||
|
}
|
||||||
|
n += 1;
|
||||||
|
};
|
||||||
|
self.push_undo_snapshot(i, "MLEADERSTYLE NEW");
|
||||||
|
let mut style = acadrust::objects::MultiLeaderStyle::new(&new_name);
|
||||||
|
let nh = acadrust::Handle::new(self.tabs[i].scene.document.next_handle());
|
||||||
|
style.handle = nh;
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.insert(nh, ObjectType::MultiLeaderStyle(style));
|
||||||
|
self.mleaderstyle_selected = new_name;
|
||||||
|
self.load_mleaderstyle_bufs(i);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MLeaderStyleDialogDelete => {
|
||||||
|
use acadrust::objects::ObjectType;
|
||||||
|
let i = self.active_tab;
|
||||||
|
let name = self.mleaderstyle_selected.clone();
|
||||||
|
if name.eq_ignore_ascii_case("Standard") {
|
||||||
|
self.command_line
|
||||||
|
.push_error("Cannot delete the Standard style.");
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
let handle = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.find_map(|(&h, o)| match o {
|
||||||
|
ObjectType::MultiLeaderStyle(s) if s.name == name => Some(h),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
if let Some(h) = handle {
|
||||||
|
self.push_undo_snapshot(i, "MLEADERSTYLE DEL");
|
||||||
|
self.tabs[i].scene.document.objects.remove(&h);
|
||||||
|
self.mleaderstyle_selected = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.values()
|
||||||
|
.find_map(|o| {
|
||||||
|
if let ObjectType::MultiLeaderStyle(s) = o {
|
||||||
|
Some(s.name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "Standard".to_string());
|
||||||
|
self.load_mleaderstyle_bufs(i);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MLeaderStyleEdit { field, value } => {
|
||||||
|
match field {
|
||||||
|
"landing_distance" => self.mls_landing_distance = value,
|
||||||
|
"landing_gap" => self.mls_landing_gap = value,
|
||||||
|
"arrowhead_size" => self.mls_arrowhead_size = value,
|
||||||
|
"text_height" => self.mls_text_height = value,
|
||||||
|
"scale_factor" => self.mls_scale_factor = value,
|
||||||
|
"break_gap" => self.mls_break_gap = value,
|
||||||
|
"first_seg_angle" => self.mls_first_seg_angle = value,
|
||||||
|
"second_seg_angle" => self.mls_second_seg_angle = value,
|
||||||
|
"max_points" => self.mls_max_points = value,
|
||||||
|
"default_text" => self.mls_default_text = value,
|
||||||
|
"line_color" => self.mls_line_color = value,
|
||||||
|
"text_color" => self.mls_text_color = value,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MLeaderStyleToggle(field) => {
|
||||||
|
let i = self.active_tab;
|
||||||
|
if let Some(s) = self.mleaderstyle_mut(i) {
|
||||||
|
match field {
|
||||||
|
"enable_landing" => s.enable_landing = !s.enable_landing,
|
||||||
|
"enable_dogleg" => s.enable_dogleg = !s.enable_dogleg,
|
||||||
|
"text_frame" => s.text_frame = !s.text_frame,
|
||||||
|
"text_always_left" => s.text_always_left = !s.text_always_left,
|
||||||
|
"annotative" => s.is_annotative = !s.is_annotative,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
self.push_undo_snapshot(i, "MLEADERSTYLE EDIT");
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.tabs[i].scene.bump_geometry();
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MLeaderStyleCycle(field) => {
|
||||||
|
use acadrust::objects::{
|
||||||
|
LeaderContentType, MultiLeaderPathType, TextAlignmentType, TextAngleType,
|
||||||
|
};
|
||||||
|
let i = self.active_tab;
|
||||||
|
if let Some(s) = self.mleaderstyle_mut(i) {
|
||||||
|
match field {
|
||||||
|
"path_type" => {
|
||||||
|
s.path_type = match s.path_type {
|
||||||
|
MultiLeaderPathType::Invisible => {
|
||||||
|
MultiLeaderPathType::StraightLineSegments
|
||||||
|
}
|
||||||
|
MultiLeaderPathType::StraightLineSegments => {
|
||||||
|
MultiLeaderPathType::Spline
|
||||||
|
}
|
||||||
|
MultiLeaderPathType::Spline => MultiLeaderPathType::Invisible,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"content_type" => {
|
||||||
|
s.content_type = match s.content_type {
|
||||||
|
LeaderContentType::None => LeaderContentType::Block,
|
||||||
|
LeaderContentType::Block => LeaderContentType::MText,
|
||||||
|
LeaderContentType::MText => LeaderContentType::Tolerance,
|
||||||
|
LeaderContentType::Tolerance => LeaderContentType::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"text_angle_type" => {
|
||||||
|
s.text_angle_type = match s.text_angle_type {
|
||||||
|
TextAngleType::ParallelToLastLeaderLine => TextAngleType::Horizontal,
|
||||||
|
TextAngleType::Horizontal => TextAngleType::Optimized,
|
||||||
|
TextAngleType::Optimized => {
|
||||||
|
TextAngleType::ParallelToLastLeaderLine
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"text_alignment" => {
|
||||||
|
s.text_alignment = match s.text_alignment {
|
||||||
|
TextAlignmentType::Left => TextAlignmentType::Center,
|
||||||
|
TextAlignmentType::Center => TextAlignmentType::Right,
|
||||||
|
TextAlignmentType::Right => TextAlignmentType::Left,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
self.push_undo_snapshot(i, "MLEADERSTYLE EDIT");
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.tabs[i].scene.bump_geometry();
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MLeaderStyleApply => {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let (
|
||||||
|
ld,
|
||||||
|
lg,
|
||||||
|
asz,
|
||||||
|
th,
|
||||||
|
sf,
|
||||||
|
bg,
|
||||||
|
fsa,
|
||||||
|
ssa,
|
||||||
|
mp,
|
||||||
|
dt,
|
||||||
|
lc,
|
||||||
|
tc,
|
||||||
|
) = (
|
||||||
|
self.mls_landing_distance.parse::<f64>().ok(),
|
||||||
|
self.mls_landing_gap.parse::<f64>().ok(),
|
||||||
|
self.mls_arrowhead_size.parse::<f64>().ok(),
|
||||||
|
self.mls_text_height.parse::<f64>().ok(),
|
||||||
|
self.mls_scale_factor.parse::<f64>().ok(),
|
||||||
|
self.mls_break_gap.parse::<f64>().ok(),
|
||||||
|
self.mls_first_seg_angle.parse::<f64>().ok(),
|
||||||
|
self.mls_second_seg_angle.parse::<f64>().ok(),
|
||||||
|
self.mls_max_points.parse::<i32>().ok(),
|
||||||
|
self.mls_default_text.clone(),
|
||||||
|
self.mls_line_color.parse::<i16>().ok(),
|
||||||
|
self.mls_text_color.parse::<i16>().ok(),
|
||||||
|
);
|
||||||
|
if let Some(s) = self.mleaderstyle_mut(i) {
|
||||||
|
if let Some(v) = ld {
|
||||||
|
s.landing_distance = v;
|
||||||
|
}
|
||||||
|
if let Some(v) = lg {
|
||||||
|
s.landing_gap = v;
|
||||||
|
}
|
||||||
|
if let Some(v) = asz {
|
||||||
|
s.arrowhead_size = v;
|
||||||
|
}
|
||||||
|
if let Some(v) = th {
|
||||||
|
s.text_height = v;
|
||||||
|
}
|
||||||
|
if let Some(v) = sf {
|
||||||
|
s.scale_factor = v;
|
||||||
|
}
|
||||||
|
if let Some(v) = bg {
|
||||||
|
s.break_gap_size = v;
|
||||||
|
}
|
||||||
|
if let Some(v) = fsa {
|
||||||
|
s.first_segment_angle = v;
|
||||||
|
}
|
||||||
|
if let Some(v) = ssa {
|
||||||
|
s.second_segment_angle = v;
|
||||||
|
}
|
||||||
|
if let Some(v) = mp {
|
||||||
|
s.max_leader_points = v;
|
||||||
|
}
|
||||||
|
s.default_text = dt;
|
||||||
|
if let Some(v) = lc {
|
||||||
|
s.line_color = acadrust::types::Color::from_index(v);
|
||||||
|
}
|
||||||
|
if let Some(v) = tc {
|
||||||
|
s.text_color = acadrust::types::Color::from_index(v);
|
||||||
|
}
|
||||||
|
self.push_undo_snapshot(i, "MLEADERSTYLE EDIT");
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.tabs[i].scene.bump_geometry();
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
// ── DimStyle Dialog ───────────────────────────────────────────────
|
// ── DimStyle Dialog ───────────────────────────────────────────────
|
||||||
Message::DimStyleDialogOpen => {
|
Message::DimStyleDialogOpen => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
|
|
@ -6544,6 +6844,62 @@ impl OpenCADStudio {
|
||||||
Some(task)
|
Some(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mutable access to the currently selected multileader style.
|
||||||
|
fn mleaderstyle_mut(
|
||||||
|
&mut self,
|
||||||
|
tab: usize,
|
||||||
|
) -> Option<&mut acadrust::objects::MultiLeaderStyle> {
|
||||||
|
use acadrust::objects::ObjectType;
|
||||||
|
let name = self.mleaderstyle_selected.clone();
|
||||||
|
self.tabs[tab]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.values_mut()
|
||||||
|
.find_map(|o| match o {
|
||||||
|
ObjectType::MultiLeaderStyle(s) if s.name == name => Some(s),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Populate all edit buffers from the currently selected multileader style.
|
||||||
|
fn load_mleaderstyle_bufs(&mut self, tab: usize) {
|
||||||
|
use acadrust::objects::ObjectType;
|
||||||
|
let name = self.mleaderstyle_selected.clone();
|
||||||
|
let Some(s) = self.tabs[tab]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.values()
|
||||||
|
.find_map(|o| match o {
|
||||||
|
ObjectType::MultiLeaderStyle(s) if s.name == name => Some(s),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.mls_landing_distance = format!("{:.4}", s.landing_distance);
|
||||||
|
self.mls_landing_gap = format!("{:.4}", s.landing_gap);
|
||||||
|
self.mls_arrowhead_size = format!("{:.4}", s.arrowhead_size);
|
||||||
|
self.mls_text_height = format!("{:.4}", s.text_height);
|
||||||
|
self.mls_scale_factor = format!("{:.4}", s.scale_factor);
|
||||||
|
self.mls_break_gap = format!("{:.4}", s.break_gap_size);
|
||||||
|
self.mls_first_seg_angle = format!("{:.4}", s.first_segment_angle);
|
||||||
|
self.mls_second_seg_angle = format!("{:.4}", s.second_segment_angle);
|
||||||
|
self.mls_max_points = s.max_leader_points.to_string();
|
||||||
|
self.mls_default_text = s.default_text.clone();
|
||||||
|
self.mls_line_color = s
|
||||||
|
.line_color
|
||||||
|
.index()
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
self.mls_text_color = s
|
||||||
|
.text_color
|
||||||
|
.index()
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
}
|
||||||
|
|
||||||
/// Populate all edit buffers from the currently selected dim style.
|
/// Populate all edit buffers from the currently selected dim style.
|
||||||
fn load_dimstyle_bufs(&mut self, tab: usize) {
|
fn load_dimstyle_bufs(&mut self, tab: usize) {
|
||||||
let doc = &self.tabs[tab].scene.document;
|
let doc = &self.tabs[tab].scene.document;
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,52 @@ impl OpenCADStudio {
|
||||||
tab.scene.document.header.multiline_style.clone(),
|
tab.scene.document.header.multiline_style.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if Some(window_id) == self.mleaderstyle_window {
|
||||||
|
use acadrust::objects::ObjectType;
|
||||||
|
let tab = &self.tabs[self.active_tab];
|
||||||
|
let styles: Vec<String> = tab
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.values()
|
||||||
|
.filter_map(|o| {
|
||||||
|
if let ObjectType::MultiLeaderStyle(s) = o {
|
||||||
|
Some(s.name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let selected_style = tab.scene.document.objects.values().find_map(|o| {
|
||||||
|
if let ObjectType::MultiLeaderStyle(s) = o {
|
||||||
|
if s.name == self.mleaderstyle_selected {
|
||||||
|
Some(s)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return crate::ui::mleaderstyle::view_window(crate::ui::mleaderstyle::MLeaderStyleView {
|
||||||
|
styles,
|
||||||
|
selected: &self.mleaderstyle_selected,
|
||||||
|
style: selected_style,
|
||||||
|
current: tab.active_mleader_style.clone(),
|
||||||
|
landing_distance: &self.mls_landing_distance,
|
||||||
|
landing_gap: &self.mls_landing_gap,
|
||||||
|
arrowhead_size: &self.mls_arrowhead_size,
|
||||||
|
text_height: &self.mls_text_height,
|
||||||
|
scale_factor: &self.mls_scale_factor,
|
||||||
|
break_gap: &self.mls_break_gap,
|
||||||
|
first_seg_angle: &self.mls_first_seg_angle,
|
||||||
|
second_seg_angle: &self.mls_second_seg_angle,
|
||||||
|
max_points: &self.mls_max_points,
|
||||||
|
default_text: &self.mls_default_text,
|
||||||
|
line_color: &self.mls_line_color,
|
||||||
|
text_color: &self.mls_text_color,
|
||||||
|
});
|
||||||
|
}
|
||||||
if Some(window_id) == self.layout_manager_window {
|
if Some(window_id) == self.layout_manager_window {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let layouts = self.tabs[i].scene.layout_names();
|
let layouts = self.tabs[i].scene.layout_names();
|
||||||
|
|
|
||||||
340
src/ui/mleaderstyle.rs
Normal file
340
src/ui/mleaderstyle.rs
Normal file
|
|
@ -0,0 +1,340 @@
|
||||||
|
//! Multileader Style Manager window — fills the entire OS window.
|
||||||
|
|
||||||
|
use crate::app::Message;
|
||||||
|
use iced::widget::{button, checkbox, column, container, row, scrollable, text, text_input, Space};
|
||||||
|
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||||
|
|
||||||
|
const TB: Color = Color {
|
||||||
|
r: 0.13,
|
||||||
|
g: 0.13,
|
||||||
|
b: 0.13,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const BG: Color = Color {
|
||||||
|
r: 0.15,
|
||||||
|
g: 0.15,
|
||||||
|
b: 0.15,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const BORDER: Color = Color {
|
||||||
|
r: 0.35,
|
||||||
|
g: 0.35,
|
||||||
|
b: 0.35,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const TEXT: Color = Color {
|
||||||
|
r: 0.88,
|
||||||
|
g: 0.88,
|
||||||
|
b: 0.88,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const DIM: Color = Color {
|
||||||
|
r: 0.55,
|
||||||
|
g: 0.55,
|
||||||
|
b: 0.55,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const ACCENT: Color = Color {
|
||||||
|
r: 0.25,
|
||||||
|
g: 0.50,
|
||||||
|
b: 0.85,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const ACTIVE: Color = Color {
|
||||||
|
r: 0.20,
|
||||||
|
g: 0.40,
|
||||||
|
b: 0.70,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const LIST: Color = Color {
|
||||||
|
r: 0.12,
|
||||||
|
g: 0.12,
|
||||||
|
b: 0.12,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
|
move |_: &Theme, st| button::Style {
|
||||||
|
background: Some(Background::Color(match (accent, st) {
|
||||||
|
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||||
|
r: 0.20,
|
||||||
|
g: 0.42,
|
||||||
|
b: 0.72,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||||
|
r: 0.28,
|
||||||
|
g: 0.28,
|
||||||
|
b: 0.28,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
(true, _) => ACCENT,
|
||||||
|
_ => Color {
|
||||||
|
r: 0.22,
|
||||||
|
g: 0.22,
|
||||||
|
b: 0.22,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
text_color: TEXT,
|
||||||
|
border: Border {
|
||||||
|
color: BORDER,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 4.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
|
move |_: &Theme, st| button::Style {
|
||||||
|
background: Some(Background::Color(match (active, st) {
|
||||||
|
(true, _) => ACTIVE,
|
||||||
|
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||||
|
r: 0.26,
|
||||||
|
g: 0.26,
|
||||||
|
b: 0.26,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
_ => Color::TRANSPARENT,
|
||||||
|
})),
|
||||||
|
text_color: TEXT,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hdivider<'a>() -> Element<'a, Message> {
|
||||||
|
container(Space::new().width(Fill).height(1))
|
||||||
|
.width(Fill)
|
||||||
|
.height(1)
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(BORDER)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vsep<'a>() -> Element<'a, Message> {
|
||||||
|
container(Space::new().width(1).height(Fill))
|
||||||
|
.width(1)
|
||||||
|
.height(Fill)
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(BORDER)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// View-model borrowed from the app for one render of the dialog.
|
||||||
|
pub struct MLeaderStyleView<'a> {
|
||||||
|
pub styles: Vec<String>,
|
||||||
|
pub selected: &'a str,
|
||||||
|
pub style: Option<&'a acadrust::objects::MultiLeaderStyle>,
|
||||||
|
pub current: String,
|
||||||
|
pub landing_distance: &'a str,
|
||||||
|
pub landing_gap: &'a str,
|
||||||
|
pub arrowhead_size: &'a str,
|
||||||
|
pub text_height: &'a str,
|
||||||
|
pub scale_factor: &'a str,
|
||||||
|
pub break_gap: &'a str,
|
||||||
|
pub first_seg_angle: &'a str,
|
||||||
|
pub second_seg_angle: &'a str,
|
||||||
|
pub max_points: &'a str,
|
||||||
|
pub default_text: &'a str,
|
||||||
|
pub line_color: &'a str,
|
||||||
|
pub text_color: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn section<'a>(label: &'static str) -> Element<'a, Message> {
|
||||||
|
text(label).size(11).color(ACCENT).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn num_row<'a>(
|
||||||
|
label: &'static str,
|
||||||
|
placeholder: &'static str,
|
||||||
|
value: &'a str,
|
||||||
|
field: &'static str,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
row![
|
||||||
|
text(label).size(11).color(DIM).width(150),
|
||||||
|
text_input(placeholder, value)
|
||||||
|
.on_input(move |v| Message::MLeaderStyleEdit { field, value: v })
|
||||||
|
.size(11)
|
||||||
|
.width(110),
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enum_row<'a>(
|
||||||
|
label: &'static str,
|
||||||
|
val: String,
|
||||||
|
field: &'static str,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
row![
|
||||||
|
text(label).size(11).color(DIM).width(150),
|
||||||
|
text(val).size(11).width(150),
|
||||||
|
button(text("Cycle").size(10))
|
||||||
|
.on_press(Message::MLeaderStyleCycle(field))
|
||||||
|
.style(btn_s(false))
|
||||||
|
.padding([2, 8]),
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chk<'a>(label: &'static str, val: bool, field: &'static str) -> Element<'a, Message> {
|
||||||
|
checkbox(val)
|
||||||
|
.label(label)
|
||||||
|
.on_toggle(move |_| Message::MLeaderStyleToggle(field))
|
||||||
|
.size(14)
|
||||||
|
.text_size(11)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
||||||
|
// ── Toolbar ───────────────────────────────────────────────────────────
|
||||||
|
let toolbar = container(
|
||||||
|
row![
|
||||||
|
button(text("New").size(11))
|
||||||
|
.on_press(Message::MLeaderStyleDialogNew)
|
||||||
|
.style(btn_s(true))
|
||||||
|
.padding([4, 10]),
|
||||||
|
button(text("Delete").size(11))
|
||||||
|
.on_press(Message::MLeaderStyleDialogDelete)
|
||||||
|
.style(btn_s(false))
|
||||||
|
.padding([4, 10]),
|
||||||
|
button(text("Set Current").size(11))
|
||||||
|
.on_press(Message::MLeaderStyleDialogSetCurrent)
|
||||||
|
.style(btn_s(false))
|
||||||
|
.padding([4, 10]),
|
||||||
|
]
|
||||||
|
.spacing(4)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
)
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(TB)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Fill)
|
||||||
|
.padding([5, 8]);
|
||||||
|
|
||||||
|
// ── Left: Style list ──────────────────────────────────────────────────
|
||||||
|
let style_items: Vec<Element<'_, Message>> = v
|
||||||
|
.styles
|
||||||
|
.iter()
|
||||||
|
.map(|name| {
|
||||||
|
let is_sel = name.as_str() == v.selected;
|
||||||
|
button(text(name.clone()).size(11))
|
||||||
|
.on_press(Message::MLeaderStyleDialogSelect(name.clone()))
|
||||||
|
.style(list_item(is_sel))
|
||||||
|
.padding([4, 8])
|
||||||
|
.width(Fill)
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let style_list = container(
|
||||||
|
column![
|
||||||
|
text(format!("Current: {}", v.current)).size(10).color(DIM),
|
||||||
|
container(scrollable(column(style_items).spacing(2)).height(Fill))
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(LIST)),
|
||||||
|
border: Border {
|
||||||
|
color: BORDER,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 3.0.into()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.padding(2),
|
||||||
|
]
|
||||||
|
.spacing(4)
|
||||||
|
.height(Fill),
|
||||||
|
)
|
||||||
|
.width(200)
|
||||||
|
.height(Fill)
|
||||||
|
.padding(iced::Padding {
|
||||||
|
top: 12.0,
|
||||||
|
right: 8.0,
|
||||||
|
bottom: 12.0,
|
||||||
|
left: 12.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Right: Details panel ──────────────────────────────────────────────
|
||||||
|
let details: Element<'_, Message> = if let Some(s) = v.style {
|
||||||
|
scrollable(
|
||||||
|
column![
|
||||||
|
row![
|
||||||
|
text("Name:").size(11).color(DIM).width(150),
|
||||||
|
text(s.name.clone()).size(11),
|
||||||
|
]
|
||||||
|
.spacing(8),
|
||||||
|
// Leader Format
|
||||||
|
section("Leader Format"),
|
||||||
|
enum_row("Path type:", format!("{:?}", s.path_type), "path_type"),
|
||||||
|
num_row("Line color (ACI):", "256", v.line_color, "line_color"),
|
||||||
|
num_row("Arrowhead size:", "0.18", v.arrowhead_size, "arrowhead_size"),
|
||||||
|
num_row("Break gap size:", "0.125", v.break_gap, "break_gap"),
|
||||||
|
// Leader Structure
|
||||||
|
section("Leader Structure"),
|
||||||
|
chk("Enable landing", s.enable_landing, "enable_landing"),
|
||||||
|
chk("Enable dogleg", s.enable_dogleg, "enable_dogleg"),
|
||||||
|
num_row("Landing distance:", "8.0", v.landing_distance, "landing_distance"),
|
||||||
|
num_row("Landing gap:", "0.09", v.landing_gap, "landing_gap"),
|
||||||
|
num_row("Max leader points:", "2", v.max_points, "max_points"),
|
||||||
|
num_row("First seg. angle:", "0", v.first_seg_angle, "first_seg_angle"),
|
||||||
|
num_row("Second seg. angle:", "0", v.second_seg_angle, "second_seg_angle"),
|
||||||
|
num_row("Scale factor:", "1.0", v.scale_factor, "scale_factor"),
|
||||||
|
chk("Annotative", s.is_annotative, "annotative"),
|
||||||
|
// Content
|
||||||
|
section("Content"),
|
||||||
|
enum_row("Content type:", format!("{:?}", s.content_type), "content_type"),
|
||||||
|
num_row("Default text:", "", v.default_text, "default_text"),
|
||||||
|
num_row("Text height:", "0.18", v.text_height, "text_height"),
|
||||||
|
num_row("Text color (ACI):", "256", v.text_color, "text_color"),
|
||||||
|
enum_row(
|
||||||
|
"Text angle:",
|
||||||
|
format!("{:?}", s.text_angle_type),
|
||||||
|
"text_angle_type"
|
||||||
|
),
|
||||||
|
enum_row(
|
||||||
|
"Text alignment:",
|
||||||
|
format!("{:?}", s.text_alignment),
|
||||||
|
"text_alignment"
|
||||||
|
),
|
||||||
|
chk("Text frame", s.text_frame, "text_frame"),
|
||||||
|
chk("Text always left", s.text_always_left, "text_always_left"),
|
||||||
|
button(text("Apply").size(11))
|
||||||
|
.on_press(Message::MLeaderStyleApply)
|
||||||
|
.style(btn_s(true))
|
||||||
|
.padding([4, 14]),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.padding([12, 12]),
|
||||||
|
)
|
||||||
|
.height(Fill)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
container(text("Select a style to view details.").size(11).color(DIM))
|
||||||
|
.padding([12, 12])
|
||||||
|
.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
let right_panel = container(details).width(Fill).height(Fill);
|
||||||
|
|
||||||
|
let body = row![style_list, vsep(), right_panel].height(Fill);
|
||||||
|
|
||||||
|
container(column![toolbar, hdivider(), body].spacing(0))
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(BG)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ pub mod command_line;
|
||||||
pub mod dimstyle;
|
pub mod dimstyle;
|
||||||
pub mod layers;
|
pub mod layers;
|
||||||
pub mod layout_manager;
|
pub mod layout_manager;
|
||||||
|
pub mod mleaderstyle;
|
||||||
pub mod mlstyle;
|
pub mod mlstyle;
|
||||||
pub mod open_progress;
|
pub mod open_progress;
|
||||||
pub mod overlay;
|
pub mod overlay;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue