Files
OneCAD-Draft/src/ui/mleaderstyle.rs
T
Hakan SevenandClaude Opus 4.8 3d050fe1ab feat(ui): move full ACI palette to a separate "Select Color" window (#94)
The colour selector popup now lists only the named colours (ByLayer,
ByBlock, ACI 1-9) plus a "More…" entry. "More…" opens a standalone
"Select Color" window with the full 256-colour grid; picking there applies
to the originating field (dimension/multileader/table style) and closes
the window. Routed via a ColorPickTarget so one window serves every field.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 11:14:14 +03:00

432 lines
15 KiB
Rust

//! Multileader Style Manager window — fills the entire OS window.
use crate::app::Message;
use iced::widget::{
button, checkbox, column, container, pick_list, row, scrollable, text, text_input,
};
use iced::{Background, Border, Color, Element, Fill, Theme};
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,
};
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()
}
}
/// 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,
pub description: &'a str,
pub line_weight: &'a str,
pub align_space: &'a str,
pub block_color: &'a str,
pub block_rotation: &'a str,
pub block_scale_x: &'a str,
pub block_scale_y: &'a str,
pub block_scale_z: &'a str,
// Handle dropdown option lists + currently-selected record names.
pub block_opts: Vec<String>,
pub lt_opts: Vec<String>,
pub textstyle_opts: Vec<String>,
pub line_type_name: String,
pub arrowhead_name: String,
pub text_style_name: String,
pub block_content_name: String,
/// Name of the style being renamed inline (double-clicked), if any.
pub rename_active: Option<&'a str>,
/// Edit buffer for the inline rename text input.
pub rename_buf: &'a str,
/// Colour field whose expanded palette is currently open.
pub color_open: Option<&'static 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()
}
/// Shared colour selector row. Reuses MLeaderStyleEdit by sending the chosen
/// colour as an ACI string; `open` shows the expanded palette.
fn color_row<'a>(
label: &'static str,
value: &'a str,
field: &'static str,
open: bool,
) -> Element<'a, Message> {
let cur = crate::ui::color_select::aci_string_to_color(value);
let selector = crate::ui::color_select::color_selector(
cur,
open,
crate::ui::color_select::ColorExtras {
by_layer: true,
by_block: true,
},
move |c| Message::MLeaderStyleEdit {
field,
value: crate::ui::color_select::color_to_aci_string(c),
},
Message::MLeaderColorMore(field),
Message::OpenColorWindow(crate::app::ColorPickTarget::MLeader(field)),
);
row![text(label).size(11).color(DIM).width(150), selector]
.spacing(8)
.align_y(iced::Center)
.into()
}
fn enum_row<'a>(
label: &'static str,
options: Vec<String>,
selected: String,
field: &'static str,
) -> Element<'a, Message> {
row![
text(label).size(11).color(DIM).width(150),
pick_list(options, Some(selected), move |value| {
Message::MLeaderStyleSetEnum { field, value }
})
.text_size(11)
.width(190),
]
.spacing(8)
.align_y(iced::Center)
.into()
}
/// The 11 horizontal text-attachment variants (debug names).
const ATTACH_OPTS: [&str; 11] = [
"TopOfTopLine",
"MiddleOfTopLine",
"MiddleOfText",
"MiddleOfBottomLine",
"BottomOfBottomLine",
"BottomLine",
"BottomOfTopLineUnderlineBottomLine",
"BottomOfTopLineUnderlineTopLine",
"BottomOfTopLineUnderlineAll",
"CenterOfText",
"CenterOfTextOverline",
];
fn opts(list: &[&str]) -> Vec<String> {
list.iter().map(|s| s.to_string()).collect()
}
/// Dropdown for an Option<Handle> field (linetype / arrowhead / text style /
/// block content). Options are record names; "None" clears the handle.
fn handle_row<'a>(
label: &'static str,
options: Vec<String>,
selected: String,
field: &'static str,
) -> Element<'a, Message> {
row![
text(label).size(11).color(DIM).width(150),
pick_list(options, Some(selected), move |value| {
Message::MLeaderStyleSetHandle { field, value }
})
.text_size(11)
.width(190),
]
.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> {
// ── 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),
num_row("Description:", "", v.description, "description"),
// Leader Format
section("Leader Format"),
enum_row(
"Path type:",
opts(&["Invisible", "StraightLineSegments", "Spline"]),
format!("{:?}", s.path_type),
"path_type"
),
color_row("Line color (ACI):", v.line_color, "line_color", v.color_open == Some("line_color")),
num_row("Line weight:", "-2", v.line_weight, "line_weight"),
handle_row(
"Line type:",
v.lt_opts.clone(),
v.line_type_name.clone(),
"line_type_handle"
),
handle_row(
"Arrowhead block:",
v.block_opts.clone(),
v.arrowhead_name.clone(),
"arrowhead_handle"
),
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"),
num_row("Align space:", "4.0", v.align_space, "align_space"),
enum_row(
"Leader draw order:",
opts(&["LeaderHeadFirst", "LeaderTailFirst"]),
format!("{:?}", s.leader_draw_order),
"leader_draw_order"
),
enum_row(
"Multileader draw order:",
opts(&["ContentFirst", "LeaderFirst"]),
format!("{:?}", s.multileader_draw_order),
"multileader_draw_order"
),
chk("Annotative", s.is_annotative, "annotative"),
// Content
section("Content"),
enum_row(
"Content type:",
opts(&["None", "Block", "MText", "Tolerance"]),
format!("{:?}", s.content_type),
"content_type"
),
num_row("Default text:", "", v.default_text, "default_text"),
handle_row(
"Text style:",
v.textstyle_opts.clone(),
v.text_style_name.clone(),
"text_style_handle"
),
num_row("Text height:", "0.18", v.text_height, "text_height"),
color_row("Text color (ACI):", v.text_color, "text_color", v.color_open == Some("text_color")),
enum_row(
"Text angle:",
opts(&["ParallelToLastLeaderLine", "Horizontal", "Optimized"]),
format!("{:?}", s.text_angle_type),
"text_angle_type"
),
enum_row(
"Text alignment:",
opts(&["Left", "Center", "Right"]),
format!("{:?}", s.text_alignment),
"text_alignment"
),
enum_row(
"Left attachment:",
opts(&ATTACH_OPTS),
format!("{:?}", s.text_left_attachment),
"text_left_attachment"
),
enum_row(
"Right attachment:",
opts(&ATTACH_OPTS),
format!("{:?}", s.text_right_attachment),
"text_right_attachment"
),
enum_row(
"Top attachment:",
opts(&ATTACH_OPTS),
format!("{:?}", s.text_top_attachment),
"text_top_attachment"
),
enum_row(
"Bottom attachment:",
opts(&ATTACH_OPTS),
format!("{:?}", s.text_bottom_attachment),
"text_bottom_attachment"
),
enum_row(
"Attachment direction:",
opts(&["Horizontal", "Vertical"]),
format!("{:?}", s.text_attachment_direction),
"text_attachment_direction"
),
chk("Text frame", s.text_frame, "text_frame"),
chk("Text always left", s.text_always_left, "text_always_left"),
// Block Content
section("Block Content"),
handle_row(
"Block:",
v.block_opts.clone(),
v.block_content_name.clone(),
"block_content_handle"
),
color_row("Block color (ACI):", v.block_color, "block_color", v.color_open == Some("block_color")),
enum_row(
"Block connection:",
opts(&["BlockExtents", "BasePoint"]),
format!("{:?}", s.block_content_connection),
"block_content_connection"
),
num_row("Block rotation:", "0", v.block_rotation, "block_rotation"),
num_row("Block scale X:", "1.0", v.block_scale_x, "block_scale_x"),
num_row("Block scale Y:", "1.0", v.block_scale_y, "block_scale_y"),
num_row("Block scale Z:", "1.0", v.block_scale_z, "block_scale_z"),
chk(
"Enable block scale",
s.enable_block_scale,
"enable_block_scale"
),
chk(
"Enable block rotation",
s.enable_block_rotation,
"enable_block_rotation"
),
button(text("Apply").size(11))
.on_press(Message::MLeaderStyleApply)
.style(btn_s(true))
.padding([4, 14]),
]
.spacing(6)
.padding([12, 12]),
)
.width(Fill)
.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);
crate::ui::style_manager::view(crate::ui::style_manager::Scaffold {
kind: crate::app::StyleKind::MLeader,
styles: &v.styles,
selected: v.selected,
current: Some(v.current.as_str()),
rename_active: v.rename_active,
rename_buf: v.rename_buf,
on_new: Message::MLeaderStyleDialogNew,
on_copy: Message::MLeaderStyleDialogCopy,
on_delete: Message::MLeaderStyleDialogDelete,
on_select: Message::MLeaderStyleDialogSelect,
on_set_current: Message::MLeaderStyleDialogSetCurrent,
on_apply: Message::MLeaderStyleApply,
editor: right_panel.into(),
})
}