feat: MLineStyle manager (MLSTYLE command + dialog)
- MLSTYLE command: LIST, NEW, SET, DEL subcommands - Multiline Style Manager dialog: style list, element details (offset, color, linetype), Set Current / New / Delete buttons - MLINE/ML command now reads document header.multiline_style to apply the current style to newly created MLine entities - MlineCommand.with_style() constructor for style-aware multiline drawing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7e6de57c8d
commit
34076b26bb
5 changed files with 407 additions and 6 deletions
179
src/app/view.rs
179
src/app/view.rs
|
|
@ -227,6 +227,21 @@ impl H7CAD {
|
|||
iced::widget::Space::new().width(0).height(0).into()
|
||||
};
|
||||
|
||||
let mlstyle_layer: Element<'_, Message> = if self.mlstyle_open {
|
||||
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::MLineStyle(s) = o { Some(s.name.clone()) } else { None })
|
||||
.collect();
|
||||
let selected_style = tab.scene.document.objects.values()
|
||||
.find_map(|o| if let ObjectType::MLineStyle(s) = o {
|
||||
if s.name == self.mlstyle_selected { Some(s) } else { None }
|
||||
} else { None });
|
||||
mlstyle_overlay(styles, &self.mlstyle_selected, selected_style, tab.scene.document.header.multiline_style.clone())
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).height(0).into()
|
||||
};
|
||||
|
||||
let dimstyle_layer: Element<'_, Message> = if self.dimstyle_open {
|
||||
let tab = &self.tabs[self.active_tab];
|
||||
let styles: Vec<String> = tab.scene.document.dim_styles
|
||||
|
|
@ -241,7 +256,7 @@ impl H7CAD {
|
|||
iced::widget::Space::new().width(0).height(0).into()
|
||||
};
|
||||
|
||||
stack![main_ui, self.app_menu.view(), snap_layer, dropdown_layer, layout_ctx_layer, page_setup_layer, dimstyle_layer].into()
|
||||
stack![main_ui, self.app_menu.view(), snap_layer, dropdown_layer, layout_ctx_layer, page_setup_layer, mlstyle_layer, dimstyle_layer].into()
|
||||
}
|
||||
|
||||
pub fn subscription(&self) -> Subscription<Message> {
|
||||
|
|
@ -1021,3 +1036,165 @@ fn dimstyle_overlay<'a>(
|
|||
|
||||
stack![catcher, positioned].into()
|
||||
}
|
||||
|
||||
// ── MLineStyle Dialog overlay ───────────────────────────────────────────────
|
||||
|
||||
fn mlstyle_overlay<'a>(
|
||||
styles: Vec<String>,
|
||||
selected: &'a str,
|
||||
selected_style: Option<&'a acadrust::objects::MLineStyle>,
|
||||
current_style: String,
|
||||
) -> Element<'a, Message> {
|
||||
use iced::Length::Shrink;
|
||||
|
||||
const PANEL_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_COL: Color = Color { r: 0.88, g: 0.88, b: 0.88, a: 1.0 };
|
||||
const DIM_COL: 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_BG: Color = Color { r: 0.20, g: 0.40, b: 0.70, a: 1.0 };
|
||||
|
||||
let btn_style = |accent: bool| move |_: &Theme, status: button::Status| button::Style {
|
||||
background: Some(Background::Color(match (accent, status) {
|
||||
(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_COL,
|
||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Style list.
|
||||
let style_items: Vec<Element<'_, Message>> = styles.iter().map(|name| {
|
||||
let is_sel = name.as_str() == selected;
|
||||
let is_cur = *name == current_style;
|
||||
let label = if is_cur {
|
||||
format!("{} ◀", name)
|
||||
} else {
|
||||
name.clone()
|
||||
};
|
||||
button(text(label).size(11).color(TEXT_COL))
|
||||
.on_press(Message::MlStyleDialogSelect(name.clone()))
|
||||
.style(move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (is_sel, st) {
|
||||
(true, _) => ACTIVE_BG,
|
||||
(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_COL,
|
||||
..Default::default()
|
||||
})
|
||||
.padding([3, 8])
|
||||
.width(Fill)
|
||||
.into()
|
||||
}).collect();
|
||||
|
||||
let style_panel = container(
|
||||
column(style_items).spacing(2)
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 })),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.padding(4)
|
||||
.width(160)
|
||||
.height(240);
|
||||
|
||||
// Right panel: details for selected style.
|
||||
let details: Element<'_, Message> = if let Some(s) = selected_style {
|
||||
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM_COL).width(110),
|
||||
text(val).size(11).color(TEXT_COL),
|
||||
].spacing(8).align_y(iced::Center).into()
|
||||
};
|
||||
let elem_rows: Vec<Element<'_, Message>> = s.elements.iter().enumerate().map(|(idx, e)| {
|
||||
let color_str: String = match &e.color {
|
||||
acadrust::types::Color::ByLayer => "ByLayer".into(),
|
||||
acadrust::types::Color::ByBlock => "ByBlock".into(),
|
||||
acadrust::types::Color::Index(i) => format!("ACI {}", i),
|
||||
acadrust::types::Color::Rgb { r, g, b } => format!("#{:02X}{:02X}{:02X}", r, g, b),
|
||||
};
|
||||
let lt = if e.linetype.is_empty() { "ByLayer" } else { e.linetype.as_str() };
|
||||
row![
|
||||
text(format!(" {}:", idx)).size(10).color(DIM_COL).width(20),
|
||||
text(format!("{:+.3}", e.offset)).size(10).color(TEXT_COL).width(60),
|
||||
text(color_str).size(10).color(TEXT_COL).width(80),
|
||||
text(lt).size(10).color(TEXT_COL),
|
||||
].spacing(4).align_y(iced::Center).into()
|
||||
}).collect();
|
||||
|
||||
let mut col_items: Vec<Element<'_, Message>> = vec![
|
||||
info_row("Name:", s.name.clone()),
|
||||
info_row("Elements:", s.elements.len().to_string()),
|
||||
text(" Off Color Ltype").size(10).color(DIM_COL).into(),
|
||||
];
|
||||
col_items.extend(elem_rows);
|
||||
column(col_items).spacing(6).into()
|
||||
} else {
|
||||
text("No style selected.").size(11).color(DIM_COL).into()
|
||||
};
|
||||
|
||||
let right_panel = column![
|
||||
details,
|
||||
Space::new().height(Fill),
|
||||
row![
|
||||
button(text("Set Current").size(11))
|
||||
.on_press(Message::MlStyleDialogSetCurrent)
|
||||
.style(btn_style(true))
|
||||
.padding([5, 10]),
|
||||
button(text("New").size(11))
|
||||
.on_press(Message::MlStyleDialogNew)
|
||||
.style(btn_style(false))
|
||||
.padding([5, 10]),
|
||||
button(text("Delete").size(11))
|
||||
.on_press(Message::MlStyleDialogDelete)
|
||||
.style(btn_style(false))
|
||||
.padding([5, 10]),
|
||||
].spacing(6),
|
||||
]
|
||||
.spacing(10)
|
||||
.width(280)
|
||||
.height(240);
|
||||
|
||||
let panel = container(
|
||||
column![
|
||||
row![
|
||||
text("Multiline Style Manager").size(13).color(TEXT_COL),
|
||||
Space::new().width(Fill),
|
||||
button(text("✕").size(12).color(DIM_COL))
|
||||
.on_press(Message::MlStyleDialogClose)
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: DIM_COL,
|
||||
..Default::default()
|
||||
})
|
||||
.padding([2, 6]),
|
||||
].align_y(iced::Center),
|
||||
row![style_panel, right_panel].spacing(12).align_y(iced::Top),
|
||||
].spacing(10).padding(16)
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 6.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.width(Shrink);
|
||||
|
||||
let catcher = mouse_area(
|
||||
container(iced::widget::Space::new().width(Fill).height(Fill))
|
||||
).on_press(Message::MlStyleDialogClose);
|
||||
|
||||
let positioned = container(panel)
|
||||
.width(Fill).height(Fill)
|
||||
.align_x(iced::Alignment::Center)
|
||||
.align_y(iced::Alignment::Center);
|
||||
|
||||
stack![catcher, positioned].into()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue