forked from 1b-agent/opencadstudio-upstream
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied, so their list CRUD kept drifting and breaking: Dimension's New created nothing (#73), new text styles never reached the ribbon dropdown (#74), and several were added with a NULL handle (dropped on DWG save, #67). Collapse the duplicated parts into three shared pieces and leave only the per-manager property editor bespoke: - app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy / delete / rename) handling handle allocation, ribbon sync, Table re-key and name-reference rewrites in a single place. - ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy / Delete on the left, Set Current + Apply on the right), style list and chrome. Each manager now only builds its editor. - ui/style_list.rs: the list row — single click selects, double click starts an inline rename. New capabilities, now consistent across all five: - Copy duplicates the selected style. - Double-click a name to rename it inline. - Right side is always Set Current + Apply (added Table set-current; MLine apply is a documented placeholder until its editor is editable). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
9cdb5ea5fd
commit
f2dfd01795
12 files changed
+2113
-1607
No files matched your search
+48
-148
@@ -6,18 +6,6 @@ use iced::widget::{
|
||||
};
|
||||
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,
|
||||
@@ -42,18 +30,6 @@ const ACCENT: Color = Color {
|
||||
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 {
|
||||
@@ -88,45 +64,6 @@ fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
}
|
||||
}
|
||||
|
||||
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>,
|
||||
@@ -161,6 +98,10 @@ pub struct MLeaderStyleView<'a> {
|
||||
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,
|
||||
}
|
||||
|
||||
fn section<'a>(label: &'static str) -> Element<'a, Message> {
|
||||
@@ -254,76 +195,6 @@ fn chk<'a>(label: &'static str, val: bool, field: &'static str) -> Element<'a, M
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -356,17 +227,37 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
||||
v.arrowhead_name.clone(),
|
||||
"arrowhead_handle"
|
||||
),
|
||||
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"),
|
||||
// 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 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(
|
||||
"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(
|
||||
@@ -462,7 +353,11 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
||||
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 scale",
|
||||
s.enable_block_scale,
|
||||
"enable_block_scale"
|
||||
),
|
||||
chk(
|
||||
"Enable block rotation",
|
||||
s.enable_block_rotation,
|
||||
@@ -487,14 +382,19 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
||||
|
||||
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()
|
||||
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(),
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user