forked from 1b-agent/opencadstudio-upstream
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:
1 parent
888095cc3e
commit
fe4f9ca844
10 files changed
+1590
-1631
No files matched your search
+25
-1631
File diff suppressed because it is too large.
Load diff
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Reference in new issue
Block a user