cargo fmt

This commit is contained in:
Hakan 2026-05-12 10:36:15 +03:00
commit ca07268c7c
204 changed files with 13988 additions and 5516 deletions

View file

@ -2,12 +2,42 @@ use crate::app::Message;
use iced::widget::{button, column, container, row, text, Space};
use iced::{Background, Border, Color, Element, Fill, Theme};
const BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 };
const PANEL: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 };
const BORDER: Color = Color { r: 0.30, g: 0.30, b: 0.30, 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 WHITE: Color = Color { r: 0.92, g: 0.92, b: 0.92, a: 1.0 };
const BG: Color = Color {
r: 0.15,
g: 0.15,
b: 0.15,
a: 1.0,
};
const PANEL: Color = Color {
r: 0.12,
g: 0.12,
b: 0.12,
a: 1.0,
};
const BORDER: Color = Color {
r: 0.30,
g: 0.30,
b: 0.30,
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 WHITE: Color = Color {
r: 0.92,
g: 0.92,
b: 0.92,
a: 1.0,
};
fn info_row<'a>(label: &'static str, value: String) -> Element<'a, Message> {
row![
@ -21,28 +51,34 @@ fn info_row<'a>(label: &'static str, value: String) -> Element<'a, Message> {
}
pub fn view_window<'a>() -> Element<'a, Message> {
let version = env!("CARGO_PKG_VERSION");
let os = std::env::consts::OS;
let arch = std::env::consts::ARCH;
let version = env!("CARGO_PKG_VERSION");
let os = std::env::consts::OS;
let arch = std::env::consts::ARCH;
let logo = container(
column![
text("H7CAD").size(32).color(ACCENT),
text("CAD application for Architecture & Engineering")
.size(11).color(DIM),
.size(11)
.color(DIM),
]
.spacing(4)
.align_x(iced::Center),
)
.width(Fill)
.padding(iced::Padding { top: 20.0, right: 0.0, bottom: 16.0, left: 0.0 })
.padding(iced::Padding {
top: 20.0,
right: 0.0,
bottom: 16.0,
left: 0.0,
})
.align_x(iced::Center);
let info_block = container(
column![
info_row("Version", format!("v{}", version)),
info_row("Platform", os.to_string()),
info_row("Arch", arch.to_string()),
info_row("Version", format!("v{}", version)),
info_row("Platform", os.to_string()),
info_row("Arch", arch.to_string()),
]
.spacing(2)
.padding([12, 16]),
@ -58,33 +94,45 @@ pub fn view_window<'a>() -> Element<'a, Message> {
..Default::default()
});
let copy_btn = button(
text("Copy Info").size(11),
)
.on_press(Message::AboutCopyInfo)
.style(|_: &Theme, st| button::Style {
background: Some(Background::Color(match st {
button::Status::Hovered | button::Status::Pressed =>
Color { r: 0.20, g: 0.42, b: 0.72, a: 1.0 },
_ => ACCENT,
})),
text_color: WHITE,
border: Border { radius: 4.0.into(), ..Default::default() },
..Default::default()
})
.padding([6, 16]);
let copy_btn = button(text("Copy Info").size(11))
.on_press(Message::AboutCopyInfo)
.style(|_: &Theme, st| button::Style {
background: Some(Background::Color(match st {
button::Status::Hovered | button::Status::Pressed => Color {
r: 0.20,
g: 0.42,
b: 0.72,
a: 1.0,
},
_ => ACCENT,
})),
text_color: WHITE,
border: Border {
radius: 4.0.into(),
..Default::default()
},
..Default::default()
})
.padding([6, 16]);
let footer = row![
Space::new().width(Fill),
copy_btn,
]
.align_y(iced::Center)
.padding(iced::Padding { top: 12.0, right: 0.0, bottom: 0.0, left: 0.0 });
let footer = row![Space::new().width(Fill), copy_btn,]
.align_y(iced::Center)
.padding(iced::Padding {
top: 12.0,
right: 0.0,
bottom: 0.0,
left: 0.0,
});
container(
column![logo, info_block, footer]
.spacing(0)
.padding(iced::Padding { top: 0.0, right: 20.0, bottom: 20.0, left: 20.0 }),
.padding(iced::Padding {
top: 0.0,
right: 20.0,
bottom: 20.0,
left: 20.0,
}),
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BG)),

View file

@ -4,42 +4,122 @@ 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 };
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,
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 },
(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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 4.0.into(),
},
..Default::default()
}
}
@ -47,12 +127,26 @@ fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
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 },
(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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
}
}
@ -60,12 +154,26 @@ fn tab_btn_style(active: bool) -> impl Fn(&Theme, button::Status) -> button::Sty
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 },
(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() },
border: Border {
color: BORDER,
width: 0.0,
radius: 3.0.into(),
},
..Default::default()
}
}
@ -73,14 +181,22 @@ fn list_item_style(active: bool) -> impl Fn(&Theme, button::Status) -> button::S
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,
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)
.width(Fill)
.height(1)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -97,15 +213,30 @@ pub fn view_window<'a>(
// ── 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]),
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]),
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)
.align_y(iced::Center),
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
@ -123,7 +254,7 @@ pub fn view_window<'a>(
.on_press(Message::DimStyleDialogSelect(name.clone()))
.style(list_item_style(active))
.padding([4, 8])
.width(Fill)
.width(Fill),
);
}
let style_list = container(
@ -132,7 +263,11 @@ pub fn view_window<'a>(
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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 3.0.into()
},
..Default::default()
})
.width(180)
@ -140,20 +275,41 @@ pub fn view_window<'a>(
.padding(2),
]
.spacing(4)
.height(Fill)
.height(Fill),
)
.width(180)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 8.0, bottom: 12.0, left: 12.0 });
.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);
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);
@ -178,49 +334,159 @@ pub fn view_window<'a>(
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),
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),
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(),
]
.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(),
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),
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(),
]
.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),
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(),
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(),
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 ────────────────────────────
@ -229,20 +495,24 @@ pub fn view_window<'a>(
text(format!("Editing: {selected}")).size(11).color(DIM),
tabs,
hdivider(),
scrollable(
container(tab_content).padding([12, 12])
).height(Fill),
scrollable(container(tab_content).padding([12, 12])).height(Fill),
]
.spacing(6)
.height(Fill)
.height(Fill),
)
.height(Fill)
.width(Fill)
.padding(iced::Padding { top: 12.0, right: 12.0, bottom: 12.0, left: 0.0 });
.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)
.width(1)
.height(Fill)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -250,14 +520,12 @@ pub fn view_window<'a>(
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()
container(column![toolbar, hdivider(), body].spacing(0))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BG)),
..Default::default()
})
.width(Fill)
.height(Fill)
.into()
}

View file

@ -1,7 +1,8 @@
//! Layer Manager — floating window.
use crate::ui::properties::{color_picker_dropdown, LinetypeItem, LwItem, lw_options};
use crate::app::Message;
use crate::ui::properties::{color_picker_dropdown, lw_options, LinetypeItem, LwItem};
use crate::ui::ROW_H;
use acadrust::tables::layer::Layer as DocLayer;
use acadrust::tables::Table;
use acadrust::types::aci_table::aci_to_rgb;
@ -10,9 +11,8 @@ use acadrust::Handle;
use iced::widget::{
button, column, combo_box, container, mouse_area, row, scrollable, text, text_input,
};
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
use iced::Padding;
use crate::ui::ROW_H;
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
// ── Per-viewport column descriptor ───────────────────────────────────────────
@ -24,9 +24,9 @@ pub struct VpCol {
// ── Row-height-derived constants ─────────────────────────────────────────
/// SVG icon size inside a layer-table cell.
const ICON_SZ: f32 = ROW_H * 0.62; // ≈16 px at ROW_H=26
const ICON_SZ: f32 = ROW_H * 0.62; // ≈16 px at ROW_H=26
/// Font size for cell text.
const FONT_SZ: f32 = ROW_H * 0.42; // ≈11 px at ROW_H=26
const FONT_SZ: f32 = ROW_H * 0.42; // ≈11 px at ROW_H=26
/// Vertical padding for combo_box / text_input so their total height = ROW_H.
const COMBO_PAD_V: f32 = (ROW_H - FONT_SZ * 1.3 - 2.0) / 2.0;
@ -91,12 +91,16 @@ impl Default for LayerPanel {
editing: None,
edit_buf: String::new(),
current_layer: "0".to_string(),
linetype_items: vec![LinetypeItem { name: "Continuous".into(), art: String::new() }],
linetype_items: vec![LinetypeItem {
name: "Continuous".into(),
art: String::new(),
}],
color_picker_row: None,
color_full_palette: false,
linetype_combo: combo_box::State::new(vec![
LinetypeItem { name: "Continuous".into(), art: String::new() }
]),
linetype_combo: combo_box::State::new(vec![LinetypeItem {
name: "Continuous".into(),
art: String::new(),
}]),
lw_combo: combo_box::State::new(lw_options()),
vp_cols: vec![],
}
@ -113,7 +117,10 @@ impl LayerPanel {
) {
self.vp_cols = vp_info
.iter()
.map(|(h, label, _)| VpCol { handle: *h, label: label.clone() })
.map(|(h, label, _)| VpCol {
handle: *h,
label: label.clone(),
})
.collect();
self.layers = doc_layers
@ -179,14 +186,38 @@ impl LayerPanel {
// ── Column header ─────────────────────────────────────────────────
let mut header_row = row![
text("Status").size(10).color(DIM).width(50),
text("Name").size(10).color(DIM).width(Length::Fixed(COL_NAME)),
text("On").size(10).color(DIM).width(Length::Fixed(COL_ICON)),
text("Freeze").size(10).color(DIM).width(Length::Fixed(COL_ICON)),
text("Lock").size(10).color(DIM).width(Length::Fixed(COL_ICON)),
text("Color").size(10).color(DIM).width(Length::Fixed(COL_COLOR)),
text("Linetype").size(10).color(DIM).width(Length::Fixed(COL_LT)),
text("Lineweight").size(10).color(DIM).width(Length::Fixed(COL_LW)),
text("Transparency").size(10).color(DIM).width(Length::Fixed(COL_TRANS)),
text("Name")
.size(10)
.color(DIM)
.width(Length::Fixed(COL_NAME)),
text("On")
.size(10)
.color(DIM)
.width(Length::Fixed(COL_ICON)),
text("Freeze")
.size(10)
.color(DIM)
.width(Length::Fixed(COL_ICON)),
text("Lock")
.size(10)
.color(DIM)
.width(Length::Fixed(COL_ICON)),
text("Color")
.size(10)
.color(DIM)
.width(Length::Fixed(COL_COLOR)),
text("Linetype")
.size(10)
.color(DIM)
.width(Length::Fixed(COL_LT)),
text("Lineweight")
.size(10)
.color(DIM)
.width(Length::Fixed(COL_LW)),
text("Transparency")
.size(10)
.color(DIM)
.width(Length::Fixed(COL_TRANS)),
]
.spacing(4)
.align_y(iced::Center);
@ -203,7 +234,11 @@ impl LayerPanel {
let col_header = container(header_row)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(COL_HEADER_BG)),
border: Border { color: BORDER_COLOR, width: 1.0, radius: 0.0.into() },
border: Border {
color: BORDER_COLOR,
width: 1.0,
radius: 0.0.into(),
},
..Default::default()
})
.padding([4, 8])
@ -224,7 +259,15 @@ impl LayerPanel {
};
rows_col = rows_col.push(layer_row(
i, layer, is_sel, is_current, is_editing, &self.edit_buf, color_open, ltc, lwc,
i,
layer,
is_sel,
is_current,
is_editing,
&self.edit_buf,
color_open,
ltc,
lwc,
&self.vp_cols,
));
@ -255,11 +298,30 @@ fn toolbar_btn(label: &str, msg: Message) -> Element<'_, Message> {
.on_press(msg)
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => Color { r: 0.32, g: 0.32, b: 0.32, a: 1.0 },
button::Status::Pressed => Color { r: 0.25, g: 0.25, b: 0.25, a: 1.0 },
_ => Color { r: 0.26, g: 0.26, b: 0.26, a: 1.0 },
button::Status::Hovered => Color {
r: 0.32,
g: 0.32,
b: 0.32,
a: 1.0,
},
button::Status::Pressed => Color {
r: 0.25,
g: 0.25,
b: 0.25,
a: 1.0,
},
_ => Color {
r: 0.26,
g: 0.26,
b: 0.26,
a: 1.0,
},
})),
border: Border { radius: 3.0.into(), color: BORDER_COLOR, width: 1.0 },
border: Border {
radius: 3.0.into(),
color: BORDER_COLOR,
width: 1.0,
},
text_color: Color::WHITE,
..Default::default()
})
@ -271,14 +333,33 @@ fn toolbar_btn_cond(label: &str, msg: Message, enabled: bool) -> Element<'_, Mes
let mut b = button(text(label).size(11).color(if enabled {
Color::WHITE
} else {
Color { r: 0.45, g: 0.45, b: 0.45, a: 1.0 }
Color {
r: 0.45,
g: 0.45,
b: 0.45,
a: 1.0,
}
}))
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => Color { r: 0.32, g: 0.32, b: 0.32, a: 1.0 },
_ => Color { r: 0.26, g: 0.26, b: 0.26, a: 1.0 },
button::Status::Hovered => Color {
r: 0.32,
g: 0.32,
b: 0.32,
a: 1.0,
},
_ => Color {
r: 0.26,
g: 0.26,
b: 0.26,
a: 1.0,
},
})),
border: Border { radius: 3.0.into(), color: BORDER_COLOR, width: 1.0 },
border: Border {
radius: 3.0.into(),
color: BORDER_COLOR,
width: 1.0,
},
text_color: Color::WHITE,
..Default::default()
})
@ -313,12 +394,22 @@ fn layer_row<'a>(
.on_press(on_press)
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 },
button::Status::Hovered => Color {
r: 0.35,
g: 0.35,
b: 0.35,
a: 1.0,
},
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 4.0, right: 4.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 4.0,
right: 4.0,
})
.height(Length::Fixed(ROW_H))
.into()
};
@ -340,9 +431,25 @@ fn layer_row<'a>(
};
let status_dot: Element<'_, Message> = if is_current {
text("✓").size(13).color(Color { r: 0.25, g: 0.85, b: 0.45, a: 1.0 }).into()
text("✓")
.size(13)
.color(Color {
r: 0.25,
g: 0.85,
b: 0.45,
a: 1.0,
})
.into()
} else {
text("▣").size(11).color(Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 }).into()
text("▣")
.size(11)
.color(Color {
r: 0.55,
g: 0.55,
b: 0.55,
a: 1.0,
})
.into()
};
// Name cell
@ -351,18 +458,43 @@ fn layer_row<'a>(
.on_input(Message::LayerRenameEdit)
.on_submit(Message::LayerRenameCommit)
.size(FONT_SZ)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 4.0, right: 4.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 4.0,
right: 4.0,
})
.style(|_: &Theme, _| iced::widget::text_input::Style {
background: iced::Background::Color(Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 }),
background: iced::Background::Color(Color {
r: 0.12,
g: 0.12,
b: 0.12,
a: 1.0,
}),
border: Border {
radius: 2.0.into(),
width: 1.0,
color: Color { r: 0.45, g: 0.65, b: 0.90, a: 1.0 },
color: Color {
r: 0.45,
g: 0.65,
b: 0.90,
a: 1.0,
},
},
icon: Color::WHITE,
placeholder: Color { r: 0.4, g: 0.4, b: 0.4, a: 1.0 },
placeholder: Color {
r: 0.4,
g: 0.4,
b: 0.4,
a: 1.0,
},
value: Color::WHITE,
selection: Color { r: 0.25, g: 0.45, b: 0.75, a: 0.5 },
selection: Color {
r: 0.25,
g: 0.45,
b: 0.75,
a: 0.5,
},
})
.width(Length::Fixed(COL_NAME))
.into()
@ -371,12 +503,22 @@ fn layer_row<'a>(
.on_press(Message::LayerRenameStart(index))
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => Color { r: 0.30, g: 0.30, b: 0.30, a: 1.0 },
button::Status::Hovered => Color {
r: 0.30,
g: 0.30,
b: 0.30,
a: 1.0,
},
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 4.0, right: 4.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 4.0,
right: 4.0,
})
.height(Length::Fixed(ROW_H))
.width(Length::Fixed(COL_NAME))
.into()
@ -391,7 +533,12 @@ fn layer_row<'a>(
.style(move |_: &Theme| container::Style {
background: Some(Background::Color(layer_color)),
border: Border {
color: Color { r: 0.0, g: 0.0, b: 0.0, a: 0.5 },
color: Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.5,
},
width: 1.0,
radius: 1.0.into(),
},
@ -415,21 +562,41 @@ fn layer_row<'a>(
.on_press(Message::LayerColorPickerToggle(index))
.style(move |_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => Color { r: 0.20, g: 0.20, b: 0.20, a: 1.0 },
_ => Color { r: 0.13, g: 0.13, b: 0.13, a: 1.0 },
button::Status::Hovered => Color {
r: 0.20,
g: 0.20,
b: 0.20,
a: 1.0,
},
_ => Color {
r: 0.13,
g: 0.13,
b: 0.13,
a: 1.0,
},
})),
border: Border {
radius: 2.0.into(),
width: 1.0,
color: if color_picker_open {
Color { r: 0.45, g: 0.65, b: 0.90, a: 1.0 }
Color {
r: 0.45,
g: 0.65,
b: 0.90,
a: 1.0,
}
} else {
BORDER_COLOR
},
},
..Default::default()
})
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 4.0, right: 4.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 4.0,
right: 4.0,
})
.height(Length::Fixed(ROW_H))
.width(Length::Fixed(COL_COLOR))
.into();
@ -447,12 +614,21 @@ fn layer_row<'a>(
|item: LinetypeItem| Message::LayerLinetypeSet(item.name),
)
.size(FONT_SZ)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 4.0, right: 4.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 4.0,
right: 4.0,
})
.width(Length::Fixed(COL_LT))
.input_style(combo_input_style)
.into()
} else {
text(layer.linetype.as_str()).size(FONT_SZ).color(DIM).width(Length::Fixed(COL_LT)).into()
text(layer.linetype.as_str())
.size(FONT_SZ)
.color(DIM)
.width(Length::Fixed(COL_LT))
.into()
};
// Lineweight cell
@ -462,12 +638,21 @@ fn layer_row<'a>(
Message::LayerLineweightSet(item.0)
})
.size(FONT_SZ)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 4.0, right: 4.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 4.0,
right: 4.0,
})
.width(Length::Fixed(COL_LW))
.input_style(combo_input_style)
.into()
} else {
text(cur_lw_item.to_string()).size(FONT_SZ).color(DIM).width(Length::Fixed(COL_LW)).into()
text(cur_lw_item.to_string())
.size(FONT_SZ)
.color(DIM)
.width(Length::Fixed(COL_LW))
.into()
};
// Transparency cell
@ -475,21 +660,44 @@ fn layer_row<'a>(
let trans_cell = text_input("0", &trans_str)
.on_input(move |s| Message::LayerTransparencyEdit(index, s))
.size(FONT_SZ)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 4.0, right: 4.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 4.0,
right: 4.0,
})
.style(|_: &Theme, _| iced::widget::text_input::Style {
background: iced::Background::Color(Color::TRANSPARENT),
border: Border { radius: 2.0.into(), width: 1.0, color: BORDER_COLOR },
border: Border {
radius: 2.0.into(),
width: 1.0,
color: BORDER_COLOR,
},
icon: Color::WHITE,
placeholder: DIM,
value: ROW_TEXT,
selection: Color { r: 0.25, g: 0.45, b: 0.75, a: 0.5 },
selection: Color {
r: 0.25,
g: 0.45,
b: 0.75,
a: 0.5,
},
})
.width(Length::Fixed(COL_TRANS));
let bg = if is_selected { ROW_SEL } else if index % 2 == 0 { ROW_EVEN } else { ROW_ODD };
let bg = if is_selected {
ROW_SEL
} else if index % 2 == 0 {
ROW_EVEN
} else {
ROW_ODD
};
let mut row_content = row![
container(status_dot).width(50).align_x(iced::Center).align_y(iced::Center),
container(status_dot)
.width(50)
.align_x(iced::Center)
.align_y(iced::Center),
name_cell,
container(svg_btn(vis_svg, Message::LayerToggleVisible(index)))
.width(Length::Fixed(COL_ICON))
@ -517,9 +725,12 @@ fn layer_row<'a>(
include_bytes!("../../assets/icons/layers/laythw.svg")
};
row_content = row_content.push(
container(svg_btn(vp_frz_svg, Message::LayerToggleVpFreeze(index, vp_idx)))
.width(Length::Fixed(COL_ICON))
.align_x(iced::Center),
container(svg_btn(
vp_frz_svg,
Message::LayerToggleVpFreeze(index, vp_idx),
))
.width(Length::Fixed(COL_ICON))
.align_x(iced::Center),
);
}
@ -529,7 +740,12 @@ fn layer_row<'a>(
background: Some(Background::Color(bg)),
..Default::default()
})
.padding(Padding { top: 0.0, bottom: 0.0, left: 8.0, right: 8.0 })
.padding(Padding {
top: 0.0,
bottom: 0.0,
left: 8.0,
right: 8.0,
})
.height(Length::Fixed(ROW_H))
.width(Fill),
)
@ -539,16 +755,31 @@ fn layer_row<'a>(
// ── Combo style ────────────────────────────────────────────────────────────
fn combo_input_style(_theme: &Theme, _status: iced::widget::text_input::Status)
-> iced::widget::text_input::Style
{
fn combo_input_style(
_theme: &Theme,
_status: iced::widget::text_input::Status,
) -> iced::widget::text_input::Style {
iced::widget::text_input::Style {
background: iced::Background::Color(Color { r: 0.13, g: 0.13, b: 0.13, a: 1.0 }),
border: Border { radius: 2.0.into(), width: 1.0, color: BORDER_COLOR },
background: iced::Background::Color(Color {
r: 0.13,
g: 0.13,
b: 0.13,
a: 1.0,
}),
border: Border {
radius: 2.0.into(),
width: 1.0,
color: BORDER_COLOR,
},
icon: Color::WHITE,
placeholder: DIM,
value: Color::WHITE,
selection: Color { r: 0.25, g: 0.45, b: 0.75, a: 0.5 },
selection: Color {
r: 0.25,
g: 0.45,
b: 0.75,
a: 0.5,
},
}
}
@ -556,32 +787,26 @@ fn combo_input_style(_theme: &Theme, _status: iced::widget::text_input::Status)
fn color_picker_palette<'a>(full: bool) -> Element<'a, Message> {
// Horizontal offset to align dropdown under the Color column.
const LEFT_OFFSET: f32 = 20.0 + 4.0 + COL_NAME + 4.0 + COL_ICON + 4.0
+ COL_ICON + 4.0 + COL_ICON + 4.0 + 8.0;
const LEFT_OFFSET: f32 =
20.0 + 4.0 + COL_NAME + 4.0 + COL_ICON + 4.0 + COL_ICON + 4.0 + COL_ICON + 4.0 + 8.0;
// Use the shared color_picker_dropdown from properties — no ByLayer/ByBlock for layers.
let dropdown = color_picker_dropdown(
full,
Message::LayerColorMorePalette,
None,
None,
|aci| Message::LayerColorSet(aci),
);
let dropdown = color_picker_dropdown(full, Message::LayerColorMorePalette, None, None, |aci| {
Message::LayerColorSet(aci)
});
row![
iced::widget::Space::new().width(LEFT_OFFSET),
dropdown,
]
.into()
row![iced::widget::Space::new().width(LEFT_OFFSET), dropdown,].into()
}
// ── Display helpers ───────────────────────────────────────────────────────
#[allow(dead_code)]
fn aci_color_display(i: u8) -> (Color, &'static str) {
let (r, g, b) = aci_to_rgb(i).unwrap_or((200, 200, 200));
(Color::from_rgb(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0), "")
(
Color::from_rgb(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0),
"",
)
}
fn iced_to_aci(c: Color) -> u8 {
@ -629,7 +854,6 @@ fn color_name_to_aci(name: &str) -> u8 {
}
}
pub fn iced_color_from_acad(c: &AcadColor) -> Color {
match c {
AcadColor::Index(i) => {
@ -645,23 +869,73 @@ pub fn iced_color_from_acad(c: &AcadColor) -> Color {
// ── Column widths ─────────────────────────────────────────────────────────
const COL_NAME: f32 = 130.0;
const COL_ICON: f32 = 44.0;
const COL_NAME: f32 = 130.0;
const COL_ICON: f32 = 44.0;
const COL_COLOR: f32 = 90.0;
const COL_LT: f32 = 110.0;
const COL_LW: f32 = 90.0;
const COL_LT: f32 = 110.0;
const COL_LW: f32 = 90.0;
const COL_TRANS: f32 = 80.0;
// ── Colors ────────────────────────────────────────────────────────────────
const PANEL_BG: Color = Color { r: 0.18, g: 0.18, b: 0.18, a: 1.0 };
const TOOLBAR_BG: Color = Color { r: 0.20, g: 0.20, b: 0.20, a: 1.0 };
const COL_HEADER_BG: Color = Color { r: 0.21, g: 0.21, b: 0.21, a: 1.0 };
const ROW_EVEN: Color = Color { r: 0.18, g: 0.18, b: 0.18, a: 1.0 };
const ROW_ODD: Color = Color { r: 0.21, g: 0.21, b: 0.21, a: 1.0 };
const ROW_SEL: Color = Color { r: 0.18, g: 0.32, b: 0.52, a: 1.0 };
const ROW_TEXT: Color = Color { r: 0.85, g: 0.85, b: 0.85, a: 1.0 };
const DIM: Color = Color { r: 0.50, g: 0.50, b: 0.50, a: 1.0 };
const BORDER_COLOR: Color = Color { r: 0.30, g: 0.30, b: 0.30, a: 1.0 };
const PANEL_BG: Color = Color {
r: 0.18,
g: 0.18,
b: 0.18,
a: 1.0,
};
const TOOLBAR_BG: Color = Color {
r: 0.20,
g: 0.20,
b: 0.20,
a: 1.0,
};
const COL_HEADER_BG: Color = Color {
r: 0.21,
g: 0.21,
b: 0.21,
a: 1.0,
};
const ROW_EVEN: Color = Color {
r: 0.18,
g: 0.18,
b: 0.18,
a: 1.0,
};
const ROW_ODD: Color = Color {
r: 0.21,
g: 0.21,
b: 0.21,
a: 1.0,
};
const ROW_SEL: Color = Color {
r: 0.18,
g: 0.32,
b: 0.52,
a: 1.0,
};
const ROW_TEXT: Color = Color {
r: 0.85,
g: 0.85,
b: 0.85,
a: 1.0,
};
const DIM: Color = Color {
r: 0.50,
g: 0.50,
b: 0.50,
a: 1.0,
};
const BORDER_COLOR: Color = Color {
r: 0.30,
g: 0.30,
b: 0.30,
a: 1.0,
};
#[allow(dead_code)]
const ICON_COLOR: Color = Color { r: 0.80, g: 0.80, b: 0.80, a: 1.0 };
const ICON_COLOR: Color = Color {
r: 0.80,
g: 0.80,
b: 0.80,
a: 1.0,
};

View file

@ -4,27 +4,96 @@ 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 };
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 },
(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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 4.0.into(),
},
..Default::default()
}
}
@ -32,8 +101,13 @@ fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
move |_: &Theme, st| button::Style {
background: Some(Background::Color(match (active, st) {
(true, _) => ACTIVE,
(false, button::Status::Hovered | button::Status::Pressed) => Color { r: 0.26, g: 0.26, b: 0.26, a: 1.0 },
(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,
@ -44,14 +118,22 @@ fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
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,
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)
.width(Fill)
.height(1)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -61,7 +143,8 @@ fn hdivider<'a>() -> Element<'a, Message> {
fn vsep<'a>() -> Element<'a, Message> {
container(Space::new().width(1).height(Fill))
.width(1).height(Fill)
.width(1)
.height(Fill)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -80,26 +163,52 @@ pub fn view_window<'a>(
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
row![
button(text("New Layout").size(11)).on_press(Message::LayoutManagerNew).style(btn_s(false)).padding([4, 10]),
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 },
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() },
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]),
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)
.align_y(iced::Center),
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
@ -109,17 +218,24 @@ pub fn view_window<'a>(
.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 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![
@ -127,7 +243,11 @@ pub fn view_window<'a>(
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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 3.0.into()
},
..Default::default()
})
.width(Fill)
@ -135,28 +255,50 @@ pub fn view_window<'a>(
.padding(2),
]
.spacing(4)
.height(Fill)
.height(Fill),
)
.width(220)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 8.0, bottom: 12.0, left: 12.0 });
.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),
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),
]
.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),
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![
@ -170,23 +312,23 @@ pub fn view_window<'a>(
.on_press(Message::LayoutManagerRenameCommit)
.style(btn_s(true))
.padding([4, 10]),
].spacing(6).align_y(iced::Center),
]
.spacing(6)
.align_y(iced::Center),
]
.spacing(8)
.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()
container(column![toolbar, hdivider(), body].spacing(0))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BG)),
..Default::default()
})
.width(Fill)
.height(Fill)
.into()
}

View file

@ -4,25 +4,84 @@ 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 };
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 },
(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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 4.0.into(),
},
..Default::default()
}
}
@ -30,8 +89,13 @@ fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
move |_: &Theme, st| button::Style {
background: Some(Background::Color(match (active, st) {
(true, _) => ACTIVE,
(false, button::Status::Hovered | button::Status::Pressed) => Color { r: 0.26, g: 0.26, b: 0.26, a: 1.0 },
(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,
@ -41,7 +105,8 @@ fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
fn hdivider<'a>() -> Element<'a, Message> {
container(Space::new().width(Fill).height(1))
.width(Fill).height(1)
.width(Fill)
.height(1)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -51,7 +116,8 @@ fn hdivider<'a>() -> Element<'a, Message> {
fn vsep<'a>() -> Element<'a, Message> {
container(Space::new().width(1).height(Fill))
.width(1).height(Fill)
.width(1)
.height(Fill)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -68,12 +134,21 @@ pub fn view_window<'a>(
// ── 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]),
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)
.align_y(iced::Center),
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
@ -83,17 +158,24 @@ pub fn view_window<'a>(
.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_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![
@ -101,7 +183,11 @@ pub fn view_window<'a>(
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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 3.0.into()
},
..Default::default()
})
.width(Fill)
@ -109,62 +195,85 @@ pub fn view_window<'a>(
.padding(2),
]
.spacing(4)
.height(Fill)
.height(Fill),
)
.width(200)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 8.0, bottom: 12.0, left: 12.0 });
.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()
]
.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 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(),
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()
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()
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 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()
container(column![toolbar, hdivider(), body].spacing(0))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BG)),
..Default::default()
})
.width(Fill)
.height(Fill)
.into()
}

View file

@ -14,8 +14,8 @@ pub mod page_setup;
pub mod plotstyle;
pub mod properties;
pub mod ribbon;
pub mod shortcuts;
pub mod scale_popup;
pub mod shortcuts;
pub mod snap_popup;
pub mod statusbar;
pub mod tablestyle;

View file

@ -7,9 +7,9 @@ use iced::mouse;
use iced::widget::{button, canvas, column, container, row, text};
use iced::{Background, Border, Color, Element, Length, Point, Size, Theme};
use crate::app::Message;
use crate::scene::object::GripShape;
use crate::scene::SelectionState;
use crate::app::Message;
/// Half-size of the crosshair center square in screen pixels (square = SQ*2 × SQ*2).
pub const CROSSHAIR_SQ: f32 = 7.5;
@ -246,8 +246,10 @@ impl canvas::Program<Message> for SelectionCanvas {
use crate::scene::{VIEWCUBE_DRAW_PX, VIEWCUBE_PAD};
let vc_x = bounds.width - VIEWCUBE_DRAW_PX - VIEWCUBE_PAD;
let vc_y = VIEWCUBE_PAD;
if pos.x >= vc_x && pos.x <= vc_x + VIEWCUBE_DRAW_PX
&& pos.y >= vc_y && pos.y <= vc_y + VIEWCUBE_DRAW_PX
if pos.x >= vc_x
&& pos.x <= vc_x + VIEWCUBE_DRAW_PX
&& pos.y >= vc_y
&& pos.y <= vc_y + VIEWCUBE_DRAW_PX
{
return mouse::Interaction::None;
}
@ -526,56 +528,58 @@ impl canvas::Program<Message> for SelectionCanvas {
cursor.position_in(bounds).map_or(false, |pos| {
let vc_x = bounds.width - VIEWCUBE_DRAW_PX - VIEWCUBE_PAD;
let vc_y = VIEWCUBE_PAD;
pos.x >= vc_x && pos.x <= vc_x + VIEWCUBE_DRAW_PX
&& pos.y >= vc_y && pos.y <= vc_y + VIEWCUBE_DRAW_PX
pos.x >= vc_x
&& pos.x <= vc_x + VIEWCUBE_DRAW_PX
&& pos.y >= vc_y
&& pos.y <= vc_y + VIEWCUBE_DRAW_PX
})
};
if !over_viewcube {
if let Some(cp) = self.selection.last_move_pos {
let color = Color {
r: 0.85,
g: 0.85,
b: 0.85,
a: 0.90,
};
let stroke = canvas::Stroke {
width: 1.0,
style: canvas::Style::Solid(color),
..Default::default()
};
let sq = CROSSHAIR_SQ; // square half-size → 15×15
let arm = CROSSHAIR_ARM; // crosshair arm length from center
if let Some(cp) = self.selection.last_move_pos {
let color = Color {
r: 0.85,
g: 0.85,
b: 0.85,
a: 0.90,
};
let stroke = canvas::Stroke {
width: 1.0,
style: canvas::Style::Solid(color),
..Default::default()
};
let sq = CROSSHAIR_SQ; // square half-size → 15×15
let arm = CROSSHAIR_ARM; // crosshair arm length from center
// Horizontal arms (start at square edge, end at arm length)
let h_left = canvas::Path::new(|b| {
b.move_to(Point::new(cp.x - sq, cp.y));
b.line_to(Point::new(cp.x - arm, cp.y));
});
let h_right = canvas::Path::new(|b| {
b.move_to(Point::new(cp.x + sq, cp.y));
b.line_to(Point::new(cp.x + arm, cp.y));
});
// Vertical arms
let v_top = canvas::Path::new(|b| {
b.move_to(Point::new(cp.x, cp.y - sq));
b.line_to(Point::new(cp.x, cp.y - arm));
});
let v_bot = canvas::Path::new(|b| {
b.move_to(Point::new(cp.x, cp.y + sq));
b.line_to(Point::new(cp.x, cp.y + arm));
});
// Center square
let square = canvas::Path::rectangle(
Point::new(cp.x - sq, cp.y - sq),
Size::new(sq * 2.0, sq * 2.0),
);
// Horizontal arms (start at square edge, end at arm length)
let h_left = canvas::Path::new(|b| {
b.move_to(Point::new(cp.x - sq, cp.y));
b.line_to(Point::new(cp.x - arm, cp.y));
});
let h_right = canvas::Path::new(|b| {
b.move_to(Point::new(cp.x + sq, cp.y));
b.line_to(Point::new(cp.x + arm, cp.y));
});
// Vertical arms
let v_top = canvas::Path::new(|b| {
b.move_to(Point::new(cp.x, cp.y - sq));
b.line_to(Point::new(cp.x, cp.y - arm));
});
let v_bot = canvas::Path::new(|b| {
b.move_to(Point::new(cp.x, cp.y + sq));
b.line_to(Point::new(cp.x, cp.y + arm));
});
// Center square
let square = canvas::Path::rectangle(
Point::new(cp.x - sq, cp.y - sq),
Size::new(sq * 2.0, sq * 2.0),
);
frame.stroke(&h_left, stroke.clone());
frame.stroke(&h_right, stroke.clone());
frame.stroke(&v_top, stroke.clone());
frame.stroke(&v_bot, stroke.clone());
frame.stroke(&square, stroke);
}
frame.stroke(&h_left, stroke.clone());
frame.stroke(&h_right, stroke.clone());
frame.stroke(&v_top, stroke.clone());
frame.stroke(&v_bot, stroke.clone());
frame.stroke(&square, stroke);
}
} // end !over_viewcube
// ── UCS icon ──────────────────────────────────────────────────────
@ -588,7 +592,12 @@ impl canvas::Program<Message> for SelectionCanvas {
let tp = ost.screen;
let cx = self.cursor_screen.x;
let cy = self.cursor_screen.y;
let track_color = Color { r: 0.15, g: 0.85, b: 0.95, a: 0.7 };
let track_color = Color {
r: 0.15,
g: 0.85,
b: 0.95,
a: 0.7,
};
let dash_stroke = canvas::Stroke::default()
.with_color(track_color)
.with_width(1.0);
@ -606,12 +615,24 @@ impl canvas::Program<Message> for SelectionCanvas {
// Small cross at the tracking point.
let sz = 5.0_f32;
let h = canvas::Path::line(
Point { x: tp.x - sz, y: tp.y },
Point { x: tp.x + sz, y: tp.y },
Point {
x: tp.x - sz,
y: tp.y,
},
Point {
x: tp.x + sz,
y: tp.y,
},
);
let v = canvas::Path::line(
Point { x: tp.x, y: tp.y - sz },
Point { x: tp.x, y: tp.y + sz },
Point {
x: tp.x,
y: tp.y - sz,
},
Point {
x: tp.x,
y: tp.y + sz,
},
);
frame.stroke(&h, dash_stroke.clone());
frame.stroke(&v, dash_stroke);
@ -789,8 +810,8 @@ fn draw_axes(frame: &mut canvas::Frame, vp: Mat4, bounds: iced::Rectangle, exten
// drawn as outlined circles with reduced opacity.
const UCS_ICON_MARGIN: f32 = 50.0;
const UCS_ICON_LEN: f32 = 38.0; // longest axis arm in screen pixels
const UCS_ICON_TIP: f32 = 7.0; // arrowhead size in pixels
const UCS_ICON_LEN: f32 = 38.0; // longest axis arm in screen pixels
const UCS_ICON_TIP: f32 = 7.0; // arrowhead size in pixels
fn draw_ucs_icon(frame: &mut canvas::Frame, vp: Mat4, bounds: iced::Rectangle) {
if bounds.width < 10.0 || bounds.height < 10.0 {
@ -813,9 +834,9 @@ fn draw_ucs_icon(frame: &mut canvas::Frame, vp: Mat4, bounds: iced::Rectangle) {
};
let Some(org) = w2ndc(Vec3::ZERO) else { return };
let Some(xn) = w2ndc(Vec3::X) else { return };
let Some(yn) = w2ndc(Vec3::Y) else { return };
let Some(zn) = w2ndc(Vec3::Z) else { return };
let Some(xn) = w2ndc(Vec3::X) else { return };
let Some(yn) = w2ndc(Vec3::Y) else { return };
let Some(zn) = w2ndc(Vec3::Z) else { return };
let org_s = ndc2s(org);
let icon_origin = Point::new(
@ -843,19 +864,61 @@ fn draw_ucs_icon(frame: &mut canvas::Frame, vp: Mat4, bounds: iced::Rectangle) {
// depth > 0 → tip is farther from viewer than origin (axis going into screen).
// depth < 0 → tip is closer (axis coming toward viewer).
struct AxisInfo {
dx: f32, dy: f32, sc_len: f32, depth: f32,
r: f32, g: f32, b: f32, label: &'static str,
dx: f32,
dy: f32,
sc_len: f32,
depth: f32,
r: f32,
g: f32,
b: f32,
label: &'static str,
}
let mut axes = [
AxisInfo { dx: xdx*sc, dy: xdy*sc, sc_len: xlen*sc, depth: xn.z - org.z, r: 0.90, g: 0.22, b: 0.22, label: "X" },
AxisInfo { dx: ydx*sc, dy: ydy*sc, sc_len: ylen*sc, depth: yn.z - org.z, r: 0.22, g: 0.85, b: 0.22, label: "Y" },
AxisInfo { dx: zdx*sc, dy: zdy*sc, sc_len: zlen*sc, depth: zn.z - org.z, r: 0.22, g: 0.45, b: 0.90, label: "Z" },
AxisInfo {
dx: xdx * sc,
dy: xdy * sc,
sc_len: xlen * sc,
depth: xn.z - org.z,
r: 0.90,
g: 0.22,
b: 0.22,
label: "X",
},
AxisInfo {
dx: ydx * sc,
dy: ydy * sc,
sc_len: ylen * sc,
depth: yn.z - org.z,
r: 0.22,
g: 0.85,
b: 0.22,
label: "Y",
},
AxisInfo {
dx: zdx * sc,
dy: zdy * sc,
sc_len: zlen * sc,
depth: zn.z - org.z,
r: 0.22,
g: 0.45,
b: 0.90,
label: "Z",
},
];
// Back-to-front: draw axis farthest from viewer first.
axes.sort_by(|a, b| b.depth.partial_cmp(&a.depth).unwrap_or(std::cmp::Ordering::Equal));
axes.sort_by(|a, b| {
b.depth
.partial_cmp(&a.depth)
.unwrap_or(std::cmp::Ordering::Equal)
});
for ax in &axes {
let col = Color { r: ax.r, g: ax.g, b: ax.b, a: 1.0 };
let col = Color {
r: ax.r,
g: ax.g,
b: ax.b,
a: 1.0,
};
let tip = Point::new(icon_origin.x + ax.dx, icon_origin.y + ax.dy);
// Shaft
@ -922,7 +985,15 @@ fn draw_ucs_icon(frame: &mut canvas::Frame, vp: Mat4, bounds: iced::Rectangle) {
// Origin dot.
let circle = canvas::Path::circle(icon_origin, 3.5);
frame.fill(&circle, Color { r: 0.9, g: 0.9, b: 0.9, a: 0.95 });
frame.fill(
&circle,
Color {
r: 0.9,
g: 0.9,
b: 0.9,
a: 0.95,
},
);
}
// ── Dynamic Input overlay ─────────────────────────────────────────────────
@ -931,14 +1002,14 @@ fn draw_ucs_icon(frame: &mut canvas::Frame, vp: Mat4, bounds: iced::Rectangle) {
///
/// `cursor_screen` — cursor position in viewport pixels.
/// `label` — text to display (e.g. "X: 12.34 Y: 56.78").
pub fn dynamic_input_overlay<'a>(
cursor_screen: Point,
label: String,
) -> Element<'a, Message> {
canvas(DynInputCanvas { cursor_screen, label })
.width(Length::Fill)
.height(Length::Fill)
.into()
pub fn dynamic_input_overlay<'a>(cursor_screen: Point, label: String) -> Element<'a, Message> {
canvas(DynInputCanvas {
cursor_screen,
label,
})
.width(Length::Fill)
.height(Length::Fill)
.into()
}
struct DynInputCanvas {
@ -980,27 +1051,52 @@ impl canvas::Program<Message> for DynInputCanvas {
let mut by = self.cursor_screen.y + OFFSET_Y;
// Keep box inside viewport.
if bx + BOX_W > bounds.width { bx = self.cursor_screen.x - BOX_W - 4.0; }
if by + BOX_H > bounds.height { by = self.cursor_screen.y - BOX_H - 4.0; }
if bx + BOX_W > bounds.width {
bx = self.cursor_screen.x - BOX_W - 4.0;
}
if by + BOX_H > bounds.height {
by = self.cursor_screen.y - BOX_H - 4.0;
}
let bg = canvas::Path::rectangle(
Point { x: bx, y: by },
Size { width: BOX_W, height: BOX_H },
Size {
width: BOX_W,
height: BOX_H,
},
);
frame.fill(
&bg,
Color { r: 0.05, g: 0.05, b: 0.12, a: 0.85 },
Color {
r: 0.05,
g: 0.05,
b: 0.12,
a: 0.85,
},
);
frame.stroke(
&bg,
canvas::Stroke::default()
.with_color(Color { r: 0.35, g: 0.55, b: 0.90, a: 0.9 })
.with_color(Color {
r: 0.35,
g: 0.55,
b: 0.90,
a: 0.9,
})
.with_width(1.0),
);
frame.fill_text(canvas::Text {
content: self.label.clone(),
position: Point { x: bx + PAD, y: by + PAD },
color: Color { r: 0.90, g: 0.90, b: 0.90, a: 1.0 },
position: Point {
x: bx + PAD,
y: by + PAD,
},
color: Color {
r: 0.90,
g: 0.90,
b: 0.90,
a: 1.0,
},
size: iced::Pixels(FONT_SIZE),
..Default::default()
});

View file

@ -4,25 +4,84 @@ 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 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 },
(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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 4.0.into(),
},
shadow: iced::Shadow::default(),
snap: false,
}
@ -31,12 +90,26 @@ fn btn(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
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 },
(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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 3.0.into(),
},
shadow: iced::Shadow::default(),
snap: false,
}
@ -45,14 +118,22 @@ fn pill(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
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,
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)
.width(Fill)
.height(1)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -87,7 +168,7 @@ pub fn view_window<'a>(
.style(btn(true))
.padding([4, 20]),
]
.align_y(iced::Center)
.align_y(iced::Center),
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
@ -100,42 +181,110 @@ pub fn view_window<'a>(
// ── 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);
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);
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);
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);
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);
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![
@ -144,34 +293,72 @@ pub fn view_window<'a>(
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),
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),
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),
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)
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),
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),
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,
@ -186,14 +373,12 @@ pub fn view_window<'a>(
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()
container(column![toolbar, hdivider(), content].spacing(0))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BG)),
..Default::default()
})
.width(Fill)
.height(Fill)
.into()
}

View file

@ -4,26 +4,90 @@ 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 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 },
(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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 4.0.into(),
},
..Default::default()
}
}
@ -31,14 +95,22 @@ fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
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,
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)
.width(Fill)
.height(1)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -48,7 +120,8 @@ fn hdivider<'a>() -> Element<'a, Message> {
fn vsep<'a>() -> Element<'a, Message> {
container(Space::new().width(1).height(Fill))
.width(1).height(Fill)
.width(1)
.height(Fill)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -63,19 +136,30 @@ pub fn view_window<'a>(
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)");
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]),
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)
.align_y(iced::Center),
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
@ -85,73 +169,105 @@ pub fn view_window<'a>(
.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_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),
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)
.height(Fill),
)
.width(280)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 8.0, bottom: 12.0, left: 12.0 });
.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 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);
@ -160,19 +276,27 @@ pub fn view_window<'a>(
row![
text("ACI:").size(11).color(DIM).width(100),
text(format!("{selected_aci}")).size(11),
].spacing(8).align_y(iced::Center),
]
.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]),
.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]),
.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]),
.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),
@ -185,7 +309,7 @@ pub fn view_window<'a>(
.padding([5, 10]),
]
.spacing(8)
.height(Fill)
.height(Fill),
)
.width(Fill)
.height(Fill)
@ -193,14 +317,12 @@ pub fn view_window<'a>(
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()
container(column![toolbar, hdivider(), body].spacing(0))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BG)),
..Default::default()
})
.width(Fill)
.height(Fill)
.into()
}

View file

@ -10,19 +10,19 @@
use std::collections::HashMap;
use std::fmt;
use crate::ui::ROW_H;
use acadrust::types::{Color as AcadColor, LineWeight};
use acadrust::Handle;
use iced::widget::{button, column, combo_box, container, row, scrollable, text, text_input};
use iced::{Background, Border, Color, Element, Length, Padding, Theme};
use crate::ui::ROW_H;
// ── Row-height-derived constants ─────────────────────────────────────────
const FONT_SZ: f32 = ROW_H * 0.42; // ≈11 px
const COMBO_PAD_V: f32 = (ROW_H - FONT_SZ * 1.3 - 2.0) / 2.0; // fills combo to ROW_H
const SWATCH_SZ: f32 = ROW_H * 0.54; // ≈14 px color swatch
const FONT_SZ: f32 = ROW_H * 0.42; // ≈11 px
const COMBO_PAD_V: f32 = (ROW_H - FONT_SZ * 1.3 - 2.0) / 2.0; // fills combo to ROW_H
const SWATCH_SZ: f32 = ROW_H * 0.54; // ≈14 px color swatch
use crate::scene::object::{PropSection, PropValue};
use crate::app::Message;
use crate::scene::object::{PropSection, PropValue};
const VARIES_LABEL: &str = "*VARIES*";
@ -321,7 +321,12 @@ impl PropertiesPanel {
Message::PropLayerChanged,
)
.size(FONT_SZ)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 6.0, right: 6.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 6.0,
right: 6.0,
})
.input_style(combo_input_style)
.width(Length::Fill);
@ -360,7 +365,12 @@ impl PropertiesPanel {
)
.on_press(Message::PropColorPickerToggle)
.style(combo_btn_style)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 6.0, right: 6.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 6.0,
right: 6.0,
})
.width(Length::Fill);
let color_row = prop_row_widget(label, color_btn.into());
@ -409,7 +419,12 @@ impl PropertiesPanel {
)
.on_press(Message::PropColorPickerToggle)
.style(combo_btn_style)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 6.0, right: 6.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 6.0,
right: 6.0,
})
.width(Length::Fill);
let color_row = prop_row_widget(label, color_btn.into());
@ -443,7 +458,12 @@ impl PropertiesPanel {
|item: LwItem| Message::PropLwChanged(item.0),
)
.size(FONT_SZ)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 6.0, right: 6.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 6.0,
right: 6.0,
})
.input_style(combo_input_style)
.width(Length::Fill);
@ -458,7 +478,12 @@ impl PropertiesPanel {
|item: LwItem| Message::PropLwChanged(item.0),
)
.size(FONT_SZ)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 6.0, right: 6.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 6.0,
right: 6.0,
})
.input_style(combo_input_style)
.width(Length::Fill);
@ -486,7 +511,12 @@ impl PropertiesPanel {
|item: LinetypeItem| Message::PropLinetypeChanged(item.name),
)
.size(FONT_SZ)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 6.0, right: 6.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 6.0,
right: 6.0,
})
.input_style(combo_input_style)
.width(Length::Fill);
@ -513,7 +543,12 @@ impl PropertiesPanel {
Message::PropGeomChoiceChanged { field, value }
})
.size(FONT_SZ)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 6.0, right: 6.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 6.0,
right: 6.0,
})
.input_style(combo_input_style)
.width(Length::Fill);
@ -562,7 +597,12 @@ impl PropertiesPanel {
Message::PropHatchPatternChanged,
)
.size(FONT_SZ)
.padding(Padding { top: COMBO_PAD_V, bottom: COMBO_PAD_V, left: 6.0, right: 6.0 })
.padding(Padding {
top: COMBO_PAD_V,
bottom: COMBO_PAD_V,
left: 6.0,
right: 6.0,
})
.input_style(combo_input_style)
.width(Length::Fill);
@ -583,17 +623,19 @@ pub fn color_picker_dropdown<'a>(
on_aci: impl Fn(u8) -> Message + 'a,
) -> Element<'a, Message> {
// ByLayer / ByBlock row (optional)
let extras: Option<Element<'a, Message>> =
match (by_layer_msg, by_block_msg) {
(Some(bl), Some(bb)) => Some(
row![picker_text_btn("ByLayer", bl), picker_text_btn("ByBlock", bb)]
.spacing(4)
.into(),
),
(Some(bl), None) => Some(picker_text_btn("ByLayer", bl)),
(None, Some(bb)) => Some(picker_text_btn("ByBlock", bb)),
(None, None) => None,
};
let extras: Option<Element<'a, Message>> = match (by_layer_msg, by_block_msg) {
(Some(bl), Some(bb)) => Some(
row![
picker_text_btn("ByLayer", bl),
picker_text_btn("ByBlock", bb)
]
.spacing(4)
.into(),
),
(Some(bl), None) => Some(picker_text_btn("ByLayer", bl)),
(None, Some(bb)) => Some(picker_text_btn("ByBlock", bb)),
(None, None) => None,
};
// 9 standard ACI swatches (1-9)
let standard: Element<'a, Message> = (1u8..=9u8)
@ -612,7 +654,11 @@ pub fn color_picker_dropdown<'a>(
} else {
Color::BLACK
},
width: if matches!(status, button::Status::Hovered) { 1.5 } else { 1.0 },
width: if matches!(status, button::Status::Hovered) {
1.5
} else {
1.0
},
radius: 2.0.into(),
},
..Default::default()
@ -624,9 +670,13 @@ pub fn color_picker_dropdown<'a>(
// "More Colors…" toggle button
let more_btn = button(
text(if palette_open { "▲ Less" } else { "▼ More Colors…" })
.size(10)
.color(HINT_COLOR),
text(if palette_open {
"▲ Less"
} else {
"▼ More Colors…"
})
.size(10)
.color(HINT_COLOR),
)
.on_press(palette_toggle_msg)
.style(|_: &Theme, _| button::Style {
@ -646,7 +696,11 @@ pub fn color_picker_dropdown<'a>(
let mut col = column![container(inner)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(PICKER_BG)),
border: Border { color: BORDER, width: 1.0, radius: 0.0.into() },
border: Border {
color: BORDER,
width: 1.0,
radius: 0.0.into()
},
..Default::default()
})
.padding([6, 8])
@ -661,7 +715,9 @@ pub fn color_picker_dropdown<'a>(
while idx <= 255 {
let mut r = row![].spacing(1);
for _ in 0..COLS {
if idx > 255 { break; }
if idx > 255 {
break;
}
let ci = idx as u8;
let (bg, _) = acad_color_display(AcadColor::Index(ci));
let msg = on_aci(ci);
@ -674,9 +730,18 @@ pub fn color_picker_dropdown<'a>(
color: if matches!(status, button::Status::Hovered) {
Color::WHITE
} else {
Color { r: 0.0, g: 0.0, b: 0.0, a: 0.4 }
Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.4,
}
},
width: if matches!(status, button::Status::Hovered) {
1.5
} else {
1.0
},
width: if matches!(status, button::Status::Hovered) { 1.5 } else { 1.0 },
radius: 1.0.into(),
},
..Default::default()
@ -691,7 +756,11 @@ pub fn color_picker_dropdown<'a>(
container(scrollable(rows).height(160))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(PICKER_BG)),
border: Border { color: BORDER, width: 1.0, radius: 0.0.into() },
border: Border {
color: BORDER,
width: 1.0,
radius: 0.0.into(),
},
..Default::default()
})
.padding([4, 6])
@ -707,30 +776,31 @@ pub fn color_picker_dropdown<'a>(
/// A boolean toggle button row (for "Invisible" etc.).
fn render_bool_row<'a>(label: &'a str, field: &'static str, value: bool) -> Element<'a, Message> {
let btn_label = if value { "Yes" } else { "No" };
let btn = button(
text(btn_label)
.size(FONT_SZ)
.color(if value { WARN_COLOR } else { VALUE_COLOR }),
)
.on_press(Message::PropBoolToggle(field))
.style(move |_: &Theme, status| {
let bg = match status {
button::Status::Hovered | button::Status::Pressed => HOVER_BG,
_ => VALUE_BG,
};
button::Style {
background: Some(Background::Color(bg)),
border: Border {
color: BORDER,
width: 1.0,
radius: 2.0.into(),
},
text_color: if value { WARN_COLOR } else { VALUE_COLOR },
..Default::default()
}
})
.padding([2, 6])
.width(Length::Fill);
let btn =
button(
text(btn_label)
.size(FONT_SZ)
.color(if value { WARN_COLOR } else { VALUE_COLOR }),
)
.on_press(Message::PropBoolToggle(field))
.style(move |_: &Theme, status| {
let bg = match status {
button::Status::Hovered | button::Status::Pressed => HOVER_BG,
_ => VALUE_BG,
};
button::Style {
background: Some(Background::Color(bg)),
border: Border {
color: BORDER,
width: 1.0,
radius: 2.0.into(),
},
text_color: if value { WARN_COLOR } else { VALUE_COLOR },
..Default::default()
}
})
.padding([2, 6])
.width(Length::Fill);
prop_row_widget(label, btn.into())
}
@ -744,7 +814,12 @@ fn render_ro_row<'a>(label: &'a str, value: &'a str) -> Element<'a, Message> {
.width(Length::FillPortion(5))
.height(Length::Fixed(ROW_H))
.align_y(iced::Center)
.padding(Padding { top: 0.0, bottom: 0.0, left: 6.0, right: 6.0 });
.padding(Padding {
top: 0.0,
bottom: 0.0,
left: 6.0,
right: 6.0,
});
let value_col = container(text(value).size(FONT_SZ).color(VALUE_COLOR))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(VALUE_BG)),
@ -753,7 +828,12 @@ fn render_ro_row<'a>(label: &'a str, value: &'a str) -> Element<'a, Message> {
.width(Length::FillPortion(6))
.height(Length::Fixed(ROW_H))
.align_y(iced::Center)
.padding(Padding { top: 0.0, bottom: 0.0, left: 6.0, right: 6.0 });
.padding(Padding {
top: 0.0,
bottom: 0.0,
left: 6.0,
right: 6.0,
});
container(row![label_col, value_col])
.height(Length::Fixed(ROW_H))
.style(|_: &Theme| container::Style {
@ -777,7 +857,12 @@ fn prop_row_widget<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<
.width(Length::FillPortion(5))
.height(Length::Fixed(ROW_H))
.align_y(iced::Center)
.padding(Padding { top: 0.0, bottom: 0.0, left: 6.0, right: 6.0 });
.padding(Padding {
top: 0.0,
bottom: 0.0,
left: 6.0,
right: 6.0,
});
let value_col = container(widget)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(VALUE_BG)),
@ -786,7 +871,12 @@ fn prop_row_widget<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<
.width(Length::FillPortion(6))
.height(Length::Fixed(ROW_H))
.align_y(iced::Center)
.padding(Padding { top: 0.0, bottom: 0.0, left: 2.0, right: 2.0 });
.padding(Padding {
top: 0.0,
bottom: 0.0,
left: 2.0,
right: 2.0,
});
container(row![label_col, value_col])
.height(Length::Fixed(ROW_H))
.style(|_: &Theme| container::Style {

View file

@ -12,10 +12,10 @@ use acadrust::types::{Color as AcadColor, LineWeight};
use iced::widget::{button, column, container, mouse_area, row, scrollable, svg, text};
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
use crate::app::Message;
use crate::modules::registry;
use crate::modules::{CadModule, IconKind, RibbonItem};
use crate::ui::properties::{LinetypeItem, color_picker_dropdown, lw_options};
use crate::app::Message;
use crate::ui::properties::{color_picker_dropdown, lw_options, LinetypeItem};
mod widgets;
use widgets::{StyleContext, *};
@ -104,10 +104,14 @@ impl Ribbon {
pub fn set_styles(
&mut self,
text: Vec<String>, active_text: &str,
dim: Vec<String>, active_dim: &str,
mleader: Vec<String>, active_mleader: &str,
table: Vec<String>, active_table: &str,
text: Vec<String>,
active_text: &str,
dim: Vec<String>,
active_dim: &str,
mleader: Vec<String>,
active_mleader: &str,
table: Vec<String>,
active_table: &str,
) {
self.text_style_names = text;
self.active_text_style = active_text.to_string();
@ -187,7 +191,12 @@ impl Ribbon {
// ── View ──────────────────────────────────────────────────────────────
pub fn view(&self, is_paper: bool, undo_count: usize, redo_count: usize) -> Element<'_, Message> {
pub fn view(
&self,
is_paper: bool,
undo_count: usize,
redo_count: usize,
) -> Element<'_, Message> {
// ── Logo ──────────────────────────────────────────────────────────
let logo_svg = {
let handle = svg::Handle::from_memory(include_bytes!("../../../assets/logo.svg"));
@ -217,14 +226,28 @@ impl Ribbon {
// ── Tab buttons ───────────────────────────────────────────────────
let history_controls = row![
render_history_control("↶", "Undo", UNDO_HISTORY_ID, undo_count, &self.open_dropdown),
render_history_control("↷", "Redo", REDO_HISTORY_ID, redo_count, &self.open_dropdown),
render_history_control(
"↶",
"Undo",
UNDO_HISTORY_ID,
undo_count,
&self.open_dropdown
),
render_history_control(
"↷",
"Redo",
REDO_HISTORY_ID,
redo_count,
&self.open_dropdown
),
]
.spacing(TOP_HIST_GAP)
.align_y(iced::Center);
let tab_buttons = self.modules.iter().enumerate().fold(
row![logo, history_controls].align_y(iced::Center).spacing(6),
row![logo, history_controls]
.align_y(iced::Center)
.spacing(6),
|row_acc, (i, module)| {
if module.id() == "layout" && !is_paper {
return row_acc;
@ -232,16 +255,40 @@ impl Ribbon {
let is_active = i == self.active;
let is_contextual = module.id() == "layout";
let accent = if is_contextual { ACCENT_GOLD } else { ACCENT_BLUE };
let text_inactive = if is_contextual {
Color { r: 0.90, g: 0.72, b: 0.30, a: 1.0 }
let accent = if is_contextual {
ACCENT_GOLD
} else {
Color { r: 0.75, g: 0.75, b: 0.75, a: 1.0 }
ACCENT_BLUE
};
let text_inactive = if is_contextual {
Color {
r: 0.90,
g: 0.72,
b: 0.30,
a: 1.0,
}
} else {
Color {
r: 0.75,
g: 0.75,
b: 0.75,
a: 1.0,
}
};
let hover_bg = if is_contextual {
Color { r: 0.28, g: 0.24, b: 0.12, a: 1.0 }
Color {
r: 0.28,
g: 0.24,
b: 0.12,
a: 1.0,
}
} else {
Color { r: 0.25, g: 0.25, b: 0.25, a: 1.0 }
Color {
r: 0.25,
g: 0.25,
b: 0.25,
a: 1.0,
}
};
let btn = container(
button(text(module.title()).size(12))
@ -252,9 +299,17 @@ impl Ribbon {
(false, button::Status::Hovered) => hover_bg,
_ => Color::TRANSPARENT,
})),
text_color: if is_active { Color::WHITE } else { text_inactive },
text_color: if is_active {
Color::WHITE
} else {
text_inactive
},
border: Border {
color: if is_active { accent } else { Color::TRANSPARENT },
color: if is_active {
accent
} else {
Color::TRANSPARENT
},
width: if is_active { 2.0 } else { 0.0 },
radius: 0.0.into(),
},
@ -265,7 +320,11 @@ impl Ribbon {
)
.style(move |_: &Theme| container::Style {
border: Border {
color: if is_active { accent } else { Color::TRANSPARENT },
color: if is_active {
accent
} else {
Color::TRANSPARENT
},
width: if is_active { 2.0 } else { 0.0 },
radius: 0.0.into(),
},
@ -344,10 +403,10 @@ impl Ribbon {
let is_large = matches!(
&item,
RibbonItem::LargeTool(_)
| RibbonItem::LargeDropdown { .. }
| RibbonItem::LayerComboGroup { .. }
| RibbonItem::PropertiesGroup { .. }
| RibbonItem::StyleComboGroup { .. }
| RibbonItem::LargeDropdown { .. }
| RibbonItem::LayerComboGroup { .. }
| RibbonItem::PropertiesGroup { .. }
| RibbonItem::StyleComboGroup { .. }
);
if is_large {
@ -391,8 +450,7 @@ impl Ribbon {
widgets.push(
column![
tools_el,
container(text(group.title).size(9).color(GROUP_LABEL))
.padding([1, 4]),
container(text(group.title).size(9).color(GROUP_LABEL)).padding([1, 4]),
]
.spacing(0)
.padding([3u16, 4])
@ -518,8 +576,12 @@ impl Ribbon {
'outer: for group in &groups {
for item in &group.tools {
let (id, items, default) = match item {
RibbonItem::Dropdown { id, items, default, .. } => (id, items, default),
RibbonItem::LargeDropdown { id, items, default, .. } => (id, items, default),
RibbonItem::Dropdown {
id, items, default, ..
} => (id, items, default),
RibbonItem::LargeDropdown {
id, items, default, ..
} => (id, items, default),
_ => continue,
};
if *id == open_id {
@ -539,7 +601,11 @@ impl Ribbon {
let is_current = *cmd == last_cmd;
let checkmark = text(if is_current { "✓" } else { " " })
.size(11)
.color(if is_current { CHECK_COLOR } else { Color::TRANSPARENT })
.color(if is_current {
CHECK_COLOR
} else {
Color::TRANSPARENT
})
.width(Length::Fixed(14.0));
let icon_el: Element<Message> = match *item_icon {
IconKind::Glyph(s) => text(s)
@ -552,9 +618,10 @@ impl Ribbon {
svg(handle).width(20).height(20).into()
}
};
let label_el = text(*label)
.size(11)
.color(if is_current { LABEL_ON } else { LABEL_OFF });
let label_el =
text(*label)
.size(11)
.color(if is_current { LABEL_ON } else { LABEL_OFF });
button(
row![checkmark, icon_el, label_el]
@ -626,7 +693,12 @@ impl Ribbon {
.style(move |_: &Theme| container::Style {
background: Some(Background::Color(lc)),
border: Border {
color: Color { r: 0.0, g: 0.0, b: 0.0, a: 0.5 },
color: Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.5,
},
width: 1.0,
radius: 1.0.into(),
},
@ -635,30 +707,63 @@ impl Ribbon {
.width(12)
.height(12);
let vis = text(if lv { "●" } else { "○" })
.size(10)
.color(if lv {
Color { r: 0.95, g: 0.85, b: 0.20, a: 1.0 }
} else {
Color { r: 0.45, g: 0.45, b: 0.45, a: 1.0 }
});
let freeze = text("✱").size(10).color(if lf {
Color { r: 0.40, g: 0.80, b: 1.00, a: 1.0 }
let vis = text(if lv { "●" } else { "○" }).size(10).color(if lv {
Color {
r: 0.95,
g: 0.85,
b: 0.20,
a: 1.0,
}
} else {
Color { r: 0.95, g: 0.85, b: 0.20, a: 1.0 }
Color {
r: 0.45,
g: 0.45,
b: 0.45,
a: 1.0,
}
});
let freeze = text("✱").size(10).color(if lf {
Color {
r: 0.40,
g: 0.80,
b: 1.00,
a: 1.0,
}
} else {
Color {
r: 0.95,
g: 0.85,
b: 0.20,
a: 1.0,
}
});
let lock = text(if ll { "🔒" } else { "🔓" }).size(10).color(if ll {
Color { r: 0.95, g: 0.70, b: 0.20, a: 1.0 }
Color {
r: 0.95,
g: 0.70,
b: 0.20,
a: 1.0,
}
} else {
Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 }
Color {
r: 0.55,
g: 0.55,
b: 0.55,
a: 1.0,
}
});
let checkmark = text(if is_active { "✓" } else { " " })
.size(11)
.color(if is_active { CHECK_COLOR } else { Color::TRANSPARENT })
.color(if is_active {
CHECK_COLOR
} else {
Color::TRANSPARENT
})
.width(Length::Fixed(14.0));
let label = text(&info.name)
.size(11)
.color(if is_active { LABEL_ON } else { LABEL_OFF });
let label =
text(&info.name)
.size(11)
.color(if is_active { LABEL_ON } else { LABEL_OFF });
button(
row![checkmark, vis, freeze, lock, swatch, label]
@ -682,7 +787,11 @@ impl Ribbon {
let panel = container(column(rows))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(PANEL_BG)),
border: Border { color: PANEL_BORDER, width: 1.0, radius: 3.0.into() },
border: Border {
color: PANEL_BORDER,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
})
.width(Length::Fixed(220.0));
@ -693,11 +802,19 @@ impl Ribbon {
let positioned = container(panel)
.align_left(Fill)
.align_top(Fill)
.padding(Padding { top: top_offset, left: left_offset, ..Default::default() })
.padding(Padding {
top: top_offset,
left: left_offset,
..Default::default()
})
.width(Fill)
.height(Fill);
Some(mouse_area(positioned).on_press(Message::CloseRibbonDropdown).into())
Some(
mouse_area(positioned)
.on_press(Message::CloseRibbonDropdown)
.into(),
)
}
fn prop_color_overlay(&self) -> Option<Element<'_, Message>> {
@ -712,7 +829,11 @@ impl Ribbon {
let panel = container(picker)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(PANEL_BG)),
border: Border { color: PANEL_BORDER, width: 1.0, radius: 3.0.into() },
border: Border {
color: PANEL_BORDER,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
})
.width(Length::Fixed(200.0));
@ -723,19 +844,33 @@ impl Ribbon {
let positioned = container(panel)
.align_left(Fill)
.align_top(Fill)
.padding(Padding { top: top_offset, left: left_offset, ..Default::default() })
.padding(Padding {
top: top_offset,
left: left_offset,
..Default::default()
})
.width(Fill)
.height(Fill);
Some(mouse_area(positioned).on_press(Message::CloseRibbonDropdown).into())
Some(
mouse_area(positioned)
.on_press(Message::CloseRibbonDropdown)
.into(),
)
}
fn prop_linetype_overlay(&self) -> Option<Element<'_, Message>> {
let active_lt = &self.active_linetype;
let mut items: Vec<LinetypeItem> = vec![
LinetypeItem { name: "ByLayer".to_string(), art: String::new() },
LinetypeItem { name: "ByBlock".to_string(), art: String::new() },
LinetypeItem {
name: "ByLayer".to_string(),
art: String::new(),
},
LinetypeItem {
name: "ByBlock".to_string(),
art: String::new(),
},
];
for lt in &self.available_linetypes {
if lt.name != "ByLayer" && lt.name != "ByBlock" {
@ -749,34 +884,50 @@ impl Ribbon {
let is_cur = lt.name == *active_lt;
let check = text(if is_cur { "✓" } else { " " })
.size(11)
.color(if is_cur { CHECK_COLOR } else { Color::TRANSPARENT })
.color(if is_cur {
CHECK_COLOR
} else {
Color::TRANSPARENT
})
.width(Length::Fixed(14.0));
let name_col = text(lt.name.clone())
.size(11)
.color(if is_cur { LABEL_ON } else { LABEL_OFF })
.width(Length::Fixed(90.0));
let art_col =
text(lt.art.clone()).size(9).color(Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 });
let art_col = text(lt.art.clone()).size(9).color(Color {
r: 0.55,
g: 0.55,
b: 0.55,
a: 1.0,
});
let name = lt.name.clone();
button(row![check, name_col, art_col].spacing(4).align_y(iced::Center))
.on_press(Message::RibbonLinetypeChanged(name))
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.width(Fill)
.padding([4, 6])
.into()
button(
row![check, name_col, art_col]
.spacing(4)
.align_y(iced::Center),
)
.on_press(Message::RibbonLinetypeChanged(name))
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.width(Fill)
.padding([4, 6])
.into()
})
.collect();
let list = container(scrollable(column(rows)).height(Length::Fixed(200.0)))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(PANEL_BG)),
border: Border { color: PANEL_BORDER, width: 1.0, radius: 3.0.into() },
border: Border {
color: PANEL_BORDER,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
})
.width(Length::Fixed(220.0));
@ -787,11 +938,19 @@ impl Ribbon {
let positioned = container(list)
.align_left(Fill)
.align_top(Fill)
.padding(Padding { top: top_offset, left: left_offset, ..Default::default() })
.padding(Padding {
top: top_offset,
left: left_offset,
..Default::default()
})
.width(Fill)
.height(Fill);
Some(mouse_area(positioned).on_press(Message::CloseRibbonDropdown).into())
Some(
mouse_area(positioned)
.on_press(Message::CloseRibbonDropdown)
.into(),
)
}
fn prop_lw_overlay(&self) -> Option<Element<'_, Message>> {
@ -803,7 +962,11 @@ impl Ribbon {
let label = item.to_string();
let check = text(if is_cur { "✓" } else { " " })
.size(11)
.color(if is_cur { CHECK_COLOR } else { Color::TRANSPARENT })
.color(if is_cur {
CHECK_COLOR
} else {
Color::TRANSPARENT
})
.width(Length::Fixed(14.0));
button(
row![
@ -841,7 +1004,11 @@ impl Ribbon {
let panel = container(column(rows))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(PANEL_BG)),
border: Border { color: PANEL_BORDER, width: 1.0, radius: 3.0.into() },
border: Border {
color: PANEL_BORDER,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
})
.width(Length::Fixed(width));
@ -852,11 +1019,19 @@ impl Ribbon {
let positioned = container(panel)
.align_left(Fill)
.align_top(Fill)
.padding(Padding { top: top_offset, left: left_offset, ..Default::default() })
.padding(Padding {
top: top_offset,
left: left_offset,
..Default::default()
})
.width(Fill)
.height(Fill);
Some(mouse_area(positioned).on_press(Message::CloseRibbonDropdown).into())
Some(
mouse_area(positioned)
.on_press(Message::CloseRibbonDropdown)
.into(),
)
}
}

View file

@ -11,7 +11,7 @@ use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
use crate::app::Message;
use crate::modules::{IconKind, ModuleEvent, RibbonGroup, RibbonItem, StyleKey, ToolDef};
use crate::ui::properties::{LwItem, acad_color_display};
use crate::ui::properties::{acad_color_display, LwItem};
use super::LayerInfo;
@ -51,24 +51,114 @@ pub(super) const PROP_LW_ID: &str = "PROP_LW";
// ── Colours ────────────────────────────────────────────────────────────────
pub(super) const LOGO_RED: Color = Color { r: 0.75, g: 0.10, b: 0.10, a: 1.0 };
pub(super) const TOPBAR_BG: Color = Color { r: 0.17, g: 0.17, b: 0.17, a: 1.0 };
pub(super) const RIBBON_BG: Color = Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 };
pub(super) const BORDER_DARK: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 };
pub(super) const ACCENT_BLUE: Color = Color { r: 0.20, g: 0.55, b: 0.90, a: 1.0 };
pub(super) const ACCENT_GOLD: Color = Color { r: 0.90, g: 0.65, b: 0.10, a: 1.0 };
pub(super) const LABEL_COLOR: Color = Color { r: 0.82, g: 0.82, b: 0.82, a: 1.0 };
pub(super) const GROUP_LABEL: Color = Color { r: 0.50, g: 0.50, b: 0.50, a: 1.0 };
pub(super) const TOOL_HOVER: Color = Color { r: 0.32, g: 0.32, b: 0.32, a: 1.0 };
pub(super) const TOOL_ACTIVE: Color = Color { r: 0.18, g: 0.42, b: 0.70, a: 1.0 };
pub(super) const ARROW_COLOR: Color = Color { r: 0.65, g: 0.65, b: 0.65, a: 1.0 };
pub(super) const PANEL_BG: Color = Color { r: 0.16, g: 0.16, b: 0.16, a: 0.98 };
pub(super) const PANEL_BORDER: Color = Color { r: 0.32, g: 0.32, b: 0.32, a: 1.0 };
pub(super) const ROW_HOVER: Color = Color { r: 0.24, g: 0.24, b: 0.24, a: 1.0 };
pub(super) const CHECK_COLOR: Color = Color { r: 0.20, g: 0.75, b: 0.35, a: 1.0 };
pub(super) const ICON_COLOR: Color = Color { r: 0.25, g: 0.75, b: 0.45, a: 1.0 };
pub(super) const LABEL_ON: Color = Color { r: 0.92, g: 0.92, b: 0.92, a: 1.0 };
pub(super) const LABEL_OFF: Color = Color { r: 0.72, g: 0.72, b: 0.72, a: 1.0 };
pub(super) const LOGO_RED: Color = Color {
r: 0.75,
g: 0.10,
b: 0.10,
a: 1.0,
};
pub(super) const TOPBAR_BG: Color = Color {
r: 0.17,
g: 0.17,
b: 0.17,
a: 1.0,
};
pub(super) const RIBBON_BG: Color = Color {
r: 0.22,
g: 0.22,
b: 0.22,
a: 1.0,
};
pub(super) const BORDER_DARK: Color = Color {
r: 0.12,
g: 0.12,
b: 0.12,
a: 1.0,
};
pub(super) const ACCENT_BLUE: Color = Color {
r: 0.20,
g: 0.55,
b: 0.90,
a: 1.0,
};
pub(super) const ACCENT_GOLD: Color = Color {
r: 0.90,
g: 0.65,
b: 0.10,
a: 1.0,
};
pub(super) const LABEL_COLOR: Color = Color {
r: 0.82,
g: 0.82,
b: 0.82,
a: 1.0,
};
pub(super) const GROUP_LABEL: Color = Color {
r: 0.50,
g: 0.50,
b: 0.50,
a: 1.0,
};
pub(super) const TOOL_HOVER: Color = Color {
r: 0.32,
g: 0.32,
b: 0.32,
a: 1.0,
};
pub(super) const TOOL_ACTIVE: Color = Color {
r: 0.18,
g: 0.42,
b: 0.70,
a: 1.0,
};
pub(super) const ARROW_COLOR: Color = Color {
r: 0.65,
g: 0.65,
b: 0.65,
a: 1.0,
};
pub(super) const PANEL_BG: Color = Color {
r: 0.16,
g: 0.16,
b: 0.16,
a: 0.98,
};
pub(super) const PANEL_BORDER: Color = Color {
r: 0.32,
g: 0.32,
b: 0.32,
a: 1.0,
};
pub(super) const ROW_HOVER: Color = Color {
r: 0.24,
g: 0.24,
b: 0.24,
a: 1.0,
};
pub(super) const CHECK_COLOR: Color = Color {
r: 0.20,
g: 0.75,
b: 0.35,
a: 1.0,
};
pub(super) const ICON_COLOR: Color = Color {
r: 0.25,
g: 0.75,
b: 0.45,
a: 1.0,
};
pub(super) const LABEL_ON: Color = Color {
r: 0.92,
g: 0.92,
b: 0.92,
a: 1.0,
};
pub(super) const LABEL_OFF: Color = Color {
r: 0.72,
g: 0.72,
b: 0.72,
a: 1.0,
};
// ── Style context (passed from Ribbon to render_large) ────────────────────
@ -86,18 +176,18 @@ pub(super) struct StyleContext {
impl StyleContext {
fn names_for(&self, key: StyleKey) -> &[String] {
match key {
StyleKey::TextStyle => &self.text_style_names,
StyleKey::DimStyle => &self.dim_style_names,
StyleKey::TextStyle => &self.text_style_names,
StyleKey::DimStyle => &self.dim_style_names,
StyleKey::MLeaderStyle => &self.mleader_style_names,
StyleKey::TableStyle => &self.table_style_names,
StyleKey::TableStyle => &self.table_style_names,
}
}
fn active_for(&self, key: StyleKey) -> &str {
match key {
StyleKey::TextStyle => &self.active_text_style,
StyleKey::DimStyle => &self.active_dim_style,
StyleKey::TextStyle => &self.active_text_style,
StyleKey::DimStyle => &self.active_dim_style,
StyleKey::MLeaderStyle => &self.active_mleader_style,
StyleKey::TableStyle => &self.active_table_style,
StyleKey::TableStyle => &self.active_table_style,
}
}
}
@ -177,7 +267,12 @@ pub(super) fn tip_style(_theme: &Theme) -> container::Style {
a: 0.97,
})),
border: Border {
color: Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 },
color: Color {
r: 0.35,
g: 0.35,
b: 0.35,
a: 1.0,
},
width: 1.0,
radius: 3.0.into(),
},
@ -216,7 +311,13 @@ pub(super) fn render_small<'a>(
.into()
}
RibbonItem::Dropdown { id, icon, items, default, .. } => {
RibbonItem::Dropdown {
id,
icon,
items,
default,
..
} => {
let active = active_tool.as_deref() == Some(id)
|| items
.iter()
@ -227,7 +328,10 @@ pub(super) fn render_small<'a>(
.get(id)
.copied()
.and_then(|cmd| {
items.iter().find(|(c, _, _)| *c == cmd).map(|(_, _, ik)| *ik)
items
.iter()
.find(|(c, _, _)| *c == cmd)
.map(|(_, _, ik)| *ik)
})
.or_else(|| items.first().map(|(_, _, ik)| *ik))
.unwrap_or(icon);
@ -236,7 +340,10 @@ pub(super) fn render_small<'a>(
.get(id)
.copied()
.and_then(|cmd| {
items.iter().find(|(c, _, _)| *c == cmd).map(|(_, lbl, _)| *lbl)
items
.iter()
.find(|(c, _, _)| *c == cmd)
.map(|(_, lbl, _)| *lbl)
})
.or_else(|| items.first().map(|(_, lbl, _)| *lbl))
.unwrap_or(id);
@ -267,7 +374,10 @@ pub(super) fn render_small<'a>(
_ if dd_open => TOOL_ACTIVE,
_ => Color::TRANSPARENT,
})),
border: Border { radius: 2.0.into(), ..Default::default() },
border: Border {
radius: 2.0.into(),
..Default::default()
},
..Default::default()
})
.width(Length::Fixed(ARROW_W))
@ -283,7 +393,10 @@ pub(super) fn render_small<'a>(
.delay(Duration::from_millis(400))
.style(tip_style);
row![icon_with_tip, arr_with_tip].spacing(0).height(ROW_H).into()
row![icon_with_tip, arr_with_tip]
.spacing(0)
.height(ROW_H)
.into()
}
_ => text("").into(),
@ -325,7 +438,12 @@ pub(super) fn render_large<'a>(
.style(move |_: &Theme, status| tool_btn_style(active, status))
.width(Length::Fixed(LARGE_W))
.height(Fill)
.padding(Padding { top: 6.0, right: 4.0, bottom: 4.0, left: 4.0 });
.padding(Padding {
top: 6.0,
right: 4.0,
bottom: 4.0,
left: 4.0,
});
tooltip(btn, make_tip(tip_text), TipPos::Bottom)
.gap(6.0)
.delay(Duration::from_millis(400))
@ -333,7 +451,13 @@ pub(super) fn render_large<'a>(
.into()
}
RibbonItem::LargeDropdown { id, label, icon, items, default } => {
RibbonItem::LargeDropdown {
id,
label,
icon,
items,
default,
} => {
let active = active_tool.as_deref() == Some(id)
|| items
.iter()
@ -344,7 +468,10 @@ pub(super) fn render_large<'a>(
.get(id)
.copied()
.and_then(|cmd| {
items.iter().find(|(c, _, _)| *c == cmd).map(|(_, _, ik)| *ik)
items
.iter()
.find(|(c, _, _)| *c == cmd)
.map(|(_, _, ik)| *ik)
})
.or_else(|| items.first().map(|(_, _, ik)| *ik))
.unwrap_or(icon);
@ -353,7 +480,10 @@ pub(super) fn render_large<'a>(
.get(id)
.copied()
.and_then(|cmd| {
items.iter().find(|(c, _, _)| *c == cmd).map(|(_, lbl, _)| *lbl)
items
.iter()
.find(|(c, _, _)| *c == cmd)
.map(|(_, lbl, _)| *lbl)
})
.or_else(|| items.first().map(|(_, lbl, _)| *lbl))
.unwrap_or(label);
@ -375,7 +505,12 @@ pub(super) fn render_large<'a>(
.style(move |_: &Theme, status| tool_btn_style(active, status))
.width(Length::Fixed(LARGE_W))
.height(Fill)
.padding(Padding { top: 6.0, right: 4.0, bottom: 2.0, left: 4.0 });
.padding(Padding {
top: 6.0,
right: 4.0,
bottom: 2.0,
left: 4.0,
});
let arr_btn = button(
container(text("▾").size(9).color(ARROW_COLOR))
@ -391,7 +526,10 @@ pub(super) fn render_large<'a>(
_ if dd_open => TOOL_ACTIVE,
_ => Color::TRANSPARENT,
})),
border: Border { radius: 3.0.into(), ..Default::default() },
border: Border {
radius: 3.0.into(),
..Default::default()
},
..Default::default()
})
.width(Length::Fixed(LARGE_W))
@ -424,23 +562,61 @@ pub(super) fn render_large<'a>(
let ll = info.map(|l| l.locked).unwrap_or(false);
let is_open = open_dd.as_deref() == Some(LAYER_COMBO_ID);
let vis_icon = text(if lv { "●" } else { "○" }).size(10).color(
if lv { Color { r: 0.95, g: 0.85, b: 0.20, a: 1.0 } }
else { Color { r: 0.45, g: 0.45, b: 0.45, a: 1.0 } },
);
let freeze_icon = text("✱").size(10).color(
if lf { Color { r: 0.40, g: 0.80, b: 1.00, a: 1.0 } }
else { Color { r: 0.95, g: 0.85, b: 0.20, a: 1.0 } },
);
let lock_icon = text(if ll { "🔒" } else { "🔓" }).size(10).color(
if ll { Color { r: 0.95, g: 0.70, b: 0.20, a: 1.0 } }
else { Color { r: 0.65, g: 0.65, b: 0.65, a: 1.0 } },
);
let vis_icon = text(if lv { "●" } else { "○" }).size(10).color(if lv {
Color {
r: 0.95,
g: 0.85,
b: 0.20,
a: 1.0,
}
} else {
Color {
r: 0.45,
g: 0.45,
b: 0.45,
a: 1.0,
}
});
let freeze_icon = text("✱").size(10).color(if lf {
Color {
r: 0.40,
g: 0.80,
b: 1.00,
a: 1.0,
}
} else {
Color {
r: 0.95,
g: 0.85,
b: 0.20,
a: 1.0,
}
});
let lock_icon = text(if ll { "🔒" } else { "🔓" }).size(10).color(if ll {
Color {
r: 0.95,
g: 0.70,
b: 0.20,
a: 1.0,
}
} else {
Color {
r: 0.65,
g: 0.65,
b: 0.65,
a: 1.0,
}
});
let swatch = container(text(""))
.style(move |_: &Theme| container::Style {
background: Some(Background::Color(lc)),
border: Border {
color: Color { r: 0.0, g: 0.0, b: 0.0, a: 0.5 },
color: Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.5,
},
width: 1.0,
radius: 1.0.into(),
},
@ -454,10 +630,19 @@ pub(super) fn render_large<'a>(
let combo_btn = button(
row![
vis_icon, freeze_icon, lock_icon, swatch,
vis_icon,
freeze_icon,
lock_icon,
swatch,
container(text(active_layer).size(11).color(Color::WHITE))
.width(name_w).clip(true),
text("▾").size(9).color(Color { r: 0.7, g: 0.7, b: 0.7, a: 1.0 }),
.width(name_w)
.clip(true),
text("▾").size(9).color(Color {
r: 0.7,
g: 0.7,
b: 0.7,
a: 1.0
}),
]
.spacing(4)
.align_y(iced::Center),
@ -465,14 +650,34 @@ pub(super) fn render_large<'a>(
.on_press(Message::ToggleRibbonDropdown(LAYER_COMBO_ID.to_string()))
.style(move |_: &Theme, status| button::Style {
background: Some(Background::Color(match (is_open, status) {
(true, _) => Color { r: 0.14, g: 0.14, b: 0.14, a: 1.0 },
(_, button::Status::Hovered) => Color { r: 0.26, g: 0.26, b: 0.26, a: 1.0 },
_ => Color { r: 0.18, g: 0.18, b: 0.18, a: 1.0 },
(true, _) => Color {
r: 0.14,
g: 0.14,
b: 0.14,
a: 1.0,
},
(_, button::Status::Hovered) => Color {
r: 0.26,
g: 0.26,
b: 0.26,
a: 1.0,
},
_ => Color {
r: 0.18,
g: 0.18,
b: 0.18,
a: 1.0,
},
})),
border: Border {
radius: 3.0.into(),
width: 1.0,
color: Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 },
color: Color {
r: 0.35,
g: 0.35,
b: 0.35,
a: 1.0,
},
},
..Default::default()
})
@ -488,12 +693,12 @@ pub(super) fn render_large<'a>(
let event = t.event.clone();
let icon_el: Element<Message> = match t.icon {
IconKind::Glyph(g) => text(g).size(13).color(Color::WHITE).into(),
IconKind::Svg(bytes) => iced::widget::svg(
iced::widget::svg::Handle::from_memory(bytes),
)
.width(16)
.height(16)
.into(),
IconKind::Svg(bytes) => {
iced::widget::svg(iced::widget::svg::Handle::from_memory(bytes))
.width(16)
.height(16)
.into()
}
};
let msg = module_event_to_message(event);
tooltip(
@ -524,7 +729,12 @@ pub(super) fn render_large<'a>(
.width(Length::Fixed(COMBO_W))
.height(Fill)
.align_y(iced::Center)
.padding(Padding { top: 4.0, bottom: 4.0, left: 4.0, right: 4.0 })
.padding(Padding {
top: 4.0,
bottom: 4.0,
left: 4.0,
right: 4.0,
})
.into()
}
@ -541,11 +751,19 @@ pub(super) fn render_large<'a>(
.align_x(iced::Center)
.spacing(3),
)
.on_press(Message::RibbonToolClick { tool_id: mp_id, event: mp_event })
.on_press(Message::RibbonToolClick {
tool_id: mp_id,
event: mp_event,
})
.style(move |_: &Theme, status| tool_btn_style(mp_active, status))
.width(Length::Fixed(LARGE_W))
.height(Fill)
.padding(Padding { top: 6.0, right: 4.0, bottom: 4.0, left: 4.0 });
.padding(Padding {
top: 6.0,
right: 4.0,
bottom: 4.0,
left: 4.0,
});
let mp_el = tooltip(mp_btn, make_tip(mp_tip), TipPos::Bottom)
.gap(6.0)
.delay(Duration::from_millis(400))
@ -560,7 +778,12 @@ pub(super) fn render_large<'a>(
.style(move |_: &Theme| container::Style {
background: Some(Background::Color(c)),
border: Border {
color: Color { r: 0.0, g: 0.0, b: 0.0, a: 0.5 },
color: Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.5,
},
width: 1.0,
radius: 1.0.into(),
},
@ -578,9 +801,12 @@ pub(super) fn render_large<'a>(
container(text(label).size(10).color(Color::WHITE))
.width(Fill)
.clip(true),
text(if is_open { "▲" } else { "▼" })
.size(7)
.color(Color { r: 0.6, g: 0.6, b: 0.6, a: 1.0 }),
text(if is_open { "▲" } else { "▼" }).size(7).color(Color {
r: 0.6,
g: 0.6,
b: 0.6,
a: 1.0
}),
]
.spacing(4)
.align_y(iced::Center),
@ -588,19 +814,42 @@ pub(super) fn render_large<'a>(
.on_press(Message::ToggleRibbonDropdown(dd_id.to_string()))
.style(move |_: &Theme, status| button::Style {
background: Some(Background::Color(match (is_open, status) {
(true, _) => Color { r: 0.14, g: 0.14, b: 0.14, a: 1.0 },
(_, button::Status::Hovered) => {
Color { r: 0.26, g: 0.26, b: 0.26, a: 1.0 }
}
_ => Color { r: 0.18, g: 0.18, b: 0.18, a: 1.0 },
(true, _) => Color {
r: 0.14,
g: 0.14,
b: 0.14,
a: 1.0,
},
(_, button::Status::Hovered) => Color {
r: 0.26,
g: 0.26,
b: 0.26,
a: 1.0,
},
_ => Color {
r: 0.18,
g: 0.18,
b: 0.18,
a: 1.0,
},
})),
border: Border {
radius: 2.0.into(),
width: 1.0,
color: if is_open {
Color { r: 0.45, g: 0.65, b: 0.90, a: 1.0 }
Color {
r: 0.45,
g: 0.65,
b: 0.90,
a: 1.0,
}
} else {
Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 }
Color {
r: 0.35,
g: 0.35,
b: 0.35,
a: 1.0,
}
},
},
..Default::default()
@ -615,11 +864,18 @@ pub(super) fn render_large<'a>(
let lw_row = prop_row(LwItem(active_lineweight).to_string(), PROP_LW_ID, None);
let combos = container(
column![color_row, lt_row, lw_row].spacing(2).align_x(iced::Left),
column![color_row, lt_row, lw_row]
.spacing(2)
.align_x(iced::Left),
)
.height(Fill)
.align_y(iced::Center)
.padding(Padding { top: 4.0, bottom: 4.0, left: 0.0, right: 4.0 });
.padding(Padding {
top: 4.0,
bottom: 4.0,
left: 0.0,
right: 4.0,
});
row![mp_el, combos]
.spacing(4)
@ -628,7 +884,12 @@ pub(super) fn render_large<'a>(
.into()
}
RibbonItem::StyleComboGroup { style_key, combo_id, manager_cmd, rows } => {
RibbonItem::StyleComboGroup {
style_key,
combo_id,
manager_cmd,
rows,
} => {
const STYLE_COMBO_W: f32 = LARGE_W * 2.3;
let names: Vec<String> = style_ctx.names_for(style_key).to_vec();
let active: String = style_ctx.active_for(style_key).to_string();
@ -640,9 +901,12 @@ pub(super) fn render_large<'a>(
container(text(active.clone()).size(11).color(Color::WHITE))
.width(Fill)
.clip(true),
text(if is_open { "▲" } else { "▾" })
.size(9)
.color(Color { r: 0.7, g: 0.7, b: 0.7, a: 1.0 }),
text(if is_open { "▲" } else { "▾" }).size(9).color(Color {
r: 0.7,
g: 0.7,
b: 0.7,
a: 1.0
}),
]
.spacing(4)
.align_y(iced::Center),
@ -650,17 +914,42 @@ pub(super) fn render_large<'a>(
.on_press(Message::ToggleRibbonDropdown(combo_id.to_string()))
.style(move |_: &Theme, status| button::Style {
background: Some(Background::Color(match (is_open, status) {
(true, _) => Color { r: 0.14, g: 0.14, b: 0.14, a: 1.0 },
(_, button::Status::Hovered) => Color { r: 0.26, g: 0.26, b: 0.26, a: 1.0 },
_ => Color { r: 0.18, g: 0.18, b: 0.18, a: 1.0 },
(true, _) => Color {
r: 0.14,
g: 0.14,
b: 0.14,
a: 1.0,
},
(_, button::Status::Hovered) => Color {
r: 0.26,
g: 0.26,
b: 0.26,
a: 1.0,
},
_ => Color {
r: 0.18,
g: 0.18,
b: 0.18,
a: 1.0,
},
})),
border: Border {
radius: 3.0.into(),
width: 1.0,
color: if is_open {
Color { r: 0.45, g: 0.65, b: 0.90, a: 1.0 }
Color {
r: 0.45,
g: 0.65,
b: 0.90,
a: 1.0,
}
} else {
Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 }
Color {
r: 0.35,
g: 0.35,
b: 0.35,
a: 1.0,
}
},
},
..Default::default()
@ -681,7 +970,12 @@ pub(super) fn render_large<'a>(
text(if is_sel { "✓" } else { " " })
.size(10)
.color(if is_sel {
Color { r: 0.2, g: 0.8, b: 0.4, a: 1.0 }
Color {
r: 0.2,
g: 0.8,
b: 0.4,
a: 1.0,
}
} else {
Color::TRANSPARENT
}),
@ -693,11 +987,24 @@ pub(super) fn render_large<'a>(
.on_press(Message::RibbonStyleChanged { key, name: n })
.style(move |_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered | button::Status::Pressed => {
Color { r: 0.28, g: 0.28, b: 0.28, a: 1.0 }
}
_ if is_sel => Color { r: 0.20, g: 0.35, b: 0.55, a: 1.0 },
_ => Color { r: 0.16, g: 0.16, b: 0.16, a: 1.0 },
button::Status::Hovered | button::Status::Pressed => Color {
r: 0.28,
g: 0.28,
b: 0.28,
a: 1.0,
},
_ if is_sel => Color {
r: 0.20,
g: 0.35,
b: 0.55,
a: 1.0,
},
_ => Color {
r: 0.16,
g: 0.16,
b: 0.16,
a: 1.0,
},
})),
..Default::default()
})
@ -711,15 +1018,27 @@ pub(super) fn render_large<'a>(
let mut full_col = items_col;
if let Some(mgr_cmd) = manager_cmd {
full_col.push(
button(
text(format!("Manage…")).size(10)
.color(Color { r: 0.5, g: 0.8, b: 1.0, a: 1.0 }),
)
button(text(format!("Manage…")).size(10).color(Color {
r: 0.5,
g: 0.8,
b: 1.0,
a: 1.0,
}))
.on_press(Message::Command(mgr_cmd.to_string()))
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => Color { r: 0.24, g: 0.24, b: 0.24, a: 1.0 },
_ => Color { r: 0.13, g: 0.13, b: 0.13, a: 1.0 },
button::Status::Hovered => Color {
r: 0.24,
g: 0.24,
b: 0.24,
a: 1.0,
},
_ => Color {
r: 0.13,
g: 0.13,
b: 0.13,
a: 1.0,
},
})),
..Default::default()
})
@ -740,11 +1059,19 @@ pub(super) fn render_large<'a>(
.max_height(180.0)
.width(Length::Fixed(STYLE_COMBO_W))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(
Color { r: 0.14, g: 0.14, b: 0.14, a: 0.98 },
)),
background: Some(Background::Color(Color {
r: 0.14,
g: 0.14,
b: 0.14,
a: 0.98,
})),
border: Border {
color: Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 },
color: Color {
r: 0.35,
g: 0.35,
b: 0.35,
a: 1.0,
},
width: 1.0,
radius: 4.0.into(),
},
@ -765,12 +1092,12 @@ pub(super) fn render_large<'a>(
let event = t.event.clone();
let icon_el: Element<Message> = match t.icon {
IconKind::Glyph(g) => text(g).size(13).color(Color::WHITE).into(),
IconKind::Svg(bytes) => iced::widget::svg(
iced::widget::svg::Handle::from_memory(bytes),
)
.width(16)
.height(16)
.into(),
IconKind::Svg(bytes) => {
iced::widget::svg(iced::widget::svg::Handle::from_memory(bytes))
.width(16)
.height(16)
.into()
}
};
let msg = module_event_to_message(event);
tooltip(
@ -790,11 +1117,10 @@ pub(super) fn render_large<'a>(
row(btns).spacing(2).align_y(iced::Center).into()
};
let mut col_items: Vec<Element<Message>> = vec![
container(row![combo_btn, items_panel].spacing(0))
let mut col_items: Vec<Element<Message>> =
vec![container(row![combo_btn, items_panel].spacing(0))
.width(Fill)
.into(),
];
.into()];
for row_tools in rows {
col_items.push(make_tool_row(row_tools));
}
@ -803,7 +1129,12 @@ pub(super) fn render_large<'a>(
.width(Length::Fixed(STYLE_COMBO_W))
.height(Fill)
.align_y(iced::Center)
.padding(Padding { top: 4.0, bottom: 4.0, left: 4.0, right: 4.0 })
.padding(Padding {
top: 4.0,
bottom: 4.0,
left: 4.0,
right: 4.0,
})
.into()
}
@ -814,10 +1145,7 @@ pub(super) fn render_large<'a>(
// ── Dropdown position helpers ──────────────────────────────────────────────
/// Calculate the left pixel offset of a dropdown button inside the ribbon tool area.
pub(super) fn compute_dropdown_left(
groups: &[RibbonGroup],
open_id: &str,
) -> f32 {
pub(super) fn compute_dropdown_left(groups: &[RibbonGroup], open_id: &str) -> f32 {
let sum_with_spacing = |widths: &[f32]| -> f32 {
widths
.iter()
@ -826,7 +1154,11 @@ pub(super) fn compute_dropdown_left(
.sum::<f32>()
};
let next_item_x = |widths: &[f32]| -> f32 {
if widths.is_empty() { 0.0 } else { sum_with_spacing(widths) + 2.0 }
if widths.is_empty() {
0.0
} else {
sum_with_spacing(widths) + 2.0
}
};
let mut x = 0.0f32;
@ -950,7 +1282,9 @@ pub(super) fn render_history_control<'a>(
let main_btn = {
let btn = button(
text(glyph).size(14).color(if active { Color::WHITE } else { LABEL_OFF }),
text(glyph)
.size(14)
.color(if active { Color::WHITE } else { LABEL_OFF }),
)
.style(move |_: &Theme, status| top_hist_btn_style(active, dd_open, status))
.width(Length::Fixed(TOP_HIST_W))
@ -978,7 +1312,9 @@ pub(super) fn render_history_control<'a>(
let arrow_btn = {
let btn = button(
container(
text("▾").size(7).color(if active { ARROW_COLOR } else { LABEL_OFF }),
text("▾")
.size(7)
.color(if active { ARROW_COLOR } else { LABEL_OFF }),
)
.width(Fill)
.height(Fill)
@ -1014,7 +1350,12 @@ pub(super) fn top_hist_btn_style(
) -> button::Style {
button::Style {
background: Some(Background::Color(match (active, open, status) {
(false, _, _) => Color { r: 0.20, g: 0.20, b: 0.20, a: 1.0 },
(false, _, _) => Color {
r: 0.20,
g: 0.20,
b: 0.20,
a: 1.0,
},
(_, true, _) => TOOL_ACTIVE,
(_, _, button::Status::Hovered) => TOOL_HOVER,
(_, _, button::Status::Pressed) => TOOL_ACTIVE,

View file

@ -12,15 +12,15 @@ use crate::app::Message;
/// vp_scale = custom_scale value on the Viewport entity (0.02 for "1:50")
const COMMON_SCALES: &[(&str, f32, f64)] = &[
("1:100", 100.0, 0.01),
("1:50", 50.0, 0.02),
("1:20", 20.0, 0.05),
("1:10", 10.0, 0.10),
("1:5", 5.0, 0.20),
("1:2", 2.0, 0.50),
("1:1", 1.0, 1.00),
("2:1", 0.5, 2.00),
("5:1", 0.2, 5.00),
("10:1", 0.1, 10.00),
("1:50", 50.0, 0.02),
("1:20", 20.0, 0.05),
("1:10", 10.0, 0.10),
("1:5", 5.0, 0.20),
("1:2", 2.0, 0.50),
("1:1", 1.0, 1.00),
("2:1", 0.5, 2.00),
("5:1", 0.2, 5.00),
("10:1", 0.1, 10.00),
];
#[allow(dead_code)]
@ -93,7 +93,11 @@ pub fn scale_popup_overlay(
fn scale_row(label: &'static str, active: bool, msg: Message) -> Element<'static, Message> {
let check = text(if active { "✓" } else { " " })
.size(11)
.color(if active { CHECK_COLOR } else { Color::TRANSPARENT })
.color(if active {
CHECK_COLOR
} else {
Color::TRANSPARENT
})
.width(Length::Fixed(14.0));
let lbl = text(label)
@ -118,9 +122,39 @@ fn scale_row(label: &'static str, active: bool, msg: Message) -> Element<'static
// ── Colours ───────────────────────────────────────────────────────────────
const PANEL_BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 };
const PANEL_BORDER: Color = Color { r: 0.32, g: 0.32, b: 0.32, a: 1.0 };
const ROW_HOVER: Color = Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 };
const CHECK_COLOR: Color = Color { r: 0.35, g: 0.75, b: 1.00, a: 1.0 };
const LABEL_ON: Color = Color { r: 0.92, g: 0.92, b: 0.92, a: 1.0 };
const LABEL_OFF: Color = Color { r: 0.65, g: 0.65, b: 0.65, a: 1.0 };
const PANEL_BG: Color = Color {
r: 0.15,
g: 0.15,
b: 0.15,
a: 1.0,
};
const PANEL_BORDER: Color = Color {
r: 0.32,
g: 0.32,
b: 0.32,
a: 1.0,
};
const ROW_HOVER: Color = Color {
r: 0.22,
g: 0.22,
b: 0.22,
a: 1.0,
};
const CHECK_COLOR: Color = Color {
r: 0.35,
g: 0.75,
b: 1.00,
a: 1.0,
};
const LABEL_ON: Color = Color {
r: 0.92,
g: 0.92,
b: 0.92,
a: 1.0,
};
const LABEL_OFF: Color = Color {
r: 0.65,
g: 0.65,
b: 0.65,
a: 1.0,
};

View file

@ -4,16 +4,47 @@ 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 };
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)
.width(Fill)
.height(1)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -23,7 +54,11 @@ fn hdivider<'a>() -> Element<'a, Message> {
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(key)
.size(11)
.color(KEY)
.font(iced::Font::MONOSPACE)
.width(160),
text(action).size(11),
]
.spacing(8)
@ -34,7 +69,12 @@ fn shortcut_row<'a>(key: &'static str, action: &'static str) -> Element<'a, Mess
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 })
.padding(iced::Padding {
top: 6.0,
right: 0.0,
bottom: 2.0,
left: 0.0,
})
.into()
}
@ -48,7 +88,7 @@ pub fn view_window<'a>(
.size(10)
.color(DIM),
]
.align_y(iced::Center)
.align_y(iced::Center),
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
@ -60,36 +100,40 @@ pub fn view_window<'a>(
// ── 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"),
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"),
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"),
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) ──────────────────"));
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()
.size(11)
.color(DIM)
.into(),
);
} else {
let mut sorted: Vec<_> = overrides.iter().collect();
@ -97,21 +141,25 @@ pub fn view_window<'a>(
for (key, cmd) in sorted {
rows.push(
row![
text(key.as_str()).size(11).color(KEY).font(iced::Font::MONOSPACE).width(160),
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()
.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);
let content = scrollable(column(rows).spacing(3).padding([12, 16]))
.width(Fill)
.height(Fill);
// ── Header row with accent ────────────────────────────────────────────
let header = container(
@ -120,22 +168,25 @@ pub fn view_window<'a>(
text("Action").size(10).color(ACCENT),
]
.spacing(8)
.padding([4, 16])
.padding([4, 16]),
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(Color { r: 0.13, g: 0.13, b: 0.18, a: 1.0 })),
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()
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()
}

View file

@ -6,8 +6,8 @@
use iced::widget::{button, column, container, mouse_area, row, text};
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
use crate::snap::{SnapType, Snapper, ALL_SNAP_MODES};
use crate::app::Message;
use crate::snap::{SnapType, Snapper, ALL_SNAP_MODES};
/// Returns a full-screen overlay element:
/// - a transparent click-catcher that closes the popup on outside click

View file

@ -4,8 +4,8 @@ use iced::widget::tooltip::Position as TipPos;
use iced::widget::{button, container, mouse_area, row, text, text_input, tooltip, Row};
use iced::{Background, Border, Color, Element, Length, Theme};
use crate::snap::Snapper;
use crate::app::Message;
use crate::snap::Snapper;
#[derive(Clone, Default)]
pub struct StatusBar {
@ -83,7 +83,10 @@ impl StatusBar {
format_scale(viewport_scale)
};
let scale_element: Element<'_, Message> = if scale_pill_enabled {
tip(scale_popup_btn(&scale_label, scale_popup_open), "Annotation / Viewport Scale\nClick to change")
tip(
scale_popup_btn(&scale_label, scale_popup_open),
"Annotation / Viewport Scale\nClick to change",
)
} else {
status_pill(scale_label).into()
};
@ -234,13 +237,11 @@ fn polar_pill(active: bool, increment_deg: f32) -> Element<'static, Message> {
increment_deg as u32
);
let bg_color = move |hovered: bool| {
match (active, hovered) {
(true, true) => SNAP_ON_HOVER,
(true, false) => SNAP_ON_BG,
(false, true) => SNAP_OFF_HOVER,
(false, false) => SNAP_OFF_BG,
}
let bg_color = move |hovered: bool| match (active, hovered) {
(true, true) => SNAP_ON_HOVER,
(true, false) => SNAP_ON_BG,
(false, true) => SNAP_OFF_HOVER,
(false, false) => SNAP_OFF_BG,
};
// Cycle to the next common angle on right-click.
@ -251,9 +252,11 @@ fn polar_pill(active: bool, increment_deg: f32) -> Element<'static, Message> {
_ => 15.0,
};
let inner = container(
text(label).size(10).color(if active { OSNAP_ON_TEXT } else { OSNAP_OFF_TEXT })
)
let inner = container(text(label).size(10).color(if active {
OSNAP_ON_TEXT
} else {
OSNAP_OFF_TEXT
}))
.style(move |_: &Theme| container::Style {
background: Some(Background::Color(bg_color(false))),
border: Border {
@ -280,7 +283,12 @@ fn polar_pill(active: bool, increment_deg: f32) -> Element<'static, Message> {
a: 0.95,
})),
border: Border {
color: Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 },
color: Color {
r: 0.35,
g: 0.35,
b: 0.35,
a: 1.0,
},
width: 1.0,
radius: 3.0.into(),
},
@ -389,7 +397,11 @@ fn osnap_btn(active: bool, snap_enabled: bool, open: bool) -> Element<'static, M
/// When `rename_edit` is `Some(value)` the tab shows an inline text input
/// instead of the normal button. The tab is not renameable when it is the
/// "Model" tab (callers simply never pass `Some` for that name).
fn space_tab<'a>(label: String, is_active: bool, rename_edit: Option<&'a str>) -> Element<'a, Message> {
fn space_tab<'a>(
label: String,
is_active: bool,
rename_edit: Option<&'a str>,
) -> Element<'a, Message> {
let bg = move |is_active: bool, hovered: bool| {
if is_active {
TAB_ACTIVE
@ -401,7 +413,11 @@ fn space_tab<'a>(label: String, is_active: bool, rename_edit: Option<&'a str>) -
};
let border = Border {
color: if is_active { ACCENT } else { Color::TRANSPARENT },
color: if is_active {
ACCENT
} else {
Color::TRANSPARENT
},
width: if is_active { 1.0 } else { 0.0 },
radius: 2.0.into(),
};
@ -409,7 +425,12 @@ fn space_tab<'a>(label: String, is_active: bool, rename_edit: Option<&'a str>) -
let text_color = if is_active {
Color::WHITE
} else {
Color { r: 0.65, g: 0.65, b: 0.65, a: 1.0 }
Color {
r: 0.65,
g: 0.65,
b: 0.65,
a: 1.0,
}
};
if let Some(edit_val) = rename_edit {
@ -426,16 +447,29 @@ fn space_tab<'a>(label: String, is_active: bool, rename_edit: Option<&'a str>) -
radius: 2.0.into(),
},
icon: Color::WHITE,
placeholder: Color { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
placeholder: Color {
r: 0.5,
g: 0.5,
b: 0.5,
a: 1.0,
},
value: Color::WHITE,
selection: Color { r: 0.20, g: 0.55, b: 0.90, a: 0.4 },
selection: Color {
r: 0.20,
g: 0.55,
b: 0.90,
a: 0.4,
},
})
.padding([2, 6])
.width(Length::Fixed(90.0));
let cancel_btn = button(
text("✕").size(10).color(Color { r: 0.65, g: 0.65, b: 0.65, a: 1.0 }),
)
let cancel_btn = button(text("✕").size(10).color(Color {
r: 0.65,
g: 0.65,
b: 0.65,
a: 1.0,
}))
.on_press(Message::LayoutRenameCancel)
.style(|_: &Theme, _| button::Style {
background: Some(Background::Color(Color::TRANSPARENT)),
@ -446,18 +480,19 @@ fn space_tab<'a>(label: String, is_active: bool, rename_edit: Option<&'a str>) -
})
.padding([2, 4]);
row![input, cancel_btn].spacing(0).align_y(iced::Center).into()
row![input, cancel_btn]
.spacing(0)
.align_y(iced::Center)
.into()
} else {
// Normal clickable tab — left click switches, right click opens context menu.
let display = container(
text(label.clone()).size(11).color(text_color),
)
.style(move |_: &Theme| container::Style {
background: Some(Background::Color(bg(is_active, false))),
border,
..Default::default()
})
.padding([3, 10]);
let display = container(text(label.clone()).size(11).color(text_color))
.style(move |_: &Theme| container::Style {
background: Some(Background::Color(bg(is_active, false))),
border,
..Default::default()
})
.padding([3, 10]);
let switch_msg = Message::LayoutSwitch(label.clone());
let ctx_msg = Message::LayoutContextMenu(label.clone());
@ -489,9 +524,17 @@ fn space_mode_btn(current_layout: &str, in_mspace: bool) -> Element<'static, Mes
("PAPER", false, Some(Message::MspaceCommand))
};
let text_color = if active { SNAP_BORDER_ON } else { OSNAP_OFF_TEXT };
let text_color = if active {
SNAP_BORDER_ON
} else {
OSNAP_OFF_TEXT
};
let bg_normal = if active { SNAP_ON_BG } else { SNAP_OFF_BG };
let bg_hover = if active { SNAP_ON_HOVER } else { SNAP_OFF_HOVER };
let bg_hover = if active {
SNAP_ON_HOVER
} else {
SNAP_OFF_HOVER
};
let border_color = if active { SNAP_BORDER_ON } else { BORDER_COLOR };
let clickable = on_press.is_some();
@ -631,26 +674,30 @@ const SNAP_OFF_HOVER: Color = Color {
fn scale_popup_btn(label: &str, open: bool) -> Element<'static, Message> {
let label = label.to_string();
button(text(label).size(10).color(if open { SNAP_BORDER_ON } else { OSNAP_OFF_TEXT }))
.on_press(Message::ToggleScalePopup)
.style(move |_: &Theme, status| button::Style {
background: Some(Background::Color(match (open, status) {
(true, button::Status::Hovered) => SNAP_ON_HOVER,
(true, _) => SNAP_ON_BG,
(false, button::Status::Hovered) => SNAP_OFF_HOVER,
(false, _) => SNAP_OFF_BG,
})),
border: Border {
color: if open { SNAP_BORDER_ON } else { BORDER_COLOR },
width: 1.0,
radius: 2.0.into(),
},
text_color: if open { SNAP_BORDER_ON } else { OSNAP_OFF_TEXT },
shadow: iced::Shadow::default(),
snap: false,
})
.padding([2, 6])
.into()
button(
text(label)
.size(10)
.color(if open { SNAP_BORDER_ON } else { OSNAP_OFF_TEXT }),
)
.on_press(Message::ToggleScalePopup)
.style(move |_: &Theme, status| button::Style {
background: Some(Background::Color(match (open, status) {
(true, button::Status::Hovered) => SNAP_ON_HOVER,
(true, _) => SNAP_ON_BG,
(false, button::Status::Hovered) => SNAP_OFF_HOVER,
(false, _) => SNAP_OFF_BG,
})),
border: Border {
color: if open { SNAP_BORDER_ON } else { BORDER_COLOR },
width: 1.0,
radius: 2.0.into(),
},
text_color: if open { SNAP_BORDER_ON } else { OSNAP_OFF_TEXT },
shadow: iced::Shadow::default(),
snap: false,
})
.padding([2, 6])
.into()
}
// ── Scale display ─────────────────────────────────────────────────────────
@ -686,6 +733,8 @@ fn format_scale(scale: Option<f64>) -> String {
}
// Fall back to a decimal string.
format!("{:.4}", s).trim_end_matches('0').trim_end_matches('.').to_string()
format!("{:.4}", s)
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
}

View file

@ -4,25 +4,84 @@ 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 };
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 },
(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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 4.0.into(),
},
..Default::default()
}
}
@ -30,8 +89,13 @@ fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
move |_: &Theme, st| button::Style {
background: Some(Background::Color(match (active, st) {
(true, _) => ACTIVE,
(false, button::Status::Hovered | button::Status::Pressed) => Color { r: 0.26, g: 0.26, b: 0.26, a: 1.0 },
(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,
@ -41,7 +105,8 @@ fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
fn hdivider<'a>() -> Element<'a, Message> {
container(Space::new().width(Fill).height(1))
.width(Fill).height(1)
.width(Fill)
.height(1)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -51,7 +116,8 @@ fn hdivider<'a>() -> Element<'a, Message> {
fn vsep<'a>() -> Element<'a, Message> {
container(Space::new().width(1).height(Fill))
.width(1).height(Fill)
.width(1)
.height(Fill)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -67,11 +133,17 @@ pub fn view_window<'a>(
// ── 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]),
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)
.align_y(iced::Center),
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
@ -81,15 +153,18 @@ pub fn view_window<'a>(
.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_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![
@ -97,7 +172,11 @@ pub fn view_window<'a>(
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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 3.0.into()
},
..Default::default()
})
.width(Fill)
@ -105,28 +184,39 @@ pub fn view_window<'a>(
.padding(2),
]
.spacing(4)
.height(Fill)
.height(Fill),
)
.width(200)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 8.0, bottom: 12.0, left: 12.0 });
.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()
]
.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 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(
@ -139,28 +229,28 @@ pub fn view_window<'a>(
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()
]
.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()
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 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()
container(column![toolbar, hdivider(), body].spacing(0))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BG)),
..Default::default()
})
.width(Fill)
.height(Fill)
.into()
}

View file

@ -4,34 +4,113 @@ 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 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",
"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 },
(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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 4.0.into(),
},
..Default::default()
}
}
@ -39,8 +118,13 @@ fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
move |_: &Theme, st| button::Style {
background: Some(Background::Color(match (active, st) {
(true, _) => ACTIVE,
(false, button::Status::Hovered | button::Status::Pressed) => Color { r: 0.26, g: 0.26, b: 0.26, a: 1.0 },
(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,
@ -51,14 +135,22 @@ fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
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,
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)
.width(Fill)
.height(1)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -68,7 +160,8 @@ fn hdivider<'a>() -> Element<'a, Message> {
fn vsep<'a>() -> Element<'a, Message> {
container(Space::new().width(1).height(Fill))
.width(1).height(Fill)
.width(1)
.height(Fill)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BORDER)),
..Default::default()
@ -86,14 +179,26 @@ pub fn view_window<'a>(
// ── 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]),
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]),
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)
.align_y(iced::Center),
)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(TB)),
@ -103,15 +208,18 @@ pub fn view_window<'a>(
.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_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![
@ -119,7 +227,11 @@ pub fn view_window<'a>(
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() },
border: Border {
color: BORDER,
width: 1.0,
radius: 3.0.into()
},
..Default::default()
})
.width(Fill)
@ -127,22 +239,30 @@ pub fn view_window<'a>(
.padding(2),
]
.spacing(4)
.height(Fill)
.height(Fill),
)
.width(170)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 8.0, bottom: 12.0, left: 12.0 });
.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_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![
@ -150,20 +270,27 @@ pub fn view_window<'a>(
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() },
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 })
.on_input(|v| Message::TextStyleEdit {
field: "font",
value: v
})
.style(field_style)
.size(11)
.width(Fill),
]
.spacing(6)
.height(Fill)
.height(Fill),
)
.width(190)
.height(Fill)
@ -176,45 +303,64 @@ pub fn view_window<'a>(
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),
.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),
.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),
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)
.height(Fill),
)
.width(Fill)
.height(Fill)
.padding(iced::Padding { top: 12.0, right: 12.0, bottom: 12.0, left: 8.0 });
.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()
container(column![toolbar, hdivider(), body].spacing(0))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(BG)),
..Default::default()
})
.width(Fill)
.height(Fill)
.into()
}