refactor(styles): replace cycle buttons with dropdowns

The enum/handle pickers in the MLeader, Table and Dimension style
editors used "Cycle" buttons, which were awkward. Replace them with
pick_list dropdowns:
- MLeader: path type, content type, text angle, text alignment.
- Table per-cell: alignment (9 anchors).
- Dimension: arrowhead blocks (DIMBLK/1/2/LDRBLK) and ext-line linetypes
  (DIMLTYPE/LTEX1/LTEX2), options resolved from block_records/line_types.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-03 20:44:37 +03:00
1 parent 1b7edf6cae
commit bd120ca307
6 files changed
+161 -102

No files matched your search

+15 -6
View File
@@ -1026,8 +1026,11 @@ pub enum Message {
}, },
/// Toggle background fill on a cell style. /// Toggle background fill on a cell style.
TableStyleCellToggleFill(u8), TableStyleCellToggleFill(u8),
/// Cycle the alignment of a cell style. /// Set the alignment of a cell style from the dropdown.
TableStyleCellCycleAlign(u8), TableStyleCellSetAlign {
row: u8,
value: String,
},
/// Write a cell's edit buffers back into the selected table style. /// Write a cell's edit buffers back into the selected table style.
TableStyleCellApply(u8), TableStyleCellApply(u8),
// ── MLineStyle Dialog ───────────────────────────────────────────────── // ── MLineStyle Dialog ─────────────────────────────────────────────────
@@ -1051,7 +1054,10 @@ pub enum Message {
value: String, value: String,
}, },
MLeaderStyleToggle(&'static str), MLeaderStyleToggle(&'static str),
MLeaderStyleCycle(&'static str), MLeaderStyleSetEnum {
field: &'static str,
value: String,
},
MLeaderStyleApply, MLeaderStyleApply,
// ── DimStyle Dialog ─────────────────────────────────────────────────── // ── DimStyle Dialog ───────────────────────────────────────────────────
DimStyleDialogOpen, DimStyleDialogOpen,
@@ -1071,9 +1077,12 @@ pub enum Message {
// Field edit messages: // Field edit messages:
DsEdit(DsField, String), DsEdit(DsField, String),
DsToggle(DsField), DsToggle(DsField),
/// Cycle a block/linetype Handle field on the selected dim style to the /// Set a block/linetype Handle field on the selected dim style from a
/// next available block-record / linetype. /// dropdown of available block-records / linetypes (by name).
DsCycleHandle(&'static str), DsSetHandle {
field: &'static str,
value: String,
},
// ── Raster Image ────────────────────────────────────────────────────── // ── Raster Image ──────────────────────────────────────────────────────
/// Open file-picker dialog for IMAGE command (async). /// Open file-picker dialog for IMAGE command (async).
ImagePick, ImagePick,
+50 -61
View File
@@ -5810,21 +5810,21 @@ impl OpenCADStudio {
Task::none() Task::none()
} }
Message::TableStyleCellCycleAlign(row) => { Message::TableStyleCellSetAlign { row, value } => {
use acadrust::objects::CellAlignment; use acadrust::objects::CellAlignment;
let i = self.active_tab; let i = self.active_tab;
if let Some(s) = self.tablestyle_mut(i) { if let Some(s) = self.tablestyle_mut(i) {
if let Some(c) = Self::ts_cell_of(s, row) { if let Some(c) = Self::ts_cell_of(s, row) {
c.alignment = match c.alignment { c.alignment = match value.as_str() {
CellAlignment::TopLeft => CellAlignment::TopCenter, "TopLeft" => CellAlignment::TopLeft,
CellAlignment::TopCenter => CellAlignment::TopRight, "TopCenter" => CellAlignment::TopCenter,
CellAlignment::TopRight => CellAlignment::MiddleLeft, "TopRight" => CellAlignment::TopRight,
CellAlignment::MiddleLeft => CellAlignment::MiddleCenter, "MiddleLeft" => CellAlignment::MiddleLeft,
CellAlignment::MiddleCenter => CellAlignment::MiddleRight, "MiddleRight" => CellAlignment::MiddleRight,
CellAlignment::MiddleRight => CellAlignment::BottomLeft, "BottomLeft" => CellAlignment::BottomLeft,
CellAlignment::BottomLeft => CellAlignment::BottomCenter, "BottomCenter" => CellAlignment::BottomCenter,
CellAlignment::BottomCenter => CellAlignment::BottomRight, "BottomRight" => CellAlignment::BottomRight,
CellAlignment::BottomRight => CellAlignment::TopLeft, _ => CellAlignment::MiddleCenter,
}; };
} }
self.push_undo_snapshot(i, "TABLESTYLE EDIT"); self.push_undo_snapshot(i, "TABLESTYLE EDIT");
@@ -6298,7 +6298,7 @@ impl OpenCADStudio {
} }
Task::none() Task::none()
} }
Message::MLeaderStyleCycle(field) => { Message::MLeaderStyleSetEnum { field, value } => {
use acadrust::objects::{ use acadrust::objects::{
LeaderContentType, MultiLeaderPathType, TextAlignmentType, TextAngleType, LeaderContentType, MultiLeaderPathType, TextAlignmentType, TextAngleType,
}; };
@@ -6306,38 +6306,34 @@ impl OpenCADStudio {
if let Some(s) = self.mleaderstyle_mut(i) { if let Some(s) = self.mleaderstyle_mut(i) {
match field { match field {
"path_type" => { "path_type" => {
s.path_type = match s.path_type { s.path_type = match value.as_str() {
MultiLeaderPathType::Invisible => { "Invisible" => MultiLeaderPathType::Invisible,
MultiLeaderPathType::StraightLineSegments "Spline" => MultiLeaderPathType::Spline,
} _ => MultiLeaderPathType::StraightLineSegments,
MultiLeaderPathType::StraightLineSegments => {
MultiLeaderPathType::Spline
}
MultiLeaderPathType::Spline => MultiLeaderPathType::Invisible,
}; };
} }
"content_type" => { "content_type" => {
s.content_type = match s.content_type { s.content_type = match value.as_str() {
LeaderContentType::None => LeaderContentType::Block, "None" => LeaderContentType::None,
LeaderContentType::Block => LeaderContentType::MText, "Block" => LeaderContentType::Block,
LeaderContentType::MText => LeaderContentType::Tolerance, "Tolerance" => LeaderContentType::Tolerance,
LeaderContentType::Tolerance => LeaderContentType::None, _ => LeaderContentType::MText,
}; };
} }
"text_angle_type" => { "text_angle_type" => {
s.text_angle_type = match s.text_angle_type { s.text_angle_type = match value.as_str() {
TextAngleType::ParallelToLastLeaderLine => TextAngleType::Horizontal, "ParallelToLastLeaderLine" => {
TextAngleType::Horizontal => TextAngleType::Optimized,
TextAngleType::Optimized => {
TextAngleType::ParallelToLastLeaderLine TextAngleType::ParallelToLastLeaderLine
} }
"Optimized" => TextAngleType::Optimized,
_ => TextAngleType::Horizontal,
}; };
} }
"text_alignment" => { "text_alignment" => {
s.text_alignment = match s.text_alignment { s.text_alignment = match value.as_str() {
TextAlignmentType::Left => TextAlignmentType::Center, "Center" => TextAlignmentType::Center,
TextAlignmentType::Center => TextAlignmentType::Right, "Right" => TextAlignmentType::Right,
TextAlignmentType::Right => TextAlignmentType::Left, _ => TextAlignmentType::Left,
}; };
} }
_ => {} _ => {}
@@ -6534,7 +6530,7 @@ impl OpenCADStudio {
self.apply_ds_toggle(field); self.apply_ds_toggle(field);
Task::none() Task::none()
} }
Message::DsCycleHandle(field) => { Message::DsSetHandle { field, value } => {
let i = self.active_tab; let i = self.active_tab;
let name = self.dimstyle_selected.clone(); let name = self.dimstyle_selected.clone();
let is_lt = matches!( let is_lt = matches!(
@@ -6542,38 +6538,31 @@ impl OpenCADStudio {
"dimltex_handle" | "dimltex1_handle" | "dimltex2_handle" "dimltex_handle" | "dimltex1_handle" | "dimltex2_handle"
); );
let doc = &self.tabs[i].scene.document; let doc = &self.tabs[i].scene.document;
let mut candidates: Vec<acadrust::types::Handle> = let handle = if value == "Default" || value == "ByBlock" {
vec![acadrust::types::Handle::NULL]; acadrust::types::Handle::NULL
if is_lt { } else if is_lt {
candidates.extend(doc.line_types.iter().map(|lt| lt.handle)); doc.line_types
.iter()
.find(|lt| lt.name == value)
.map(|lt| lt.handle)
.unwrap_or(acadrust::types::Handle::NULL)
} else { } else {
candidates.extend(doc.block_records.iter().map(|b| b.handle)); doc.block_records
} .iter()
let Some(ds) = doc.dim_styles.get(&name) else { .find(|b| b.name == value)
return Task::none(); .map(|b| b.handle)
.unwrap_or(acadrust::types::Handle::NULL)
}; };
let cur = match field {
"dimblk" => ds.dimblk,
"dimblk1" => ds.dimblk1,
"dimblk2" => ds.dimblk2,
"dimldrblk" => ds.dimldrblk,
"dimltex_handle" => ds.dimltex_handle,
"dimltex1_handle" => ds.dimltex1_handle,
"dimltex2_handle" => ds.dimltex2_handle,
_ => return Task::none(),
};
let idx = candidates.iter().position(|h| *h == cur).unwrap_or(0);
let next = candidates[(idx + 1) % candidates.len()];
self.push_undo_snapshot(i, "DIMSTYLE EDIT"); self.push_undo_snapshot(i, "DIMSTYLE EDIT");
if let Some(ds) = self.tabs[i].scene.document.dim_styles.get_mut(&name) { if let Some(ds) = self.tabs[i].scene.document.dim_styles.get_mut(&name) {
match field { match field {
"dimblk" => ds.dimblk = next, "dimblk" => ds.dimblk = handle,
"dimblk1" => ds.dimblk1 = next, "dimblk1" => ds.dimblk1 = handle,
"dimblk2" => ds.dimblk2 = next, "dimblk2" => ds.dimblk2 = handle,
"dimldrblk" => ds.dimldrblk = next, "dimldrblk" => ds.dimldrblk = handle,
"dimltex_handle" => ds.dimltex_handle = next, "dimltex_handle" => ds.dimltex_handle = handle,
"dimltex1_handle" => ds.dimltex1_handle = next, "dimltex1_handle" => ds.dimltex1_handle = handle,
"dimltex2_handle" => ds.dimltex2_handle = next, "dimltex2_handle" => ds.dimltex2_handle = handle,
_ => {} _ => {}
} }
} }
+11 -3
View File
@@ -235,6 +235,12 @@ impl OpenCADStudio {
.map(|s| s.name.clone()) .map(|s| s.name.clone())
.collect(); .collect();
let doc = &tab.scene.document; let doc = &tab.scene.document;
// Dropdown options (names must match the records exactly so the
// selection can be resolved back to a handle on the update side).
let mut block_opts: Vec<String> = vec!["Default".to_string()];
block_opts.extend(doc.block_records.iter().map(|b| b.name.clone()));
let mut lt_opts: Vec<String> = vec!["ByBlock".to_string()];
lt_opts.extend(doc.line_types.iter().map(|lt| lt.name.clone()));
let blk_name = |h: acadrust::types::Handle| -> String { let blk_name = |h: acadrust::types::Handle| -> String {
if h.is_null() { if h.is_null() {
"Default".to_string() "Default".to_string()
@@ -242,8 +248,8 @@ impl OpenCADStudio {
doc.block_records doc.block_records
.iter() .iter()
.find(|b| b.handle == h) .find(|b| b.handle == h)
.map(|b| b.name.trim_start_matches('_').to_string()) .map(|b| b.name.clone())
.unwrap_or_else(|| "?".to_string()) .unwrap_or_else(|| "Default".to_string())
} }
}; };
let lt_name = |h: acadrust::types::Handle| -> String { let lt_name = |h: acadrust::types::Handle| -> String {
@@ -254,7 +260,7 @@ impl OpenCADStudio {
.iter() .iter()
.find(|lt| lt.handle == h) .find(|lt| lt.handle == h)
.map(|lt| lt.name.clone()) .map(|lt| lt.name.clone())
.unwrap_or_else(|| "?".to_string()) .unwrap_or_else(|| "ByBlock".to_string())
} }
}; };
let ds_sel = doc.dim_styles.get(&self.dimstyle_selected); let ds_sel = doc.dim_styles.get(&self.dimstyle_selected);
@@ -359,6 +365,8 @@ impl OpenCADStudio {
dimltex_name, dimltex_name,
dimltex1_name, dimltex1_name,
dimltex2_name, dimltex2_name,
block_opts,
lt_opts,
}, },
); );
} }
+26 -17
View File
@@ -1,7 +1,9 @@
//! Dimension Style Manager window — fills the entire OS window. //! Dimension Style Manager window — fills the entire OS window.
use crate::app::{DsField, Message}; use crate::app::{DsField, Message};
use iced::widget::{button, checkbox, column, container, row, scrollable, text, text_input, Space}; use iced::widget::{
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
};
use iced::{Background, Border, Color, Element, Fill, Theme}; use iced::{Background, Border, Color, Element, Fill, Theme};
const TB: Color = Color { const TB: Color = Color {
@@ -130,7 +132,7 @@ pub struct DimStyleValues<'a> {
pub dimalttz: &'a str, pub dimalttz: &'a str,
pub dimtolj: &'a str, pub dimtolj: &'a str,
pub dimtzin: &'a str, pub dimtzin: &'a str,
// Resolved display names for the block/linetype Handle fields. // Resolved selected names for the block/linetype Handle fields.
pub dimblk_name: String, pub dimblk_name: String,
pub dimblk1_name: String, pub dimblk1_name: String,
pub dimblk2_name: String, pub dimblk2_name: String,
@@ -138,6 +140,9 @@ pub struct DimStyleValues<'a> {
pub dimltex_name: String, pub dimltex_name: String,
pub dimltex1_name: String, pub dimltex1_name: String,
pub dimltex2_name: String, pub dimltex2_name: String,
// Dropdown option lists shared by the arrowhead / linetype fields.
pub block_opts: Vec<String>,
pub lt_opts: Vec<String>,
} }
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style { fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
@@ -384,16 +389,20 @@ pub fn view_window<'a>(
.into() .into()
}; };
// Block / linetype Handle cycler: shows the resolved name and a button that // Block / linetype Handle dropdown: pick a block-record (arrowheads) or a
// advances to the next available block-record (arrowheads) or linetype. // linetype by name from the available records.
let hrow = move |label: &'static str, name: String, field: &'static str| -> Element<'a, Message> { let hrow = move |label: &'static str,
options: Vec<String>,
selected: String,
field: &'static str|
-> Element<'a, Message> {
row![ row![
lbl(label), lbl(label),
text(name).size(11).width(110), pick_list(options, Some(selected), move |value| {
button(text("Cycle").size(10)) Message::DsSetHandle { field, value }
.on_press(Message::DsCycleHandle(field)) })
.style(btn_s(false)) .text_size(11)
.padding([2, 8]), .width(150),
] ]
.spacing(8) .spacing(8)
.align_y(iced::Center) .align_y(iced::Center)
@@ -444,18 +453,18 @@ pub fn view_window<'a>(
row![lbl("Ext line weight (DIMLWE)"), mk_field(DsField::Dimlwe, vals.dimlwe)].spacing(8).align_y(iced::Center), row![lbl("Ext line weight (DIMLWE)"), mk_field(DsField::Dimlwe, vals.dimlwe)].spacing(8).align_y(iced::Center),
chk("Fixed-length ext lines (DIMFXLON)", vals.dimfxlon, DsField::Dimfxlon), chk("Fixed-length ext lines (DIMFXLON)", vals.dimfxlon, DsField::Dimfxlon),
row![lbl("Fixed length (DIMFXL)"), mk_field(DsField::Dimfxl, vals.dimfxl)].spacing(8).align_y(iced::Center), row![lbl("Fixed length (DIMFXL)"), mk_field(DsField::Dimfxl, vals.dimfxl)].spacing(8).align_y(iced::Center),
hrow("Dim line linetype (DIMLTYPE)", vals.dimltex_name.clone(), "dimltex_handle"), hrow("Dim line linetype (DIMLTYPE)", vals.lt_opts.clone(), vals.dimltex_name.clone(), "dimltex_handle"),
hrow("Ext line 1 linetype (DIMLTEX1)", vals.dimltex1_name.clone(), "dimltex1_handle"), hrow("Ext line 1 linetype (DIMLTEX1)", vals.lt_opts.clone(), vals.dimltex1_name.clone(), "dimltex1_handle"),
hrow("Ext line 2 linetype (DIMLTEX2)", vals.dimltex2_name.clone(), "dimltex2_handle"), hrow("Ext line 2 linetype (DIMLTEX2)", vals.lt_opts.clone(), vals.dimltex2_name.clone(), "dimltex2_handle"),
] ]
.spacing(7) .spacing(7)
.into(), .into(),
1 => column![ 1 => column![
text("Arrows").size(11).color(ACCENT), text("Arrows").size(11).color(ACCENT),
hrow("Arrowhead (DIMBLK)", vals.dimblk_name.clone(), "dimblk"), hrow("Arrowhead (DIMBLK)", vals.block_opts.clone(), vals.dimblk_name.clone(), "dimblk"),
hrow("1st arrowhead (DIMBLK1)", vals.dimblk1_name.clone(), "dimblk1"), hrow("1st arrowhead (DIMBLK1)", vals.block_opts.clone(), vals.dimblk1_name.clone(), "dimblk1"),
hrow("2nd arrowhead (DIMBLK2)", vals.dimblk2_name.clone(), "dimblk2"), hrow("2nd arrowhead (DIMBLK2)", vals.block_opts.clone(), vals.dimblk2_name.clone(), "dimblk2"),
hrow("Leader arrowhead (DIMLDRBLK)", vals.dimldrblk_name.clone(), "dimldrblk"), hrow("Leader arrowhead (DIMLDRBLK)", vals.block_opts.clone(), vals.dimldrblk_name.clone(), "dimldrblk"),
row![ row![
lbl("Arrow size (DIMASZ)"), lbl("Arrow size (DIMASZ)"),
mk_field(DsField::Dimasz, vals.dimasz) mk_field(DsField::Dimasz, vals.dimasz)
+36 -9
View File
@@ -1,7 +1,9 @@
//! Multileader Style Manager window — fills the entire OS window. //! Multileader Style Manager window — fills the entire OS window.
use crate::app::Message; use crate::app::Message;
use iced::widget::{button, checkbox, column, container, row, scrollable, text, text_input, Space}; use iced::widget::{
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
};
use iced::{Background, Border, Color, Element, Fill, Theme}; use iced::{Background, Border, Color, Element, Fill, Theme};
const TB: Color = Color { const TB: Color = Color {
@@ -169,16 +171,17 @@ fn num_row<'a>(
fn enum_row<'a>( fn enum_row<'a>(
label: &'static str, label: &'static str,
val: String, options: Vec<String>,
selected: String,
field: &'static str, field: &'static str,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
row![ row![
text(label).size(11).color(DIM).width(150), text(label).size(11).color(DIM).width(150),
text(val).size(11).width(150), pick_list(options, Some(selected), move |value| {
button(text("Cycle").size(10)) Message::MLeaderStyleSetEnum { field, value }
.on_press(Message::MLeaderStyleCycle(field)) })
.style(btn_s(false)) .text_size(11)
.padding([2, 8]), .width(190),
] ]
.spacing(8) .spacing(8)
.align_y(iced::Center) .align_y(iced::Center)
@@ -276,7 +279,15 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
.spacing(8), .spacing(8),
// Leader Format // Leader Format
section("Leader Format"), section("Leader Format"),
enum_row("Path type:", format!("{:?}", s.path_type), "path_type"), enum_row(
"Path type:",
["Invisible", "StraightLineSegments", "Spline"]
.iter()
.map(|s| s.to_string())
.collect(),
format!("{:?}", s.path_type),
"path_type"
),
num_row("Line color (ACI):", "256", v.line_color, "line_color"), num_row("Line color (ACI):", "256", v.line_color, "line_color"),
num_row("Arrowhead size:", "0.18", v.arrowhead_size, "arrowhead_size"), num_row("Arrowhead size:", "0.18", v.arrowhead_size, "arrowhead_size"),
num_row("Break gap size:", "0.125", v.break_gap, "break_gap"), num_row("Break gap size:", "0.125", v.break_gap, "break_gap"),
@@ -293,17 +304,33 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
chk("Annotative", s.is_annotative, "annotative"), chk("Annotative", s.is_annotative, "annotative"),
// Content // Content
section("Content"), section("Content"),
enum_row("Content type:", format!("{:?}", s.content_type), "content_type"), enum_row(
"Content type:",
["None", "Block", "MText", "Tolerance"]
.iter()
.map(|s| s.to_string())
.collect(),
format!("{:?}", s.content_type),
"content_type"
),
num_row("Default text:", "", v.default_text, "default_text"), num_row("Default text:", "", v.default_text, "default_text"),
num_row("Text height:", "0.18", v.text_height, "text_height"), num_row("Text height:", "0.18", v.text_height, "text_height"),
num_row("Text color (ACI):", "256", v.text_color, "text_color"), num_row("Text color (ACI):", "256", v.text_color, "text_color"),
enum_row( enum_row(
"Text angle:", "Text angle:",
["ParallelToLastLeaderLine", "Horizontal", "Optimized"]
.iter()
.map(|s| s.to_string())
.collect(),
format!("{:?}", s.text_angle_type), format!("{:?}", s.text_angle_type),
"text_angle_type" "text_angle_type"
), ),
enum_row( enum_row(
"Text alignment:", "Text alignment:",
["Left", "Center", "Right"]
.iter()
.map(|s| s.to_string())
.collect(),
format!("{:?}", s.text_alignment), format!("{:?}", s.text_alignment),
"text_alignment" "text_alignment"
), ),
+23 -6
View File
@@ -1,7 +1,9 @@
//! Table Style Manager window — fills the entire OS window. //! Table Style Manager window — fills the entire OS window.
use crate::app::Message; use crate::app::Message;
use iced::widget::{button, checkbox, column, container, row, scrollable, text, text_input, Space}; use iced::widget::{
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
};
use iced::{Background, Border, Color, Element, Fill, Theme}; use iced::{Background, Border, Color, Element, Fill, Theme};
const TB: Color = Color { const TB: Color = Color {
@@ -241,11 +243,26 @@ pub fn view_window<'a>(
cell_in(" Fill color (ACI):", "256", &cell_fillcolor[r], "fillcolor"), cell_in(" Fill color (ACI):", "256", &cell_fillcolor[r], "fillcolor"),
row![ row![
text(" Alignment:").size(11).color(DIM).width(150), text(" Alignment:").size(11).color(DIM).width(150),
text(format!("{:?}", rs.alignment)).size(11).width(120), pick_list(
button(text("Cycle").size(10)) [
.on_press(Message::TableStyleCellCycleAlign(row)) "TopLeft",
.style(btn_s(false)) "TopCenter",
.padding([2, 8]), "TopRight",
"MiddleLeft",
"MiddleCenter",
"MiddleRight",
"BottomLeft",
"BottomCenter",
"BottomRight",
]
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
Some(format!("{:?}", rs.alignment)),
move |value| Message::TableStyleCellSetAlign { row, value },
)
.text_size(11)
.width(140),
] ]
.spacing(8) .spacing(8)
.align_y(iced::Center), .align_y(iced::Center),