WIP: continued improvements to skinning

This commit is contained in:
Will McCormick 2026-03-26 15:58:13 -04:00
parent 3bfc65d634
commit 551565cdd8
26 changed files with 258 additions and 187 deletions

View file

@ -158,7 +158,7 @@ phf = { version = "0.13.1", features = ["macros"] }
[dependencies.icetron_assets]
git = "ssh://git@bitbucket.org/playtron-one/icetron.git"
rev = "a1bf042"
rev = "6db4fa7"
[dependencies.cosmic-theme]
path = "cosmic-theme"

View file

@ -32,6 +32,7 @@ pub enum Button {
MenuItem,
MenuRoot,
NavToggle,
Secondary,
#[default]
Standard,
Suggested,
@ -52,14 +53,32 @@ pub fn appearance(
let mut appearance = Style::new();
let hc = theme.theme_type.is_high_contrast();
match style {
Button::Standard
| Button::Text
| Button::Suggested
Button::Standard => {
corner_radii = &cosmic.corner_radii.radius_m;
appearance.background = Some(Background::Color(crate::theme::STATE_DEFAULT_COLOR));
appearance.text_color = Some(Color::WHITE);
appearance.icon_color = Some(Color::WHITE);
}
Button::Secondary => {
corner_radii = &cosmic.corner_radii.radius_m;
appearance.background = Some(Background::Color(Color::from_rgb8(224, 224, 224)));
appearance.text_color = Some(Color::BLACK);
appearance.icon_color = Some(Color::BLACK);
}
Button::Text => {
let (background, _, _) = color(&cosmic.text_button);
appearance.background = Some(Background::Color(background));
appearance.text_color = Some(crate::theme::STATE_DEFAULT_COLOR);
appearance.icon_color = Some(crate::theme::STATE_DEFAULT_COLOR);
corner_radii = &cosmic.corner_radii.radius_m;
}
Button::Suggested
| Button::Destructive
| Button::Transparent => {
let style_component = match style {
Button::Standard => &cosmic.button,
Button::Text => &cosmic.text_button,
Button::Suggested => &cosmic.accent_button,
Button::Destructive => &cosmic.destructive_button,
Button::Transparent => &TRANSPARENT_COMPONENT,
@ -68,13 +87,8 @@ pub fn appearance(
let (background, text, icon) = color(style_component);
appearance.background = Some(Background::Color(background));
if !matches!(style, Button::Standard) {
appearance.text_color = text;
appearance.icon_color = icon;
} else if hc {
appearance.border_color = style_component.border.into();
appearance.border_width = 1.;
}
appearance.text_color = text;
appearance.icon_color = icon;
}
Button::Icon | Button::IconVertical | Button::HeaderBar | Button::NavToggle => {
@ -254,12 +268,18 @@ impl Catalog for crate::Theme {
Some(component.on.into())
};
if matches!(style, Button::ListItem) {
if matches!(style, Button::ListItem | Button::Text) {
(
crate::theme::STATE_DEFAULT_BG,
text_color,
text_color,
)
} else if matches!(style, Button::MenuItem | Button::MenuFolder) {
(
Color::from_rgb8(230, 230, 230),
text_color,
text_color,
)
} else {
(component.hover.into(), text_color, text_color)
}
@ -283,12 +303,18 @@ impl Catalog for crate::Theme {
Some(component.on.into())
};
if matches!(style, Button::ListItem) {
if matches!(style, Button::ListItem | Button::Text) {
(
crate::theme::STATE_DEFAULT_BG,
text_color,
text_color,
)
} else if matches!(style, Button::MenuItem | Button::MenuFolder) {
(
Color::from_rgb8(220, 220, 220),
text_color,
text_color,
)
} else {
(component.pressed.into(), text_color, text_color)
}

View file

@ -654,16 +654,16 @@ impl iced_container::Catalog for Theme {
Container::Dialog => iced_container::Style {
icon_color: Some(Color::from(cosmic.primary.on)),
text_color: Some(Color::from(cosmic.primary.on)),
background: Some(iced::Background::Color(cosmic.primary.base.into())),
background: Some(iced::Background::Color(Color::from_rgb8(245, 245, 245))),
border: Border {
color: cosmic.primary.divider.into(),
width: 1.0,
color: Color::TRANSPARENT,
width: 0.0,
radius: cosmic.corner_radii.radius_m.into(),
},
shadow: Shadow {
color: cosmic.shade.into(),
offset: Vector::new(0.0, 4.0),
blur_radius: 16.0,
color: Color::from_rgba(0.0, 0.0, 0.0, 0.25),
offset: Vector::new(0.0, 8.0),
blur_radius: 32.0,
},
},
}
@ -698,7 +698,7 @@ impl slider::Catalog for Theme {
Slider::Standard =>
//TODO: no way to set rail thickness
{
let empty_track: Color = Color::from_rgb8(224, 224, 224);
let empty_track: Color = Color::from_rgb8(240, 240, 240);
slider::Style {
rail: Rail {
backgrounds: (
@ -715,7 +715,7 @@ impl slider::Catalog for Theme {
handle: slider::Handle {
shape: slider::HandleShape::Circle {
radius: 8.0,
radius: 6.0,
},
border_color: Color::from_rgba8(0, 0, 0, 0.12),
border_width: 1.0,
@ -739,7 +739,7 @@ impl slider::Catalog for Theme {
slider::Status::Hovered => match class {
Slider::Standard => {
appearance.handle.shape = slider::HandleShape::Circle {
radius: 10.0,
radius: 7.0,
};
appearance.handle.border_width = 1.0;
appearance.handle.border_color = Color::from_rgba8(0, 0, 0, 0.12);
@ -755,7 +755,7 @@ impl slider::Catalog for Theme {
slider::Status::Dragged => match class {
Slider::Standard => {
appearance.handle.shape = slider::HandleShape::Circle {
radius: 10.0,
radius: 7.0,
};
appearance.handle.border_width = 1.0;
appearance.handle.border_color = Color::from_rgba8(0, 0, 0, 0.12);
@ -897,17 +897,22 @@ impl toggler::Catalog for Theme {
fn style(&self, class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style {
let cosmic = self.cosmic();
const HANDLE_MARGIN: f32 = 2.0;
let neutral_10 = cosmic.palette.neutral_10.with_alpha(0.1);
let mut active = toggler::Style {
background: if matches!(status, toggler::Status::Active { is_toggled: true }) {
cosmic.accent.base.into()
} else if cosmic.is_dark {
cosmic.palette.neutral_6.into()
} else {
cosmic.palette.neutral_5.into()
},
foreground: cosmic.palette.neutral_2.into(),
let is_toggled = matches!(
status,
toggler::Status::Active { is_toggled: true }
| toggler::Status::Hovered { is_toggled: true }
);
let track_color = if is_toggled {
crate::theme::STATE_DEFAULT_COLOR
} else {
Color::from_rgb8(224, 224, 224)
};
let mut style = toggler::Style {
background: track_color,
foreground: Color::WHITE,
border_radius: cosmic.radius_xl().into(),
handle_radius: cosmic
.radius_xl()
@ -920,30 +925,10 @@ impl toggler::Catalog for Theme {
foreground_border_color: Color::TRANSPARENT,
};
match status {
toggler::Status::Active { is_toggled } => active,
toggler::Status::Hovered { is_toggled } => {
let is_active = matches!(status, toggler::Status::Hovered { is_toggled: true });
toggler::Style {
background: if is_active {
over(neutral_10, cosmic.accent_color())
} else {
over(
neutral_10,
if cosmic.is_dark {
cosmic.palette.neutral_6
} else {
cosmic.palette.neutral_5
},
)
}
.into(),
..active
}
}
toggler::Status::Active { .. } | toggler::Status::Hovered { .. } => style,
toggler::Status::Disabled => {
active.background.a /= 2.;
active.foreground.a /= 2.;
active
style.background.a /= 2.;
style
}
}
}
@ -1334,7 +1319,6 @@ impl text_input::Catalog for Theme {
fn style(&self, class: &Self::Class<'_>, status: text_input::Status) -> text_input::Style {
let palette = self.cosmic();
let bg = self.current_container().small_widget.with_alpha(0.25);
let neutral_9 = palette.palette.neutral_9;
let value = neutral_9.into();
@ -1343,7 +1327,7 @@ impl text_input::Catalog for Theme {
let mut appearance = match class {
TextInput::Default => text_input::Style {
background: Color::from(bg).into(),
background: Color::WHITE.into(),
border: Border {
radius: palette.corner_radii.radius_s.into(),
width: 1.0,
@ -1355,7 +1339,7 @@ impl text_input::Catalog for Theme {
selection,
},
TextInput::Search => text_input::Style {
background: Color::from(bg).into(),
background: Color::WHITE.into(),
border: Border {
radius: palette.corner_radii.radius_m.into(),
..Default::default()
@ -1370,11 +1354,9 @@ impl text_input::Catalog for Theme {
match status {
text_input::Status::Active => appearance,
text_input::Status::Hovered => {
let bg = self.current_container().small_widget.with_alpha(0.25);
match class {
TextInput::Default => text_input::Style {
background: Color::from(bg).into(),
background: Color::WHITE.into(),
border: Border {
radius: palette.corner_radii.radius_s.into(),
width: 1.0,
@ -1386,7 +1368,7 @@ impl text_input::Catalog for Theme {
selection,
},
TextInput::Search => text_input::Style {
background: Color::from(bg).into(),
background: Color::WHITE.into(),
border: Border {
radius: palette.corner_radii.radius_m.into(),
..Default::default()
@ -1399,11 +1381,9 @@ impl text_input::Catalog for Theme {
}
}
text_input::Status::Focused => {
let bg = self.current_container().small_widget.with_alpha(0.25);
match class {
TextInput::Default => text_input::Style {
background: Color::from(bg).into(),
background: Color::WHITE.into(),
border: Border {
radius: palette.corner_radii.radius_s.into(),
width: 1.0,
@ -1415,7 +1395,7 @@ impl text_input::Catalog for Theme {
selection,
},
TextInput::Search => text_input::Style {
background: Color::from(bg).into(),
background: Color::WHITE.into(),
border: Border {
radius: palette.corner_radii.radius_m.into(),
..Default::default()

View file

@ -289,7 +289,7 @@ mod horizontal {
let rad_0 = cosmic.corner_radii.radius_0;
ItemStatusAppearance {
background: Some(Background::Color(
cosmic.palette.neutral_5.with_alpha(0.2).into(),
cosmic.palette.neutral_5.with_alpha(0.05).into(),
)),
first: ItemAppearance {
border: Border {
@ -381,7 +381,7 @@ mod vertical {
pub fn tab_bar_active(cosmic: &cosmic_theme::Theme) -> ItemStatusAppearance {
ItemStatusAppearance {
background: Some(Background::Color(
cosmic.palette.neutral_5.with_alpha(0.2).into(),
cosmic.palette.neutral_5.with_alpha(0.05).into(),
)),
first: ItemAppearance {
border: Border {

View file

@ -6,7 +6,6 @@
use crate::ext::ColorExt;
use crate::widget::text_input::{Appearance, StyleSheet};
use iced_core::Color;
use palette::WithAlpha;
#[derive(Default)]
pub enum TextInput {
@ -32,13 +31,11 @@ impl StyleSheet for crate::Theme {
let palette = self.cosmic();
let container = self.current_container();
let background: Color = container.small_widget.with_alpha(0.25).into();
let corner = palette.corner_radii;
let label_color = palette.palette.neutral_9;
match style {
TextInput::Default => Appearance {
background: background.into(),
background: Color::WHITE.into(),
border_radius: corner.radius_s.into(),
border_width: 2.0,
border_offset: None,
@ -47,7 +44,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -63,7 +60,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -79,14 +76,14 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Search => Appearance {
background: background.into(),
background: Color::WHITE.into(),
border_radius: corner.radius_xl.into(),
border_width: 2.0,
border_offset: None,
@ -95,7 +92,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -111,7 +108,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -125,15 +122,12 @@ impl StyleSheet for crate::Theme {
let palette = self.cosmic();
let container = self.current_container();
let mut background: Color = container.small_widget.into();
background.a = 0.25;
let corner = palette.corner_radii;
let label_color = palette.palette.neutral_9;
match style {
TextInput::Default => Appearance {
background: background.into(),
background: Color::WHITE.into(),
border_radius: corner.radius_s.into(),
border_width: 2.0,
border_offset: Some(2.0),
@ -142,14 +136,14 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Search | TextInput::ExpandableSearch => Appearance {
background: background.into(),
background: Color::WHITE.into(),
border_radius: corner.radius_xl.into(),
border_width: 0.0,
border_offset: None,
@ -158,7 +152,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -174,7 +168,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -188,15 +182,12 @@ impl StyleSheet for crate::Theme {
let palette = self.cosmic();
let container = self.current_container();
let mut background: Color = container.small_widget.into();
background.a = 0.25;
let corner = palette.corner_radii;
let label_color = palette.palette.neutral_9;
match style {
TextInput::Default => Appearance {
background: background.into(),
background: Color::WHITE.into(),
border_radius: corner.radius_s.into(),
border_width: 2.0,
border_offset: None,
@ -205,14 +196,14 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Search => Appearance {
background: background.into(),
background: Color::WHITE.into(),
border_radius: corner.radius_xl.into(),
border_offset: None,
border_width: 2.0,
@ -221,14 +212,14 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::ExpandableSearch => Appearance {
background: background.into(),
background: Color::WHITE.into(),
border_radius: corner.radius_xl.into(),
border_offset: None,
border_width: 0.0,
@ -237,7 +228,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -253,7 +244,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -269,7 +260,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -283,15 +274,12 @@ impl StyleSheet for crate::Theme {
let palette = self.cosmic();
let container = self.current_container();
let mut background: Color = container.small_widget.into();
background.a = 0.25;
let corner = palette.corner_radii;
let label_color = palette.palette.neutral_9;
match style {
TextInput::Default => Appearance {
background: background.into(),
background: Color::WHITE.into(),
border_radius: corner.radius_s.into(),
border_width: 2.0,
border_offset: Some(2.0),
@ -300,14 +288,14 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Search | TextInput::ExpandableSearch => Appearance {
background: background.into(),
background: Color::WHITE.into(),
border_radius: corner.radius_xl.into(),
border_width: 2.0,
border_offset: Some(2.0),
@ -316,7 +304,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -332,7 +320,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
@ -348,7 +336,7 @@ impl StyleSheet for crate::Theme {
text_color: None,
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
color.blend_alpha(Color::WHITE, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),

View file

@ -101,7 +101,7 @@ pub fn about<'a, Message: Clone + 'static>(
.push(widget::text(name))
.push(horizontal_space())
.push_maybe(
(!url.is_empty()).then_some(crate::widget::icon::from_name("link-symbolic").icon()),
(!url.is_empty()).then_some(crate::widget::icon::from_svg_bytes(icetron_assets::icons::system::EXTERNAL_LINK_LINE).icon()),
)
.align_y(Alignment::Center)
.apply(widget::button::custom)

View file

@ -61,7 +61,7 @@ impl<'a, Message> Button<'a, Message> {
#[inline(never)]
pub fn icon() -> Handle {
icon::from_svg_bytes(&include_bytes!("external-link.svg")[..]).symbolic(true)
icon::from_svg_bytes(icetron_assets::icons::system::EXTERNAL_LINK_LINE).symbolic(true)
}
impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> {

View file

@ -32,7 +32,7 @@ mod text;
#[doc(inline)]
pub use text::Button as TextButton;
#[doc(inline)]
pub use text::{destructive, standard, suggested, text};
pub use text::{destructive, secondary, standard, suggested, text};
mod widget;
#[doc(inline)]

View file

@ -35,6 +35,13 @@ pub fn text<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message>
.class(ButtonClass::Text)
}
/// A text button with the secondary style
pub fn secondary<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
Button::new(Text::new())
.label(label)
.class(ButtonClass::Secondary)
}
/// The text variant of a button.
pub struct Text {
pub(super) leading_icon: Option<icon::Handle>,

View file

@ -105,14 +105,7 @@ impl<'a, Message: Clone + 'a> Button<'a, Message> {
style: crate::theme::Button::default(),
variant: Variant::Image {
on_remove,
close_icon: crate::widget::icon::from_name("window-close-symbolic")
.size(8)
.icon()
.into_svg_handle()
.unwrap_or_else(|| {
let bytes: &'static [u8] = &[];
iced_core::svg::Handle::from_memory(bytes)
}),
close_icon: iced_core::svg::Handle::from_memory(icetron_assets::icons::system::CLOSE_LINE),
},
}
}

View file

@ -161,12 +161,12 @@ where
let month_controls = row::with_capacity(2)
.push(
icon::from_name("go-previous-symbolic")
icon::from_svg_bytes(icetron_assets::icons::system::ARROW_LEFT_S_LINE)
.apply(button::icon)
.on_press((this.on_prev)()),
)
.push(
icon::from_name("go-next-symbolic")
icon::from_svg_bytes(icetron_assets::icons::system::ARROW_RIGHT_S_LINE)
.apply(button::icon)
.on_press((this.on_next)()),
);

View file

@ -30,7 +30,7 @@ use iced_widget::{Row, canvas, column, horizontal_space, row, scrollable, vertic
use palette::{FromColor, RgbHue};
use super::divider::horizontal;
use super::icon::{self, from_name};
use super::icon;
use super::segmented_button::{self, SingleSelect};
use super::{Icon, button, segmented_control, text, text_input, tooltip};
@ -404,8 +404,8 @@ where
// TODO copy paste input contents
.trailing_icon({
let button = button::custom(crate::widget::icon(
from_name("edit-copy-symbolic").size(spacing.space_s).into(),
))
icon::from_svg_bytes(icetron_assets::icons::document::FILE_COPY_LINE),
).size(spacing.space_s))
.on_press(on_update(ColorPickerUpdate::Copied(Instant::now())))
.class(Button::Text);
@ -819,9 +819,8 @@ pub fn color_button<'a, Message: Clone + 'static>(
row![
horizontal_space().width(Length::FillPortion(6)),
Icon::from(
icon::from_name("list-add-symbolic")
.prefer_svg(true)
.symbolic(true)
icon::from_svg_bytes(icetron_assets::icons::system::ADD_LINE)
.icon()
.size(64)
)
.width(icon_portion)

View file

@ -1,18 +1,10 @@
use crate::widget::svg;
use std::sync::OnceLock;
/// Static `svg::Handle` to the `object-select-symbolic` icon.
pub fn object_select() -> &'static svg::Handle {
static SELECTION_ICON: OnceLock<svg::Handle> = OnceLock::new();
SELECTION_ICON.get_or_init(|| {
crate::widget::icon::from_name("object-select-symbolic")
.size(16)
.icon()
.into_svg_handle()
.unwrap_or_else(|| {
let bytes: &'static [u8] = &[];
iced_core::svg::Handle::from_memory(bytes)
})
iced_core::svg::Handle::from_memory(icetron_assets::icons::system::CHECK_LINE)
})
}

View file

@ -71,7 +71,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
let header_row = row::with_capacity(2).push(actions_slot).push(
button::text(fl!("close"))
.trailing_icon(icon::from_name("go-next-symbolic"))
.trailing_icon(icon::from_svg_bytes(icetron_assets::icons::system::ARROW_RIGHT_S_LINE))
.on_press(on_close),
);
let header = column::with_capacity(3)

View file

@ -3,7 +3,6 @@
// SPDX-License-Identifier: MPL-2.0 AND MIT
use super::menu::{self, Menu};
use crate::widget::icon;
use derive_setters::Setters;
use iced_core::event::{self, Event};
use iced_core::text::{self, Paragraph, Text};
@ -229,10 +228,7 @@ impl<Item: Clone + PartialEq + 'static> State<Item> {
/// Creates a new [`State`] for a [`Dropdown`].
pub fn new() -> Self {
Self {
icon: match icon::from_name("pan-down-symbolic").size(16).handle().data {
icon::Data::Svg(handle) => Some(handle),
icon::Data::Image(_) => None,
},
icon: Some(iced_core::svg::Handle::from_memory(icetron_assets::icons::system::ARROW_DOWN_S_LINE)),
menu: menu::State::default(),
keyboard_modifiers: keyboard::Modifiers::default(),
is_open: false,

View file

@ -413,10 +413,7 @@ impl State {
/// Creates a new [`State`] for a [`Dropdown`].
pub fn new() -> Self {
Self {
icon: match icon::from_name("pan-down-symbolic").size(16).handle().data {
icon::Data::Svg(handle) => Some(handle),
icon::Data::Image(_) => None,
},
icon: Some(iced_core::svg::Handle::from_memory(icetron_assets::icons::system::ARROW_DOWN_S_LINE)),
menu: menu::State::default(),
keyboard_modifiers: keyboard::Modifiers::default(),
is_open: Arc::new(AtomicBool::new(false)),

View file

@ -552,10 +552,10 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
/// Creates the widget for window controls.
fn window_controls(&mut self) -> Element<'a, Message> {
const ICON_MINIMIZE: &[u8] = include_bytes!("../../res/icons/window-minimize.svg");
const ICON_MAXIMIZE: &[u8] = include_bytes!("../../res/icons/window-maximize.svg");
const ICON_RESTORE: &[u8] = include_bytes!("../../res/icons/window-restore.svg");
const ICON_CLOSE: &[u8] = include_bytes!("../../res/icons/window-close.svg");
const ICON_MINIMIZE: &[u8] = icetron_assets::icons::system::SUBTRACT_LINE;
const ICON_MAXIMIZE: &[u8] = icetron_assets::icons::system::CHECKBOX_BLANK_LINE;
const ICON_RESTORE: &[u8] = icetron_assets::icons::system::CHECKBOX_MULTIPLE_BLANK_LINE;
const ICON_CLOSE: &[u8] = icetron_assets::icons::system::CLOSE_LINE;
macro_rules! wc_icon {
($svg_bytes:expr, $size:expr, $on_press:expr, $is_close:expr) => {{

View file

@ -94,7 +94,7 @@ pub fn from_raster_pixels(
/// Create a SVG handle from memory.
pub fn from_svg_bytes(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
Handle {
symbolic: false,
symbolic: true,
data: Data::Svg(svg::Handle::from_memory(bytes)),
}
}

View file

@ -290,9 +290,9 @@ pub fn menu_items<
let key = find_key(&action, key_binds);
let mut items = vec![
if value {
widget::icon::from_name("object-select-symbolic")
.size(16)
widget::icon::from_svg_bytes(icetron_assets::icons::system::CHECK_LINE)
.icon()
.size(16)
.class(theme::Svg::Custom(Rc::new(|theme| {
iced_widget::svg::Style {
color: Some(theme.cosmic().accent_text_color().into()),
@ -326,9 +326,9 @@ pub fn menu_items<
menu_button::<'static, _>(vec![
widget::text(l.clone()).into(),
widget::horizontal_space().into(),
widget::icon::from_name("pan-end-symbolic")
.size(16)
widget::icon::from_svg_bytes(icetron_assets::icons::system::ARROW_RIGHT_S_LINE)
.icon()
.size(16)
.into(),
])
.class(

View file

@ -343,7 +343,12 @@ where
position.x = position.x.round();
position.y = position.y.round();
node.move_to(position)
if self.modal {
let child = node.move_to(Point::new(position.x, position.y));
layout::Node::with_children(bounds, vec![child])
} else {
node.move_to(position)
}
}
fn operate(
@ -352,9 +357,14 @@ where
renderer: &Renderer,
operation: &mut dyn Operation<()>,
) {
let content_layout = if self.modal {
layout.children().next().unwrap()
} else {
layout
};
self.content
.as_widget()
.operate(self.tree, layout, renderer, operation);
.operate(self.tree, content_layout, renderer, operation);
}
fn on_event(
@ -366,9 +376,15 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let content_layout = if self.modal {
layout.children().next().unwrap()
} else {
layout
};
if self.modal
&& matches!(event, Event::Mouse(_) | Event::Touch(_))
&& !cursor_position.is_over(layout.bounds())
&& !cursor_position.is_over(content_layout.bounds())
{
return event::Status::Captured;
}
@ -376,12 +392,12 @@ where
self.content.as_widget_mut().on_event(
self.tree,
event,
layout,
content_layout,
cursor_position,
renderer,
clipboard,
shell,
&layout.bounds(),
&content_layout.bounds(),
)
}
@ -392,13 +408,19 @@ where
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
if self.modal && !cursor_position.is_over(layout.bounds()) {
let content_layout = if self.modal {
layout.children().next().unwrap()
} else {
layout
};
if self.modal && !cursor_position.is_over(content_layout.bounds()) {
return mouse::Interaction::None;
}
self.content.as_widget().mouse_interaction(
self.tree,
layout,
content_layout,
cursor_position,
viewport,
renderer,
@ -413,16 +435,42 @@ where
layout: Layout<'_>,
cursor_position: mouse::Cursor,
) {
let bounds = layout.bounds();
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
layout,
cursor_position,
&bounds,
);
if self.modal {
// Draw over the full window (layout covers entire window)
let full_bounds = layout.bounds();
renderer.fill_quad(
renderer::Quad {
bounds: full_bounds,
border: iced_core::Border::default(),
shadow: iced_core::Shadow::default(),
},
iced_core::Background::Color(iced_core::Color::from_rgba(0.0, 0.0, 0.0, 0.4)),
);
// Draw dialog content from the child node
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
content_layout,
cursor_position,
&content_bounds,
);
} else {
let bounds = layout.bounds();
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
layout,
cursor_position,
&bounds,
);
}
}
fn overlay<'c>(
@ -430,9 +478,14 @@ where
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'c, Message, crate::Theme, Renderer>> {
let content_layout = if self.modal {
layout.children().next().unwrap()
} else {
layout
};
self.content
.as_widget_mut()
.overlay(self.tree, layout, renderer, Default::default())
.overlay(self.tree, content_layout, renderer, Default::default())
}
}

View file

@ -124,7 +124,7 @@ impl ResponsiveMenuBar {
id_container(
menu::bar(vec![menu::Tree::<_>::with_children(
Element::from(
button::icon(icon::from_name("open-menu-symbolic"))
button::icon(icon::from_svg_bytes(icetron_assets::icons::system::MENU_LINE))
.padding([4, 12])
.class(crate::theme::Button::MenuRoot),
),

View file

@ -204,7 +204,7 @@ where
Self {
model,
id: Id::unique(),
close_icon: icon::from_name("window-close-symbolic").size(16).icon(),
close_icon: icon::from_svg_bytes(icetron_assets::icons::system::CLOSE_LINE).icon().size(16),
scrollable_focus: false,
show_close_icon_on_hover: false,
button_alignment: Alignment::Start,
@ -855,6 +855,7 @@ where
tab_drag_candidate: None,
dragging_tab: None,
drop_hint: None,
close_hovered: None,
offer_mimes: Vec::new(),
})
}
@ -1009,6 +1010,7 @@ where
"offer leave id={my_id:?} entity={entity:?}"
);
state.hovered = Item::None;
state.close_hovered = None;
for key in self.model.order.iter().copied() {
self.update_entity_paragraph(state, key);
}
@ -1063,6 +1065,7 @@ where
}
} else if entity.is_some() {
state.hovered = Item::None;
state.close_hovered = None;
for key in self.model.order.iter().copied() {
self.update_entity_paragraph(state, key);
}
@ -1153,6 +1156,7 @@ where
if let Some(event) = pending_reorder {
state.focused_item = Item::Tab(event.dragged);
state.hovered = Item::None;
state.close_hovered = None;
for key in self.model.order.iter().copied() {
self.update_entity_paragraph(state, key);
}
@ -1241,6 +1245,13 @@ where
let over_close_button = self.model.items[key].closable
&& cursor_position.is_over(close_button_bounds);
// Track close button hover state
state.close_hovered = if over_close_button {
Some(key)
} else {
None
};
// If marked as closable, show a close icon.
if self.model.items[key].closable {
// Emit close message if the close button is pressed.
@ -1353,6 +1364,7 @@ where
break;
} else if state.hovered == Item::Tab(key) {
state.hovered = Item::None;
state.close_hovered = None;
self.update_entity_paragraph(state, key);
}
}
@ -2030,6 +2042,28 @@ where
if show_close_button {
let close_button_bounds = close_bounds(original_bounds, close_icon_width);
// Draw a red background on the close button when hovered
if state.close_hovered == Some(key) {
let padding = 4.0;
let bg_bounds = Rectangle {
x: close_button_bounds.x - padding,
y: close_button_bounds.y - padding,
width: close_button_bounds.width + padding * 2.0,
height: close_button_bounds.height + padding * 2.0,
};
renderer.fill_quad(
renderer::Quad {
bounds: bg_bounds,
border: Border {
radius: (bg_bounds.width / 2.0).into(),
..Default::default()
},
shadow: Shadow::default(),
},
Background::Color(iced_core::Color::from_rgba(1.0, 0.0, 0.0, 0.1)),
);
}
draw_icon::<Message>(
renderer,
theme,
@ -2338,6 +2372,8 @@ pub struct LocalState {
dragging_tab: Option<Entity>,
/// Current drop hint for drag-and-drop indicator
drop_hint: Option<DropHint>,
/// Track whether the close button is hovered on a specific entity
close_hovered: Option<Entity>,
}
#[derive(Debug, Default, PartialEq)]
@ -2465,6 +2501,7 @@ mod tests {
tab_drag_candidate: None,
dragging_tab: Some(dragging),
drop_hint: None,
close_hovered: None,
offer_mimes: Vec::new(),
};
state.buttons_visible = len;

View file

@ -103,8 +103,8 @@ where
.spacing(val.icon_spacing)
.push(widget::text::heading(category.to_string()))
.push_maybe(match sort_state {
1 => Some(widget::icon::from_name("pan-up-symbolic").icon()),
2 => Some(widget::icon::from_name("pan-down-symbolic").icon()),
1 => Some(widget::icon::from_svg_bytes(icetron_assets::icons::system::ARROW_UP_S_LINE).icon()),
2 => Some(widget::icon::from_svg_bytes(icetron_assets::icons::system::ARROW_DOWN_S_LINE).icon()),
_ => None,
})
.apply(container)

View file

@ -96,7 +96,8 @@ where
.padding([0, spacing])
.style(crate::theme::TextInput::Search)
.leading_icon(
crate::widget::icon::from_name("system-search-symbolic")
crate::widget::icon::from_svg_bytes(icetron_assets::icons::system::SEARCH_LINE)
.icon()
.size(16)
.apply(crate::widget::container)
.padding(8)
@ -120,7 +121,8 @@ where
.padding([0, spacing])
.style(crate::theme::TextInput::Default)
.leading_icon(
crate::widget::icon::from_name("system-lock-screen-symbolic")
crate::widget::icon::from_svg_bytes(icetron_assets::icons::system::LOCK_LINE)
.icon()
.size(16)
.apply(crate::widget::container)
.padding(8)
@ -131,11 +133,12 @@ where
}
if let Some(msg) = on_visible_toggle {
input.trailing_icon(
crate::widget::icon::from_name(if hidden {
"document-properties-symbolic"
crate::widget::icon::from_svg_bytes(if hidden {
icetron_assets::icons::system::EYE_LINE
} else {
"image-red-eye-symbolic"
icetron_assets::icons::system::EYE_OFF_LINE
})
.icon()
.size(16)
.apply(crate::widget::button::custom)
.class(crate::theme::Button::Icon)
@ -537,7 +540,8 @@ where
pub fn on_clear(self, on_clear: Message) -> Self {
self.trailing_icon(
crate::widget::icon::from_name("edit-clear-symbolic")
crate::widget::icon::from_svg_bytes(icetron_assets::icons::system::CLOSE_CIRCLE_LINE)
.icon()
.size(16)
.apply(crate::widget::button::custom)
.class(crate::theme::Button::Icon)

View file

@ -42,7 +42,7 @@ pub fn toaster<'a, Message: Clone + 'static>(
button::text(&action.description).on_press((action.message)(id))
}))
.push(
button::icon(icon::from_name("window-close-symbolic"))
button::icon(icon::from_svg_bytes(icetron_assets::icons::system::CLOSE_LINE))
.on_press((toasts.on_close)(id)),
)
.align_y(iced::Alignment::Center)

View file

@ -33,8 +33,7 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> {
pub fn into_widget(self) -> widget::Container<'a, Message, crate::Theme, Renderer> {
let label = widget::container(crate::widget::text(self.message)).width(Length::Fill);
let close_button = icon::from_name("window-close-symbolic")
.size(16)
let close_button = icon::from_svg_bytes(icetron_assets::icons::system::CLOSE_LINE)
.apply(widget::button::icon)
.on_press_maybe(self.on_close);