libcosmic/src/theme/style/button.rs

286 lines
9.6 KiB
Rust
Raw Normal View History

// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Contains stylesheet implementation for [`crate::widget::button`].
use cosmic_theme::Component;
use iced_core::{Background, Color};
use crate::{
theme::TRANSPARENT_COMPONENT,
2024-10-16 20:36:46 -04:00
widget::button::{Catalog, Style},
};
#[derive(Default)]
pub enum Button {
AppletIcon,
AppletMenu,
Custom {
2024-10-16 20:36:46 -04:00
active: Box<dyn Fn(bool, &crate::Theme) -> Style>,
disabled: Box<dyn Fn(&crate::Theme) -> Style>,
hovered: Box<dyn Fn(bool, &crate::Theme) -> Style>,
pressed: Box<dyn Fn(bool, &crate::Theme) -> Style>,
},
Destructive,
HeaderBar,
Icon,
IconVertical,
Image,
Link,
2024-12-10 16:35:24 +01:00
ListItem,
2024-09-27 09:34:37 -06:00
MenuFolder,
MenuItem,
MenuRoot,
NavToggle,
#[default]
Standard,
Suggested,
Text,
Transparent,
}
pub fn appearance(
theme: &crate::Theme,
focused: bool,
selected: bool,
disabled: bool,
style: &Button,
color: impl Fn(&Component) -> (Color, Option<Color>, Option<Color>),
2024-10-16 20:36:46 -04:00
) -> Style {
let cosmic = theme.cosmic();
let mut corner_radii = &cosmic.corner_radii.radius_xl;
2024-10-16 20:36:46 -04:00
let mut appearance = Style::new();
2025-03-07 16:53:22 -05:00
let hc = theme.theme_type.is_high_contrast();
match style {
Button::Standard
| Button::Text
| 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,
_ => return 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;
2025-03-07 16:53:22 -05:00
} else if hc {
appearance.border_color = style_component.border.into();
appearance.border_width = 1.;
}
}
Button::Icon | Button::IconVertical | Button::HeaderBar | Button::NavToggle => {
2023-11-28 19:02:08 +00:00
if matches!(style, Button::IconVertical) {
corner_radii = &cosmic.corner_radii.radius_m;
if selected {
appearance.overlay = Some(Background::Color(Color::from(
cosmic.icon_button.selected_state_color(),
)));
}
}
if matches!(style, Button::NavToggle) {
corner_radii = &cosmic.corner_radii.radius_s;
}
let (background, text, icon) = color(&cosmic.icon_button);
appearance.background = Some(Background::Color(background));
// Only override icon button colors when it is disabled
appearance.icon_color = if disabled { icon } else { None };
appearance.text_color = if disabled { text } else { None };
}
Button::Image => {
appearance.background = None;
2025-07-21 10:49:47 -04:00
appearance.text_color = Some(cosmic.accent_text_color().into());
appearance.icon_color = Some(cosmic.accent.base.into());
corner_radii = &cosmic.corner_radii.radius_s;
appearance.border_radius = (*corner_radii).into();
if focused || selected {
appearance.border_width = 2.0;
appearance.border_color = cosmic.accent.base.into();
2025-03-07 16:53:22 -05:00
} else if hc {
appearance.border_color = theme.current_container().component.divider.into();
appearance.border_width = 1.;
}
return appearance;
}
Button::Link => {
appearance.background = None;
appearance.icon_color = Some(cosmic.accent.base.into());
2025-07-21 10:49:47 -04:00
appearance.text_color = Some(cosmic.accent_text_color().into());
corner_radii = &cosmic.corner_radii.radius_0;
}
Button::Custom { .. } => (),
Button::AppletMenu => {
let (background, _, _) = color(&cosmic.text_button);
appearance.background = Some(Background::Color(background));
appearance.icon_color = Some(cosmic.background.on.into());
appearance.text_color = Some(cosmic.background.on.into());
corner_radii = &cosmic.corner_radii.radius_0;
}
Button::AppletIcon => {
let (background, _, _) = color(&cosmic.text_button);
appearance.background = Some(Background::Color(background));
appearance.icon_color = Some(cosmic.background.on.into());
appearance.text_color = Some(cosmic.background.on.into());
}
2024-09-27 09:34:37 -06:00
Button::MenuFolder => {
// Menu folders cannot be disabled, ignore customized icon and text color
let component = &cosmic.background.component;
let (background, _, _) = color(component);
appearance.background = Some(Background::Color(background));
appearance.icon_color = Some(component.on.into());
appearance.text_color = Some(component.on.into());
corner_radii = &cosmic.corner_radii.radius_s;
2023-11-16 08:00:11 -07:00
}
2024-12-10 16:35:24 +01:00
Button::ListItem => {
corner_radii = &[0.0; 4];
let (background, text, icon) = color(&cosmic.background.component);
if selected {
appearance.background =
Some(Background::Color(cosmic.primary.component.hover.into()));
appearance.icon_color = Some(cosmic.accent.base.into());
2025-07-21 10:49:47 -04:00
appearance.text_color = Some(cosmic.accent_text_color().into());
2024-12-10 16:35:24 +01:00
} else {
appearance.background = Some(Background::Color(background));
appearance.icon_color = icon;
appearance.text_color = text;
}
}
2023-11-16 08:00:11 -07:00
Button::MenuItem => {
let (background, text, icon) = color(&cosmic.background.component);
2023-11-16 08:00:11 -07:00
appearance.background = Some(Background::Color(background));
appearance.icon_color = icon;
appearance.text_color = text;
2023-11-16 08:00:11 -07:00
corner_radii = &cosmic.corner_radii.radius_s;
}
2024-09-27 09:34:37 -06:00
Button::MenuRoot => {
appearance.background = None;
appearance.icon_color = None;
appearance.text_color = None;
}
}
appearance.border_radius = (*corner_radii).into();
if focused {
appearance.outline_width = 1.0;
appearance.outline_color = cosmic.accent.base.into();
appearance.border_width = 2.0;
appearance.border_color = Color::TRANSPARENT;
}
appearance
}
2024-10-16 20:36:46 -04:00
impl Catalog for crate::Theme {
type Class = Button;
2024-10-16 20:36:46 -04:00
fn active(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
if let Button::Custom { active, .. } = style {
return active(focused, self);
}
appearance(self, focused, selected, false, style, move |component| {
let text_color = if matches!(
style,
Button::Icon | Button::IconVertical | Button::HeaderBar
) && selected
{
2025-07-21 10:49:47 -04:00
Some(self.cosmic().accent_text_color().into())
2023-10-12 14:51:59 +02:00
} else {
Some(component.on.into())
};
(component.base.into(), text_color, text_color)
})
}
2024-10-16 20:36:46 -04:00
fn disabled(&self, style: &Self::Class) -> Style {
if let Button::Custom { disabled, .. } = style {
return disabled(self);
}
appearance(self, false, false, true, style, |component| {
let mut background = Color::from(component.base);
background.a *= 0.5;
(
background,
Some(component.on_disabled.into()),
Some(component.on_disabled.into()),
)
})
}
2024-10-16 20:36:46 -04:00
fn drop_target(&self, style: &Self::Class) -> Style {
self.active(false, false, style)
}
2024-10-16 20:36:46 -04:00
fn hovered(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
if let Button::Custom { hovered, .. } = style {
return hovered(focused, self);
}
appearance(
self,
focused || matches!(style, Button::Image),
selected,
false,
style,
|component| {
let text_color = if matches!(
style,
Button::Icon | Button::IconVertical | Button::HeaderBar
) && selected
{
2025-07-21 10:49:47 -04:00
Some(self.cosmic().accent_text_color().into())
} else {
Some(component.on.into())
};
(component.hover.into(), text_color, text_color)
},
)
}
2024-10-16 20:36:46 -04:00
fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
if let Button::Custom { pressed, .. } = style {
return pressed(focused, self);
}
appearance(self, focused, selected, false, style, |component| {
let text_color = if matches!(
style,
Button::Icon | Button::IconVertical | Button::HeaderBar
) && selected
{
2025-07-21 10:49:47 -04:00
Some(self.cosmic().accent_text_color().into())
} else {
Some(component.on.into())
};
(component.pressed.into(), text_color, text_color)
})
}
fn selection_background(&self) -> Background {
Background::Color(self.cosmic().primary.base.into())
}
}