1use crate::element::{ElementName, StyleSpec};
20use crate::palette::{
21 Palette, catppuccin_latte_palette, default_palette, dracula_dark_palette,
22 dracula_light_palette, everforest_dark_palette, everforest_light_palette, gruvbox_dark_palette,
23 gruvbox_light_palette, macchiato_palette, monokai_dark_palette, monokai_light_palette,
24 nord_dark_palette, nord_light_palette, one_dark_palette, one_light_palette,
25 rosepine_dark_palette, rosepine_light_palette, solarized_dark_palette, solarized_light_palette,
26 tokyonight_dark_palette, tokyonight_light_palette,
27};
28
29pub struct NamedTheme {
32 pub name: &'static str,
33 pub palette: Palette,
34 pub overrides: Vec<(ElementName, StyleSpec)>,
35}
36
37pub fn builtin_themes() -> Vec<NamedTheme> {
42 vec![
43 NamedTheme {
44 name: "catppuccin-mocha",
45 palette: default_palette(),
46 overrides: Vec::new(),
47 },
48 NamedTheme {
49 name: "catppuccin-macchiato",
50 palette: macchiato_palette(),
51 overrides: Vec::new(),
52 },
53 NamedTheme {
57 name: "catppuccin-latte",
58 palette: catppuccin_latte_palette(),
59 overrides: Vec::new(),
60 },
61 NamedTheme {
65 name: "gruvbox-dark",
66 palette: gruvbox_dark_palette(),
67 overrides: Vec::new(),
68 },
69 NamedTheme {
70 name: "gruvbox-light",
71 palette: gruvbox_light_palette(),
72 overrides: Vec::new(),
73 },
74 NamedTheme {
75 name: "tokyonight-dark",
76 palette: tokyonight_dark_palette(),
77 overrides: Vec::new(),
78 },
79 NamedTheme {
80 name: "tokyonight-light",
81 palette: tokyonight_light_palette(),
82 overrides: Vec::new(),
83 },
84 NamedTheme {
85 name: "dracula-dark",
86 palette: dracula_dark_palette(),
87 overrides: Vec::new(),
88 },
89 NamedTheme {
90 name: "dracula-light",
91 palette: dracula_light_palette(),
92 overrides: Vec::new(),
93 },
94 NamedTheme {
95 name: "nord-dark",
96 palette: nord_dark_palette(),
97 overrides: Vec::new(),
98 },
99 NamedTheme {
100 name: "nord-light",
101 palette: nord_light_palette(),
102 overrides: Vec::new(),
103 },
104 NamedTheme {
105 name: "solarized-dark",
106 palette: solarized_dark_palette(),
107 overrides: Vec::new(),
108 },
109 NamedTheme {
110 name: "solarized-light",
111 palette: solarized_light_palette(),
112 overrides: Vec::new(),
113 },
114 NamedTheme {
115 name: "one-dark",
116 palette: one_dark_palette(),
117 overrides: Vec::new(),
118 },
119 NamedTheme {
120 name: "one-light",
121 palette: one_light_palette(),
122 overrides: Vec::new(),
123 },
124 NamedTheme {
125 name: "everforest-dark",
126 palette: everforest_dark_palette(),
127 overrides: Vec::new(),
128 },
129 NamedTheme {
130 name: "everforest-light",
131 palette: everforest_light_palette(),
132 overrides: Vec::new(),
133 },
134 NamedTheme {
135 name: "rosepine-dark",
136 palette: rosepine_dark_palette(),
137 overrides: Vec::new(),
138 },
139 NamedTheme {
140 name: "rosepine-light",
141 palette: rosepine_light_palette(),
142 overrides: Vec::new(),
143 },
144 NamedTheme {
145 name: "monokai-dark",
146 palette: monokai_dark_palette(),
147 overrides: Vec::new(),
148 },
149 NamedTheme {
150 name: "monokai-light",
151 palette: monokai_light_palette(),
152 overrides: Vec::new(),
153 },
154 ]
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::Color;
161
162 #[test]
163 fn builtin_themes_has_both_flavours() {
164 let themes = builtin_themes();
165 let names: Vec<&str> = themes.iter().map(|t| t.name).collect();
166 assert!(names.contains(&"catppuccin-mocha"));
167 assert!(names.contains(&"catppuccin-macchiato"));
168 }
169
170 #[test]
171 fn macchiato_mauve_differs_from_mocha() {
172 let themes = builtin_themes();
173 let mocha = themes
174 .iter()
175 .find(|t| t.name == "catppuccin-mocha")
176 .expect("mocha registered");
177 let mac = themes
178 .iter()
179 .find(|t| t.name == "catppuccin-macchiato")
180 .expect("macchiato registered");
181 let mauve = crate::PaletteKey::from_static("purple");
182 assert_ne!(mocha.palette.get(&mauve), mac.palette.get(&mauve));
183 assert_eq!(mac.palette.get(&mauve), Some(Color::Rgb(0xc6, 0xa0, 0xf6)));
184 }
185
186 #[test]
187 fn latte_is_a_registered_light_theme() {
188 let themes = builtin_themes();
194 let latte = themes
195 .iter()
196 .find(|t| t.name == "catppuccin-latte")
197 .expect("catppuccin-latte registered");
198 let base = crate::PaletteKey::from_static("base");
199 let text = crate::PaletteKey::from_static("text");
200 assert_eq!(latte.palette.get(&base), Some(Color::Rgb(0xef, 0xf1, 0xf5)));
201 assert_eq!(latte.palette.get(&text), Some(Color::Rgb(0x4c, 0x4f, 0x69)));
202 let mocha = themes
204 .iter()
205 .find(|t| t.name == "catppuccin-mocha")
206 .unwrap();
207 let (Some(Color::Rgb(lr, lg, lb)), Some(Color::Rgb(mr, mg, mb))) =
208 (latte.palette.get(&base), mocha.palette.get(&base))
209 else {
210 panic!("both bases are rgb");
211 };
212 assert!(
213 lr > mr && lg > mg && lb > mb,
214 "Latte base must be lighter than Mocha"
215 );
216 }
217
218 #[test]
219 fn every_builtin_theme_covers_the_full_role_key_set() {
220 let reference = default_palette();
225 for theme in builtin_themes() {
226 for key in reference.keys() {
227 assert!(
228 theme.palette.get(key).is_some(),
229 "theme `{}` is missing role key `{}`",
230 theme.name,
231 key.as_str()
232 );
233 }
234 }
235 }
236
237 #[test]
238 fn light_themes_are_light_and_dark_themes_are_dark() {
239 fn channel_sum(c: Color) -> u32 {
245 match c {
246 Color::Rgb(r, g, b) => r as u32 + g as u32 + b as u32,
247 other => {
248 let rgb = other.to_rgb_u32(0);
249 ((rgb >> 16) & 0xff) + ((rgb >> 8) & 0xff) + (rgb & 0xff)
250 }
251 }
252 }
253 let base = crate::PaletteKey::from_static("base");
254 let text = crate::PaletteKey::from_static("text");
255 for theme in builtin_themes() {
256 let b = channel_sum(theme.palette.get(&base).expect("base defined"));
257 let t = channel_sum(theme.palette.get(&text).expect("text defined"));
258 if theme.name.ends_with("-light") || theme.name.ends_with("-latte") {
259 assert!(
260 b > t,
261 "light theme `{}`: base ({}) must be brighter than text ({})",
262 theme.name,
263 b,
264 t
265 );
266 } else {
267 assert!(
268 b < t,
269 "dark theme `{}`: base ({}) must be darker than text ({})",
270 theme.name,
271 b,
272 t
273 );
274 }
275 }
276 }
277}