feat!(widget): rewrite button & icon widget APIs

This commit is contained in:
Michael Aaron Murphy 2023-09-01 07:29:19 +02:00 committed by Michael Murphy
parent 18debe546d
commit 4e4eeaac12
60 changed files with 2191 additions and 1113 deletions

113
src/theme/button.rs Normal file
View file

@ -0,0 +1,113 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use cosmic_theme::Component;
use iced_core::{Background, Color};
use palette::{rgb::Rgb, Alpha};
use crate::{
app,
widget::button::{Appearance, StyleSheet},
};
#[derive(Copy, Clone, Debug, Default)]
pub enum Button {
Destructive,
Link,
Icon,
#[default]
Standard,
Suggested,
Text,
}
pub fn appearance(
theme: &crate::Theme,
focused: bool,
style: &Button,
color: fn(&Component<Alpha<Rgb, f32>>) -> Color,
) -> Appearance {
let cosmic = theme.cosmic();
let mut corner_radii = &cosmic.corner_radii.radius_xl;
let mut appearance = Appearance::new();
match style {
Button::Standard => {
let component = &theme.current_container().component;
appearance.background = Some(Background::Color(color(component)));
appearance.text_color = component.on.into();
}
Button::Icon | Button::Text => {
let component = &cosmic.text_button;
appearance.background = None;
appearance.text_color = component.on.into();
}
Button::Suggested => {
let component = &cosmic.accent_button;
appearance.background = Some(Background::Color(color(component)));
appearance.icon_color = Some(component.on.into());
appearance.text_color = component.on.into();
}
Button::Destructive => {
let component = &cosmic.destructive_button;
appearance.background = Some(Background::Color(color(component)));
appearance.icon_color = Some(component.on.into());
appearance.text_color = component.on.into();
}
Button::Link => {
appearance.background = None;
appearance.icon_color = Some(cosmic.accent.base.into());
appearance.text_color = cosmic.accent.base.into();
corner_radii = &cosmic.corner_radii.radius_0;
}
}
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 StyleSheet for crate::Theme {
type Style = Button;
fn active(&self, focused: bool, style: &Self::Style) -> Appearance {
appearance(self, focused, style, |component| component.base.into())
}
fn disabled(&self, style: &Self::Style) -> Appearance {
appearance(self, false, style, |component| {
let mut color = Color::from(component.base);
color.a *= 0.5;
color
})
}
fn drop_target(&self, style: &Self::Style) -> Appearance {
let mut appearance = self.active(false, style);
appearance
}
fn hovered(&self, focused: bool, style: &Self::Style) -> Appearance {
appearance(self, focused, style, |component| component.hover.into())
}
fn pressed(&self, focused: bool, style: &Self::Style) -> Appearance {
appearance(self, focused, style, |component| component.pressed.into())
}
fn selected(&self, focused: bool, style: &Self::Style) -> Appearance {
appearance(self, focused, style, |component| component.selected.into())
}
}

View file

@ -4,17 +4,18 @@
//! Use COSMIC's themes and styles.
pub mod expander;
mod button;
pub use self::button::Button;
mod segmented_button;
pub use self::segmented_button::SegmentedButton;
use std::cell::RefCell;
use std::f32::consts::PI;
use std::hash::Hash;
use std::hash::Hasher;
use std::rc::Rc;
use std::sync::Arc;
pub use self::segmented_button::SegmentedButton;
use cosmic_config::config_subscription;
use cosmic_config::CosmicConfigEntry;
use cosmic_theme::composite::over;
@ -26,7 +27,7 @@ use iced_core::BorderRadius;
use iced_core::Radians;
use iced_futures::Subscription;
use iced_style::application;
use iced_style::button;
use iced_style::button as iced_button;
use iced_style::checkbox;
use iced_style::container;
use iced_style::menu;
@ -195,6 +196,7 @@ impl application::StyleSheet for Theme {
match style {
Application::Default => application::Appearance {
icon_color: cosmic.bg_color().into(),
background_color: cosmic.bg_color().into(),
text_color: cosmic.on_bg_color().into(),
},
@ -203,13 +205,13 @@ impl application::StyleSheet for Theme {
}
}
/*
* TODO: Button
*/
pub enum Button {
/// Styles for the button widget from iced-rs.
#[derive(Default)]
pub enum IcedButton {
Deactivated,
Destructive,
Positive,
#[default]
Primary,
Secondary,
Text,
@ -218,110 +220,104 @@ pub enum Button {
Transparent,
Card,
Custom {
active: Box<dyn Fn(&Theme) -> button::Appearance>,
hover: Box<dyn Fn(&Theme) -> button::Appearance>,
active: Box<dyn Fn(&Theme) -> iced_button::Appearance>,
hover: Box<dyn Fn(&Theme) -> iced_button::Appearance>,
},
}
impl Default for Button {
fn default() -> Self {
Self::Primary
}
}
impl Button {
impl IcedButton {
#[allow(clippy::trivially_copy_pass_by_ref)]
#[allow(clippy::match_same_arms)]
fn cosmic<'a>(&'a self, theme: &'a Theme) -> &CosmicComponent {
let cosmic = theme.cosmic();
match self {
Button::Primary => &cosmic.accent_button,
Button::Secondary => &theme.current_container().component,
Button::Positive => &cosmic.success_button,
Button::Destructive => &cosmic.destructive_button,
Button::Text => &cosmic.text_button,
Button::Link => &cosmic.accent_button,
Button::LinkActive => &cosmic.accent_button,
Button::Transparent => &TRANSPARENT_COMPONENT,
Button::Deactivated => &theme.current_container().component,
Button::Card => &theme.current_container().component,
Button::Custom { .. } => &TRANSPARENT_COMPONENT,
IcedButton::Primary => &cosmic.accent_button,
IcedButton::Secondary => &theme.current_container().component,
IcedButton::Positive => &cosmic.success_button,
IcedButton::Destructive => &cosmic.destructive_button,
IcedButton::Text => &cosmic.text_button,
IcedButton::Link => &cosmic.accent_button,
IcedButton::LinkActive => &cosmic.accent_button,
IcedButton::Transparent => &TRANSPARENT_COMPONENT,
IcedButton::Deactivated => &theme.current_container().component,
IcedButton::Card => &theme.current_container().component,
IcedButton::Custom { .. } => &TRANSPARENT_COMPONENT,
}
}
}
impl button::StyleSheet for Theme {
type Style = Button;
impl iced_button::StyleSheet for Theme {
type Style = IcedButton;
fn active(&self, style: &Self::Style) -> button::Appearance {
if let Button::Custom { active, .. } = style {
fn active(&self, style: &Self::Style) -> iced_button::Appearance {
if let IcedButton::Custom { active, .. } = style {
return active(self);
}
let corner_radii = &self.cosmic().corner_radii;
let component = style.cosmic(self);
button::Appearance {
iced_button::Appearance {
border_radius: match style {
Button::Link => corner_radii.radius_0.into(),
Button::Card => corner_radii.radius_xs.into(),
IcedButton::Link => corner_radii.radius_0.into(),
IcedButton::Card => corner_radii.radius_xs.into(),
_ => corner_radii.radius_xl.into(),
},
background: match style {
Button::Link | Button::Text => None,
Button::LinkActive => Some(Background::Color(component.divider.into())),
IcedButton::Link | IcedButton::Text => None,
IcedButton::LinkActive => Some(Background::Color(component.divider.into())),
_ => Some(Background::Color(component.base.into())),
},
text_color: match style {
Button::Link | Button::LinkActive => component.base.into(),
IcedButton::Link | IcedButton::LinkActive => component.base.into(),
_ => component.on.into(),
},
..button::Appearance::default()
..iced_button::Appearance::default()
}
}
fn hovered(&self, style: &Self::Style) -> button::Appearance {
if let Button::Custom { hover, .. } = style {
fn hovered(&self, style: &Self::Style) -> iced_button::Appearance {
if let IcedButton::Custom { hover, .. } = style {
return hover(self);
}
let active = self.active(style);
let component = style.cosmic(self);
button::Appearance {
iced_button::Appearance {
background: match style {
Button::Link => None,
Button::LinkActive => Some(Background::Color(component.divider.into())),
IcedButton::Link => None,
IcedButton::LinkActive => Some(Background::Color(component.divider.into())),
_ => Some(Background::Color(component.hover.into())),
},
..active
}
}
fn focused(&self, style: &Self::Style) -> button::Appearance {
if let Button::Custom { hover, .. } = style {
fn focused(&self, style: &Self::Style) -> iced_button::Appearance {
if let IcedButton::Custom { hover, .. } = style {
return hover(self);
}
let active = self.active(style);
let component = style.cosmic(self);
button::Appearance {
iced_button::Appearance {
background: match style {
Button::Link => None,
Button::LinkActive => Some(Background::Color(component.divider.into())),
IcedButton::Link => None,
IcedButton::LinkActive => Some(Background::Color(component.divider.into())),
_ => Some(Background::Color(component.hover.into())),
},
..active
}
}
fn disabled(&self, style: &Self::Style) -> button::Appearance {
fn disabled(&self, style: &Self::Style) -> iced_button::Appearance {
let active = self.active(style);
if matches!(style, Button::Card) {
if matches!(style, IcedButton::Card) {
return active;
}
button::Appearance {
iced_button::Appearance {
shadow_offset: iced_core::Vector::default(),
background: active.background.map(|background| match background {
Background::Color(color) => Background::Color(Color {
@ -563,6 +559,7 @@ impl container::StyleSheet for Theme {
let palette = self.cosmic();
container::Appearance {
icon_color: Some(Color::from(palette.background.on)),
text_color: Some(Color::from(palette.background.on)),
background: Some(iced::Background::Color(palette.background.base.into())),
border_radius: 2.0.into(),
@ -577,6 +574,7 @@ impl container::StyleSheet for Theme {
header_top.alpha = 0.8;
container::Appearance {
icon_color: Some(Color::from(palette.accent.base)),
text_color: Some(Color::from(palette.background.on)),
background: Some(iced::Background::Gradient(iced_core::Gradient::Linear(
Linear::new(Radians(3.0 * PI / 2.0))
@ -592,6 +590,7 @@ impl container::StyleSheet for Theme {
let palette = self.cosmic();
container::Appearance {
icon_color: Some(Color::from(palette.primary.on)),
text_color: Some(Color::from(palette.primary.on)),
background: Some(iced::Background::Color(palette.primary.base.into())),
border_radius: 2.0.into(),
@ -603,6 +602,7 @@ impl container::StyleSheet for Theme {
let palette = self.cosmic();
container::Appearance {
icon_color: Some(Color::from(palette.secondary.on)),
text_color: Some(Color::from(palette.secondary.on)),
background: Some(iced::Background::Color(palette.secondary.base.into())),
border_radius: 2.0.into(),
@ -615,6 +615,7 @@ impl container::StyleSheet for Theme {
match self.layer {
cosmic_theme::Layer::Background => container::Appearance {
icon_color: Some(Color::from(palette.background.component.on)),
text_color: Some(Color::from(palette.background.component.on)),
background: Some(iced::Background::Color(
palette.background.component.base.into(),
@ -624,6 +625,7 @@ impl container::StyleSheet for Theme {
border_color: Color::TRANSPARENT,
},
cosmic_theme::Layer::Primary => container::Appearance {
icon_color: Some(Color::from(palette.primary.component.on)),
text_color: Some(Color::from(palette.primary.component.on)),
background: Some(iced::Background::Color(
palette.primary.component.base.into(),
@ -633,6 +635,7 @@ impl container::StyleSheet for Theme {
border_color: Color::TRANSPARENT,
},
cosmic_theme::Layer::Secondary => container::Appearance {
icon_color: Some(Color::from(palette.secondary.component.on)),
text_color: Some(Color::from(palette.secondary.component.on)),
background: Some(iced::Background::Color(
palette.secondary.component.base.into(),
@ -1021,29 +1024,6 @@ pub enum Svg {
/// No filtering is applied
#[default]
Default,
/// Icon fill color will match text color
Symbolic,
/// Icon fill color will match accent color
SymbolicActive,
/// Icon fill color will match on primary color
SymbolicPrimary,
/// Icon fill color will use accent color
SymbolicLink,
}
impl Hash for Svg {
fn hash<H: Hasher>(&self, state: &mut H) {
let id = match self {
Svg::Custom(_) => 0,
Svg::Default => 1,
Svg::Symbolic => 2,
Svg::SymbolicActive => 3,
Svg::SymbolicPrimary => 4,
Svg::SymbolicLink => 5,
};
id.hash(state);
}
}
impl Svg {
@ -1060,18 +1040,6 @@ impl svg::StyleSheet for Theme {
match style {
Svg::Default => svg::Appearance::default(),
Svg::Custom(appearance) => appearance(self),
Svg::Symbolic => svg::Appearance {
color: Some(self.current_container().on.into()),
},
Svg::SymbolicActive => svg::Appearance {
color: Some(self.cosmic().accent.base.into()),
},
Svg::SymbolicPrimary => svg::Appearance {
color: Some(self.cosmic().accent.on.into()),
},
Svg::SymbolicLink => svg::Appearance {
color: Some(self.cosmic().accent.base.into()),
},
}
}
}