feat(theme): improvements and refactoring of theme module

fix: theme rebase
This commit is contained in:
Michael Aaron Murphy 2023-09-13 15:49:11 +02:00 committed by Michael Murphy
parent 4c6c678f88
commit 7f0943924a
9 changed files with 1635 additions and 1228 deletions

134
src/theme/style/button.rs Normal file
View file

@ -0,0 +1,134 @@
// 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 palette::{rgb::Rgb, Alpha};
use crate::widget::button::{Appearance, StyleSheet};
#[derive(Copy, Clone, Debug, Default)]
pub enum Button {
Destructive,
Link,
Icon,
IconVertical,
#[default]
Standard,
Suggested,
Text,
}
pub fn appearance(
theme: &crate::Theme,
focused: bool,
style: &Button,
color: impl Fn(&Component<Alpha<Rgb, f32>>) -> (Color, Color, Option<Color>),
) -> Appearance {
let cosmic = theme.cosmic();
let mut corner_radii = &cosmic.corner_radii.radius_xl;
let mut appearance = Appearance::new();
match style {
Button::Standard | Button::Text | Button::Suggested | Button::Destructive => {
let style_component = match style {
Button::Standard => &cosmic.button,
Button::Text => &cosmic.text_button,
Button::Suggested => &cosmic.accent_button,
Button::Destructive => &cosmic.destructive_button,
_ => return appearance,
};
let (background, text, icon) = color(style_component);
appearance.background = Some(Background::Color(background));
appearance.text_color = text;
appearance.icon_color = icon;
}
Button::Icon | Button::IconVertical => {
if let Button::IconVertical = style {
corner_radii = &cosmic.corner_radii.radius_m;
}
let (background, text, icon) = color(&cosmic.icon_button);
appearance.background = Some(Background::Color(background));
if focused {
appearance.text_color = cosmic.accent.on.into();
appearance.icon_color = Some(cosmic.accent.on.into());
} else {
appearance.text_color = text;
appearance.icon_color = icon;
}
}
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(),
component.on.into(),
Some(component.on.into()),
)
})
}
fn disabled(&self, style: &Self::Style) -> Appearance {
appearance(self, false, style, |component| {
let mut background = Color::from(component.base);
background.a *= 0.5;
(
background,
component.on_disabled.into(),
Some(component.on_disabled.into()),
)
})
}
fn drop_target(&self, style: &Self::Style) -> Appearance {
self.active(false, style)
}
fn hovered(&self, focused: bool, style: &Self::Style) -> Appearance {
appearance(self, focused, style, |component| {
(
component.hover.into(),
component.on.into(),
Some(component.on.into()),
)
})
}
fn pressed(&self, focused: bool, style: &Self::Style) -> Appearance {
appearance(self, focused, style, |component| {
(
component.pressed.into(),
component.on.into(),
Some(component.on.into()),
)
})
}
}

1057
src/theme/style/iced.rs Normal file

File diff suppressed because it is too large Load diff

22
src/theme/style/mod.rs Normal file
View file

@ -0,0 +1,22 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Stylesheet implements for [`crate::Theme`]
mod button;
pub use self::button::Button;
pub mod iced;
pub use iced::Application;
pub use iced::Checkbox;
pub use iced::Container;
pub use iced::ProgressBar;
pub use iced::Rule;
pub use iced::Svg;
pub use iced::Text;
mod segmented_button;
pub use self::segmented_button::SegmentedButton;
mod text_input;
pub use self::text_input::TextInput;

View file

@ -0,0 +1,273 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Contains stylesheet implementation for [`crate::widget::segmented_button`].
use crate::widget::segmented_button::{Appearance, ItemAppearance, StyleSheet};
use crate::{theme::Theme, widget::segmented_button::ItemStatusAppearance};
use iced_core::{Background, BorderRadius};
use palette::{rgb::Rgb, Alpha};
#[derive(Default)]
pub enum SegmentedButton {
/// A tabbed widget for switching between views in an interface.
#[default]
ViewSwitcher,
/// A widget for multiple choice selection.
Selection,
/// Or implement any custom theme of your liking.
Custom(Box<dyn Fn(&Theme) -> Appearance>),
}
impl StyleSheet for Theme {
type Style = SegmentedButton;
#[allow(clippy::too_many_lines)]
fn horizontal(&self, style: &Self::Style) -> Appearance {
match style {
SegmentedButton::ViewSwitcher => {
let cosmic = self.cosmic();
let active = horizontal::view_switcher_active(cosmic);
Appearance {
border_radius: BorderRadius::from(0.0),
inactive: ItemStatusAppearance {
background: None,
first: ItemAppearance {
border_radius: BorderRadius::from(0.0),
border_bottom: Some((1.0, cosmic.accent.base.into())),
..Default::default()
},
middle: ItemAppearance {
border_radius: BorderRadius::from(0.0),
border_bottom: Some((1.0, cosmic.accent.base.into())),
..Default::default()
},
last: ItemAppearance {
border_radius: BorderRadius::from(0.0),
border_bottom: Some((1.0, cosmic.accent.base.into())),
..Default::default()
},
text_color: cosmic.on_bg_color().into(),
},
hover: hover(cosmic, &active),
focus: focus(cosmic, &active),
active,
..Default::default()
}
}
SegmentedButton::Selection => {
let cosmic = self.cosmic();
let active = horizontal::selection_active(cosmic);
let mut neutral_5 = cosmic.palette.neutral_5;
neutral_5.alpha = 0.2;
Appearance {
border_radius: BorderRadius::from(0.0),
inactive: ItemStatusAppearance {
background: Some(Background::Color(neutral_5.into())),
first: ItemAppearance {
border_radius: BorderRadius::from([24.0, 0.0, 0.0, 24.0]),
..Default::default()
},
middle: ItemAppearance {
border_radius: BorderRadius::from(0.0),
..Default::default()
},
last: ItemAppearance {
border_radius: BorderRadius::from([0.0, 24.0, 24.0, 0.0]),
..Default::default()
},
text_color: cosmic.on_bg_color().into(),
},
hover: hover(cosmic, &active),
focus: focus(cosmic, &active),
active,
..Default::default()
}
}
SegmentedButton::Custom(func) => func(self),
}
}
#[allow(clippy::too_many_lines)]
fn vertical(&self, style: &Self::Style) -> Appearance {
match style {
SegmentedButton::ViewSwitcher => {
let cosmic = self.cosmic();
let active = vertical::view_switcher_active(cosmic);
Appearance {
border_radius: BorderRadius::from(0.0),
inactive: ItemStatusAppearance {
background: None,
text_color: cosmic.on_bg_color().into(),
..active
},
hover: hover(cosmic, &active),
focus: focus(cosmic, &active),
active,
..Default::default()
}
}
SegmentedButton::Selection => {
let cosmic = self.cosmic();
let active = vertical::selection_active(cosmic);
let mut neutral_5 = cosmic.palette.neutral_5;
neutral_5.alpha = 0.2;
Appearance {
border_radius: BorderRadius::from(0.0),
inactive: ItemStatusAppearance {
background: Some(Background::Color(neutral_5.into())),
first: ItemAppearance {
border_radius: BorderRadius::from([24.0, 24.0, 0.0, 0.0]),
..Default::default()
},
middle: ItemAppearance {
border_radius: BorderRadius::from(0.0),
..Default::default()
},
last: ItemAppearance {
border_radius: BorderRadius::from([0.0, 0.0, 24.0, 24.0]),
..Default::default()
},
text_color: cosmic.on_bg_color().into(),
},
hover: hover(cosmic, &active),
focus: focus(cosmic, &active),
active,
..Default::default()
}
}
SegmentedButton::Custom(func) => func(self),
}
}
}
mod horizontal {
use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance};
use iced_core::{Background, BorderRadius};
use palette::{rgb::Rgb, Alpha};
pub fn selection_active(cosmic: &cosmic_theme::Theme<Alpha<Rgb, f32>>) -> ItemStatusAppearance {
let mut neutral_5 = cosmic.palette.neutral_5;
neutral_5.alpha = 0.2;
ItemStatusAppearance {
background: Some(Background::Color(neutral_5.into())),
first: ItemAppearance {
border_radius: BorderRadius::from([24.0, 0.0, 0.0, 24.0]),
..Default::default()
},
middle: ItemAppearance {
border_radius: BorderRadius::from(0.0),
..Default::default()
},
last: ItemAppearance {
border_radius: BorderRadius::from([0.0, 24.0, 24.0, 0.0]),
..Default::default()
},
text_color: cosmic.accent.base.into(),
}
}
pub fn view_switcher_active(
cosmic: &cosmic_theme::Theme<Alpha<Rgb, f32>>,
) -> ItemStatusAppearance {
let mut neutral_5 = cosmic.palette.neutral_5;
neutral_5.alpha = 0.2;
ItemStatusAppearance {
background: Some(Background::Color(neutral_5.into())),
first: ItemAppearance {
border_radius: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_bottom: Some((4.0, cosmic.accent.base.into())),
..Default::default()
},
middle: ItemAppearance {
border_radius: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_bottom: Some((4.0, cosmic.accent.base.into())),
..Default::default()
},
last: ItemAppearance {
border_radius: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_bottom: Some((4.0, cosmic.accent.base.into())),
..Default::default()
},
text_color: cosmic.accent.base.into(),
}
}
}
pub fn focus(
cosmic: &cosmic_theme::Theme<Alpha<Rgb, f32>>,
default: &ItemStatusAppearance,
) -> ItemStatusAppearance {
// TODO: This is a hack to make the hover color lighter than the selected color
// I'm not sure why the alpha is being applied differently here than in figma
let mut neutral_5 = cosmic.palette.neutral_5;
neutral_5.alpha = 0.2;
ItemStatusAppearance {
background: Some(Background::Color(neutral_5.into())),
text_color: cosmic.accent.base.into(),
..*default
}
}
pub fn hover(
cosmic: &cosmic_theme::Theme<Alpha<Rgb, f32>>,
default: &ItemStatusAppearance,
) -> ItemStatusAppearance {
let mut neutral_10 = cosmic.palette.neutral_10;
neutral_10.alpha = 0.1;
ItemStatusAppearance {
background: Some(Background::Color(neutral_10.into())),
text_color: cosmic.accent.base.into(),
..*default
}
}
mod vertical {
use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance};
use iced_core::{Background, BorderRadius};
use palette::{rgb::Rgb, Alpha};
pub fn selection_active(cosmic: &cosmic_theme::Theme<Alpha<Rgb, f32>>) -> ItemStatusAppearance {
let mut neutral_5 = cosmic.palette.neutral_5;
neutral_5.alpha = 0.2;
ItemStatusAppearance {
background: Some(Background::Color(neutral_5.into())),
first: ItemAppearance {
border_radius: BorderRadius::from([24.0, 24.0, 0.0, 0.0]),
..Default::default()
},
middle: ItemAppearance {
border_radius: BorderRadius::from(0.0),
..Default::default()
},
last: ItemAppearance {
border_radius: BorderRadius::from([0.0, 0.0, 24.0, 24.0]),
..Default::default()
},
text_color: cosmic.accent.base.into(),
}
}
pub fn view_switcher_active(
cosmic: &cosmic_theme::Theme<Alpha<Rgb, f32>>,
) -> ItemStatusAppearance {
let mut neutral_5 = cosmic.palette.neutral_5;
neutral_5.alpha = 0.2;
ItemStatusAppearance {
background: Some(Background::Color(neutral_5.into())),
first: ItemAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
middle: ItemAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
last: ItemAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
text_color: cosmic.accent.base.into(),
}
}
}

View file

@ -0,0 +1,328 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Contains stylesheet implementation for [`cosmic::widget::text_input`].
use crate::ext::ColorExt;
use crate::widget::text_input::{Appearance, StyleSheet};
use iced_core::Color;
#[derive(Default)]
pub enum TextInput {
#[default]
Default,
ExpandableSearch,
Search,
Inline,
Custom {
active: Box<dyn Fn(&crate::Theme) -> Appearance>,
error: Box<dyn Fn(&crate::Theme) -> Appearance>,
hovered: Box<dyn Fn(&crate::Theme) -> Appearance>,
focused: Box<dyn Fn(&crate::Theme) -> Appearance>,
disabled: Box<dyn Fn(&crate::Theme) -> Appearance>,
},
}
impl StyleSheet for crate::Theme {
type Style = TextInput;
fn active(&self, style: &Self::Style) -> Appearance {
let palette = self.cosmic();
let container = self.current_container();
let mut background: Color = container.component.base.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(),
border_radius: corner.radius_s.into(),
border_width: 1.0,
border_offset: None,
border_color: container.component.divider.into(),
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 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: Color::TRANSPARENT.into(),
border_radius: corner.radius_xl.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 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(),
border_radius: corner.radius_xl.into(),
border_width: 1.0,
border_offset: None,
border_color: container.component.divider.into(),
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Inline => Appearance {
background: Color::TRANSPARENT.into(),
border_radius: corner.radius_0.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Custom { active, .. } => active(self),
}
}
fn error(&self, style: &Self::Style) -> Appearance {
let palette = self.cosmic();
let container = self.current_container();
let mut background: Color = container.component.base.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(),
border_radius: corner.radius_s.into(),
border_width: 1.0,
border_offset: Some(2.0),
border_color: Color::from(palette.destructive_color()),
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 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(),
border_radius: corner.radius_xl.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Inline => Appearance {
background: Color::TRANSPARENT.into(),
border_radius: corner.radius_0.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Custom { error, .. } => error(self),
}
}
fn hovered(&self, style: &Self::Style) -> Appearance {
let palette = self.cosmic();
let container = self.current_container();
let mut background: Color = container.component.base.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(),
border_radius: corner.radius_s.into(),
border_width: 1.0,
border_offset: None,
border_color: palette.accent.base.into(),
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 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(),
border_radius: corner.radius_xl.into(),
border_offset: None,
border_width: 1.0,
border_color: palette.accent.base.into(),
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 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(),
border_radius: corner.radius_xl.into(),
border_offset: None,
border_width: 0.0,
border_color: Color::TRANSPARENT,
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Inline => Appearance {
background: Color::from(container.component.hover).into(),
border_radius: corner.radius_0.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Custom { hovered, .. } => hovered(self),
}
}
fn focused(&self, style: &Self::Style) -> Appearance {
let palette = self.cosmic();
let container = self.current_container();
let mut background: Color = container.component.base.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(),
border_radius: corner.radius_s.into(),
border_width: 1.0,
border_offset: Some(2.0),
border_color: palette.accent.base.into(),
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 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(),
border_radius: corner.radius_xl.into(),
border_width: 1.0,
border_offset: Some(2.0),
border_color: palette.accent.base.into(),
icon_color: container.on.into(),
text_color: container.on.into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Inline => Appearance {
background: Color::from(palette.accent.base).into(),
border_radius: corner.radius_0.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
icon_color: container.on.into(),
// TODO use regular text color here after text rendering handles multiple colors
// in this case, for selected and unselected text
text_color: palette.on_accent_color().into(),
placeholder_color: {
let color: Color = container.on.into();
color.blend_alpha(background, 0.7)
},
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Custom { focused, .. } => focused(self),
}
}
fn disabled(&self, style: &Self::Style) -> Appearance {
if let TextInput::Custom { disabled, .. } = style {
return disabled(self);
}
let mut appearance = self.active(style);
// TODO: iced will not render alpha itself on text or icon colors.
let background: Color = self.current_container().component.base.into();
appearance.text_color = appearance.text_color.blend_alpha(background, 0.5);
appearance.icon_color = appearance.icon_color.blend_alpha(background, 0.5);
appearance
}
}