system_theme/theme/
mod.rs

1//! Theme definitions
2mod palette;
3
4use std::fmt::Debug;
5
6#[doc(inline)]
7pub use palette::ThemePalette;
8
9/// Theme scheme
10#[derive(Debug, Default, PartialEq, Clone, Copy)]
11pub enum ThemeScheme {
12    /// Light mode
13    #[default]
14    Light,
15    /// Dark mode
16    Dark,
17}
18
19/// Theme contrast
20#[derive(Debug, Default, PartialEq, Clone, Copy)]
21pub enum ThemeContrast {
22    /// Normal contrast
23    #[default]
24    Normal,
25    /// High contrast
26    High,
27}
28
29/// Theme color
30#[derive(PartialEq, Clone, Copy)]
31pub struct ThemeColor {
32    /// Red component (0.0 - 1.0)
33    pub red: f32,
34    /// Green component (0.0 - 1.0)
35    pub green: f32,
36    /// Blue component (0.0 - 1.0)
37    pub blue: f32,
38}
39
40impl Debug for ThemeColor {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        write!(
43            f,
44            "#{:02X}{:02X}{:02X}",
45            (self.red * 255.0) as u8,
46            (self.green * 255.0) as u8,
47            (self.blue * 255.0) as u8
48        )
49    }
50}
51
52impl ThemeColor {
53    /// White color (#FFFFFF)
54    pub const WHITE: Self = Self {
55        red: 1.0,
56        green: 1.0,
57        blue: 1.0,
58    };
59
60    /// Black color (#000000)
61    pub const BLACK: Self = Self {
62        red: 0.0,
63        green: 0.0,
64        blue: 0.0,
65    };
66
67    /// Create a color from RGB 8-bit values.
68    pub const fn from_rgb8(red: u8, green: u8, blue: u8) -> Self {
69        Self {
70            red: red as f32 / 255.0,
71            green: green as f32 / 255.0,
72            blue: blue as f32 / 255.0,
73        }
74    }
75}
76
77/// Theme kind
78#[derive(Debug, Default, PartialEq, Clone, Copy)]
79pub enum ThemeKind {
80    /// Microsoft Windows
81    #[cfg_attr(target_os = "windows", default)]
82    Windows,
83    /// Apple MacOS
84    #[cfg_attr(target_os = "macos", default)]
85    MacOS,
86    /// GTK (GNOME)
87    #[cfg_attr(not(any(target_os = "windows", target_os = "macos")), default)]
88    Gtk,
89    /// Qt (KDE)
90    Qt,
91}
92
93/// Theme
94#[derive(Debug, Clone, PartialEq)]
95pub struct Theme {
96    /// Theme name
97    pub name: String,
98    /// Theme palette
99    pub palette: ThemePalette,
100}
101
102impl Default for Theme {
103    fn default() -> Self {
104        Theme::new(
105            ThemeKind::default(),
106            ThemeScheme::default(),
107            ThemeContrast::default(),
108            None,
109        )
110    }
111}
112
113impl Theme {
114    /// Get the theme for the given theme kind, scheme, contrast, and (optionally) accent color.
115    pub fn new(
116        kind: ThemeKind,
117        scheme: ThemeScheme,
118        contrast: ThemeContrast,
119        accent: Option<ThemeColor>,
120    ) -> Self {
121        let (mut name, mut palette) = match kind {
122            ThemeKind::Windows => match scheme {
123                ThemeScheme::Light => ("FluentLight".to_string(), palette::FLUENT_LIGHT),
124                ThemeScheme::Dark => ("FluentDark".to_string(), palette::FLUENT_DARK),
125            },
126            ThemeKind::MacOS => match scheme {
127                ThemeScheme::Light => ("AquaLight".to_string(), palette::AQUA_LIGHT),
128                ThemeScheme::Dark => ("AquaDark".to_string(), palette::AQUA_DARK),
129            },
130            ThemeKind::Gtk => match scheme {
131                ThemeScheme::Light => ("AdwaitaLight".to_string(), palette::ADWAITA_LIGHT),
132                ThemeScheme::Dark => ("AdwaitaDark".to_string(), palette::ADWAITA_DARK),
133            },
134            ThemeKind::Qt => match scheme {
135                ThemeScheme::Light => ("BreezeLight".to_string(), palette::BREEZE_LIGHT),
136                ThemeScheme::Dark => ("BreezeDark".to_string(), palette::BREEZE_DARK),
137            },
138        };
139
140        // Change background/foreground colors if high contrast
141        if contrast == ThemeContrast::High {
142            // Append to name the variant
143            name.push_str("HC");
144
145            match scheme {
146                ThemeScheme::Light => {
147                    palette.background = ThemeColor::WHITE;
148                    palette.foreground = ThemeColor::BLACK;
149                }
150                ThemeScheme::Dark => {
151                    palette.background = ThemeColor::BLACK;
152                    palette.foreground = ThemeColor::WHITE;
153                }
154            }
155        }
156
157        // Set accent color if provided
158        if let Some(accent) = accent {
159            palette.accent = accent;
160        }
161
162        Theme { name, palette }
163    }
164}