1use std::{rc::Rc, sync::Arc};
2
3use anyhow::Result;
4use gpui::{Hsla, SharedString};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 highlighter::{HighlightTheme, HighlightThemeStyle},
10 Colorize, Theme, ThemeColor, ThemeMode,
11};
12
13#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
15#[serde(default)]
16pub struct ThemeSet {
17 pub name: SharedString,
19 pub author: Option<SharedString>,
21 pub url: Option<SharedString>,
23
24 #[serde(rename = "font.size")]
26 pub font_size: Option<f32>,
27
28 #[serde(rename = "themes")]
29 pub themes: Vec<ThemeConfig>,
30}
31
32#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
33#[serde(default)]
34pub struct ThemeConfig {
35 pub is_default: bool,
37 pub name: SharedString,
39 pub mode: ThemeMode,
41 pub colors: ThemeConfigColors,
43 pub highlight: Option<HighlightThemeStyle>,
47}
48
49#[derive(Debug, Default, Clone, JsonSchema, Serialize, Deserialize)]
50pub struct ThemeConfigColors {
51 #[serde(rename = "accent.background")]
53 pub accent: Option<SharedString>,
54 #[serde(rename = "accent.foreground")]
56 pub accent_foreground: Option<SharedString>,
57 #[serde(rename = "accordion.background")]
59 pub accordion: Option<SharedString>,
60 #[serde(rename = "accordion.hover.background")]
62 pub accordion_hover: Option<SharedString>,
63 #[serde(rename = "background")]
65 pub background: Option<SharedString>,
66 #[serde(rename = "border")]
68 pub border: Option<SharedString>,
69 #[serde(rename = "group_box.background")]
71 pub group_box: Option<SharedString>,
72 #[serde(rename = "group_box.foreground")]
74 pub group_box_foreground: Option<SharedString>,
75 #[serde(rename = "group_box.title.foreground")]
77 pub group_box_title_foreground: Option<SharedString>,
78 #[serde(rename = "caret")]
80 pub caret: Option<SharedString>,
81 #[serde(rename = "chart.1")]
83 pub chart_1: Option<SharedString>,
84 #[serde(rename = "chart.2")]
86 pub chart_2: Option<SharedString>,
87 #[serde(rename = "chart.3")]
89 pub chart_3: Option<SharedString>,
90 #[serde(rename = "chart.4")]
92 pub chart_4: Option<SharedString>,
93 #[serde(rename = "chart.5")]
95 pub chart_5: Option<SharedString>,
96 #[serde(rename = "danger.background")]
98 pub danger: Option<SharedString>,
99 #[serde(rename = "danger.active.background")]
101 pub danger_active: Option<SharedString>,
102 #[serde(rename = "danger.foreground")]
104 pub danger_foreground: Option<SharedString>,
105 #[serde(rename = "danger.hover.background")]
107 pub danger_hover: Option<SharedString>,
108 #[serde(rename = "description_list.label.background")]
110 pub description_list_label: Option<SharedString>,
111 #[serde(rename = "description_list.label.foreground")]
113 pub description_list_label_foreground: Option<SharedString>,
114 #[serde(rename = "drag.border")]
116 pub drag_border: Option<SharedString>,
117 #[serde(rename = "drop_target.background")]
119 pub drop_target: Option<SharedString>,
120 #[serde(rename = "foreground")]
122 pub foreground: Option<SharedString>,
123 #[serde(rename = "info.background")]
125 pub info: Option<SharedString>,
126 #[serde(rename = "info.active.background")]
128 pub info_active: Option<SharedString>,
129 #[serde(rename = "info.foreground")]
131 pub info_foreground: Option<SharedString>,
132 #[serde(rename = "info.hover.background")]
134 pub info_hover: Option<SharedString>,
135 #[serde(rename = "input.border")]
137 pub input: Option<SharedString>,
138 #[serde(rename = "link")]
140 pub link: Option<SharedString>,
141 #[serde(rename = "link.active")]
143 pub link_active: Option<SharedString>,
144 #[serde(rename = "link.hover")]
146 pub link_hover: Option<SharedString>,
147 #[serde(rename = "list.background")]
149 pub list: Option<SharedString>,
150 #[serde(rename = "list.active.background")]
152 pub list_active: Option<SharedString>,
153 #[serde(rename = "list.active.border")]
155 pub list_active_border: Option<SharedString>,
156 #[serde(rename = "list.even.background")]
158 pub list_even: Option<SharedString>,
159 #[serde(rename = "list.head.background")]
161 pub list_head: Option<SharedString>,
162 #[serde(rename = "list.hover.background")]
164 pub list_hover: Option<SharedString>,
165 #[serde(rename = "muted.background")]
167 pub muted: Option<SharedString>,
168 #[serde(rename = "muted.foreground")]
170 pub muted_foreground: Option<SharedString>,
171 #[serde(rename = "popover.background")]
173 pub popover: Option<SharedString>,
174 #[serde(rename = "popover.foreground")]
176 pub popover_foreground: Option<SharedString>,
177 #[serde(rename = "primary.background")]
179 pub primary: Option<SharedString>,
180 #[serde(rename = "primary.active.background")]
182 pub primary_active: Option<SharedString>,
183 #[serde(rename = "primary.foreground")]
185 pub primary_foreground: Option<SharedString>,
186 #[serde(rename = "primary.hover.background")]
188 pub primary_hover: Option<SharedString>,
189 #[serde(rename = "progress.bar.background")]
191 pub progress_bar: Option<SharedString>,
192 #[serde(rename = "ring")]
194 pub ring: Option<SharedString>,
195 #[serde(rename = "scrollbar.background")]
197 pub scrollbar: Option<SharedString>,
198 #[serde(rename = "scrollbar.thumb.background")]
200 pub scrollbar_thumb: Option<SharedString>,
201 #[serde(rename = "scrollbar.thumb.hover.background")]
203 pub scrollbar_thumb_hover: Option<SharedString>,
204 #[serde(rename = "secondary.background")]
206 pub secondary: Option<SharedString>,
207 #[serde(rename = "secondary.active.background")]
209 pub secondary_active: Option<SharedString>,
210 #[serde(rename = "secondary.foreground")]
212 pub secondary_foreground: Option<SharedString>,
213 #[serde(rename = "secondary.hover.background")]
215 pub secondary_hover: Option<SharedString>,
216 #[serde(rename = "selection.background")]
218 pub selection: Option<SharedString>,
219 #[serde(rename = "sidebar.background")]
221 pub sidebar: Option<SharedString>,
222 #[serde(rename = "sidebar.accent.background")]
224 pub sidebar_accent: Option<SharedString>,
225 #[serde(rename = "sidebar.accent.foreground")]
227 pub sidebar_accent_foreground: Option<SharedString>,
228 #[serde(rename = "sidebar.border")]
230 pub sidebar_border: Option<SharedString>,
231 #[serde(rename = "sidebar.foreground")]
233 pub sidebar_foreground: Option<SharedString>,
234 #[serde(rename = "sidebar.primary.background")]
236 pub sidebar_primary: Option<SharedString>,
237 #[serde(rename = "sidebar.primary.foreground")]
239 pub sidebar_primary_foreground: Option<SharedString>,
240 #[serde(rename = "skeleton.background")]
242 pub skeleton: Option<SharedString>,
243 #[serde(rename = "slider.background")]
245 pub slider_bar: Option<SharedString>,
246 #[serde(rename = "slider.thumb.background")]
248 pub slider_thumb: Option<SharedString>,
249 #[serde(rename = "success.background")]
251 pub success: Option<SharedString>,
252 #[serde(rename = "success.foreground")]
254 pub success_foreground: Option<SharedString>,
255 #[serde(rename = "success.hover.background")]
257 pub success_hover: Option<SharedString>,
258 #[serde(rename = "success.active.background")]
260 pub success_active: Option<SharedString>,
261 #[serde(rename = "switch.background")]
263 pub switch: Option<SharedString>,
264 #[serde(rename = "tab.background")]
266 pub tab: Option<SharedString>,
267 #[serde(rename = "tab.active.background")]
269 pub tab_active: Option<SharedString>,
270 #[serde(rename = "tab.active.foreground")]
272 pub tab_active_foreground: Option<SharedString>,
273 #[serde(rename = "tab_bar.background")]
275 pub tab_bar: Option<SharedString>,
276 #[serde(rename = "tab_bar.segmented.background")]
278 pub tab_bar_segmented: Option<SharedString>,
279 #[serde(rename = "tab.foreground")]
281 pub tab_foreground: Option<SharedString>,
282 #[serde(rename = "table.background")]
284 pub table: Option<SharedString>,
285 #[serde(rename = "table.active.background")]
287 pub table_active: Option<SharedString>,
288 #[serde(rename = "table.active.border")]
290 pub table_active_border: Option<SharedString>,
291 #[serde(rename = "table.even.background")]
293 pub table_even: Option<SharedString>,
294 #[serde(rename = "table.head.background")]
296 pub table_head: Option<SharedString>,
297 #[serde(rename = "table.head.foreground")]
299 pub table_head_foreground: Option<SharedString>,
300 #[serde(rename = "table.hover.background")]
302 pub table_hover: Option<SharedString>,
303 #[serde(rename = "table.row.border")]
305 pub table_row_border: Option<SharedString>,
306 #[serde(rename = "title_bar.background")]
308 pub title_bar: Option<SharedString>,
309 #[serde(rename = "title_bar.border")]
311 pub title_bar_border: Option<SharedString>,
312 #[serde(rename = "tiles.background")]
314 pub tiles: Option<SharedString>,
315 #[serde(rename = "warning.background")]
317 pub warning: Option<SharedString>,
318 #[serde(rename = "warning.active.background")]
320 pub warning_active: Option<SharedString>,
321 #[serde(rename = "warning.hover.background")]
323 pub warning_hover: Option<SharedString>,
324 #[serde(rename = "warning.foreground")]
326 pub warning_foreground: Option<SharedString>,
327 #[serde(rename = "overlay")]
329 pub overlay: Option<SharedString>,
330 #[serde(rename = "window.border")]
336 pub window_border: Option<SharedString>,
337
338 #[serde(rename = "base.blue")]
340 blue: Option<String>,
341 #[serde(rename = "base.blue.light")]
343 blue_light: Option<String>,
344 #[serde(rename = "base.cyan")]
346 cyan: Option<String>,
347 #[serde(rename = "base.cyan.light")]
349 cyan_light: Option<String>,
350 #[serde(rename = "base.green")]
352 green: Option<String>,
353 #[serde(rename = "base.green.light")]
355 green_light: Option<String>,
356 #[serde(rename = "base.magenta")]
358 magenta: Option<String>,
359 #[serde(rename = "base.magenta.light")]
360 magenta_light: Option<String>,
361 #[serde(rename = "base.red")]
363 red: Option<String>,
364 #[serde(rename = "base.red.light")]
366 red_light: Option<String>,
367 #[serde(rename = "base.yellow")]
369 yellow: Option<String>,
370 #[serde(rename = "base.yellow.light")]
372 yellow_light: Option<String>,
373}
374
375fn try_parse_color(color: &str) -> Result<Hsla> {
377 let rgba = gpui::Rgba::try_from(color)?;
378 Ok(rgba.into())
379}
380
381impl ThemeColor {
382 pub(crate) fn apply_config(&mut self, config: &ThemeConfig, default_theme: &ThemeColor) {
384 let colors = config.colors.clone();
385
386 macro_rules! apply_color {
387 ($config_field:ident) => {
388 if let Some(value) = colors.$config_field {
389 if let Ok(color) = try_parse_color(&value) {
390 self.$config_field = color;
391 } else {
392 self.$config_field = default_theme.$config_field;
393 }
394 } else {
395 self.$config_field = default_theme.$config_field;
396 }
397 };
398 ($config_field:ident, fallback = $fallback:expr) => {
400 if let Some(value) = colors.$config_field {
401 if let Ok(color) = try_parse_color(&value) {
402 self.$config_field = color;
403 }
404 } else {
405 self.$config_field = $fallback;
406 }
407 };
408 }
409
410 apply_color!(background);
411
412 apply_color!(red);
414 apply_color!(
415 red_light,
416 fallback = self.background.blend(self.red.opacity(0.8))
417 );
418 apply_color!(green);
419 apply_color!(
420 green_light,
421 fallback = self.background.blend(self.green.opacity(0.8))
422 );
423 apply_color!(blue);
424 apply_color!(
425 blue_light,
426 fallback = self.background.blend(self.blue.opacity(0.8))
427 );
428 apply_color!(magenta);
429 apply_color!(
430 magenta_light,
431 fallback = self.background.blend(self.magenta.opacity(0.8))
432 );
433 apply_color!(yellow);
434 apply_color!(
435 yellow_light,
436 fallback = self.background.blend(self.yellow.opacity(0.8))
437 );
438 apply_color!(cyan);
439 apply_color!(
440 cyan_light,
441 fallback = self.background.blend(self.cyan.opacity(0.8))
442 );
443
444 apply_color!(border);
445 apply_color!(foreground);
446 apply_color!(muted);
447 apply_color!(
448 muted_foreground,
449 fallback = self.muted.blend(self.foreground.opacity(0.7))
450 );
451
452 let active_darken = if config.mode.is_dark() { 0.2 } else { 0.1 };
454 let hover_opacity = 0.9;
455 apply_color!(primary);
456 apply_color!(primary_foreground, fallback = self.foreground);
457 apply_color!(
458 primary_hover,
459 fallback = self.background.blend(self.primary.opacity(hover_opacity))
460 );
461 apply_color!(
462 primary_active,
463 fallback = self.primary.darken(active_darken)
464 );
465 apply_color!(secondary);
466 apply_color!(secondary_foreground, fallback = self.foreground);
467 apply_color!(
468 secondary_hover,
469 fallback = self.background.blend(self.secondary.opacity(hover_opacity))
470 );
471 apply_color!(
472 secondary_active,
473 fallback = self.secondary.darken(active_darken)
474 );
475 apply_color!(success, fallback = self.green);
476 apply_color!(success_foreground, fallback = self.primary_foreground);
477 apply_color!(
478 success_hover,
479 fallback = self.background.blend(self.success.opacity(hover_opacity))
480 );
481 apply_color!(
482 success_active,
483 fallback = self.success.darken(active_darken)
484 );
485 apply_color!(info, fallback = self.cyan);
486 apply_color!(info_foreground, fallback = self.primary_foreground);
487 apply_color!(
488 info_hover,
489 fallback = self.background.blend(self.info.opacity(hover_opacity))
490 );
491 apply_color!(info_active, fallback = self.info.darken(active_darken));
492 apply_color!(warning, fallback = self.yellow);
493 apply_color!(warning_foreground, fallback = self.primary_foreground);
494 apply_color!(
495 warning_hover,
496 fallback = self.background.blend(self.warning.opacity(0.9))
497 );
498 apply_color!(
499 warning_active,
500 fallback = self.background.blend(self.warning.darken(active_darken))
501 );
502
503 apply_color!(accent, fallback = self.secondary);
505 apply_color!(accent_foreground, fallback = self.secondary_foreground);
506 apply_color!(accordion, fallback = self.background);
507 apply_color!(accordion_hover, fallback = self.accent.opacity(0.8));
508 apply_color!(
509 group_box,
510 fallback = self
511 .background
512 .blend(
513 self.secondary
514 .opacity(if config.mode.is_dark() { 0.3 } else { 0.4 })
515 )
516 );
517 apply_color!(group_box_foreground, fallback = self.secondary_foreground);
518 apply_color!(caret, fallback = self.primary);
519 apply_color!(chart_1, fallback = self.blue.lighten(0.4));
520 apply_color!(chart_2, fallback = self.blue.lighten(0.2));
521 apply_color!(chart_3, fallback = self.blue);
522 apply_color!(chart_4, fallback = self.blue.darken(0.2));
523 apply_color!(chart_5, fallback = self.blue.darken(0.4));
524 apply_color!(danger, fallback = self.red);
525 apply_color!(danger_active, fallback = self.danger.darken(active_darken));
526 apply_color!(danger_foreground, fallback = self.primary_foreground);
527 apply_color!(
528 danger_hover,
529 fallback = self.background.blend(self.danger.opacity(0.9))
530 );
531 apply_color!(
532 description_list_label,
533 fallback = self.background.blend(self.border.opacity(0.2))
534 );
535 apply_color!(
536 description_list_label_foreground,
537 fallback = self.secondary_foreground
538 );
539 apply_color!(drag_border, fallback = self.primary.opacity(0.65));
540 apply_color!(drop_target, fallback = self.primary.opacity(0.2));
541 apply_color!(input, fallback = self.border);
542 apply_color!(link, fallback = self.primary);
543 apply_color!(link_active, fallback = self.link);
544 apply_color!(link_hover, fallback = self.link);
545 apply_color!(list, fallback = self.background);
546 apply_color!(
547 list_active,
548 fallback = self.background.blend(self.primary.opacity(0.1))
549 );
550 apply_color!(
551 list_active_border,
552 fallback = self.background.blend(self.primary.opacity(0.6))
553 );
554 apply_color!(list_even, fallback = self.list);
555 apply_color!(list_head, fallback = self.list);
556 apply_color!(list_hover, fallback = self.secondary_hover);
557 apply_color!(popover, fallback = self.background);
558 apply_color!(popover_foreground, fallback = self.foreground);
559 apply_color!(progress_bar, fallback = self.primary);
560 apply_color!(ring, fallback = self.blue);
561 apply_color!(scrollbar, fallback = self.background);
562 apply_color!(scrollbar_thumb, fallback = self.accent);
563 apply_color!(scrollbar_thumb_hover, fallback = self.scrollbar_thumb);
564 apply_color!(selection, fallback = self.primary);
565 apply_color!(sidebar, fallback = self.background);
566 apply_color!(sidebar_accent, fallback = self.accent);
567 apply_color!(sidebar_accent_foreground, fallback = self.accent_foreground);
568 apply_color!(sidebar_border, fallback = self.border);
569 apply_color!(sidebar_foreground, fallback = self.foreground);
570 apply_color!(sidebar_primary, fallback = self.primary);
571 apply_color!(
572 sidebar_primary_foreground,
573 fallback = self.primary_foreground
574 );
575 apply_color!(skeleton, fallback = self.secondary);
576 apply_color!(slider_bar, fallback = self.primary);
577 apply_color!(slider_thumb, fallback = self.primary_foreground);
578 apply_color!(switch, fallback = self.secondary);
579 apply_color!(tab, fallback = self.background);
580 apply_color!(tab_active, fallback = self.background);
581 apply_color!(tab_active_foreground, fallback = self.foreground);
582 apply_color!(tab_bar, fallback = self.background);
583 apply_color!(tab_bar_segmented, fallback = self.secondary);
584 apply_color!(tab_foreground, fallback = self.secondary_foreground);
585 apply_color!(table, fallback = self.list);
586 apply_color!(table_active, fallback = self.list_active);
587 apply_color!(table_active_border, fallback = self.list_active_border);
588 apply_color!(table_even, fallback = self.list_even);
589 apply_color!(table_head, fallback = self.list_head);
590 apply_color!(table_head_foreground, fallback = self.muted_foreground);
591 apply_color!(table_hover, fallback = self.list_hover);
592 apply_color!(table_row_border, fallback = self.border);
593 apply_color!(title_bar, fallback = self.background);
594 apply_color!(title_bar_border, fallback = self.border);
595 apply_color!(tiles, fallback = self.background);
596 apply_color!(overlay);
597 apply_color!(window_border, fallback = self.border);
598
599 self.list_active = self.list_active.alpha(0.2);
603 self.table_active = self.table_active.alpha(0.2);
604 self.selection = self.selection.alpha(0.3);
605 }
606}
607
608impl Theme {
609 pub fn apply_config(&mut self, config: &Rc<ThemeConfig>) {
611 if config.mode.is_dark() {
612 self.dark_theme = config.clone();
613 } else {
614 self.light_theme = config.clone();
615 }
616 if let Some(style) = &config.highlight {
617 let highlight_theme = Arc::new(HighlightTheme {
618 name: config.name.to_string(),
619 appearance: config.mode,
620 style: style.clone(),
621 });
622 self.highlight_theme = highlight_theme.clone();
623 }
624
625 let default_theme = if config.mode.is_dark() {
626 ThemeColor::dark()
627 } else {
628 ThemeColor::light()
629 };
630
631 self.colors.apply_config(&config, &default_theme);
632 self.mode = config.mode;
633 }
634}
635
636#[cfg(test)]
637mod tests {
638 use super::try_parse_color;
639 use gpui::hsla;
640
641 #[test]
642 fn test_try_parse_color() {
643 assert_eq!(
644 try_parse_color("#F2F200").ok(),
645 Some(hsla(0.16666667, 1., 0.4745098, 1.0))
646 );
647 assert_eq!(
648 try_parse_color("#00f21888").ok(),
649 Some(hsla(0.34986225, 1.0, 0.4745098, 0.53333336))
650 );
651 }
652}