Skip to main content

zest_theme/
theme.rs

1//! Top-level [`Theme`] type plus preset theme modules.
2
3use crate::{
4    ButtonAppearance, ButtonCatalog, ButtonClass, Component, Container, CornerRadii, Palette,
5    Spacing, Status, Typography,
6};
7use embedded_graphics::{mono_font::MonoFont, pixelcolor::PixelColor};
8
9/// Top-level theme. The application owns one and passes it down to
10/// widgets at draw time.
11///
12/// Widgets do not reach into the per-role component fields directly —
13/// they go through the catalog traits ([`ButtonCatalog`], etc.), so changing
14/// the palette doesn't ripple through every widget implementation.
15#[derive(Copy, Clone, Debug, PartialEq)]
16pub struct Theme<'a, C: PixelColor> {
17    // ---- Regions --------------------------------------------------------
18    /// Background region (the outermost layer behind everything).
19    pub background: Container<C>,
20    /// Primary content region.
21    pub primary: Container<C>,
22    /// Secondary panel/sidebar region.
23    pub secondary: Container<C>,
24
25    // ---- Interactive components (consumed by catalog impls) -------------
26    /// Standard button.
27    pub button: Component<C>,
28    /// Accent / suggested-action.
29    pub accent: Component<C>,
30    /// Destructive (red).
31    pub destructive: Component<C>,
32    /// Success (green).
33    pub success: Component<C>,
34    /// Warning (yellow/orange).
35    pub warning: Component<C>,
36    /// Text-only / ghost button.
37    pub text_button: Component<C>,
38    /// Icon-only button.
39    pub icon_button: Component<C>,
40
41    // ---- Primitives -----------------------------------------------------
42    /// Named palette colors the rest of the theme is derived from.
43    pub palette: Palette<C>,
44    /// Spacing scale.
45    pub spacing: Spacing,
46    /// Corner-radius scale.
47    pub corner_radii: CornerRadii,
48
49    // ---- Typography -----------------------------------------------------
50    /// Four-role mono font scale (display / heading / body / caption).
51    pub typography: Typography<'a>,
52
53    // ---- Flags ----------------------------------------------------------
54    /// Whether this is a dark theme.
55    pub is_dark: bool,
56    /// Whether this is a high-contrast theme.
57    pub is_high_contrast: bool,
58}
59
60impl<'a, C: PixelColor> Theme<'a, C> {
61    /// Body font shortcut — what most widgets default to.
62    #[must_use]
63    pub fn default_font(&self) -> &'a MonoFont<'a> {
64        self.typography.body
65    }
66
67    /// Look up the per-role component for a [`ButtonClass`].
68    #[must_use]
69    fn component_for(&self, class: ButtonClass) -> &Component<C> {
70        match class {
71            ButtonClass::Standard => &self.button,
72            ButtonClass::Suggested => &self.accent,
73            ButtonClass::Destructive => &self.destructive,
74            ButtonClass::Success => &self.success,
75            ButtonClass::Warning => &self.warning,
76            ButtonClass::Text => &self.text_button,
77            ButtonClass::Icon => &self.icon_button,
78        }
79    }
80}
81
82impl<'a, C: PixelColor> ButtonCatalog<C> for Theme<'a, C> {
83    fn button(&self, class: ButtonClass, status: Status) -> ButtonAppearance<C> {
84        let comp = self.component_for(class);
85        let bg = match status {
86            Status::Active => comp.base,
87            Status::Focused => comp.base,
88            Status::Pressed => comp.pressed,
89            Status::Disabled => comp.disabled,
90        };
91        // Text/Icon classes paint no fill and no border by convention.
92        let (background, border) = match class {
93            ButtonClass::Text | ButtonClass::Icon => (None, None),
94            _ => {
95                let border = if status == Status::Focused {
96                    self.accent.base
97                } else {
98                    comp.border
99                };
100                (Some(bg), Some(border))
101            }
102        };
103        ButtonAppearance {
104            background,
105            border,
106            text: comp.on_base,
107        }
108    }
109}
110
111// ---- Preset theme submodules ------------------------------------------
112//
113// All preset themes are defined as `Theme<'static, Rgb888>` constants.
114// Use [`crate::convert_theme`] to obtain a theme in your target color type.
115
116pub mod catppuccin_frappe;
117pub mod catppuccin_latte;
118pub mod catppuccin_macchiato;
119pub mod catppuccin_mocha;
120pub mod custom;
121pub mod dark;
122pub mod dracula;
123pub mod dracula_at_night;
124pub mod ferra;
125pub mod kanagawa_dragon;
126pub mod kanagawa_lotus;
127pub mod kanagawa_wave;
128pub mod light;
129pub mod moonfly;
130pub mod nightfly;
131pub mod nord;
132pub mod oxocarbon;
133pub mod tokyo_night;
134pub mod tokyo_night_light;
135pub mod tokyo_night_storm;