feat(ui): add configurable Iced themes

This commit is contained in:
Hakan Seven committed 2026-07-28 09:33:12 +03:00
1 parent 73545520c8
commit 4bf14baea4
52 files changed
+3063 -5670

No files matched your search

+4 -45
View File
@@ -3,7 +3,7 @@
//! adds that object to the current selection. Clicking outside dismisses it.
use iced::widget::{button, column, container, mouse_area, opaque, text};
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
use iced::{Element, Fill, Length};
use crate::app::Message;
@@ -19,15 +19,7 @@ pub fn cycle_popup_overlay(
.collect();
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(),
},
..Default::default()
})
.style(container::bordered_box)
.width(Length::Fixed(150.0));
let positioned = crate::ui::pin_at(anchor, opaque(panel));
@@ -36,16 +28,10 @@ pub fn cycle_popup_overlay(
}
fn item_row(handle: acadrust::Handle, label: String) -> Element<'static, Message> {
let content = text(label).size(11).color(LABEL).align_y(iced::Center);
let content = text(label).size(11).align_y(iced::Center);
let btn = button(content)
.on_press(Message::CycleSelect(handle))
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => ROW_HOVER,
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.style(button::subtle)
.width(Fill)
.padding([4, 10]);
// Highlight the underlying object while the cursor is over this row.
@@ -54,30 +40,3 @@ fn item_row(handle: acadrust::Handle, label: String) -> Element<'static, Message
.on_exit(Message::CycleHoverExit(handle))
.into()
}
// ── 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.45,
b: 0.62,
a: 1.0,
};
const LABEL: Color = Color {
r: 0.92,
g: 0.92,
b: 0.92,
a: 1.0,
};
+3 -32
View File
@@ -1,7 +1,7 @@
//! Isolate / Hide / End Isolation status menu.
use iced::widget::{button, row, text};
use iced::{Background, Color, Element, Fill, Theme};
use iced::{Element, Fill};
use crate::app::Message;
use crate::ui::statusbar::status_menu::Entry;
@@ -41,19 +41,11 @@ fn action_entry(label: &'static str, enabled: bool, msg: Message) -> Entry<'stat
}
fn action_row(label: &'static str, enabled: bool, msg: Message) -> Element<'static, Message> {
let lbl = text(label)
.size(11)
.color(if enabled { LABEL_ON } else { LABEL_OFF });
let lbl = text(label).size(11);
let content = row![lbl].align_y(iced::Center);
let mut btn = button(content)
.style(move |_: &Theme, status| button::Style {
background: Some(Background::Color(match (enabled, status) {
(true, button::Status::Hovered) => ROW_HOVER,
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.style(button::subtle)
.width(Fill)
.padding([4, 12]);
if enabled {
@@ -61,24 +53,3 @@ fn action_row(label: &'static str, enabled: bool, msg: Message) -> Element<'stat
}
btn.into()
}
// ── Colours ───────────────────────────────────────────────────────────────
const ROW_HOVER: Color = Color {
r: 0.22,
g: 0.22,
b: 0.22,
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.5,
g: 0.5,
b: 0.5,
a: 1.0,
};
+5 -40
View File
@@ -1,7 +1,7 @@
//! Polar-tracking angle status menu.
use iced::widget::{button, container, row, text, text_input};
use iced::{Background, Color, Element, Fill, Length, Theme};
use iced::{Element, Fill, Length};
use crate::app::Message;
use crate::ui::statusbar::status_menu::Entry;
@@ -41,7 +41,7 @@ pub fn menu_entries<'a>(
let custom_row = container(
row![
custom_field,
text("°").size(11).color(LABEL_OFF),
text("°").size(11),
]
.spacing(4)
.align_y(iced::Center),
@@ -52,51 +52,16 @@ pub fn menu_entries<'a>(
}
fn angle_row<'a>(deg: f32, active: bool) -> Element<'a, Message> {
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
let check = crate::ui::icons::themed_check_cell(active);
let lbl = text(angle_label(deg))
.size(11)
.color(if active { LABEL_ON } else { LABEL_OFF });
let lbl = text(angle_label(deg)).size(11);
let content = row![check, lbl].spacing(6).align_y(iced::Center);
button(content)
.on_press(Message::SetPolarAngle(deg))
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => ROW_HOVER,
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.style(button::subtle)
.width(Fill)
.padding([4, 10])
.into()
}
// ── Colours ───────────────────────────────────────────────────────────────
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,
};
+6 -47
View File
@@ -1,7 +1,7 @@
//! Annotation / viewport scale status menu.
use iced::widget::{button, row, text};
use iced::{Background, Color, Element, Fill, Theme};
use iced::{Element, Fill};
use crate::app::Message;
use crate::ui::statusbar::status_menu::Entry;
@@ -44,66 +44,25 @@ pub fn menu_entries(
}
fn scale_row(label: String, active: bool, msg: Message) -> Element<'static, Message> {
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
let check = crate::ui::icons::themed_check_cell(active);
let lbl = text(label)
.size(11)
.color(if active { LABEL_ON } else { LABEL_OFF });
let lbl = text(label).size(11);
let content = row![check, lbl].spacing(6).align_y(iced::Center);
button(content)
.on_press(msg)
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => ROW_HOVER,
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.style(button::subtle)
.width(Fill)
.padding([4, 10])
.into()
}
fn manage_row() -> Element<'static, Message> {
button(text("Manage...").size(11).color(CHECK_COLOR))
button(text("Manage...").size(11))
.on_press(Message::ScaleManagerOpen)
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => ROW_HOVER,
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.style(button::primary)
.width(Fill)
.padding([5, 10])
.into()
}
// ── Colours ───────────────────────────────────────────────────────────────
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,
};
+22 -78
View File
@@ -5,7 +5,7 @@
use rustc_hash::FxHashSet as HashSet;
use iced::widget::{button, container, row, text};
use iced::{Background, Border, Color, Element, Fill, Theme};
use iced::{Background, Element, Fill, Theme};
use crate::app::Message;
use crate::ui::statusbar::status_menu::Entry;
@@ -37,8 +37,10 @@ pub fn menu_entries(
.padding([4u16, 8]);
let divider = container(iced::widget::Space::new().height(1))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(DIVIDER)),
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.weak.color,
)),
..Default::default()
})
.width(Fill)
@@ -62,99 +64,41 @@ pub fn menu_entries(
}
fn type_row(name: String, included: bool) -> Element<'static, Message> {
let check = crate::ui::icons::check_cell(included, CHECK_COLOR);
let check = crate::ui::icons::themed_check_cell(included);
let lbl = text(name.clone())
.size(11)
.color(if included { LABEL_ON } else { LABEL_OFF });
let lbl = text(name.clone()).size(11);
let content = row![check, lbl].spacing(6).align_y(iced::Center);
button(content)
.on_press(Message::ToggleSelectionFilterType(name))
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => ROW_HOVER,
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.style(button::subtle)
.width(Fill)
.padding([4, 10])
.into()
}
fn empty_row() -> Element<'static, Message> {
container(text("No objects").size(11).color(LABEL_OFF))
container(
text("No objects").size(11).style(|theme: &Theme| text::Style {
color: Some(
theme
.extended_palette()
.background
.base
.text
.scale_alpha(0.42),
),
}),
)
.padding([4, 10])
.into()
}
fn header_btn(label: &str, msg: Message, enabled: bool) -> Element<'_, Message> {
let color = if enabled {
Color { r: 0.70, g: 0.70, b: 0.70, a: 1.0 }
} else {
Color { r: 0.38, g: 0.38, b: 0.38, a: 1.0 }
};
let b = button(text(label).size(10).color(color));
let b = button(text(label).size(10));
let b = if enabled { b.on_press(msg) } else { b };
b.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => ROW_HOVER,
_ => BTN_BG,
})),
border: Border {
color: PANEL_BORDER,
width: 1.0,
radius: 2.0.into(),
},
..Default::default()
})
b.style(button::secondary)
.padding([3, 8])
.into()
}
// ── Colours ───────────────────────────────────────────────────────────────
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 DIVIDER: Color = Color {
r: 0.28,
g: 0.28,
b: 0.28,
a: 1.0,
};
const BTN_BG: Color = Color {
r: 0.20,
g: 0.20,
b: 0.20,
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.6,
g: 0.6,
b: 0.6,
a: 1.0,
};
+11 -94
View File
@@ -1,7 +1,7 @@
//! OpenCADStudio-style OSNAP status menu.
use iced::widget::{button, container, row, text};
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
use iced::{Background, Element, Fill, Length, Theme};
use crate::app::Message;
use crate::snap::{SnapType, Snapper, ALL_SNAP_MODES};
@@ -20,8 +20,10 @@ pub fn menu_entries<'a>(snapper: &'a Snapper) -> Vec<Entry<'a>> {
// Divider
let divider = container(iced::widget::Space::new().height(1))
.style(|_: &Theme| container::Style {
background: Some(Background::Color(DIVIDER)),
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.weak.color,
)),
..Default::default()
})
.width(Fill)
@@ -41,21 +43,18 @@ pub fn menu_entries<'a>(snapper: &'a Snapper) -> Vec<Entry<'a>> {
// ── Individual snap row ───────────────────────────────────────────────────
fn snap_row<'a>(snap_type: SnapType, label: &'a str, active: bool) -> Element<'a, Message> {
let checkmark = crate::ui::icons::check_cell(active, CHECK_COLOR);
let checkmark = crate::ui::icons::themed_check_cell(active);
// SVG marker (not a Unicode glyph) so the symbols render on the web build,
// whose bundled font lacks them and showed tofu boxes. (#138)
let icon_el = container(crate::ui::icons::tinted::<Message>(
let icon_el = container(crate::ui::icons::themed_success::<Message>(
crate::ui::icons::osnap(snap_type),
13.0,
ICON_COLOR,
))
.width(Length::Fixed(16.0))
.align_x(iced::Center);
let label_el = text(label)
.size(11)
.color(if active { LABEL_ON } else { LABEL_OFF });
let label_el = text(label).size(11);
let content = row![checkmark, icon_el, label_el]
.spacing(4)
@@ -63,98 +62,16 @@ fn snap_row<'a>(snap_type: SnapType, label: &'a str, active: bool) -> Element<'a
button(content)
.on_press(Message::ToggleSnap(snap_type))
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => ROW_HOVER,
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.style(button::subtle)
.width(Fill)
.padding([3, 8])
.into()
}
fn header_btn(label: &str, msg: Message, enabled: bool) -> Element<'_, Message> {
let b = button(text(label).size(10).color(if enabled {
Color {
r: 0.70,
g: 0.70,
b: 0.70,
a: 1.0,
}
} else {
Color {
r: 0.38,
g: 0.38,
b: 0.38,
a: 1.0,
}
}));
let b = button(text(label).size(10));
let b = if enabled { b.on_press(msg) } else { b };
b.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => ROW_HOVER,
_ => BTN_BG,
})),
border: Border {
color: PANEL_BORDER,
width: 1.0,
radius: 2.0.into(),
},
..Default::default()
})
b.style(button::secondary)
.padding([3, 8])
.into()
}
// ── Colours ───────────────────────────────────────────────────────────────
const PANEL_BORDER: Color = Color {
r: 0.32,
g: 0.32,
b: 0.32,
a: 1.0,
};
const DIVIDER: Color = Color {
r: 0.28,
g: 0.28,
b: 0.28,
a: 1.0,
};
const ROW_HOVER: Color = Color {
r: 0.24,
g: 0.24,
b: 0.24,
a: 1.0,
};
const BTN_BG: Color = Color {
r: 0.20,
g: 0.20,
b: 0.20,
a: 1.0,
};
const CHECK_COLOR: Color = Color {
r: 0.35,
g: 0.75,
b: 1.00,
a: 1.0,
};
const ICON_COLOR: Color = Color {
r: 0.25,
g: 0.75,
b: 0.45,
a: 1.0,
}; // green icon
const LABEL_ON: Color = Color {
r: 0.92,
g: 0.92,
b: 0.92,
a: 1.0,
};
const LABEL_OFF: Color = Color {
r: 0.52,
g: 0.52,
b: 0.52,
a: 1.0,
};
+4 -39
View File
@@ -1,7 +1,7 @@
//! Drawing-units status menu.
use iced::widget::{button, row, text};
use iced::{Background, Color, Element, Fill, Theme};
use iced::{Element, Fill};
use crate::app::Message;
use crate::ui::statusbar::status_menu::Entry;
@@ -49,51 +49,16 @@ pub fn menu_entries(current: i16) -> Vec<Entry<'static>> {
}
fn unit_row(label: &'static str, active: bool, msg: Message) -> Element<'static, Message> {
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
let check = crate::ui::icons::themed_check_cell(active);
let lbl = text(label)
.size(11)
.color(if active { LABEL_ON } else { LABEL_OFF });
let lbl = text(label).size(11);
let content = row![check, lbl].spacing(6).align_y(iced::Center);
button(content)
.on_press(msg)
.style(|_: &Theme, status| button::Style {
background: Some(Background::Color(match status {
button::Status::Hovered => ROW_HOVER,
_ => Color::TRANSPARENT,
})),
..Default::default()
})
.style(button::subtle)
.width(Fill)
.padding([4, 10])
.into()
}
// ── Colours ───────────────────────────────────────────────────────────────
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,
};