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
+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,
|
||||
};
|
||||
Reference in new issue
Block a user