1use std::sync::LazyLock;
2
3use termcolor::{Color, ColorSpec};
4
5pub(crate) static DEFAULT: LazyLock<Theme> = LazyLock::new(Theme::default);
6
7#[derive(Clone, Debug)]
8pub enum CursorShape {
9 Block,
10 Underline,
11}
12
13#[derive(Clone, Debug)]
27pub struct Theme {
28 pub title: ColorSpec,
30 pub description: ColorSpec,
32 pub cursor: ColorSpec,
34 pub cursor_str: String,
36
37 pub selected_option: ColorSpec,
39 pub selected_prefix: String,
41 pub selected_prefix_fg: ColorSpec,
43 pub unselected_option: ColorSpec,
45 pub unselected_prefix: String,
47 pub unselected_prefix_fg: ColorSpec,
49
50 pub cursor_shape: CursorShape,
52 pub cursor_style: ColorSpec,
54 pub force_style: bool,
56
57 pub input_cursor: ColorSpec,
59 pub input_placeholder: ColorSpec,
61 pub input_prompt: ColorSpec,
63
64 pub help_key: ColorSpec,
66 pub help_desc: ColorSpec,
68 pub help_sep: ColorSpec,
70
71 pub focused_button: ColorSpec,
73 pub blurred_button: ColorSpec,
75
76 pub error_indicator: ColorSpec,
78}
79
80impl Theme {
81 pub fn new() -> Self {
83 let placeholder = Color::Ansi256(8);
84
85 let mut focused_button = make_color(Color::Ansi256(0));
86 focused_button.set_bg(Some(Color::Ansi256(7)));
87
88 let mut blurred_button = make_color(Color::Ansi256(7));
89 blurred_button.set_bg(Some(Color::Ansi256(0)));
90
91 let mut cursor_style = ColorSpec::new();
93 cursor_style
94 .set_fg(Some(Color::White))
95 .set_bg(Some(Color::Black));
96
97 Self {
98 title: ColorSpec::new(),
99 error_indicator: ColorSpec::new(),
100 description: ColorSpec::new(),
101 cursor: ColorSpec::new(),
102 cursor_str: String::from("❯"),
103 selected_prefix: String::from("[•]"),
104 selected_prefix_fg: ColorSpec::new(),
105 selected_option: ColorSpec::new(),
106 unselected_prefix: String::from("[ ]"),
107 unselected_prefix_fg: ColorSpec::new(),
108 unselected_option: ColorSpec::new(),
109 input_cursor: ColorSpec::new(),
110 input_placeholder: make_color(placeholder),
111 input_prompt: ColorSpec::new(),
112 help_key: ColorSpec::new(),
113 help_desc: ColorSpec::new(),
114 help_sep: ColorSpec::new(),
115 focused_button,
116 blurred_button,
117
118 cursor_shape: CursorShape::Block,
120 cursor_style,
121 force_style: true,
122 }
123 }
124
125 pub fn real_cursor_color(&self, other: Option<&ColorSpec>) -> ColorSpec {
126 let other = if self.force_style {
128 &self.cursor_style
129 } else {
130 other.unwrap_or(&self.cursor_style)
131 };
132
133 let mut c = ColorSpec::new();
134 match self.cursor_shape {
135 CursorShape::Block => {
136 c.set_bg(other.fg().copied());
137 c.set_fg(other.bg().copied());
138 }
139 CursorShape::Underline => {
140 c.set_bg(other.bg().copied());
141 c.set_fg(other.fg().copied());
142 c.set_underline(true);
143 }
144 }
145 c
148 }
149
150 pub fn charm() -> Self {
152 let normal = Color::Ansi256(252);
153 let indigo = Color::Rgb(117, 113, 249);
154 let red = Color::Rgb(255, 70, 114);
155 let fuchsia = Color::Rgb(247, 128, 226);
156 let green = Color::Rgb(2, 191, 135);
157 let cream = Color::Rgb(255, 253, 245);
158
159 let mut title = make_color(indigo);
160 title.set_bold(true);
161
162 let mut focused_button = make_color(cream);
163 focused_button.set_bg(Some(fuchsia));
164
165 let mut blurred_button = make_color(normal);
166 blurred_button.set_bg(Some(Color::Ansi256(238)));
167
168 let mut cursor_style = ColorSpec::new();
170 cursor_style
171 .set_fg(Some(Color::White))
172 .set_bg(Some(Color::Black));
173
174 Self {
175 title,
176 error_indicator: make_color(red),
177 description: make_color(Color::Ansi256(243)),
178 cursor: make_color(fuchsia),
179 cursor_str: String::from("❯"),
180
181 selected_prefix: String::from(" ✓"),
182 selected_prefix_fg: make_color(Color::Rgb(2, 168, 119)),
183 selected_option: make_color(green),
184 unselected_prefix: String::from(" •"),
185 unselected_prefix_fg: make_color(Color::Ansi256(243)),
186 unselected_option: make_color(normal),
187
188 input_cursor: make_color(green),
189 input_placeholder: make_color(Color::Ansi256(238)),
190 input_prompt: make_color(fuchsia),
191
192 help_key: make_color(Color::Rgb(98, 98, 98)),
193 help_desc: make_color(Color::Rgb(74, 74, 74)),
194 help_sep: make_color(Color::Rgb(60, 60, 60)),
195
196 focused_button,
197 blurred_button,
198
199 cursor_shape: CursorShape::Block,
201 cursor_style,
202 force_style: true,
203 }
204 }
205
206 pub fn dracula() -> Self {
208 let background = Color::Rgb(40, 42, 54); let foreground = Color::Rgb(248, 248, 242); let comment = Color::Rgb(98, 114, 164); let green = Color::Rgb(80, 250, 123); let purple = Color::Rgb(189, 147, 249); let red = Color::Rgb(255, 85, 85); let yellow = Color::Rgb(241, 250, 140); let mut title = make_color(purple);
217 title.set_bold(true);
218
219 let mut focused_button = make_color(yellow);
220 focused_button.set_bg(Some(purple));
221
222 let mut blurred_button = make_color(foreground);
223 blurred_button.set_bg(Some(background));
224
225 let mut cursor_style = ColorSpec::new();
227 cursor_style
228 .set_fg(Some(Color::White))
229 .set_bg(Some(Color::Black));
230
231 Self {
232 title,
233 error_indicator: make_color(red),
234 description: make_color(comment),
235 cursor: make_color(yellow),
236 cursor_str: String::from("❯"),
237
238 selected_prefix: String::from(" [•]"),
239 selected_prefix_fg: make_color(green),
240 selected_option: make_color(green),
241 unselected_prefix: String::from(" [ ]"),
242 unselected_prefix_fg: make_color(comment),
243 unselected_option: make_color(foreground),
244
245 input_cursor: make_color(yellow),
246 input_placeholder: make_color(comment),
247 input_prompt: make_color(yellow),
248
249 help_key: make_color(Color::Rgb(98, 98, 98)),
250 help_desc: make_color(Color::Rgb(74, 74, 74)),
251 help_sep: make_color(Color::Rgb(60, 60, 60)),
252
253 focused_button,
254 blurred_button,
255
256 cursor_shape: CursorShape::Block,
258 cursor_style,
259 force_style: true,
260 }
261 }
262
263 pub fn base16() -> Self {
265 let mut title = make_color(Color::Ansi256(6));
266 title.set_bold(true);
267
268 let mut focused_button = make_color(Color::Ansi256(7));
269 focused_button.set_bg(Some(Color::Ansi256(5)));
270
271 let mut blurred_button = make_color(Color::Ansi256(7));
272 blurred_button.set_bg(Some(Color::Ansi256(0)));
273
274 let mut cursor_style = ColorSpec::new();
276 cursor_style
277 .set_fg(Some(Color::White))
278 .set_bg(Some(Color::Black));
279
280 Self {
281 title,
282 error_indicator: make_color(Color::Ansi256(9)),
283 description: make_color(Color::Ansi256(8)),
284 cursor: make_color(Color::Ansi256(3)),
285 cursor_str: String::from("❯"),
286
287 selected_prefix: String::from(" [•]"),
288 selected_prefix_fg: make_color(Color::Ansi256(2)),
289 selected_option: make_color(Color::Ansi256(2)),
290 unselected_prefix: String::from(" [ ]"),
291 unselected_prefix_fg: make_color(Color::Ansi256(7)),
292 unselected_option: make_color(Color::Ansi256(7)),
293
294 input_cursor: make_color(Color::Ansi256(5)),
295 input_placeholder: make_color(Color::Ansi256(8)),
296 input_prompt: make_color(Color::Ansi256(3)),
297
298 help_key: make_color(Color::Rgb(98, 98, 98)),
299 help_desc: make_color(Color::Rgb(74, 74, 74)),
300 help_sep: make_color(Color::Rgb(60, 60, 60)),
301
302 focused_button,
303 blurred_button,
304
305 cursor_shape: CursorShape::Block,
307 cursor_style,
308 force_style: true,
309 }
310 }
311
312 pub fn catppuccin() -> Self {
314 let base = Color::Rgb(30, 30, 46);
315 let text = Color::Rgb(205, 214, 244);
316 let subtext0 = Color::Rgb(166, 173, 200);
317 let overlay0 = Color::Rgb(108, 112, 134);
318 let overlay1 = Color::Rgb(127, 132, 156);
319 let green = Color::Rgb(166, 227, 161);
320 let red = Color::Rgb(243, 139, 168);
321 let pink = Color::Rgb(245, 194, 231);
322 let mauve = Color::Rgb(203, 166, 247);
323 let cursor = Color::Rgb(245, 224, 220);
324
325 let mut title = make_color(mauve);
326 title.set_bold(true);
327
328 let mut focused_button = make_color(base);
329 focused_button.set_bg(Some(pink));
330
331 let mut blurred_button = make_color(text);
332 blurred_button.set_bg(Some(base));
333
334 let mut cursor_style = ColorSpec::new();
336 cursor_style
337 .set_fg(Some(Color::White))
338 .set_bg(Some(Color::Black));
339
340 Self {
341 title,
342 error_indicator: make_color(red),
343 description: make_color(subtext0),
344 cursor: make_color(pink),
345 cursor_str: String::from("❯"),
346
347 selected_prefix: String::from(" [•]"),
348 selected_prefix_fg: make_color(green),
349 selected_option: make_color(green),
350 unselected_prefix: String::from(" [ ]"),
351 unselected_prefix_fg: make_color(text),
352 unselected_option: make_color(text),
353
354 input_cursor: make_color(cursor),
355 input_placeholder: make_color(overlay0),
356 input_prompt: make_color(pink),
357
358 help_key: make_color(subtext0),
359 help_desc: make_color(overlay1),
360 help_sep: make_color(subtext0),
361
362 focused_button,
363 blurred_button,
364
365 cursor_shape: CursorShape::Block,
367 cursor_style,
368 force_style: true,
369 }
370 }
371
372 pub fn color_rgb(r: u8, g: u8, b: u8) -> ColorSpec {
374 make_color(Color::Rgb(r, g, b))
375 }
376
377 pub fn color_ansi256(n: u8) -> ColorSpec {
379 make_color(Color::Ansi256(n))
380 }
381}
382
383impl Default for Theme {
384 fn default() -> Self {
385 if console::colors_enabled_stderr() {
386 Theme::charm()
387 } else {
388 Theme::new()
389 }
390 }
391}
392
393fn make_color(color: Color) -> ColorSpec {
394 let mut spec = ColorSpec::new();
395 spec.set_fg(Some(color));
396 spec
397}