#![allow(clippy::doc_markdown)]
use super::{Status, StyleFn};
use iced_core::{Background, Color, Theme};
#[derive(Clone, Copy, Debug)]
pub struct Style {
pub background: Background,
pub border_radius: f32,
pub border_width: f32,
pub border_color: Color,
pub text_color: Color,
pub clock_number_color: Color,
pub clock_number_background: Color,
pub clock_dots_color: Color,
pub clock_hand_color: Color,
pub clock_hand_width: f32,
}
pub trait Catalog {
type Class<'a>;
fn default<'a>() -> Self::Class<'a>;
fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
}
impl Catalog for Theme {
type Class<'a> = StyleFn<'a, Self, Style>;
fn default<'a>() -> Self::Class<'a> {
Box::new(primary)
}
fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
class(self, status)
}
}
#[must_use]
pub fn primary(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
let foreground = theme.palette();
let base = Style {
background: palette.background.base.color.into(),
border_radius: 15.0,
border_width: 1.0,
border_color: foreground.text,
text_color: foreground.text,
clock_number_color: foreground.text,
clock_number_background: palette.background.base.color,
clock_dots_color: [0.87, 0.87, 0.87].into(),
clock_hand_color: [0.87, 0.87, 0.87].into(),
clock_hand_width: 3.0,
};
match status {
Status::Focused => Style {
border_color: Color::from_rgb(0.5, 0.5, 0.5),
..base
},
Status::Hovered => Style {
clock_number_color: palette.primary.weak.text,
clock_number_background: palette.primary.weak.color,
..base
},
Status::Selected => Style {
clock_number_color: palette.primary.strong.text,
clock_number_background: palette.primary.strong.color,
..base
},
_ => base,
}
}
#[cfg(test)]
mod tests {
use super::*;
use iced_core::{Background, Theme};
#[test]
fn primary_theme_active() {
let theme = Theme::TokyoNight;
let style = primary(&theme, Status::Active);
assert!(matches!(style.background, Background::Color(_)));
assert_eq!(style.border_radius, 15.0);
assert_eq!(style.border_width, 1.0);
assert_eq!(style.clock_hand_width, 3.0);
}
#[test]
fn primary_theme_focused() {
let theme = Theme::TokyoNight;
let style = primary(&theme, Status::Focused);
assert!(matches!(style.background, Background::Color(_)));
assert_eq!(style.border_color, Color::from_rgb(0.5, 0.5, 0.5));
assert_eq!(style.border_radius, 15.0);
assert_eq!(style.border_width, 1.0);
}
#[test]
fn primary_theme_hovered() {
let theme = Theme::TokyoNight;
let style = primary(&theme, Status::Hovered);
assert!(matches!(style.background, Background::Color(_)));
assert_eq!(style.border_radius, 15.0);
assert_eq!(style.border_width, 1.0);
}
#[test]
fn primary_theme_selected() {
let theme = Theme::TokyoNight;
let style = primary(&theme, Status::Selected);
assert!(matches!(style.background, Background::Color(_)));
assert_eq!(style.border_radius, 15.0);
assert_eq!(style.border_width, 1.0);
}
#[test]
fn primary_theme_disabled() {
let theme = Theme::TokyoNight;
let style = primary(&theme, Status::Disabled);
assert!(matches!(style.background, Background::Color(_)));
assert_eq!(style.border_radius, 15.0);
assert_eq!(style.border_width, 1.0);
}
#[test]
fn focused_changes_border_color() {
let theme = Theme::TokyoNight;
let base_style = primary(&theme, Status::Active);
let focused_style = primary(&theme, Status::Focused);
assert_ne!(base_style.border_color, focused_style.border_color);
assert_eq!(focused_style.border_color, Color::from_rgb(0.5, 0.5, 0.5));
assert_eq!(base_style.background, focused_style.background);
assert_eq!(base_style.border_radius, focused_style.border_radius);
assert_eq!(base_style.border_width, focused_style.border_width);
assert_eq!(base_style.text_color, focused_style.text_color);
}
#[test]
fn hovered_changes_clock_number_styling() {
let theme = Theme::TokyoNight;
let base_style = primary(&theme, Status::Active);
let hovered_style = primary(&theme, Status::Hovered);
assert_ne!(
base_style.clock_number_color,
hovered_style.clock_number_color
);
assert_ne!(
base_style.clock_number_background,
hovered_style.clock_number_background
);
assert_eq!(base_style.background, hovered_style.background);
assert_eq!(base_style.border_color, hovered_style.border_color);
assert_eq!(base_style.text_color, hovered_style.text_color);
}
#[test]
fn selected_changes_clock_number_styling() {
let theme = Theme::TokyoNight;
let base_style = primary(&theme, Status::Active);
let selected_style = primary(&theme, Status::Selected);
assert_ne!(
base_style.clock_number_color,
selected_style.clock_number_color
);
assert_ne!(
base_style.clock_number_background,
selected_style.clock_number_background
);
assert_eq!(base_style.background, selected_style.background);
assert_eq!(base_style.border_color, selected_style.border_color);
assert_eq!(base_style.text_color, selected_style.text_color);
}
#[test]
fn selected_and_hovered_have_different_styles() {
let theme = Theme::TokyoNight;
let hovered_style = primary(&theme, Status::Hovered);
let selected_style = primary(&theme, Status::Selected);
assert_ne!(
hovered_style.clock_number_color,
selected_style.clock_number_color
);
assert_ne!(
hovered_style.clock_number_background,
selected_style.clock_number_background
);
}
#[test]
fn clock_components_have_valid_styling() {
let theme = Theme::TokyoNight;
let style = primary(&theme, Status::Active);
assert_eq!(style.clock_dots_color, [0.87, 0.87, 0.87].into());
assert_eq!(style.clock_hand_color, [0.87, 0.87, 0.87].into());
assert_eq!(style.clock_hand_width, 3.0);
}
#[test]
fn non_interactive_statuses_use_base_style() {
let theme = Theme::TokyoNight;
let active_style = primary(&theme, Status::Active);
let disabled_style = primary(&theme, Status::Disabled);
assert_eq!(active_style.background, disabled_style.background);
assert_eq!(active_style.border_color, disabled_style.border_color);
assert_eq!(active_style.text_color, disabled_style.text_color);
assert_eq!(
active_style.clock_number_color,
disabled_style.clock_number_color
);
assert_eq!(
active_style.clock_number_background,
disabled_style.clock_number_background
);
}
#[test]
fn catalog_default_class() {
let _class = <Theme as Catalog>::default();
}
#[test]
fn catalog_style() {
let theme = Theme::TokyoNight;
let class = <Theme as Catalog>::default();
let style = theme.style(&class, Status::Active);
assert!(matches!(style.background, Background::Color(_)));
assert_eq!(style.border_radius, 15.0);
assert_eq!(style.border_width, 1.0);
}
#[test]
fn catalog_style_with_different_statuses() {
let theme = Theme::TokyoNight;
let class = <Theme as Catalog>::default();
let active_style = theme.style(&class, Status::Active);
let focused_style = theme.style(&class, Status::Focused);
let hovered_style = theme.style(&class, Status::Hovered);
let selected_style = theme.style(&class, Status::Selected);
assert!(matches!(active_style.background, Background::Color(_)));
assert!(matches!(focused_style.background, Background::Color(_)));
assert!(matches!(hovered_style.background, Background::Color(_)));
assert!(matches!(selected_style.background, Background::Color(_)));
}
#[test]
fn style_fields_are_valid() {
let theme = Theme::TokyoNight;
let style = primary(&theme, Status::Active);
assert!(style.border_radius > 0.0);
assert!(style.border_width > 0.0);
assert!(style.clock_hand_width > 0.0);
}
}