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}