refactor: move all dialog UIs to dedicated files under src/ui/

Each dialog window (Page Setup, DimStyle, TextStyle, TableStyle,
MLineStyle, PlotStyle, Layout Manager, Shortcuts) is now a proper
window-filling layout in its own src/ui/<dialog>.rs file.

Pattern: toolbar strip at top + vertical separator + scrollable
content filling the rest — no modal card, no backdrop, no stack[].
Matches the existing Layer Manager window pattern.

Removed ~1600 lines of old overlay functions from view.rs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-04-08 22:56:51 +03:00
1 parent 888095cc3e
commit fe4f9ca844
10 files changed
+1590 -1631

No files matched your search

+25 -1631
View File
File diff suppressed because it is too large. Load diff
+263
View File
@@ -0,0 +1,263 @@
//! Dimension Style Manager window — fills the entire OS window.
use crate::app::{DsField, 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 FIELD: Color = Color { r: 0.10, g: 0.10, b: 0.10, a: 1.0 };
const LIST: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 };
/// All DimStyle field values needed by the view.
pub struct DimStyleValues<'a> {
pub dimdle: &'a str, pub dimdli: &'a str, pub dimgap: &'a str,
pub dimexe: &'a str, pub dimexo: &'a str,
pub dimsd1: bool, pub dimsd2: bool,
pub dimse1: bool, pub dimse2: bool,
pub dimasz: &'a str, pub dimcen: &'a str, pub dimtsz: &'a str,
pub dimtxt: &'a str, pub dimtxsty: &'a str, pub dimtad: &'a str,
pub dimtih: bool, pub dimtoh: bool,
pub dimscale: &'a str, pub dimlfac: &'a str,
pub dimlunit: &'a str, pub dimdec: &'a str, pub dimpost: &'a str,
pub dimtol: bool, pub dimlim: bool,
pub dimtp: &'a str, pub dimtm: &'a str,
pub dimtdec: &'a str, pub dimtfac: &'a str,
}
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 tab_btn_style(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.28, g: 0.28, b: 0.28, a: 1.0 },
_ => Color { r: 0.20, g: 0.20, b: 0.20, a: 1.0 },
})),
text_color: TEXT,
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
..Default::default()
}
}
fn list_item_style(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.28, g: 0.28, b: 0.28, a: 1.0 },
_ => Color { r: 0.18, g: 0.18, b: 0.18, a: 1.0 },
})),
text_color: TEXT,
border: Border { color: BORDER, width: 0.0, radius: 3.0.into() },
..Default::default()
}
}
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
text_input::Style {
background: Background::Color(FIELD),
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
icon: TEXT, placeholder: DIM, value: TEXT, selection: ACCENT,
}
}
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()
}
pub fn view_window<'a>(
styles: Vec<String>,
selected: &'a str,
tab: u8,
vals: DimStyleValues<'a>,
) -> Element<'a, Message> {
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
row![
button(text("New").size(11)).on_press(Message::DimStyleDialogNew).style(btn_s(false)).padding([4, 10]),
button(text("Delete").size(11)).on_press(Message::DimStyleDialogDelete).style(btn_s(false)).padding([4, 10]),
button(text("Set Current").size(11)).on_press(Message::DimStyleDialogSetCurrent).style(btn_s(false)).padding([4, 10]),
Space::new().width(Fill),
button(text("Apply").size(11)).on_press(Message::DimStyleDialogApply).style(btn_s(true)).padding([4, 14]),
button(text("Close").size(11)).on_press(Message::DimStyleDialogClose).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]);
// ── Style list panel ──────────────────────────────────────────────────
let mut list_col = column![].spacing(2);
for name in &styles {
let active = name.as_str() == selected;
list_col = list_col.push(
button(text(name.clone()).size(11))
.on_press(Message::DimStyleDialogSelect(name.clone()))
.style(list_item_style(active))
.padding([4, 8])
.width(Fill)
);
}
let style_list = container(
column![
text("Styles").size(10).color(DIM),
container(scrollable(list_col).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(180)
.height(Fill)
.padding(2),
]
.spacing(4)
.height(Fill)
)
.width(180)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 8.0, bottom: 12.0, left: 12.0 });
// ── Tab bar ───────────────────────────────────────────────────────────
let tabs = row![
button(text("Lines").size(11)).on_press(Message::DimStyleDialogTab(0)).style(tab_btn_style(tab == 0)).padding([4, 10]),
button(text("Arrows").size(11)).on_press(Message::DimStyleDialogTab(1)).style(tab_btn_style(tab == 1)).padding([4, 10]),
button(text("Text").size(11)).on_press(Message::DimStyleDialogTab(2)).style(tab_btn_style(tab == 2)).padding([4, 10]),
button(text("Scale/Units").size(11)).on_press(Message::DimStyleDialogTab(3)).style(tab_btn_style(tab == 3)).padding([4, 10]),
button(text("Tolerances").size(11)).on_press(Message::DimStyleDialogTab(4)).style(tab_btn_style(tab == 4)).padding([4, 10]),
].spacing(2);
let lbl = |s: &'static str| text(s).size(11).color(DIM).width(180);
let mk_field = |fld: DsField, val: &'a str| -> Element<'a, Message> {
text_input("", val)
.on_input(move |s| Message::DsEdit(fld.clone(), s))
.style(field_style)
.size(11)
.width(100)
.into()
};
let chk = |label: &'static str, val: bool, fld: DsField| -> Element<'a, Message> {
checkbox(val)
.label(label)
.on_toggle(move |_| Message::DsToggle(fld.clone()))
.size(14)
.text_size(11)
.into()
};
let tab_content: Element<'_, Message> = match tab {
0 => column![
text("Dimension Line").size(11).color(ACCENT),
row![lbl("Extension (DIMDLE)"), mk_field(DsField::Dimdle, vals.dimdle)].spacing(8).align_y(iced::Center),
row![lbl("Spacing (DIMDLI)"), mk_field(DsField::Dimdli, vals.dimdli)].spacing(8).align_y(iced::Center),
row![lbl("Text gap (DIMGAP)"), mk_field(DsField::Dimgap, vals.dimgap)].spacing(8).align_y(iced::Center),
chk("Suppress 1st line (DIMSD1)", vals.dimsd1, DsField::Dimsd1),
chk("Suppress 2nd line (DIMSD2)", vals.dimsd2, DsField::Dimsd2),
text("Extension Line").size(11).color(ACCENT),
row![lbl("Extension (DIMEXE)"), mk_field(DsField::Dimexe, vals.dimexe)].spacing(8).align_y(iced::Center),
row![lbl("Offset (DIMEXO)"), mk_field(DsField::Dimexo, vals.dimexo)].spacing(8).align_y(iced::Center),
chk("Suppress 1st line (DIMSE1)", vals.dimse1, DsField::Dimse1),
chk("Suppress 2nd line (DIMSE2)", vals.dimse2, DsField::Dimse2),
].spacing(7).into(),
1 => column![
text("Arrows").size(11).color(ACCENT),
row![lbl("Arrow size (DIMASZ)"), mk_field(DsField::Dimasz, vals.dimasz)].spacing(8).align_y(iced::Center),
row![lbl("Center mark (DIMCEN)"), mk_field(DsField::Dimcen, vals.dimcen)].spacing(8).align_y(iced::Center),
row![lbl("Tick size (DIMTSZ)"), mk_field(DsField::Dimtsz, vals.dimtsz)].spacing(8).align_y(iced::Center),
].spacing(7).into(),
2 => column![
text("Text").size(11).color(ACCENT),
row![lbl("Height (DIMTXT)"), mk_field(DsField::Dimtxt, vals.dimtxt)].spacing(8).align_y(iced::Center),
row![lbl("Style (DIMTXSTY)"), mk_field(DsField::Dimtxsty, vals.dimtxsty)].spacing(8).align_y(iced::Center),
row![lbl("Vertical pos (DIMTAD)"), mk_field(DsField::Dimtad, vals.dimtad)].spacing(8).align_y(iced::Center),
chk("Horizontal inside (DIMTIH)", vals.dimtih, DsField::Dimtih),
chk("Horizontal outside (DIMTOH)", vals.dimtoh, DsField::Dimtoh),
].spacing(7).into(),
3 => column![
text("Scale").size(11).color(ACCENT),
row![lbl("Overall scale (DIMSCALE)"), mk_field(DsField::Dimscale, vals.dimscale)].spacing(8).align_y(iced::Center),
row![lbl("Linear factor (DIMLFAC)"), mk_field(DsField::Dimlfac, vals.dimlfac)].spacing(8).align_y(iced::Center),
text("Units").size(11).color(ACCENT),
row![lbl("Format (DIMLUNIT)"), mk_field(DsField::Dimlunit, vals.dimlunit)].spacing(8).align_y(iced::Center),
row![lbl("Decimals (DIMDEC)"), mk_field(DsField::Dimdec, vals.dimdec)].spacing(8).align_y(iced::Center),
row![lbl("Suffix (DIMPOST)"), mk_field(DsField::Dimpost, vals.dimpost)].spacing(8).align_y(iced::Center),
].spacing(7).into(),
_ => column![
text("Tolerances").size(11).color(ACCENT),
chk("Generate tolerances (DIMTOL)", vals.dimtol, DsField::Dimtol),
chk("Limits generation (DIMLIM)", vals.dimlim, DsField::Dimlim),
row![lbl("Plus tolerance (DIMTP)"), mk_field(DsField::Dimtp, vals.dimtp)].spacing(8).align_y(iced::Center),
row![lbl("Minus tolerance (DIMTM)"), mk_field(DsField::Dimtm, vals.dimtm)].spacing(8).align_y(iced::Center),
row![lbl("Tol. decimals (DIMTDEC)"), mk_field(DsField::Dimtdec, vals.dimtdec)].spacing(8).align_y(iced::Center),
row![lbl("Tol. scale (DIMTFAC)"), mk_field(DsField::Dimtfac, vals.dimtfac)].spacing(8).align_y(iced::Center),
].spacing(7).into(),
};
// ── Right panel: tabs + scrollable content ────────────────────────────
let right_panel = container(
column![
text(format!("Editing: {selected}")).size(11).color(DIM),
tabs,
hdivider(),
scrollable(
container(tab_content).padding([12, 12])
).height(Fill),
]
.spacing(6)
.height(Fill)
)
.height(Fill)
.width(Fill)
.padding(iced::Padding { top: 12.0, right: 12.0, bottom: 12.0, left: 0.0 });
// ── Vertical separator ────────────────────────────────────────────────
let vsep = container(Space::new().width(1).height(Fill))
.width(1).height(Fill)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
});
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()
}
+192
View File
@@ -0,0 +1,192 @@
//! Layout Manager window — fills the entire OS window.
use crate::app::Message;
use iced::widget::{button, 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 FIELD: Color = Color { r: 0.10, g: 0.10, b: 0.10, a: 1.0 };
const LIST: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 };
const WARN: Color = Color { r: 0.80, g: 0.35, b: 0.25, 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 field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
text_input::Style {
background: Background::Color(FIELD),
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
icon: TEXT, placeholder: DIM, value: TEXT, selection: ACCENT,
}
}
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()
}
pub fn view_window<'a>(
layouts: Vec<String>,
selected: &'a str,
rename_buf: &'a str,
current: String,
) -> Element<'a, Message> {
let is_model = selected == "Model";
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
row![
button(text("New Layout").size(11)).on_press(Message::LayoutManagerNew).style(btn_s(false)).padding([4, 10]),
button(text("Delete").size(11))
.on_press(Message::LayoutManagerDelete)
.style(move |_: &Theme, st| button::Style {
background: Some(Background::Color(match st {
button::Status::Hovered | button::Status::Pressed => Color { r: 0.60, g: 0.20, b: 0.18, a: 1.0 },
_ => Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 },
})),
text_color: if is_model { DIM } else { WARN },
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
..Default::default()
})
.padding([4, 10]),
Space::new().width(Fill),
button(text("◀ Move Left").size(11)).on_press(Message::LayoutManagerMoveLeft).style(btn_s(false)).padding([4, 8]),
button(text("Move Right ▶").size(11)).on_press(Message::LayoutManagerMoveRight).style(btn_s(false)).padding([4, 8]),
button(text("Set Current").size(11)).on_press(Message::LayoutManagerSetCurrent).style(btn_s(true)).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: Layout list ─────────────────────────────────────────────────
let list_items: Vec<Element<'_, Message>> = layouts.iter().map(|name| {
let is_sel = name.as_str() == selected;
let is_cur = name.as_str() == current.as_str();
let label = if is_cur { format!("{name} ◀") } else { name.clone() };
button(text(label).size(12))
.on_press(Message::LayoutManagerSelect(name.clone()))
.style(list_item(is_sel))
.padding([5, 10])
.width(Fill)
.into()
}).collect();
let layout_list = container(
column![
text("Layouts").size(10).color(DIM),
container(scrollable(column(list_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(220)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 8.0, bottom: 12.0, left: 12.0 });
// ── Right: Details + rename ───────────────────────────────────────────
let details = container(
column![
text(if is_model { "Model Space" } else { "Paper Space Layout" })
.size(13).color(TEXT),
Space::new().height(8),
row![
text("Name:").size(11).color(DIM).width(80),
text(selected).size(11),
].spacing(8).align_y(iced::Center),
row![
text("Status:").size(11).color(DIM).width(80),
text(if selected == current.as_str() { "Active ◀" } else { "Inactive" })
.size(11)
.color(if selected == current.as_str() { ACCENT } else { DIM }),
].spacing(8).align_y(iced::Center),
Space::new().height(16),
text("Rename").size(10).color(DIM),
row![
text_input("New name…", rename_buf)
.on_input(Message::LayoutManagerRenameBuf)
.on_submit(Message::LayoutManagerRenameCommit)
.style(field_style)
.size(11)
.padding([4, 8]),
button(text("OK").size(11))
.on_press(Message::LayoutManagerRenameCommit)
.style(btn_s(true))
.padding([4, 10]),
].spacing(6).align_y(iced::Center),
]
.spacing(8)
)
.width(Fill)
.padding([12, 12]);
let body = row![layout_list, vsep(), details].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()
}
+170
View File
@@ -0,0 +1,170 @@
//! Multiline Style Manager window — fills the entire OS window.
use crate::app::Message;
use iced::widget::{button, column, container, row, scrollable, text, 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()
}
pub fn view_window<'a>(
styles: Vec<String>,
selected: &'a str,
selected_style: Option<&'a acadrust::objects::MLineStyle>,
current_style: String,
) -> Element<'a, Message> {
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
row![
button(text("Set Current").size(11)).on_press(Message::MlStyleDialogSetCurrent).style(btn_s(true)).padding([4, 10]),
button(text("New").size(11)).on_press(Message::MlStyleDialogNew).style(btn_s(false)).padding([4, 10]),
button(text("Delete").size(11)).on_press(Message::MlStyleDialogDelete).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>> = 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))
.on_press(Message::MlStyleDialogSelect(name.clone()))
.style(list_item(is_sel))
.padding([4, 8])
.width(Fill)
.into()
}).collect();
let style_list = container(
column![
text("Styles").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 info_row = |label: &'static str, val: String| -> Element<'_, Message> {
row![
text(label).size(11).color(DIM).width(120),
text(val).size(11),
].spacing(8).align_y(iced::Center).into()
};
let details: Element<'_, Message> = if let Some(s) = selected_style {
let elem_rows: Vec<Element<'_, Message>> = s.elements.iter().enumerate().map(|(idx, e)| {
let color_str = 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!("#{r:02X}{g:02X}{b:02X}"),
};
let lt = if e.linetype.is_empty() { "ByLayer" } else { &e.linetype };
row![
text(format!(" {idx}:")).size(10).color(DIM).width(24),
text(format!("{:+.3}", e.offset)).size(10).width(70),
text(color_str).size(10).width(90),
text(lt).size(10),
].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).into(),
];
col_items.extend(elem_rows);
scrollable(column(col_items).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()
}
+8
View File
@@ -4,12 +4,20 @@ pub const ROW_H: f32 = 26.0;
pub mod app_menu;
pub mod command_line;
pub mod dimstyle;
pub mod layers;
pub mod layout_manager;
pub mod mlstyle;
pub mod overlay;
pub mod page_setup;
pub mod plotstyle;
pub mod properties;
pub mod ribbon;
pub mod shortcuts;
pub mod snap_popup;
pub mod statusbar;
pub mod tablestyle;
pub mod textstyle;
pub use app_menu::AppMenu;
pub use command_line::CommandLine;
+199
View File
@@ -0,0 +1,199 @@
//! Page Setup window — fills the entire OS window.
use crate::app::Message;
use iced::widget::{button, 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 FIELD: Color = Color { r: 0.10, g: 0.10, b: 0.10, a: 1.0 };
fn btn(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() },
shadow: iced::Shadow::default(),
snap: false,
}
}
fn pill(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.28, g: 0.28, b: 0.28, a: 1.0 },
_ => Color { r: 0.20, g: 0.20, b: 0.20, a: 1.0 },
})),
text_color: TEXT,
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
shadow: iced::Shadow::default(),
snap: false,
}
}
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
text_input::Style {
background: Background::Color(FIELD),
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
icon: TEXT, placeholder: DIM, value: TEXT, selection: ACCENT,
}
}
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 section_label<'a>(s: &'static str) -> Element<'a, Message> {
text(s).size(11).color(DIM).into()
}
pub fn view_window<'a>(
w_buf: &'a str,
h_buf: &'a str,
plot_area: &'a str,
center: bool,
offset_x: &'a str,
offset_y: &'a str,
rotation: &'a str,
scale: &'a str,
) -> Element<'a, Message> {
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
row![
button(text("Cancel").size(12))
.on_press(Message::PageSetupClose)
.style(btn(false))
.padding([4, 14]),
Space::new().width(Fill),
button(text("OK").size(12))
.on_press(Message::PageSetupCommit)
.style(btn(true))
.padding([4, 20]),
]
.align_y(iced::Center)
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
..Default::default()
})
.width(Fill)
.padding([5, 10]);
let lbl = |s: &'static str| text(s).size(11).color(DIM).width(130);
// ── Paper size presets ────────────────────────────────────────────────
let presets1 = row![
button(text("A4 P").size(10)).on_press(Message::PageSetupPreset("A4 Portrait".into())).style(pill(false)).padding([3, 6]),
button(text("A4 L").size(10)).on_press(Message::PageSetupPreset("A4 Landscape".into())).style(pill(false)).padding([3, 6]),
button(text("A3 P").size(10)).on_press(Message::PageSetupPreset("A3 Portrait".into())).style(pill(false)).padding([3, 6]),
button(text("A3 L").size(10)).on_press(Message::PageSetupPreset("A3 Landscape".into())).style(pill(false)).padding([3, 6]),
].spacing(4);
let presets2 = row![
button(text("A2 L").size(10)).on_press(Message::PageSetupPreset("A2 Landscape".into())).style(pill(false)).padding([3, 6]),
button(text("A1 L").size(10)).on_press(Message::PageSetupPreset("A1 Landscape".into())).style(pill(false)).padding([3, 6]),
button(text("A0 L").size(10)).on_press(Message::PageSetupPreset("A0 Landscape".into())).style(pill(false)).padding([3, 6]),
button(text("Letter").size(10)).on_press(Message::PageSetupPreset("Letter Landscape".into())).style(pill(false)).padding([3, 6]),
].spacing(4);
// ── Rotation buttons ──────────────────────────────────────────────────
let rot_row = row![
button(text("0°").size(10)).on_press(Message::PageSetupRotation("0".into())).style(pill(rotation == "0")).padding([3, 8]),
button(text("90°").size(10)).on_press(Message::PageSetupRotation("90".into())).style(pill(rotation == "90")).padding([3, 8]),
button(text("180°").size(10)).on_press(Message::PageSetupRotation("180".into())).style(pill(rotation == "180")).padding([3, 8]),
button(text("270°").size(10)).on_press(Message::PageSetupRotation("270".into())).style(pill(rotation == "270")).padding([3, 8]),
].spacing(4);
// ── Scale buttons ─────────────────────────────────────────────────────
let scale_row1 = row![
button(text("Fit").size(10)).on_press(Message::PageSetupScale("Fit".into())).style(pill(scale == "Fit")).padding([3, 8]),
button(text("1:1").size(10)).on_press(Message::PageSetupScale("1:1".into())).style(pill(scale == "1:1")).padding([3, 8]),
button(text("1:2").size(10)).on_press(Message::PageSetupScale("1:2".into())).style(pill(scale == "1:2")).padding([3, 8]),
button(text("1:5").size(10)).on_press(Message::PageSetupScale("1:5".into())).style(pill(scale == "1:5")).padding([3, 8]),
button(text("1:10").size(10)).on_press(Message::PageSetupScale("1:10".into())).style(pill(scale == "1:10")).padding([3, 8]),
].spacing(4);
let scale_row2 = row![
button(text("1:20").size(10)).on_press(Message::PageSetupScale("1:20".into())).style(pill(scale == "1:20")).padding([3, 8]),
button(text("1:50").size(10)).on_press(Message::PageSetupScale("1:50".into())).style(pill(scale == "1:50")).padding([3, 8]),
button(text("1:100").size(10)).on_press(Message::PageSetupScale("1:100".into())).style(pill(scale == "1:100")).padding([3, 8]),
button(text("2:1").size(10)).on_press(Message::PageSetupScale("2:1".into())).style(pill(scale == "2:1")).padding([3, 8]),
].spacing(4);
// ── Main scrollable form ──────────────────────────────────────────────
let form = column![
section_label("Paper Size"),
presets1,
presets2,
row![
lbl("Width (mm)"),
text_input("297", w_buf).on_input(Message::PageSetupWidthEdit).on_submit(Message::PageSetupCommit).style(field_style).size(12).width(90),
].spacing(8).align_y(iced::Center),
row![
lbl("Height (mm)"),
text_input("210", h_buf).on_input(Message::PageSetupHeightEdit).on_submit(Message::PageSetupCommit).style(field_style).size(12).width(90),
].spacing(8).align_y(iced::Center),
hdivider(),
section_label("Plot Area"),
row![
button(text("Layout").size(10)).on_press(Message::PageSetupPlotArea("Layout".into())).style(pill(plot_area == "Layout")).padding([3, 8]),
button(text("Extents").size(10)).on_press(Message::PageSetupPlotArea("Extents".into())).style(pill(plot_area == "Extents")).padding([3, 8]),
].spacing(6),
hdivider(),
section_label("Position"),
button(
text(if center { "✓ Center on page" } else { " Center on page" }).size(11)
)
.on_press(Message::PageSetupCenterToggle)
.style(pill(center))
.padding([4, 10]),
row![
lbl("Offset X (mm)"),
text_input("0", offset_x).on_input(Message::PageSetupOffsetXEdit).style(field_style).size(12).width(90),
].spacing(8).align_y(iced::Center),
row![
lbl("Offset Y (mm)"),
text_input("0", offset_y).on_input(Message::PageSetupOffsetYEdit).style(field_style).size(12).width(90),
].spacing(8).align_y(iced::Center),
hdivider(),
section_label("Rotation"),
rot_row,
hdivider(),
section_label("Plot Scale"),
scale_row1,
scale_row2,
]
.spacing(10)
.padding(16)
.width(Fill);
let content = scrollable(form).width(Fill).height(Fill);
container(
column![toolbar, hdivider(), content].spacing(0)
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BG)),
..Default::default()
})
.width(Fill)
.height(Fill)
.into()
}
+206
View File
@@ -0,0 +1,206 @@
//! Plot Style Table Editor window — fills the entire OS window.
use crate::app::Message;
use iced::widget::{button, 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 FIELD: Color = Color { r: 0.10, g: 0.10, b: 0.10, 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 field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
text_input::Style {
background: Background::Color(FIELD),
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
icon: TEXT, placeholder: DIM, value: TEXT, selection: ACCENT,
}
}
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()
}
pub fn view_window<'a>(
table: Option<&'a crate::io::plot_style::PlotStyleTable>,
selected_aci: u8,
color_buf: &'a str,
lw_buf: &'a str,
screen_buf: &'a str,
) -> Element<'a, Message> {
let table_name = table.map(|t| t.name.as_str()).unwrap_or("(no table loaded)");
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
row![
button(text("Load CTB/STB").size(11)).on_press(Message::PlotStyleLoad).style(btn_s(false)).padding([4, 10]),
button(text("Save As…").size(11)).on_press(Message::PlotStylePanelSave).style(btn_s(false)).padding([4, 10]),
button(text("Clear Table").size(11)).on_press(Message::PlotStyleClear).style(btn_s(false)).padding([4, 10]),
Space::new().width(Fill),
text(table_name).size(10).color(DIM),
]
.spacing(4)
.align_y(iced::Center)
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
..Default::default()
})
.width(Fill)
.padding([5, 8]);
// ── Left: ACI list ────────────────────────────────────────────────────
let aci_items: Vec<Element<'_, Message>> = (1u8..=255).map(|aci| {
let is_sel = aci == selected_aci;
let has_override = table.and_then(|t| t.aci_entries.get(aci as usize))
.map(|e| e.color.is_some() || e.lineweight != 255 || e.screening != 100)
.unwrap_or(false);
let lw_str = table.and_then(|t| t.aci_entries.get(aci as usize))
.and_then(|e| {
if e.lineweight != 255 {
crate::io::plot_style::LW_TABLE.get(e.lineweight as usize)
.map(|lw| format!("{lw:.2}mm"))
} else { None }
}).unwrap_or_default();
let color_str = table.and_then(|t| t.aci_entries.get(aci as usize))
.and_then(|e| e.color.map(|[r, g, b]| format!("#{r:02X}{g:02X}{b:02X}")))
.unwrap_or_default();
let label = if has_override {
format!("{aci:>3} {color_str:<9} {lw_str}")
} else {
format!("{aci:>3} (default)")
};
button(text(label).size(10).font(iced::Font::MONOSPACE))
.on_press(Message::PlotStylePanelSelectAci(aci))
.style(move |_: &Theme, st| button::Style {
background: Some(Background::Color(match (is_sel, 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()
})
.padding([2, 8])
.width(Fill)
.into()
}).collect();
let aci_list = container(
column![
text("ACI Color Index").size(10).color(DIM),
container(
scrollable(column(aci_items).spacing(1)).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(280)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 8.0, bottom: 12.0, left: 12.0 });
// ── Right: Edit panel ─────────────────────────────────────────────────
let entry = table.and_then(|t| t.aci_entries.get(selected_aci as usize));
let cur_color = entry.and_then(|e| e.color.map(|[r,g,b]| format!("#{r:02X}{g:02X}{b:02X}"))).unwrap_or_else(|| "(none)".into());
let cur_lw = entry.map(|e| if e.lineweight == 255 { "object".into() } else {
crate::io::plot_style::LW_TABLE.get(e.lineweight as usize)
.map(|lw| format!("{lw:.2}mm (idx {})", e.lineweight))
.unwrap_or_else(|| format!("idx {}", e.lineweight))
}).unwrap_or_else(|| "—".into());
let cur_scr = entry.map(|e| format!("{}%", e.screening)).unwrap_or_else(|| "—".into());
let lbl = |s: &'static str| text(s).size(11).color(DIM);
let edit_panel = container(
column![
row![
text("ACI:").size(11).color(DIM).width(100),
text(format!("{selected_aci}")).size(11),
].spacing(8).align_y(iced::Center),
lbl("Color override (#RRGGBB):"),
text_input("#RRGGBB or blank", color_buf)
.on_input(Message::PlotStylePanelColorBuf)
.style(field_style).size(11).padding([4, 8]),
lbl("Lineweight index (0-24, 255=obj):"),
text_input("255", lw_buf)
.on_input(Message::PlotStylePanelLwBuf)
.style(field_style).size(11).padding([4, 8]),
lbl("Screening (0-100):"),
text_input("100", screen_buf)
.on_input(Message::PlotStylePanelScreenBuf)
.style(field_style).size(11).padding([4, 8]),
Space::new().height(8),
text("Current values:").size(10).color(DIM),
text(format!(" Color: {cur_color}")).size(10),
text(format!(" Lineweight: {cur_lw}")).size(10),
text(format!(" Screening: {cur_scr}")).size(10),
Space::new().height(Fill),
button(text("Apply to ACI").size(11))
.on_press(Message::PlotStylePanelApply)
.style(btn_s(true))
.padding([5, 10]),
]
.spacing(8)
.height(Fill)
)
.width(Fill)
.height(Fill)
.padding([12, 12]);
let body = row![aci_list, vsep(), edit_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()
}
+141
View File
@@ -0,0 +1,141 @@
//! Keyboard Shortcuts Reference window — fills the entire OS window.
use crate::app::Message;
use iced::widget::{column, container, row, scrollable, text, Space};
use iced::{Background, 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 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 KEY: Color = Color { r: 0.40, g: 0.70, b: 1.00, a: 1.0 };
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 shortcut_row<'a>(key: &'static str, action: &'static str) -> Element<'a, Message> {
row![
text(key).size(11).color(KEY).font(iced::Font::MONOSPACE).width(160),
text(action).size(11),
]
.spacing(8)
.align_y(iced::Center)
.padding([2, 0])
.into()
}
fn section<'a>(title: &'static str) -> Element<'a, Message> {
container(text(title).size(11).color(DIM))
.padding(iced::Padding { top: 6.0, right: 0.0, bottom: 2.0, left: 0.0 })
.into()
}
pub fn view_window<'a>(
overrides: &'a std::collections::HashMap<String, String>,
) -> Element<'a, Message> {
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
row![
text("Type SHORTCUTS SET <key> <cmd> to add custom shortcuts.")
.size(10)
.color(DIM),
]
.align_y(iced::Center)
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
..Default::default()
})
.width(Fill)
.padding([5, 10]);
// ── Shortcut entries ──────────────────────────────────────────────────
let mut rows: Vec<Element<'_, Message>> = vec![
section("── Function Keys ──────────────────────────────────────"),
shortcut_row("F3", "Toggle Object Snap"),
shortcut_row("F7", "Toggle Grid"),
shortcut_row("F8", "Toggle Ortho"),
shortcut_row("F9", "Toggle Grid Snap"),
shortcut_row("F10", "Toggle Polar Tracking"),
shortcut_row("F11", "Toggle Object Snap Tracking"),
shortcut_row("F12", "Toggle Dynamic Input"),
section("── Ctrl Shortcuts ──────────────────────────────────────"),
shortcut_row("Ctrl+N", "New Drawing"),
shortcut_row("Ctrl+O", "Open File"),
shortcut_row("Ctrl+S", "Save"),
shortcut_row("Ctrl+Shift+S", "Save As"),
shortcut_row("Ctrl+Z", "Undo"),
shortcut_row("Ctrl+Shift+Z / Ctrl+Y","Redo"),
shortcut_row("Ctrl+C", "Copy to Clipboard"),
shortcut_row("Ctrl+X", "Cut to Clipboard"),
shortcut_row("Ctrl+V", "Paste from Clipboard"),
section("── Other Keys ──────────────────────────────────────────"),
shortcut_row("Enter / Space", "Finalize command / Repeat last"),
shortcut_row("Escape", "Cancel active command"),
shortcut_row("Delete", "Delete selected entities"),
shortcut_row("↑ / ↓", "Command history navigation"),
];
// Custom overrides section
rows.push(section("── Custom Overrides (SHORTCUTS SET) ──────────────────"));
if overrides.is_empty() {
rows.push(
text(" (none — use: SHORTCUTS SET <key> <command>)")
.size(11).color(DIM).into()
);
} else {
let mut sorted: Vec<_> = overrides.iter().collect();
sorted.sort_by_key(|(k, _)| k.as_str());
for (key, cmd) in sorted {
rows.push(
row![
text(key.as_str()).size(11).color(KEY).font(iced::Font::MONOSPACE).width(160),
text(cmd.as_str()).size(11),
]
.spacing(8).align_y(iced::Center).padding([2, 0])
.into()
);
}
}
// ── Section headers styled separately ────────────────────────────────
let content = scrollable(
column(rows).spacing(3).padding([12, 16])
)
.width(Fill)
.height(Fill);
// ── Header row with accent ────────────────────────────────────────────
let header = container(
row![
text("Key").size(10).color(ACCENT).width(160),
text("Action").size(10).color(ACCENT),
]
.spacing(8)
.padding([4, 16])
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(Color { r: 0.13, g: 0.13, b: 0.18, a: 1.0 })),
..Default::default()
})
.width(Fill);
container(
column![toolbar, hdivider(), header, hdivider(), content].spacing(0)
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BG)),
..Default::default()
})
.width(Fill)
.height(Fill)
.into()
}
+166
View File
@@ -0,0 +1,166 @@
//! Table Style Manager window — fills the entire OS window.
use crate::app::Message;
use iced::widget::{button, column, container, row, scrollable, text, 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()
}
pub fn view_window<'a>(
styles: Vec<String>,
selected: &'a str,
selected_style: Option<&'a acadrust::objects::TableStyle>,
) -> Element<'a, Message> {
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
row![
button(text("New").size(11)).on_press(Message::TableStyleDialogNew).style(btn_s(true)).padding([4, 10]),
button(text("Delete").size(11)).on_press(Message::TableStyleDialogDelete).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>> = styles.iter().map(|name| {
let is_sel = name.as_str() == selected;
button(text(name.clone()).size(11))
.on_press(Message::TableStyleDialogSelect(name.clone()))
.style(list_item(is_sel))
.padding([4, 8])
.width(Fill)
.into()
}).collect();
let style_list = container(
column![
text("Styles").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 info_row = |label: &'static str, val: String| -> Element<'_, Message> {
row![
text(label).size(11).color(DIM).width(160),
text(val).size(11),
].spacing(8).align_y(iced::Center).into()
};
let row_info = |row_label: &'static str, rs: &acadrust::objects::RowCellStyle| -> Element<'_, Message> {
column![
text(row_label).size(11).color(ACCENT),
info_row(" Text Style:", rs.text_style_name.clone()),
info_row(" Text Height:", format!("{:.4}", rs.text_height)),
info_row(" Alignment:", format!("{:?}", rs.alignment)),
].spacing(3).into()
};
let details: Element<'_, Message> = if let Some(s) = selected_style {
scrollable(
column![
info_row("Name:", s.name.clone()),
info_row("H Margin:", format!("{:.4}", s.horizontal_margin)),
info_row("V Margin:", format!("{:.4}", s.vertical_margin)),
info_row("Title Suppressed:", s.title_suppressed.to_string()),
info_row("Header Suppressed:", s.header_suppressed.to_string()),
row_info("Data Row:", &s.data_row_style),
row_info("Header Row:", &s.header_row_style),
row_info("Title Row:", &s.title_row_style),
].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()
}
+220
View File
@@ -0,0 +1,220 @@
//! Text Style Font Browser window — fills the entire OS window.
use crate::app::Message;
use iced::widget::{button, 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 FIELD: Color = Color { r: 0.10, g: 0.10, b: 0.10, a: 1.0 };
const LIST: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 };
const BUILTIN_FONTS: &[&str] = &[
"CourierCad.cxf", "Cursive.cxf", "GothGBT.cxf", "GothGRT.cxf", "GothITT.cxf",
"GreekC.cxf", "GreekS.cxf", "ItalicC.cxf", "ItalicT.cxf",
"RomanC.cxf", "RomanD.cxf", "RomanS.cxf", "RomanT.cxf",
"SansND.cxf", "SansNS.cxf", "ScriptC.cxf", "ScriptS.cxf",
"Standard.cxf", "Unicode.cxf", "SymbolCad.cxf",
];
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 field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
text_input::Style {
background: Background::Color(FIELD),
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
icon: TEXT, placeholder: DIM, value: TEXT, selection: ACCENT,
}
}
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()
}
pub fn view_window<'a>(
styles: Vec<String>,
selected: &'a str,
font_buf: &'a str,
width_buf: &'a str,
oblique_buf: &'a str,
) -> Element<'a, Message> {
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
row![
button(text("New").size(11)).on_press(Message::TextStyleDialogNew).style(btn_s(false)).padding([4, 10]),
button(text("Delete").size(11)).on_press(Message::TextStyleDialogDelete).style(btn_s(false)).padding([4, 10]),
Space::new().width(Fill),
button(text("Set Current").size(11)).on_press(Message::TextStyleDialogSetCurrent).style(btn_s(false)).padding([4, 10]),
button(text("Apply").size(11)).on_press(Message::TextStyleApply).style(btn_s(true)).padding([4, 14]),
]
.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>> = styles.iter().map(|name| {
let is_sel = name.as_str() == selected;
button(text(name.clone()).size(11))
.on_press(Message::TextStyleDialogSelect(name.clone()))
.style(list_item(is_sel))
.padding([4, 8])
.width(Fill)
.into()
}).collect();
let style_panel = container(
column![
text("Styles").size(10).color(DIM),
container(scrollable(column(style_items).spacing(1)).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(170)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 8.0, bottom: 12.0, left: 12.0 });
// ── Middle: Font browser ──────────────────────────────────────────────
let font_items: Vec<Element<'_, Message>> = BUILTIN_FONTS.iter().map(|&f| {
let is_sel = font_buf == f;
button(text(f).size(10))
.on_press(Message::TextStyleFontPick(f.to_string()))
.style(list_item(is_sel))
.padding([3, 8])
.width(Fill)
.into()
}).collect();
let font_panel = container(
column![
text("Font File").size(10).color(DIM),
container(scrollable(column(font_items).spacing(1)).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),
text_input("font file…", font_buf)
.on_input(|v| Message::TextStyleEdit { field: "font", value: v })
.style(field_style)
.size(11)
.width(Fill),
]
.spacing(6)
.height(Fill)
)
.width(190)
.height(Fill)
.padding([12, 8]);
// ── Right: Properties ─────────────────────────────────────────────────
let props_panel = container(
column![
text("Properties").size(11).color(ACCENT),
row![
text("Width Factor:").size(11).color(DIM).width(120),
text_input("1.0", width_buf)
.on_input(|v| Message::TextStyleEdit { field: "width", value: v })
.style(field_style).size(11).width(90),
].spacing(6).align_y(iced::Center),
row![
text("Oblique (°):").size(11).color(DIM).width(120),
text_input("0.0", oblique_buf)
.on_input(|v| Message::TextStyleEdit { field: "oblique", value: v })
.style(field_style).size(11).width(90),
].spacing(6).align_y(iced::Center),
Space::new().height(8),
text("Preview").size(10).color(DIM),
container(
text("AaBbCc 0123").size(20)
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(FIELD)),
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
..Default::default()
})
.padding(12)
.width(Fill),
]
.spacing(10)
.height(Fill)
)
.width(Fill)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 12.0, bottom: 12.0, left: 8.0 });
let body = row![style_panel, vsep(), font_panel, vsep(), props_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()
}