feat!(widget): rewrite button & icon widget APIs
This commit is contained in:
parent
18debe546d
commit
4e4eeaac12
60 changed files with 2191 additions and 1113 deletions
113
src/theme/button.rs
Normal file
113
src/theme/button.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
144
src/theme/mod.rs
144
src/theme/mod.rs
|
|
@ -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()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue