forked from 1b-agent/opencadstudio-upstream
feat(ui): add configurable Iced themes
This commit is contained in:
1 parent
73545520c8
commit
4bf14baea4
52 files changed
+3063
-5670
No files matched your search
+22
-70
@@ -1,48 +1,16 @@
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, row, text};
|
||||
use iced::{Background, Border, Color, Element, 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,
|
||||
};
|
||||
use iced::{Background, Element, Theme};
|
||||
|
||||
fn info_row<'a>(label: &'static str, value: String) -> Element<'a, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(100),
|
||||
text(value).size(11).color(WHITE),
|
||||
text(label)
|
||||
.size(11)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
})
|
||||
.width(100),
|
||||
text(value).size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
@@ -57,10 +25,16 @@ pub fn view_window<'a>() -> Element<'a, Message> {
|
||||
|
||||
let logo = container(
|
||||
column![
|
||||
text("Open CAD Studio").size(32).color(ACCENT),
|
||||
text("Open CAD Studio")
|
||||
.size(32)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}),
|
||||
text("CAD application for Architecture & Engineering")
|
||||
.size(11)
|
||||
.color(DIM),
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_x(iced::Center),
|
||||
@@ -82,35 +56,11 @@ pub fn view_window<'a>() -> Element<'a, Message> {
|
||||
.spacing(2)
|
||||
.padding([12, 16]),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
.style(container::bordered_box);
|
||||
|
||||
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()
|
||||
})
|
||||
.style(button::primary)
|
||||
.padding([6, 16]);
|
||||
|
||||
let footer = row![copy_btn]
|
||||
@@ -132,8 +82,10 @@ pub fn view_window<'a>() -> Element<'a, Message> {
|
||||
left: 20.0,
|
||||
}),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
|
||||
use iced::{Background, Element, Fill, Length, Theme};
|
||||
|
||||
/// Which column of an alias row a text edit targets.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -18,33 +18,21 @@ pub enum AliasField {
|
||||
/// Right-hand lane reserved for the scrollbar so it never overlaps the ✕ column.
|
||||
const GUTTER: f32 = 16.0;
|
||||
|
||||
const BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 };
|
||||
const FIELD_BG: Color = Color { r: 0.12, g: 0.12, b: 0.12, 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 WHITE: Color = Color { r: 0.85, g: 0.85, b: 0.85, a: 1.0 };
|
||||
const ADD_C: Color = Color { r: 0.20, g: 0.40, b: 0.62, a: 1.0 };
|
||||
|
||||
fn field_style(_: &Theme, _s: text_input::Status) -> text_input::Style {
|
||||
text_input::Style {
|
||||
background: Background::Color(FIELD_BG),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
icon: WHITE,
|
||||
placeholder: DIM,
|
||||
value: WHITE,
|
||||
selection: Color { r: 0.25, g: 0.40, b: 0.60, a: 1.0 },
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the alias editor content. `rows` is the live working buffer.
|
||||
pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
||||
let title = text("Command Aliases").size(15).color(WHITE);
|
||||
let title = text("Command Aliases").size(15);
|
||||
let hint = text(
|
||||
"Type an alias and the command it runs (e.g. L → LINE). \
|
||||
Apply to save to ocad.pgp; closing discards unapplied edits.",
|
||||
)
|
||||
.size(11)
|
||||
.color(DIM);
|
||||
.style(muted_style);
|
||||
|
||||
// Right gutter reserved so the scrollbar has its own lane and never sits on
|
||||
// top of the row delete (✕) buttons. Applied to both the header and the
|
||||
@@ -53,8 +41,8 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
||||
|
||||
let head = container(
|
||||
row![
|
||||
container(text("Alias").size(11).color(DIM)).width(Length::Fixed(120.0)),
|
||||
container(text("Command").size(11).color(DIM)).width(Fill),
|
||||
container(text("Alias").size(11).style(muted_style)).width(Length::Fixed(120.0)),
|
||||
container(text("Command").size(11).style(muted_style)).width(Fill),
|
||||
Space::new().width(Length::Fixed(30.0)),
|
||||
]
|
||||
.spacing(8),
|
||||
@@ -65,31 +53,18 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
||||
for (idx, (alias, cmd)) in rows.iter().enumerate() {
|
||||
let alias_box = text_input("alias", alias)
|
||||
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Alias, value: v })
|
||||
.style(field_style)
|
||||
.size(13)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(120.0));
|
||||
let cmd_box = text_input("command", cmd)
|
||||
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Command, value: v })
|
||||
.style(field_style)
|
||||
.size(13)
|
||||
.padding([3, 6])
|
||||
.width(Fill);
|
||||
let del = button(crate::ui::icons::tinted(crate::ui::icons::CLOSE, 12.0, WHITE))
|
||||
let del = button(crate::ui::icons::themed_danger(crate::ui::icons::CLOSE, 12.0))
|
||||
.on_press(Message::AliasEditorRemove(idx))
|
||||
.padding([2, 6])
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = if matches!(status, button::Status::Hovered) {
|
||||
Color { r: 0.45, g: 0.22, b: 0.22, a: 1.0 }
|
||||
} else {
|
||||
Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 }
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
.style(button::danger);
|
||||
list = list.push(
|
||||
row![alias_box, cmd_box, del]
|
||||
.spacing(8)
|
||||
@@ -97,40 +72,16 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
||||
);
|
||||
}
|
||||
|
||||
let add = button(text("+ Add alias").size(12).color(WHITE))
|
||||
let add = button(text("+ Add alias").size(12))
|
||||
.on_press(Message::AliasEditorAdd)
|
||||
.padding([4, 10])
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = if matches!(status, button::Status::Hovered) {
|
||||
Color { r: 0.30, g: 0.30, b: 0.30, a: 1.0 }
|
||||
} else {
|
||||
Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 }
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
.style(button::secondary);
|
||||
|
||||
// Apply — primary action; commits the rows to ocad.pgp and stays open.
|
||||
let apply = button(text("Apply").size(12).color(WHITE))
|
||||
let apply = button(text("Apply").size(12))
|
||||
.on_press(Message::AliasEditorApply)
|
||||
.padding([4, 16])
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: 0.24, g: 0.46, b: 0.74, a: 1.0 }
|
||||
} else {
|
||||
ADD_C
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
.style(button::primary);
|
||||
|
||||
container(
|
||||
column![
|
||||
@@ -149,8 +100,10 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
||||
.padding(12)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
||||
@@ -19,23 +19,10 @@ use acadrust::types::{Color as AcadColor, LineWeight};
|
||||
use iced::widget::{
|
||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
use iced::{Background, Border, Element, Length, Theme};
|
||||
|
||||
use crate::ui::properties::{lw_options, LwItem};
|
||||
|
||||
// Palette shared with the style-manager windows so the editor reads as one of
|
||||
// them: toolbar (TB) on top, BG panels, ACTIVE for the selected tab / row.
|
||||
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 WHITE: 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 FIELD_BG: Color = Color { r: 0.10, g: 0.10, b: 0.10, a: 1.0 };
|
||||
const BORDER: Color = Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 };
|
||||
const ACTIVE: Color = Color { r: 0.20, g: 0.40, b: 0.70, a: 1.0 };
|
||||
const ROW_SEL: Color = ACTIVE;
|
||||
const ROW_BG: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 };
|
||||
|
||||
const LABEL_W: f32 = 120.0;
|
||||
|
||||
/// Which tab of the editor is showing.
|
||||
@@ -146,28 +133,25 @@ pub fn color_from_label(label: &str) -> Option<AcadColor> {
|
||||
})
|
||||
}
|
||||
|
||||
fn field_style(_t: &Theme, _s: text_input::Status) -> text_input::Style {
|
||||
text_input::Style {
|
||||
background: Background::Color(FIELD_BG),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
icon: WHITE,
|
||||
placeholder: DIM,
|
||||
value: WHITE,
|
||||
selection: ACCENT,
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn accent_btn(_t: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: 0.20, g: 0.42, b: 0.72, a: 1.0 }
|
||||
} else {
|
||||
ACCENT
|
||||
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
text_input::Style {
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border { color: border, width: 1.0, radius: 3.0.into() },
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,8 +160,10 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
||||
container(Space::new().width(Length::Fill).height(1))
|
||||
.width(Length::Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
@@ -186,7 +172,7 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
||||
/// One `label : widget` row with a fixed-width label column.
|
||||
fn field_row<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<'a, Message> {
|
||||
row![
|
||||
container(text(label).size(12).color(DIM)).width(LABEL_W),
|
||||
container(text(label).size(12).style(muted_style)).width(LABEL_W),
|
||||
widget,
|
||||
]
|
||||
.spacing(8)
|
||||
@@ -226,20 +212,28 @@ fn pick_field<'a>(
|
||||
|
||||
fn tab_button<'a>(label: &'a str, this: AttrTab, active: AttrTab) -> Element<'a, Message> {
|
||||
let is_active = this == active;
|
||||
button(text(label).size(11).color(WHITE))
|
||||
button(text(label).size(11))
|
||||
.padding([4, 12])
|
||||
.on_press(Message::AttrEditorTab(this))
|
||||
.style(move |_t: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match (is_active, status) {
|
||||
(true, _) => ACTIVE,
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (is_active, status) {
|
||||
(true, _) => palette.primary.strong,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
Color { r: 0.28, g: 0.28, b: 0.28, a: 1.0 }
|
||||
palette.background.strong
|
||||
}
|
||||
_ => Color { r: 0.20, g: 0.20, b: 0.20, a: 1.0 },
|
||||
})),
|
||||
text_color: WHITE,
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
@@ -259,20 +253,22 @@ pub fn view_window<'a>(
|
||||
) -> Element<'a, Message> {
|
||||
// ── Top toolbar: block name on the left, Apply on the right ───────────
|
||||
// Mirrors the style-manager windows (actions left, primary action right).
|
||||
let apply = button(text("Apply").size(11).color(WHITE))
|
||||
let apply = button(text("Apply").size(11))
|
||||
.padding([4, 14])
|
||||
.on_press(Message::AttrEditorApply)
|
||||
.style(accent_btn);
|
||||
.style(button::primary);
|
||||
let toolbar = container(
|
||||
row![
|
||||
text(format!("Block: {block}")).size(12).color(DIM),
|
||||
text(format!("Block: {block}")).size(12).style(muted_style),
|
||||
Space::new().width(Length::Fill),
|
||||
apply,
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::Fill)
|
||||
@@ -286,7 +282,7 @@ pub fn view_window<'a>(
|
||||
.spacing(2);
|
||||
|
||||
let body: Element<'_, Message> = if rows.is_empty() {
|
||||
text("This block has no attributes.").size(13).color(DIM).into()
|
||||
text("This block has no attributes.").size(13).style(muted_style).into()
|
||||
} else {
|
||||
match tab {
|
||||
AttrTab::Attribute => attribute_tab(rows, selected),
|
||||
@@ -303,8 +299,10 @@ pub fn view_window<'a>(
|
||||
.padding(12);
|
||||
|
||||
container(column![toolbar, hdivider(), content])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::Fill)
|
||||
@@ -315,9 +313,9 @@ pub fn view_window<'a>(
|
||||
/// Attribute tab: tag / prompt / value list with row-select, plus a value box.
|
||||
fn attribute_tab<'a>(rows: &'a [AttrRow], selected: usize) -> Element<'a, Message> {
|
||||
let head = row![
|
||||
container(text("Tag").size(11).color(DIM)).width(130),
|
||||
container(text("Prompt").size(11).color(DIM)).width(Length::Fill),
|
||||
container(text("Value").size(11).color(DIM)).width(140),
|
||||
container(text("Tag").size(11).style(muted_style)).width(130),
|
||||
container(text("Prompt").size(11).style(muted_style)).width(Length::Fill),
|
||||
container(text("Value").size(11).style(muted_style)).width(140),
|
||||
]
|
||||
.spacing(6);
|
||||
|
||||
@@ -325,27 +323,28 @@ fn attribute_tab<'a>(rows: &'a [AttrRow], selected: usize) -> Element<'a, Messag
|
||||
for (idx, r) in rows.iter().enumerate() {
|
||||
let is_sel = idx == selected;
|
||||
let line = row![
|
||||
container(text(r.tag.as_str()).size(12).color(WHITE)).width(130),
|
||||
container(text(r.prompt.as_str()).size(12).color(DIM)).width(Length::Fill),
|
||||
container(text(r.value.as_str()).size(12).color(WHITE)).width(140),
|
||||
container(text(r.tag.as_str()).size(12)).width(130),
|
||||
container(text(r.prompt.as_str()).size(12).style(muted_style)).width(Length::Fill),
|
||||
container(text(r.value.as_str()).size(12)).width(140),
|
||||
]
|
||||
.spacing(6);
|
||||
let btn = button(line)
|
||||
.on_press(Message::AttrEditorSelect(idx))
|
||||
.padding([3, 4])
|
||||
.width(Length::Fill)
|
||||
.style(move |_t: &Theme, status| {
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let hovered = matches!(status, button::Status::Hovered);
|
||||
let bg = if is_sel {
|
||||
ROW_SEL
|
||||
let pair = if is_sel {
|
||||
palette.primary.strong
|
||||
} else if hovered {
|
||||
Color { r: 0.18, g: 0.18, b: 0.18, a: 1.0 }
|
||||
palette.background.strong
|
||||
} else {
|
||||
ROW_BG
|
||||
palette.background.weak
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border::default(),
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
+147
-299
@@ -47,6 +47,35 @@ const COMBO_PAD_V: f32 = (ROW_H - FONT_SZ * 1.3 - 2.0) / 2.0;
|
||||
/// scrolled into view after it is added (#271).
|
||||
pub const LAYER_TABLE_SCROLL_ID: &str = "layer-manager-table-scroll";
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn table_input_style(
|
||||
theme: &Theme,
|
||||
status: iced::widget::text_input::Status,
|
||||
) -> iced::widget::text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
iced::widget::text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
iced::widget::text_input::Style {
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border {
|
||||
color: border,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Layer data ────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -313,39 +342,15 @@ impl LayerPanel {
|
||||
.size(FONT_SZ)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(180.0))
|
||||
.style(|_: &Theme, _| iced::widget::text_input::Style {
|
||||
background: Background::Color(Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
}),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
icon: Color::WHITE,
|
||||
placeholder: Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
},
|
||||
value: Color::WHITE,
|
||||
selection: Color {
|
||||
r: 0.20,
|
||||
g: 0.44,
|
||||
b: 0.72,
|
||||
a: 0.5,
|
||||
},
|
||||
}),
|
||||
.style(table_input_style),
|
||||
]
|
||||
.spacing(2)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TOOLBAR_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
@@ -355,13 +360,15 @@ impl LayerPanel {
|
||||
let sc = self.sort_col;
|
||||
let sa = self.sort_asc;
|
||||
let mut header_row = row![
|
||||
text("Status").size(10).color(DIM).width(50),
|
||||
text("Status").size(10).style(muted_style).width(50),
|
||||
sortable_header("Name", LayerSortCol::Name, Length::Fixed(name_col_w), sc, sa),
|
||||
// Draggable divider: adjusts the Name column width (#359).
|
||||
iced::widget::mouse_area(
|
||||
container(iced::widget::Space::new().width(2).height(14)).style(
|
||||
|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER_COLOR)),
|
||||
|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
@@ -390,20 +397,23 @@ impl LayerPanel {
|
||||
header_row = header_row.push(
|
||||
text(vp.label.as_str())
|
||||
.size(10)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(COL_ICON)),
|
||||
);
|
||||
}
|
||||
|
||||
let col_header = container(header_row)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(COL_HEADER_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 8])
|
||||
.width(Fill);
|
||||
@@ -451,8 +461,10 @@ impl LayerPanel {
|
||||
|
||||
// ── Full-window frame ─────────────────────────────────────────────
|
||||
container(column![toolbar, col_header, table].spacing(0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
@@ -478,28 +490,26 @@ fn sortable_header<'a>(
|
||||
active: Option<LayerSortCol>,
|
||||
asc: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let mut content = row![text(label).size(10).color(DIM)]
|
||||
let mut content = row![text(label).size(10).style(muted_style)]
|
||||
.spacing(3)
|
||||
.align_y(iced::Center);
|
||||
if active == Some(col) {
|
||||
content = content.push(if asc {
|
||||
crate::ui::icons::arrow_up(8.0, DIM)
|
||||
crate::ui::icons::themed_arrow_up(8.0)
|
||||
} else {
|
||||
crate::ui::icons::arrow_down(8.0, DIM)
|
||||
crate::ui::icons::themed_arrow_down(8.0)
|
||||
});
|
||||
}
|
||||
button(content)
|
||||
.on_press(Message::LayerSort(col))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
.style(|theme: &Theme, status| button::Style {
|
||||
background: matches!(
|
||||
status,
|
||||
button::Status::Hovered | button::Status::Pressed
|
||||
)
|
||||
.then_some(Background::Color(
|
||||
theme.extended_palette().background.strong.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.padding(Padding {
|
||||
@@ -517,41 +527,31 @@ fn sortable_header<'a>(
|
||||
fn toolbar_btn<'a>(icon: &'static [u8], label: &'a str, msg: Message) -> Element<'a, Message> {
|
||||
button(
|
||||
row![
|
||||
crate::ui::icons::tinted(icon, 12.0, Color::WHITE),
|
||||
text(label).size(11).color(Color::WHITE),
|
||||
crate::ui::icons::themed(icon, 12.0),
|
||||
text(label).size(11),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.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,
|
||||
},
|
||||
})),
|
||||
.style(|theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => {
|
||||
palette.background.strong
|
||||
}
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
},
|
||||
text_color: Color::WHITE,
|
||||
text_color: pair.text,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
@@ -563,46 +563,46 @@ fn toolbar_btn_cond<'a>(
|
||||
msg: Message,
|
||||
enabled: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let fg = if enabled {
|
||||
Color::WHITE
|
||||
} else {
|
||||
Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
}
|
||||
};
|
||||
let mut b = button(
|
||||
row![
|
||||
crate::ui::icons::tinted(icon, 12.0, fg),
|
||||
text(label).size(11).color(fg),
|
||||
if enabled {
|
||||
crate::ui::icons::themed(icon, 12.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_disabled(icon, 12.0)
|
||||
},
|
||||
if enabled {
|
||||
text(label).size(11)
|
||||
} else {
|
||||
text(label).size(11).style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(
|
||||
theme.extended_palette().background.base.text.scale_alpha(0.42)
|
||||
),
|
||||
})
|
||||
},
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.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,
|
||||
},
|
||||
})),
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered if enabled => palette.background.strong,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
},
|
||||
text_color: Color::WHITE,
|
||||
text_color: if enabled {
|
||||
pair.text
|
||||
} else {
|
||||
pair.text.scale_alpha(0.42)
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 10]);
|
||||
if enabled {
|
||||
@@ -616,21 +616,25 @@ fn toolbar_btn_cond<'a>(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// Hover popup showing a layer's full name when the cell truncates it.
|
||||
fn name_tip<'a>(name: &'a str) -> Element<'a, Message> {
|
||||
container(text(name).size(FONT_SZ).color(ROW_TEXT))
|
||||
container(text(name).size(FONT_SZ))
|
||||
.padding(Padding {
|
||||
top: 3.0,
|
||||
bottom: 3.0,
|
||||
left: 7.0,
|
||||
right: 7.0,
|
||||
})
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.strong.color)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: Some(palette.background.strong.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
@@ -655,16 +659,10 @@ fn layer_row<'a>(
|
||||
.height(ICON_SZ),
|
||||
)
|
||||
.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,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
.style(|theme: &Theme, status| button::Style {
|
||||
background: matches!(status, button::Status::Hovered).then_some(
|
||||
Background::Color(theme.extended_palette().background.strong.color)
|
||||
),
|
||||
..Default::default()
|
||||
})
|
||||
.padding(Padding {
|
||||
@@ -682,27 +680,9 @@ fn layer_row<'a>(
|
||||
let lck_svg = crate::ui::icons::layer_lock(layer.locked);
|
||||
|
||||
let status_dot: Element<'_, Message> = if is_current {
|
||||
crate::ui::icons::tinted(
|
||||
crate::ui::icons::CHECK,
|
||||
13.0,
|
||||
Color {
|
||||
r: 0.25,
|
||||
g: 0.85,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
},
|
||||
)
|
||||
crate::ui::icons::themed_success(crate::ui::icons::CHECK, 13.0)
|
||||
} else {
|
||||
crate::ui::icons::tinted(
|
||||
crate::ui::icons::DOT,
|
||||
9.0,
|
||||
Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
},
|
||||
)
|
||||
crate::ui::icons::themed_secondary(crate::ui::icons::DOT, 9.0)
|
||||
};
|
||||
|
||||
// Name cell
|
||||
@@ -717,38 +697,7 @@ fn layer_row<'a>(
|
||||
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,
|
||||
}),
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
width: 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,
|
||||
},
|
||||
value: Color::WHITE,
|
||||
selection: Color {
|
||||
r: 0.25,
|
||||
g: 0.45,
|
||||
b: 0.75,
|
||||
a: 0.5,
|
||||
},
|
||||
})
|
||||
.style(table_input_style)
|
||||
.width(Length::Fixed(name_col_w))
|
||||
.into()
|
||||
} else {
|
||||
@@ -756,20 +705,13 @@ fn layer_row<'a>(
|
||||
let name_budget = ((name_col_w / 6.0) as usize).max(8);
|
||||
let name_btn = button(
|
||||
text(crate::ui::text_util::elide(&layer.name, name_budget))
|
||||
.size(FONT_SZ)
|
||||
.color(ROW_TEXT),
|
||||
.size(FONT_SZ),
|
||||
)
|
||||
.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,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
.style(|theme: &Theme, status| button::Style {
|
||||
background: matches!(status, button::Status::Hovered).then_some(
|
||||
Background::Color(theme.extended_palette().background.strong.color)
|
||||
),
|
||||
..Default::default()
|
||||
})
|
||||
.padding(Padding {
|
||||
@@ -837,7 +779,7 @@ fn layer_row<'a>(
|
||||
} else {
|
||||
text(layer.linetype.as_str())
|
||||
.size(FONT_SZ)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(COL_LT))
|
||||
.into()
|
||||
};
|
||||
@@ -861,7 +803,7 @@ fn layer_row<'a>(
|
||||
} else {
|
||||
text(cur_lw_item.to_string())
|
||||
.size(FONT_SZ)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(COL_LW))
|
||||
.into()
|
||||
};
|
||||
@@ -877,33 +819,13 @@ fn layer_row<'a>(
|
||||
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,
|
||||
},
|
||||
icon: Color::WHITE,
|
||||
placeholder: DIM,
|
||||
value: ROW_TEXT,
|
||||
selection: Color {
|
||||
r: 0.25,
|
||||
g: 0.45,
|
||||
b: 0.75,
|
||||
a: 0.5,
|
||||
},
|
||||
.style(|theme: &Theme, status| {
|
||||
let mut style = table_input_style(theme, status);
|
||||
style.background = iced::Background::Color(Color::TRANSPARENT);
|
||||
style
|
||||
})
|
||||
.width(Length::Fixed(COL_TRANS));
|
||||
|
||||
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)
|
||||
@@ -945,9 +867,20 @@ fn layer_row<'a>(
|
||||
|
||||
mouse_area(
|
||||
container(row_content)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if is_selected {
|
||||
palette.primary.weak
|
||||
} else if index % 2 == 0 {
|
||||
palette.background.base
|
||||
} else {
|
||||
palette.background.weak
|
||||
};
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding(Padding {
|
||||
top: 0.0,
|
||||
@@ -965,31 +898,10 @@ fn layer_row<'a>(
|
||||
// ── Combo style ────────────────────────────────────────────────────────────
|
||||
|
||||
fn combo_input_style(
|
||||
_theme: &Theme,
|
||||
_status: iced::widget::text_input::Status,
|
||||
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,
|
||||
},
|
||||
icon: Color::WHITE,
|
||||
placeholder: DIM,
|
||||
value: Color::WHITE,
|
||||
selection: Color {
|
||||
r: 0.25,
|
||||
g: 0.45,
|
||||
b: 0.75,
|
||||
a: 0.5,
|
||||
},
|
||||
}
|
||||
table_input_style(theme, status)
|
||||
}
|
||||
|
||||
// ── Display helpers ───────────────────────────────────────────────────────
|
||||
@@ -1054,67 +966,3 @@ const COL_COLOR: 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,
|
||||
};
|
||||
#[allow(dead_code)]
|
||||
const ICON_COLOR: Color = Color {
|
||||
r: 0.80,
|
||||
g: 0.80,
|
||||
b: 0.80,
|
||||
a: 1.0,
|
||||
};
|
||||
+61
-175
@@ -2,131 +2,37 @@
|
||||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Element, Fill, Theme};
|
||||
|
||||
const TB: Color = Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 1.0,
|
||||
};
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACTIVE: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.40,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const FIELD: Color = Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
};
|
||||
const LIST: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
const WARN: Color = Color {
|
||||
r: 0.80,
|
||||
g: 0.35,
|
||||
b: 0.25,
|
||||
a: 1.0,
|
||||
};
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.20,
|
||||
g: 0.42,
|
||||
b: 0.72,
|
||||
a: 1.0,
|
||||
},
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
},
|
||||
(true, _) => ACCENT,
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: TEXT,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
move |theme: &Theme, status| {
|
||||
if accent {
|
||||
button::primary(theme, status)
|
||||
} else {
|
||||
button::secondary(theme, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (active, st) {
|
||||
(true, _) => ACTIVE,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.26,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||
text_input::Style {
|
||||
background: Background::Color(FIELD),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
icon: TEXT,
|
||||
placeholder: DIM,
|
||||
value: TEXT,
|
||||
selection: ACCENT,
|
||||
move |theme: &Theme, status| {
|
||||
if active {
|
||||
button::primary(theme, status)
|
||||
} else {
|
||||
button::subtle(theme, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +40,10 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
||||
container(Space::new().width(Fill).height(1))
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
@@ -145,8 +53,10 @@ fn vsep<'a>() -> Element<'a, Message> {
|
||||
container(Space::new().width(1).height(Fill))
|
||||
.width(1)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
@@ -169,34 +79,18 @@ pub fn view_window<'a>(
|
||||
.padding([4, 10]),
|
||||
button(text("Delete").size(11))
|
||||
.on_press(Message::LayoutManagerDelete)
|
||||
.style(move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match st {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.60,
|
||||
g: 0.20,
|
||||
b: 0.18,
|
||||
a: 1.0
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0
|
||||
},
|
||||
})),
|
||||
text_color: if is_model { DIM } else { WARN },
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
.style(move |theme: &Theme, status| {
|
||||
if is_model {
|
||||
button::secondary(theme, status)
|
||||
} else {
|
||||
button::danger(theme, status)
|
||||
}
|
||||
})
|
||||
.padding([4, 10]),
|
||||
Space::new().width(Fill),
|
||||
button(
|
||||
row![
|
||||
crate::ui::icons::tinted(crate::ui::icons::TRI_LEFT_B, 9.0, Color::WHITE),
|
||||
crate::ui::icons::themed_arrow_left(9.0),
|
||||
text("Move Left").size(11),
|
||||
]
|
||||
.spacing(4)
|
||||
@@ -208,7 +102,7 @@ pub fn view_window<'a>(
|
||||
button(
|
||||
row![
|
||||
text("Move Right").size(11),
|
||||
crate::ui::icons::arrow_right(9.0, Color::WHITE),
|
||||
crate::ui::icons::themed_arrow_right(9.0),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
@@ -224,8 +118,10 @@ pub fn view_window<'a>(
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weakest.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
@@ -241,11 +137,7 @@ pub fn view_window<'a>(
|
||||
.spacing(5)
|
||||
.align_y(iced::Center);
|
||||
if is_cur {
|
||||
item_row = item_row.push(crate::ui::icons::tinted(
|
||||
crate::ui::icons::TRI_LEFT_B,
|
||||
8.0,
|
||||
Color::WHITE,
|
||||
));
|
||||
item_row = item_row.push(crate::ui::icons::themed_arrow_left(8.0));
|
||||
}
|
||||
button(item_row)
|
||||
.on_press(Message::LayoutManagerSelect(name.clone()))
|
||||
@@ -258,17 +150,9 @@ pub fn view_window<'a>(
|
||||
|
||||
let layout_list = container(
|
||||
column![
|
||||
text("Layouts").size(10).color(DIM),
|
||||
text("Layouts").size(10).style(muted_style),
|
||||
container(scrollable(column(list_items).spacing(2)).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.padding(2),
|
||||
@@ -293,38 +177,38 @@ pub fn view_window<'a>(
|
||||
} else {
|
||||
"Paper Space Layout"
|
||||
})
|
||||
.size(13)
|
||||
.color(TEXT),
|
||||
.size(13),
|
||||
Space::new().height(8),
|
||||
row![
|
||||
text("Name:").size(11).color(DIM).width(80),
|
||||
text("Name:").size(11).style(muted_style).width(80),
|
||||
text(selected).size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
text("Status:").size(11).color(DIM).width(80),
|
||||
text("Status:").size(11).style(muted_style).width(80),
|
||||
text(if selected == current.as_str() {
|
||||
"Active"
|
||||
} else {
|
||||
"Inactive"
|
||||
})
|
||||
.size(11)
|
||||
.color(if selected == current.as_str() {
|
||||
ACCENT
|
||||
} else {
|
||||
DIM
|
||||
.style(move |theme: &Theme| {
|
||||
if selected == current.as_str() {
|
||||
primary_style(theme)
|
||||
} else {
|
||||
muted_style(theme)
|
||||
}
|
||||
}),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(16),
|
||||
text("Rename").size(10).color(DIM),
|
||||
text("Rename").size(10).style(muted_style),
|
||||
row![
|
||||
text_input("New name…", rename_buf)
|
||||
.on_input(Message::LayoutManagerRenameBuf)
|
||||
.on_submit(Message::LayoutManagerRenameCommit)
|
||||
.style(field_style)
|
||||
.size(11)
|
||||
.padding([4, 8]),
|
||||
button(text("OK").size(11))
|
||||
@@ -343,8 +227,10 @@ pub fn view_window<'a>(
|
||||
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)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use iced::time::Instant;
|
||||
use iced::widget::{button, column, container, row, stack, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::app::{
|
||||
@@ -60,13 +60,10 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
.width(Length::Fixed(fill_width))
|
||||
.height(Length::Fixed(BAR_TRACK_HEIGHT)),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.30,
|
||||
g: 0.62,
|
||||
b: 0.95,
|
||||
a: 1.0,
|
||||
})),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().primary.base.color
|
||||
)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
@@ -86,13 +83,10 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
let bar_track: Element<'_, Message> = container(
|
||||
stack![
|
||||
container(Space::new().width(Length::Fixed(BAR_TRACK_WIDTH)).height(Length::Fixed(BAR_TRACK_HEIGHT)))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
})),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.strong.color
|
||||
)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
@@ -107,9 +101,7 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
.into();
|
||||
|
||||
// ── Card body ────────────────────────────────────────────────────────
|
||||
let title = text("Opening file")
|
||||
.size(15)
|
||||
.color(Color::WHITE);
|
||||
let title = text("Opening file").size(15);
|
||||
|
||||
let name_line = text(format!(
|
||||
"{} ({})",
|
||||
@@ -117,11 +109,8 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
format_size(progress.size_bytes)
|
||||
))
|
||||
.size(13)
|
||||
.color(Color {
|
||||
r: 0.82,
|
||||
g: 0.82,
|
||||
b: 0.82,
|
||||
a: 1.0,
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.82)),
|
||||
});
|
||||
|
||||
let phase_line = text(format!(
|
||||
@@ -130,52 +119,13 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
basis_points as f32 / 100.0
|
||||
))
|
||||
.size(12)
|
||||
.color(Color {
|
||||
r: 0.70,
|
||||
g: 0.80,
|
||||
b: 0.95,
|
||||
a: 1.0,
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
});
|
||||
|
||||
let cancel_btn: Element<'_, Message> = button(text("Cancel").size(12).color(Color::WHITE))
|
||||
let cancel_btn: Element<'_, Message> = button(text("Cancel").size(12))
|
||||
.on_press(Message::OpenCancel)
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
},
|
||||
button::Status::Pressed => Color {
|
||||
r: 0.42,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.40,
|
||||
g: 0.40,
|
||||
b: 0.40,
|
||||
a: 1.0,
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: Color::WHITE,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.style(button::danger)
|
||||
.padding([4, 14])
|
||||
.into();
|
||||
|
||||
@@ -187,35 +137,25 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
.width(Length::Fixed(CARD_WIDTH)),
|
||||
)
|
||||
.padding([18, 22])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 0.98,
|
||||
})),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
},
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
|
||||
// ── Backdrop (click-blocker + dim) ────────────────────────────────────
|
||||
let backdrop: Element<'_, Message> = container(Space::new().width(Fill).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.55,
|
||||
})),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.strong.color.scale_alpha(0.72)
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
||||
+100
-73
@@ -1,96 +1,123 @@
|
||||
use crate::app::config::UiThemeConfig;
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, pick_list, row, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::widget::{
|
||||
button, column, container, pick_list, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
pub fn view_window<'a>(default_save_format: &'a str) -> Element<'a, Message> {
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.17,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.36,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.90,
|
||||
g: 0.90,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.60,
|
||||
g: 0.60,
|
||||
b: 0.64,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let selected = crate::io::SAVE_FORMAT_OPTIONS
|
||||
pub fn view_window<'a>(
|
||||
default_save_format: &'a str,
|
||||
ui_theme: &'a UiThemeConfig,
|
||||
theme_color_inputs: &'a [String; 6],
|
||||
) -> Element<'a, Message> {
|
||||
let selected_format = crate::io::SAVE_FORMAT_OPTIONS
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|candidate| *candidate == default_save_format);
|
||||
|
||||
let close = button(text("Close").size(12).color(TEXT))
|
||||
.on_press(Message::CloseModal)
|
||||
.padding([5, 16])
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.34,
|
||||
g: 0.34,
|
||||
b: 0.38,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.29,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: TEXT,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let theme_options = Theme::ALL
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.chain(std::iter::once("Custom".to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
let selected_theme = Some(ui_theme.name.clone());
|
||||
|
||||
let body = column![
|
||||
text("Open and Save").size(15).color(TEXT),
|
||||
Space::new().height(16),
|
||||
let palette = ui_theme.palette.to_iced();
|
||||
let colors = [
|
||||
("Background", palette.background),
|
||||
("Text", palette.text),
|
||||
("Primary", palette.primary),
|
||||
("Success", palette.success),
|
||||
("Warning", palette.warning),
|
||||
("Danger", palette.danger),
|
||||
];
|
||||
|
||||
let mut color_controls = column![].spacing(8);
|
||||
for (index, (label, color)) in colors.into_iter().enumerate() {
|
||||
let swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(color)),
|
||||
border: Border {
|
||||
color: theme.extended_palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
color_controls = color_controls.push(
|
||||
row![
|
||||
text(label).size(12).width(110),
|
||||
swatch,
|
||||
text_input("#RRGGBB", theme_color_inputs[index].as_str())
|
||||
.on_input(move |value| Message::OptionsThemeColorChanged(index, value))
|
||||
.width(130),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
}
|
||||
|
||||
let close = button(text("Close").size(12))
|
||||
.on_press(Message::CloseModal)
|
||||
.padding([6, 18])
|
||||
.style(button::secondary);
|
||||
|
||||
let content = column![
|
||||
text("Open and Save").size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text("Default save format:").size(12).color(TEXT).width(150),
|
||||
pick_list(crate::io::SAVE_FORMAT_OPTIONS, selected, |format: &str| {
|
||||
Message::DefaultSaveFormatChanged(format.to_string())
|
||||
})
|
||||
text("Default save format:").size(12).width(150),
|
||||
pick_list(
|
||||
crate::io::SAVE_FORMAT_OPTIONS,
|
||||
selected_format,
|
||||
|format: &str| Message::DefaultSaveFormatChanged(format.to_string())
|
||||
)
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(12)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
Space::new().height(8),
|
||||
text(
|
||||
"Used when a new drawing is saved for the first time. Existing drawings keep their current file type and version."
|
||||
"Used for the first save of a new drawing. Existing drawings keep their file type and version."
|
||||
)
|
||||
.size(11)
|
||||
.color(DIM)
|
||||
.width(Fill),
|
||||
Space::new().height(Fill),
|
||||
row![Space::new().width(Fill), close],
|
||||
.size(11),
|
||||
Space::new().height(22),
|
||||
text("Theme").size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text("Iced theme:").size(12).width(150),
|
||||
pick_list(
|
||||
theme_options,
|
||||
selected_theme,
|
||||
Message::OptionsThemeChanged,
|
||||
)
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(12)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(8),
|
||||
text(
|
||||
"Changing a base colour switches to Custom. Iced generates every component shade from these six colours."
|
||||
)
|
||||
.size(11),
|
||||
Space::new().height(12),
|
||||
color_controls,
|
||||
]
|
||||
.spacing(0)
|
||||
.width(Fill);
|
||||
|
||||
let body = column![
|
||||
scrollable(content).height(Fill),
|
||||
Space::new().height(12),
|
||||
row![Space::new().width(Fill), close],
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
container(body)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::rounded_box)
|
||||
.padding([16, 18])
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
||||
+82
-54
@@ -10,17 +10,7 @@ use iced::widget::{
|
||||
button, checkbox, column, container, mouse_area, pick_list, row, scrollable, text, text_input,
|
||||
Space,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, 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 FIELD: Color = Color { r: 0.10, g: 0.10, b: 0.10, 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 };
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
|
||||
/// Sentinel entries in the printer dropdown (not real printer names).
|
||||
pub const OUT_DEFAULT: &str = "System default printer";
|
||||
@@ -221,32 +211,49 @@ impl PlotDialogState {
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
move |theme: &Theme, st| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => palette.primary.strong,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
Color { r: 0.28, g: 0.28, b: 0.28, a: 1.0 }
|
||||
palette.background.strong
|
||||
}
|
||||
(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() },
|
||||
(true, _) => palette.primary.base,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
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,
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border { color: border, width: 1.0, radius: 3.0.into() },
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,23 +261,27 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
||||
container(Space::new().width(Fill).height(1))
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn section_label<'a>(s: &'static str) -> Element<'a, Message> {
|
||||
text(s).size(11).color(DIM).into()
|
||||
text(s).size(11).style(muted_style).into()
|
||||
}
|
||||
|
||||
fn vsep<'a>() -> Element<'a, Message> {
|
||||
container(Space::new().width(1).height(Fill))
|
||||
.width(1)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
@@ -295,13 +306,16 @@ fn setup_row<'a>(
|
||||
.into();
|
||||
}
|
||||
let is_sel = name == selected;
|
||||
let cell = container(text(name.to_string()).size(11).color(TEXT))
|
||||
let cell = container(text(name.to_string()).size(11))
|
||||
.padding([4, 8])
|
||||
.width(Fill)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: is_sel.then_some(Background::Color(ACTIVE)),
|
||||
text_color: Some(TEXT),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: is_sel.then_some(Background::Color(palette.primary.strong.color)),
|
||||
text_color: is_sel.then_some(palette.primary.strong.text),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
mouse_area(cell)
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::SelectSetup(name.to_string())))
|
||||
@@ -321,7 +335,7 @@ fn drop_row<'a>(
|
||||
.text_size(12)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fill);
|
||||
row![text(label).size(11).color(DIM).width(92), pl]
|
||||
row![text(label).size(11).style(muted_style).width(92), pl]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
.into()
|
||||
@@ -335,7 +349,7 @@ fn field_row<'a>(
|
||||
width: u16,
|
||||
) -> Element<'a, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(92),
|
||||
text(label).size(11).style(muted_style).width(92),
|
||||
text_input("", value)
|
||||
.on_input(move |s| Message::PlotDlg(ctor(s)))
|
||||
.style(field_style)
|
||||
@@ -413,8 +427,10 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
@@ -445,7 +461,7 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||
.map(|name| setup_row(name, &s.selected_setup, renaming, rename_buf))
|
||||
.collect();
|
||||
let list_body: Element<'_, Message> = if rows.is_empty() {
|
||||
container(text("(no page setups)").size(11).color(DIM))
|
||||
container(text("(no page setups)").size(11).style(muted_style))
|
||||
.padding([6, 8])
|
||||
.into()
|
||||
} else {
|
||||
@@ -453,16 +469,19 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||
};
|
||||
let list_panel = container(
|
||||
column![
|
||||
text("Page setups").size(10).color(DIM),
|
||||
text("Page setups").size(10).style(muted_style),
|
||||
container(list_body)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
@@ -550,11 +569,18 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||
hdivider(),
|
||||
section_label("Plot style table (pen assignments)"),
|
||||
row![
|
||||
container(text(style_label).size(12).color(TEXT))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(FIELD)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
container(text(style_label).size(12))
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 8])
|
||||
.width(Fill),
|
||||
@@ -583,7 +609,7 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||
.width(Length::Fill)
|
||||
)
|
||||
.width(Fill),
|
||||
text("DPI").size(11).color(DIM),
|
||||
text("DPI").size(11).style(muted_style),
|
||||
text_input("", &s.dpi)
|
||||
.on_input(move |v| Message::PlotDlg(PlotDlgMsg::Dpi(v)))
|
||||
.style(field_style)
|
||||
@@ -631,8 +657,10 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||
let body = row![list_panel, vsep(), detail].height(Fill);
|
||||
|
||||
container(column![toolbar, hdivider(), body].spacing(0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
||||
+97
-137
@@ -7,7 +7,7 @@
|
||||
use crate::app::Message;
|
||||
use crate::plugin::external::{ExternalPlugin, RegistryEntry};
|
||||
use iced::widget::{button, column, container, pick_list, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
/// Marketplace state passed to the Plugin Manager view.
|
||||
@@ -25,124 +25,105 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
names: &["PLUGINS", "PLUGINMANAGER"]
|
||||
});
|
||||
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const CARD: 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.30,
|
||||
g: 0.62,
|
||||
b: 0.95,
|
||||
a: 1.0,
|
||||
};
|
||||
const WHITE: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
fn badge<'a>(label: String) -> Element<'a, Message> {
|
||||
container(text(label).size(11).color(WHITE))
|
||||
container(text(label).size(11))
|
||||
.padding([2, 8])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.20,
|
||||
g: 0.34,
|
||||
b: 0.52,
|
||||
a: 1.0,
|
||||
})),
|
||||
.style(|theme: &Theme| {
|
||||
let pair = theme.extended_palette().primary.weak;
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn toggle_button<'a>(id: &str, disabled: bool) -> Element<'a, Message> {
|
||||
// Label shows the action the click performs.
|
||||
let (label, on, off) = if disabled {
|
||||
("Enable", Color { r: 0.18, g: 0.5, b: 0.25, a: 1.0 }, Color { r: 0.22, g: 0.6, b: 0.3, a: 1.0 })
|
||||
let label = if disabled {
|
||||
"Enable"
|
||||
} else {
|
||||
("Disable", Color { r: 0.4, g: 0.22, b: 0.22, a: 1.0 }, Color { r: 0.55, g: 0.28, b: 0.28, a: 1.0 })
|
||||
"Disable"
|
||||
};
|
||||
let want_enabled = disabled; // clicking flips the state
|
||||
let id_owned = id.to_string();
|
||||
button(text(label).size(12).color(WHITE))
|
||||
button(text(label).size(12))
|
||||
.padding([3, 12])
|
||||
.on_press(Message::SetPluginEnabled(id_owned, want_enabled))
|
||||
.style(move |_: &Theme, status| {
|
||||
let bg = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => off,
|
||||
_ => on,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.style(if disabled { button::success } else { button::danger })
|
||||
.into()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum StatusKind {
|
||||
Muted,
|
||||
Success,
|
||||
Danger,
|
||||
Warning,
|
||||
}
|
||||
|
||||
/// Coloured status pill for a discovered external package.
|
||||
fn status_badge<'a>(label: &str, color: Color) -> Element<'a, Message> {
|
||||
container(text(label.to_string()).size(11).color(WHITE))
|
||||
fn status_badge<'a>(label: &str, kind: StatusKind) -> Element<'a, Message> {
|
||||
container(text(label.to_string()).size(11))
|
||||
.padding([2, 8])
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(color)),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match kind {
|
||||
StatusKind::Muted => palette.background.weak,
|
||||
StatusKind::Success => palette.success.weak,
|
||||
StatusKind::Danger => palette.danger.weak,
|
||||
StatusKind::Warning => palette.warning.weak,
|
||||
};
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn external_card<'a>(p: &ExternalPlugin, loaded: bool, disabled: bool) -> Element<'a, Message> {
|
||||
let (status, color) = if loaded && disabled {
|
||||
("Disabled", Color { r: 0.45, g: 0.45, b: 0.45, a: 1.0 })
|
||||
let (status, kind) = if loaded && disabled {
|
||||
("Disabled", StatusKind::Muted)
|
||||
} else if loaded {
|
||||
("Loaded", Color { r: 0.2, g: 0.5, b: 0.3, a: 1.0 })
|
||||
("Loaded", StatusKind::Success)
|
||||
} else if !p.api_compatible() {
|
||||
("API incompatible", Color { r: 0.55, g: 0.28, b: 0.28, a: 1.0 })
|
||||
("API incompatible", StatusKind::Danger)
|
||||
} else if !p.lib_present {
|
||||
("No library", Color { r: 0.5, g: 0.42, b: 0.2, a: 1.0 })
|
||||
("No library", StatusKind::Warning)
|
||||
} else {
|
||||
("Restart to load", Color { r: 0.5, g: 0.42, b: 0.2, a: 1.0 })
|
||||
("Restart to load", StatusKind::Warning)
|
||||
};
|
||||
let mut header = row![
|
||||
text(p.name.clone()).size(15).color(WHITE),
|
||||
text(p.name.clone()).size(15),
|
||||
Space::new().width(8),
|
||||
badge(format!("v{}", p.version)),
|
||||
Space::new().width(8),
|
||||
badge(format!("API {}", p.api_version)),
|
||||
Space::new().width(Fill),
|
||||
status_badge(status, color),
|
||||
status_badge(status, kind),
|
||||
]
|
||||
.align_y(iced::Center);
|
||||
// A loaded plugin can be turned off (drops its ribbon tab + dispatch).
|
||||
@@ -154,75 +135,52 @@ fn external_card<'a>(p: &ExternalPlugin, loaded: bool, disabled: bool) -> Elemen
|
||||
header = header.push(pill_button(
|
||||
"Uninstall",
|
||||
Message::PluginUninstall(p.id.clone()),
|
||||
Color { r: 0.4, g: 0.25, b: 0.25, a: 1.0 },
|
||||
button::danger,
|
||||
));
|
||||
|
||||
let id_line = text(p.id.clone()).size(11).color(ACCENT);
|
||||
let id_line = text(p.id.clone()).size(11).style(primary_style);
|
||||
let mut body = column![header, id_line].spacing(5);
|
||||
if !p.description.is_empty() {
|
||||
body = body.push(text(p.description.clone()).size(12).color(DIM));
|
||||
body = body.push(text(p.description.clone()).size(12).style(muted_style));
|
||||
}
|
||||
if !p.command_prefixes.is_empty() {
|
||||
body = body.push(
|
||||
text(format!("Commands: {}", p.command_prefixes.join(", ")))
|
||||
.size(11)
|
||||
.color(DIM),
|
||||
.style(muted_style),
|
||||
);
|
||||
}
|
||||
container(body.padding([12, 14]))
|
||||
.width(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(CARD)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 6.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn pill_button<'a>(label: &str, msg: Message, bg: Color) -> Element<'a, Message> {
|
||||
button(text(label.to_string()).size(12).color(WHITE))
|
||||
fn pill_button<'a>(
|
||||
label: &str,
|
||||
msg: Message,
|
||||
style: fn(&Theme, button::Status) -> button::Style,
|
||||
) -> Element<'a, Message> {
|
||||
button(text(label.to_string()).size(12))
|
||||
.padding([4, 12])
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| {
|
||||
let c = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: bg.r + 0.08, g: bg.g + 0.08, b: bg.b + 0.08, a: 1.0 }
|
||||
} else {
|
||||
bg
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(c)),
|
||||
text_color: WHITE,
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.style(style)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Square icon variant of [`pill_button`] for glyph-free actions (e.g. remove).
|
||||
fn pill_icon_button<'a>(icon: &'static [u8], msg: Message, bg: Color) -> Element<'a, Message> {
|
||||
button(crate::ui::icons::tinted(icon, 11.0, WHITE))
|
||||
fn pill_icon_button<'a>(
|
||||
icon: &'static [u8],
|
||||
msg: Message,
|
||||
style: fn(&Theme, button::Status) -> button::Style,
|
||||
) -> Element<'a, Message> {
|
||||
button(crate::ui::icons::themed(icon, 11.0))
|
||||
.padding([5, 9])
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| {
|
||||
let c = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: bg.r + 0.08, g: bg.g + 0.08, b: bg.b + 0.08, a: 1.0 }
|
||||
} else {
|
||||
bg
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(c)),
|
||||
text_color: WHITE,
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.style(style)
|
||||
.into()
|
||||
}
|
||||
|
||||
const GREEN: Color = Color { r: 0.2, g: 0.45, b: 0.28, a: 1.0 };
|
||||
const RED: Color = Color { r: 0.4, g: 0.25, b: 0.25, a: 1.0 };
|
||||
|
||||
/// Release dropdown + Install (+ optional unlink) for one repo.
|
||||
fn install_controls<'a>(
|
||||
repo: &str,
|
||||
@@ -232,7 +190,7 @@ fn install_controls<'a>(
|
||||
) -> Element<'a, Message> {
|
||||
let repo_s = repo.to_string();
|
||||
let picker: Element<'_, Message> = if tags.is_empty() {
|
||||
text("no releases").size(11).color(DIM).into()
|
||||
text("no releases").size(11).style(muted_style).into()
|
||||
} else {
|
||||
let r = repo_s.clone();
|
||||
pick_list(tags, selected, move |tag| {
|
||||
@@ -244,7 +202,11 @@ fn install_controls<'a>(
|
||||
let mut controls = row![
|
||||
picker,
|
||||
Space::new().width(8),
|
||||
pill_button("Install", Message::PluginInstall(repo_s.clone()), GREEN),
|
||||
pill_button(
|
||||
"Install",
|
||||
Message::PluginInstall(repo_s.clone()),
|
||||
button::success,
|
||||
),
|
||||
]
|
||||
.align_y(iced::Center)
|
||||
.spacing(4);
|
||||
@@ -253,7 +215,7 @@ fn install_controls<'a>(
|
||||
controls = controls.push(pill_icon_button(
|
||||
crate::ui::icons::CLOSE,
|
||||
Message::PluginRepoRemove(repo_s),
|
||||
RED,
|
||||
button::danger,
|
||||
));
|
||||
}
|
||||
controls.into()
|
||||
@@ -262,37 +224,33 @@ fn install_controls<'a>(
|
||||
fn market_card<'a>(body: iced::widget::Column<'a, Message>) -> Element<'a, Message> {
|
||||
container(body.spacing(4).padding([10, 12]))
|
||||
.width(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(CARD)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 6.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
||||
let mut col = column![text("Available plugins").size(13).color(ACCENT)].spacing(6);
|
||||
let mut col = column![text("Available plugins").size(13).style(primary_style)].spacing(6);
|
||||
|
||||
// Curated registry entries (from the OpenCADStudio repo).
|
||||
for e in m.registry {
|
||||
let tags = m.release_tags.get(&e.repo).cloned().unwrap_or_default();
|
||||
let selected = m.selected_tag.get(&e.repo).cloned();
|
||||
let header = row![
|
||||
text(e.name.clone()).size(14).color(WHITE),
|
||||
text(e.name.clone()).size(14),
|
||||
Space::new().width(Fill),
|
||||
install_controls(&e.repo, tags, selected, false),
|
||||
]
|
||||
.align_y(iced::Center);
|
||||
let mut body = column![header, text(e.repo.clone()).size(11).color(ACCENT)];
|
||||
let mut body = column![header, text(e.repo.clone()).size(11).style(primary_style)];
|
||||
if !e.description.is_empty() {
|
||||
body = body.push(text(e.description.clone()).size(12).color(DIM));
|
||||
body = body.push(text(e.description.clone()).size(12).style(muted_style));
|
||||
}
|
||||
col = col.push(market_card(body));
|
||||
}
|
||||
|
||||
// Manual: link any repo by owner/repo.
|
||||
col = col.push(Space::new().height(6));
|
||||
col = col.push(text("Add a repository").size(12).color(DIM));
|
||||
col = col.push(text("Add a repository").size(12).style(muted_style));
|
||||
col = col.push(
|
||||
row![
|
||||
text_input("owner/repo", m.input)
|
||||
@@ -301,7 +259,7 @@ fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
||||
.size(13)
|
||||
.width(Fill),
|
||||
Space::new().width(8),
|
||||
pill_button("Add", Message::PluginRepoAdd, Color { r: 0.2, g: 0.4, b: 0.62, a: 1.0 }),
|
||||
pill_button("Add", Message::PluginRepoAdd, button::primary),
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
@@ -309,7 +267,7 @@ fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
||||
let tags = m.release_tags.get(repo).cloned().unwrap_or_default();
|
||||
let selected = m.selected_tag.get(repo).cloned();
|
||||
let header = row![
|
||||
text(repo.clone()).size(13).color(WHITE),
|
||||
text(repo.clone()).size(13),
|
||||
Space::new().width(Fill),
|
||||
install_controls(repo, tags, selected, true),
|
||||
]
|
||||
@@ -318,7 +276,7 @@ fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
||||
}
|
||||
|
||||
if !m.status.is_empty() {
|
||||
col = col.push(text(m.status.to_string()).size(11).color(DIM));
|
||||
col = col.push(text(m.status.to_string()).size(11).style(muted_style));
|
||||
}
|
||||
col.into()
|
||||
}
|
||||
@@ -329,17 +287,17 @@ pub fn view_window<'a>(
|
||||
loaded: &FxHashSet<String>,
|
||||
market: MarketView,
|
||||
) -> Element<'a, Message> {
|
||||
let title = text("Plugins").size(20).color(WHITE);
|
||||
let title = text("Plugins").size(20);
|
||||
let subtitle = text("Add-ons load from the plugins folder. Install from a repository below.")
|
||||
.size(12)
|
||||
.color(DIM);
|
||||
.style(muted_style);
|
||||
|
||||
let mut list = column![].spacing(10);
|
||||
// Installed external packages (from the plugins folder).
|
||||
if externals.is_empty() {
|
||||
list = list.push(text("No plugins installed yet.").size(13).color(DIM));
|
||||
list = list.push(text("No plugins installed yet.").size(13).style(muted_style));
|
||||
} else {
|
||||
list = list.push(text("Installed").size(13).color(ACCENT));
|
||||
list = list.push(text("Installed").size(13).style(primary_style));
|
||||
for p in externals {
|
||||
list = list.push(external_card(
|
||||
p,
|
||||
@@ -360,8 +318,10 @@ pub fn view_window<'a>(
|
||||
.width(Fill)
|
||||
.height(Fill),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
||||
+35
-57
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{column, container, row, scrollable, text, Space};
|
||||
use iced::{Background, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Element, Fill, Theme};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Display name of the primary accelerator modifier on this platform.
|
||||
@@ -14,49 +14,26 @@ const MOD: &str = "Cmd";
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
const MOD: &str = "Ctrl";
|
||||
|
||||
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 muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
fn hdivider<'a>() -> Element<'a, Message> {
|
||||
container(Space::new().width(Fill).height(1))
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
@@ -69,7 +46,7 @@ fn shortcut_row<'a>(
|
||||
row![
|
||||
text(key.into())
|
||||
.size(11)
|
||||
.color(KEY)
|
||||
.style(primary_style)
|
||||
.font(iced::Font::MONOSPACE)
|
||||
.width(160),
|
||||
text(action).size(11),
|
||||
@@ -81,7 +58,7 @@ fn shortcut_row<'a>(
|
||||
}
|
||||
|
||||
fn section<'a>(title: impl Into<Cow<'static, str>>) -> Element<'a, Message> {
|
||||
container(text(title.into()).size(11).color(DIM))
|
||||
container(text(title.into()).size(11).style(muted_style))
|
||||
.padding(iced::Padding {
|
||||
top: 6.0,
|
||||
right: 0.0,
|
||||
@@ -99,12 +76,14 @@ pub fn view_window<'a>(
|
||||
row![
|
||||
text("Type SHORTCUTS SET <key> <cmd> to add custom shortcuts.")
|
||||
.size(10)
|
||||
.color(DIM),
|
||||
.style(muted_style),
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weakest.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
@@ -147,7 +126,7 @@ pub fn view_window<'a>(
|
||||
rows.push(
|
||||
text(" (none — use: SHORTCUTS SET <key> <command>)")
|
||||
.size(11)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.into(),
|
||||
);
|
||||
} else {
|
||||
@@ -158,7 +137,7 @@ pub fn view_window<'a>(
|
||||
row![
|
||||
text(key.as_str())
|
||||
.size(11)
|
||||
.color(KEY)
|
||||
.style(primary_style)
|
||||
.font(iced::Font::MONOSPACE)
|
||||
.width(160),
|
||||
text(cmd.as_str()).size(11),
|
||||
@@ -179,26 +158,25 @@ pub fn view_window<'a>(
|
||||
// ── Header row with accent ────────────────────────────────────────────
|
||||
let header = container(
|
||||
row![
|
||||
text("Key").size(10).color(ACCENT).width(160),
|
||||
text("Action").size(10).color(ACCENT),
|
||||
text("Key").size(10).style(primary_style).width(160),
|
||||
text("Action").size(10).style(primary_style),
|
||||
]
|
||||
.spacing(8)
|
||||
.padding([4, 16]),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
})),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().primary.weak.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill);
|
||||
|
||||
container(column![toolbar, hdivider(), header, hdivider(), content].spacing(0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
||||
+51
-112
@@ -1,60 +1,33 @@
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Border, 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,
|
||||
};
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders one of the two "Installed" / "Latest" cards. The `highlight`
|
||||
/// flag tints the border + label with the accent colour, making the new
|
||||
/// version the visual anchor of the row.
|
||||
fn version_card<'a>(label: &'static str, value: String, highlight: bool) -> Element<'a, Message> {
|
||||
let label_color = if highlight { ACCENT } else { DIM };
|
||||
let value_color = WHITE;
|
||||
let border_color = if highlight { ACCENT } else { BORDER };
|
||||
let bg = if highlight {
|
||||
Color { r: 0.10, g: 0.16, b: 0.22, a: 1.0 } // accent-tinted dark
|
||||
} else {
|
||||
PANEL
|
||||
};
|
||||
container(
|
||||
column![
|
||||
text(label).size(10).color(label_color),
|
||||
text(value).size(20).color(value_color),
|
||||
text(label)
|
||||
.size(10)
|
||||
.style(move |theme: &Theme| if highlight {
|
||||
primary_style(theme)
|
||||
} else {
|
||||
muted_style(theme)
|
||||
}),
|
||||
text(value).size(20),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_x(iced::Center),
|
||||
@@ -67,14 +40,26 @@ fn version_card<'a>(label: &'static str, value: String, highlight: bool) -> Elem
|
||||
left: 12.0,
|
||||
})
|
||||
.align_x(iced::Center)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if highlight {
|
||||
palette.primary.weak
|
||||
} else {
|
||||
palette.background.base
|
||||
};
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
color: border_color,
|
||||
color: if highlight {
|
||||
palette.primary.base.color
|
||||
} else {
|
||||
palette.background.neutral.color
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
@@ -98,24 +83,23 @@ fn render_notes_line<'a>(raw: &str) -> Element<'a, Message> {
|
||||
if let Some(rest) = trimmed.strip_prefix("## ") {
|
||||
return text(strip_inline_md(rest))
|
||||
.size(13)
|
||||
.color(ACCENT)
|
||||
.style(primary_style)
|
||||
.into();
|
||||
}
|
||||
if let Some(rest) = trimmed.strip_prefix("### ") {
|
||||
return text(strip_inline_md(rest))
|
||||
.size(12)
|
||||
.color(WHITE)
|
||||
.into();
|
||||
}
|
||||
if let Some(rest) = trimmed.strip_prefix("- ").or_else(|| trimmed.strip_prefix("* ")) {
|
||||
return row![
|
||||
container(crate::ui::icons::tinted(crate::ui::icons::DOT, 5.0, DIM)).width(14),
|
||||
text(strip_inline_md(rest)).size(11).color(WHITE),
|
||||
container(crate::ui::icons::themed_secondary(crate::ui::icons::DOT, 5.0)).width(14),
|
||||
text(strip_inline_md(rest)).size(11),
|
||||
]
|
||||
.spacing(4)
|
||||
.into();
|
||||
}
|
||||
text(strip_inline_md(trimmed)).size(11).color(WHITE).into()
|
||||
text(strip_inline_md(trimmed)).size(11).into()
|
||||
}
|
||||
|
||||
/// Drop `**…**` and `` `…` `` markers without preserving emphasis (iced 0.14
|
||||
@@ -139,10 +123,10 @@ fn strip_inline_md(s: &str) -> String {
|
||||
pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||
let header = container(
|
||||
column![
|
||||
text("New Release Available").size(20).color(ACCENT),
|
||||
text("New Release Available").size(20).style(primary_style),
|
||||
text("A newer Open CAD Studio version is published on GitHub.")
|
||||
.size(11)
|
||||
.color(DIM),
|
||||
.style(muted_style),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_x(iced::Center),
|
||||
@@ -165,10 +149,9 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||
false,
|
||||
);
|
||||
let latest_card = version_card("Latest", format!("v{}", latest), true);
|
||||
let arrow = container(crate::ui::icons::tinted(
|
||||
let arrow = container(crate::ui::icons::themed_secondary(
|
||||
crate::ui::icons::ARROW_LONG_RIGHT,
|
||||
20.0,
|
||||
DIM,
|
||||
))
|
||||
.width(iced::Length::Fixed(32.0))
|
||||
.height(Fill)
|
||||
@@ -181,50 +164,12 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||
|
||||
let later_btn = button(text("Later").size(11))
|
||||
.on_press(Message::UpdateNoticeClose)
|
||||
.style(|_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match st {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: WHITE,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::secondary)
|
||||
.padding([6, 16]);
|
||||
|
||||
let open_btn = button(text("Open Release Page").size(11))
|
||||
.on_press(Message::UpdateNoticeOpenRelease)
|
||||
.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()
|
||||
})
|
||||
.style(button::primary)
|
||||
.padding([6, 16]);
|
||||
|
||||
let footer = row![Space::new().width(Fill), later_btn, open_btn]
|
||||
@@ -240,7 +185,7 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||
// Release notes panel. Rendered as a light-markdown column inside a
|
||||
// bordered scrollable so long bodies stay contained and don't
|
||||
// explode the window. Empty body → "No release notes provided."
|
||||
let notes_heading = container(text("What's new").size(11).color(DIM))
|
||||
let notes_heading = container(text("What's new").size(11).style(muted_style))
|
||||
.padding(iced::Padding {
|
||||
top: 10.0,
|
||||
right: 0.0,
|
||||
@@ -251,7 +196,7 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||
let notes_body: Element<'a, Message> = if body.trim().is_empty() {
|
||||
text("No release notes provided.")
|
||||
.size(11)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.into()
|
||||
} else {
|
||||
let mut col = column![].spacing(4);
|
||||
@@ -264,15 +209,7 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||
let notes_block = container(notes_body)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
.style(container::bordered_box);
|
||||
|
||||
// Wrap notes_block in a Fill-height container outside the column so it
|
||||
// greedily claims every pixel left over after the fixed-height rows
|
||||
@@ -294,8 +231,10 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||
left: 20.0,
|
||||
}),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
||||
Reference in new issue
Block a user