Skip to main content

guise/theme/
presets.rs

1//! Prebuilt themes — well-known palettes expressed as override sets over the
2//! base light/dark themes. Grab one directly (`Theme::dracula().init(cx)`) or
3//! look it up by name with [`Theme::preset`].
4
5use super::{Color, Theme};
6
7/// The names [`Theme::preset`] recognizes.
8pub const PRESET_NAMES: [&str; 6] = [
9    "catppuccin",
10    "nord",
11    "tokyonight",
12    "gruvbox",
13    "dracula",
14    "solarizedlight",
15];
16
17fn hex(value: &'static str) -> Color {
18    Color::hex(value)
19}
20
21impl Theme {
22    /// Look up a prebuilt theme by name (see [`PRESET_NAMES`]).
23    pub fn preset(name: &str) -> Option<Theme> {
24        match name {
25            "catppuccin" => Some(Theme::catppuccin()),
26            "nord" => Some(Theme::nord()),
27            "tokyonight" => Some(Theme::tokyonight()),
28            "gruvbox" => Some(Theme::gruvbox()),
29            "dracula" => Some(Theme::dracula()),
30            "solarizedlight" => Some(Theme::solarized_light()),
31            _ => None,
32        }
33    }
34
35    /// Catppuccin Mocha (dark).
36    pub fn catppuccin() -> Theme {
37        Theme::dark()
38            .with_primary(hex("#89b4fa"))
39            .with_body(hex("#1e1e2e"))
40            .with_surface(hex("#181825"))
41            .with_surface_hover(hex("#313244"))
42            .with_text(hex("#cdd6f4"))
43            .with_dimmed(hex("#a6adc8"))
44            .with_border(hex("#45475a"))
45            .with_success(hex("#a6e3a1"))
46            .with_warning(hex("#f9e2af"))
47            .with_danger(hex("#f38ba8"))
48            .with_info(hex("#89dceb"))
49    }
50
51    /// Nord (dark).
52    pub fn nord() -> Theme {
53        Theme::dark()
54            .with_primary(hex("#88c0d0"))
55            .with_body(hex("#2e3440"))
56            .with_surface(hex("#3b4252"))
57            .with_surface_hover(hex("#434c5e"))
58            .with_text(hex("#eceff4"))
59            .with_dimmed(hex("#d8dee9"))
60            .with_border(hex("#4c566a"))
61            .with_success(hex("#a3be8c"))
62            .with_warning(hex("#ebcb8b"))
63            .with_danger(hex("#bf616a"))
64            .with_info(hex("#81a1c1"))
65    }
66
67    /// Tokyo Night (dark).
68    pub fn tokyonight() -> Theme {
69        Theme::dark()
70            .with_primary(hex("#7aa2f7"))
71            .with_body(hex("#1a1b26"))
72            .with_surface(hex("#16161e"))
73            .with_surface_hover(hex("#292e42"))
74            .with_text(hex("#c0caf5"))
75            .with_dimmed(hex("#565f89"))
76            .with_border(hex("#3b4261"))
77            .with_success(hex("#9ece6a"))
78            .with_warning(hex("#e0af68"))
79            .with_danger(hex("#f7768e"))
80            .with_info(hex("#7dcfff"))
81    }
82
83    /// Gruvbox (dark, hard contrast accents).
84    pub fn gruvbox() -> Theme {
85        Theme::dark()
86            .with_primary(hex("#fe8019"))
87            .with_body(hex("#282828"))
88            .with_surface(hex("#3c3836"))
89            .with_surface_hover(hex("#504945"))
90            .with_text(hex("#ebdbb2"))
91            .with_dimmed(hex("#a89984"))
92            .with_border(hex("#665c54"))
93            .with_success(hex("#b8bb26"))
94            .with_warning(hex("#fabd2f"))
95            .with_danger(hex("#fb4934"))
96            .with_info(hex("#83a598"))
97    }
98
99    /// Dracula (dark).
100    pub fn dracula() -> Theme {
101        Theme::dark()
102            .with_primary(hex("#bd93f9"))
103            .with_body(hex("#282a36"))
104            .with_surface(hex("#21222c"))
105            .with_surface_hover(hex("#44475a"))
106            .with_text(hex("#f8f8f2"))
107            .with_dimmed(hex("#6272a4"))
108            .with_border(hex("#44475a"))
109            .with_success(hex("#50fa7b"))
110            .with_warning(hex("#f1fa8c"))
111            .with_danger(hex("#ff5555"))
112            .with_info(hex("#8be9fd"))
113    }
114
115    /// Solarized (light).
116    pub fn solarized_light() -> Theme {
117        Theme::light()
118            .with_primary(hex("#268bd2"))
119            .with_body(hex("#fdf6e3"))
120            .with_surface(hex("#eee8d5"))
121            .with_surface_hover(hex("#e4dcc4"))
122            .with_text(hex("#657b83"))
123            .with_dimmed(hex("#93a1a1"))
124            .with_border(hex("#d5cdb4"))
125            .with_success(hex("#859900"))
126            .with_warning(hex("#b58900"))
127            .with_danger(hex("#dc322f"))
128            .with_info(hex("#2aa198"))
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn every_name_resolves_and_overrides_all_slots() {
138        for name in PRESET_NAMES {
139            let theme = Theme::preset(name).unwrap_or_else(|| panic!("missing preset {name}"));
140            let o = &theme.overrides;
141            for (slot, value) in [
142                ("primary", o.primary),
143                ("body", o.body),
144                ("surface", o.surface),
145                ("surface_hover", o.surface_hover),
146                ("text", o.text),
147                ("dimmed", o.dimmed),
148                ("border", o.border),
149                ("success", o.success),
150                ("warning", o.warning),
151                ("danger", o.danger),
152                ("info", o.info),
153            ] {
154                assert!(value.is_some(), "{name} leaves {slot} unset");
155            }
156        }
157        assert!(Theme::preset("nope").is_none());
158    }
159
160    #[test]
161    fn presets_pick_sensible_schemes() {
162        assert!(Theme::catppuccin().scheme.is_dark());
163        assert!(!Theme::solarized_light().scheme.is_dark());
164    }
165}