use cosmic_theme::Component;
use iced_core::{Background, Color};
use crate::{
theme::TRANSPARENT_COMPONENT,
widget::button::{Catalog, Style},
};
#[derive(Default)]
pub enum Button {
AppletIcon,
Custom {
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>,
},
AppletMenu,
Destructive,
HeaderBar,
Icon,
IconVertical,
Image,
Link,
MenuFolder,
MenuItem,
MenuRoot,
#[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>),
) -> Style {
let cosmic = theme.cosmic();
let mut corner_radii = &cosmic.corner_radii.radius_xl;
let mut appearance = Style::new();
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;
}
}
Button::Icon | Button::IconVertical | Button::HeaderBar => {
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(),
)));
}
}
let (background, text, icon) = color(&cosmic.icon_button);
appearance.background = Some(Background::Color(background));
appearance.icon_color = if disabled { icon } else { None };
appearance.text_color = if disabled { text } else { None };
}
Button::Image => {
appearance.background = None;
appearance.text_color = Some(cosmic.accent.base.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();
}
return appearance;
}
Button::Link => {
appearance.background = None;
appearance.icon_color = Some(cosmic.accent.base.into());
appearance.text_color = Some(cosmic.accent.base.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());
}
Button::MenuFolder => {
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;
}
Button::MenuItem => {
let (background, text, icon) = color(&cosmic.background.component);
appearance.background = Some(Background::Color(background));
appearance.icon_color = icon;
appearance.text_color = text;
corner_radii = &cosmic.corner_radii.radius_s;
}
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
}
impl Catalog for crate::Theme {
type Class = Button;
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
{
Some(self.cosmic().accent_color().into())
} else {
Some(component.on.into())
};
(component.base.into(), text_color, text_color)
})
}
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()),
)
})
}
fn drop_target(&self, style: &Self::Class) -> Style {
self.active(false, false, style)
}
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
{
Some(self.cosmic().accent_color().into())
} else {
Some(component.on.into())
};
(component.hover.into(), text_color, text_color)
},
)
}
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
{
Some(self.cosmic().accent_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())
}
}