1mod theme_registry;
2
3use gpui::{
4 color::Color,
5 elements::{ContainerStyle, ImageStyle, LabelStyle, TooltipStyle},
6 fonts::{HighlightStyle, TextStyle},
7 Border, MouseState,
8};
9use serde::{de::DeserializeOwned, Deserialize};
10use serde_json::Value;
11use std::{collections::HashMap, sync::Arc};
12
13pub use theme_registry::*;
14
15#[derive(Deserialize, Default)]
16pub struct Theme {
17 #[serde(default)]
18 pub meta: ThemeMeta,
19 pub workspace: Workspace,
20 pub context_menu: ContextMenu,
21 pub chat_panel: ChatPanel,
22 pub contacts_panel: ContactsPanel,
23 pub contact_finder: ContactFinder,
24 pub project_panel: ProjectPanel,
25 pub command_palette: CommandPalette,
26 pub picker: Picker,
27 pub editor: Editor,
28 pub search: Search,
29 pub project_diagnostics: ProjectDiagnostics,
30 pub breadcrumbs: ContainedText,
31 pub contact_notification: ContactNotification,
32 pub update_notification: UpdateNotification,
33 pub project_shared_notification: ProjectSharedNotification,
34 pub tooltip: TooltipStyle,
35 pub terminal: TerminalStyle,
36}
37
38#[derive(Deserialize, Default, Clone)]
39pub struct ThemeMeta {
40 pub name: String,
41 pub is_light: bool,
42}
43
44#[derive(Deserialize, Default)]
45pub struct Workspace {
46 pub background: Color,
47 pub titlebar: Titlebar,
48 pub tab_bar: TabBar,
49 pub pane_divider: Border,
50 pub leader_border_opacity: f32,
51 pub leader_border_width: f32,
52 pub sidebar: Sidebar,
53 pub status_bar: StatusBar,
54 pub toolbar: Toolbar,
55 pub disconnected_overlay: ContainedText,
56 pub modal: ContainerStyle,
57 pub notification: ContainerStyle,
58 pub notifications: Notifications,
59 pub joining_project_avatar: ImageStyle,
60 pub joining_project_message: ContainedText,
61 pub dock: Dock,
62}
63
64#[derive(Clone, Deserialize, Default)]
65pub struct Titlebar {
66 #[serde(flatten)]
67 pub container: ContainerStyle,
68 pub height: f32,
69 pub title: TextStyle,
70 pub avatar_width: f32,
71 pub avatar_margin: f32,
72 pub avatar_ribbon: AvatarRibbon,
73 pub offline_icon: OfflineIcon,
74 pub avatar: ImageStyle,
75 pub inactive_avatar: ImageStyle,
76 pub sign_in_prompt: Interactive<ContainedText>,
77 pub outdated_warning: ContainedText,
78 pub share_button: Interactive<ContainedText>,
79 pub toggle_contacts_button: Interactive<IconButton>,
80 pub contacts_popover: AddParticipantPopover,
81}
82
83#[derive(Clone, Deserialize, Default)]
84pub struct AddParticipantPopover {
85 #[serde(flatten)]
86 pub container: ContainerStyle,
87 pub height: f32,
88 pub width: f32,
89}
90
91#[derive(Clone, Deserialize, Default)]
92pub struct TabBar {
93 #[serde(flatten)]
94 pub container: ContainerStyle,
95 pub pane_button: Interactive<IconButton>,
96 pub pane_button_container: ContainerStyle,
97 pub active_pane: TabStyles,
98 pub inactive_pane: TabStyles,
99 pub dragged_tab: Tab,
100 pub height: f32,
101 pub drop_target_overlay_color: Color,
102}
103
104impl TabBar {
105 pub fn tab_style(&self, pane_active: bool, tab_active: bool) -> &Tab {
106 let tabs = if pane_active {
107 &self.active_pane
108 } else {
109 &self.inactive_pane
110 };
111
112 if tab_active {
113 &tabs.active_tab
114 } else {
115 &tabs.inactive_tab
116 }
117 }
118}
119
120#[derive(Clone, Deserialize, Default)]
121pub struct TabStyles {
122 pub active_tab: Tab,
123 pub inactive_tab: Tab,
124}
125
126#[derive(Clone, Deserialize, Default)]
127pub struct AvatarRibbon {
128 #[serde(flatten)]
129 pub container: ContainerStyle,
130 pub width: f32,
131 pub height: f32,
132}
133
134#[derive(Clone, Deserialize, Default)]
135pub struct OfflineIcon {
136 #[serde(flatten)]
137 pub container: ContainerStyle,
138 pub width: f32,
139 pub color: Color,
140}
141
142#[derive(Clone, Deserialize, Default)]
143pub struct Tab {
144 pub height: f32,
145 #[serde(flatten)]
146 pub container: ContainerStyle,
147 #[serde(flatten)]
148 pub label: LabelStyle,
149 pub description: ContainedText,
150 pub spacing: f32,
151 pub icon_width: f32,
152 pub icon_close: Color,
153 pub icon_close_active: Color,
154 pub icon_dirty: Color,
155 pub icon_conflict: Color,
156}
157
158#[derive(Clone, Deserialize, Default)]
159pub struct Toolbar {
160 #[serde(flatten)]
161 pub container: ContainerStyle,
162 pub height: f32,
163 pub item_spacing: f32,
164 pub nav_button: Interactive<IconButton>,
165}
166
167#[derive(Clone, Deserialize, Default)]
168pub struct Dock {
169 pub initial_size_right: f32,
170 pub initial_size_bottom: f32,
171 pub wash_color: Color,
172 pub panel: ContainerStyle,
173 pub maximized: ContainerStyle,
174}
175
176#[derive(Clone, Deserialize, Default)]
177pub struct Notifications {
178 #[serde(flatten)]
179 pub container: ContainerStyle,
180 pub width: f32,
181}
182
183#[derive(Clone, Deserialize, Default)]
184pub struct Search {
185 #[serde(flatten)]
186 pub container: ContainerStyle,
187 pub editor: FindEditor,
188 pub invalid_editor: ContainerStyle,
189 pub option_button_group: ContainerStyle,
190 pub option_button: Interactive<ContainedText>,
191 pub match_background: Color,
192 pub match_index: ContainedText,
193 pub results_status: TextStyle,
194 pub tab_icon_width: f32,
195 pub tab_icon_spacing: f32,
196}
197
198#[derive(Clone, Deserialize, Default)]
199pub struct FindEditor {
200 #[serde(flatten)]
201 pub input: FieldEditor,
202 pub min_width: f32,
203 pub max_width: f32,
204}
205
206#[derive(Deserialize, Default)]
207pub struct StatusBar {
208 #[serde(flatten)]
209 pub container: ContainerStyle,
210 pub height: f32,
211 pub item_spacing: f32,
212 pub cursor_position: TextStyle,
213 pub auto_update_progress_message: TextStyle,
214 pub auto_update_done_message: TextStyle,
215 pub lsp_status: Interactive<StatusBarLspStatus>,
216 pub feedback: Interactive<TextStyle>,
217 pub sidebar_buttons: StatusBarSidebarButtons,
218 pub diagnostic_summary: Interactive<StatusBarDiagnosticSummary>,
219 pub diagnostic_message: Interactive<ContainedText>,
220}
221
222#[derive(Deserialize, Default)]
223pub struct StatusBarSidebarButtons {
224 pub group_left: ContainerStyle,
225 pub group_right: ContainerStyle,
226 pub item: Interactive<SidebarItem>,
227 pub badge: ContainerStyle,
228}
229
230#[derive(Deserialize, Default)]
231pub struct StatusBarDiagnosticSummary {
232 pub container_ok: ContainerStyle,
233 pub container_warning: ContainerStyle,
234 pub container_error: ContainerStyle,
235 pub text: TextStyle,
236 pub icon_color_ok: Color,
237 pub icon_color_warning: Color,
238 pub icon_color_error: Color,
239 pub height: f32,
240 pub icon_width: f32,
241 pub icon_spacing: f32,
242 pub summary_spacing: f32,
243}
244
245#[derive(Deserialize, Default)]
246pub struct StatusBarLspStatus {
247 #[serde(flatten)]
248 pub container: ContainerStyle,
249 pub height: f32,
250 pub icon_spacing: f32,
251 pub icon_color: Color,
252 pub icon_width: f32,
253 pub message: TextStyle,
254}
255
256#[derive(Deserialize, Default)]
257pub struct Sidebar {
258 pub initial_size: f32,
259 #[serde(flatten)]
260 pub container: ContainerStyle,
261}
262
263#[derive(Clone, Copy, Deserialize, Default)]
264pub struct SidebarItem {
265 #[serde(flatten)]
266 pub container: ContainerStyle,
267 pub icon_color: Color,
268 pub icon_size: f32,
269}
270
271#[derive(Deserialize, Default)]
272pub struct ChatPanel {
273 #[serde(flatten)]
274 pub container: ContainerStyle,
275 pub message: ChatMessage,
276 pub pending_message: ChatMessage,
277 pub channel_select: ChannelSelect,
278 pub input_editor: FieldEditor,
279 pub sign_in_prompt: TextStyle,
280 pub hovered_sign_in_prompt: TextStyle,
281}
282
283#[derive(Deserialize, Default)]
284pub struct ProjectPanel {
285 #[serde(flatten)]
286 pub container: ContainerStyle,
287 pub entry: Interactive<ProjectPanelEntry>,
288 pub cut_entry_fade: f32,
289 pub ignored_entry_fade: f32,
290 pub filename_editor: FieldEditor,
291 pub indent_width: f32,
292}
293
294#[derive(Clone, Debug, Deserialize, Default)]
295pub struct ProjectPanelEntry {
296 pub height: f32,
297 #[serde(flatten)]
298 pub container: ContainerStyle,
299 pub text: TextStyle,
300 pub icon_color: Color,
301 pub icon_size: f32,
302 pub icon_spacing: f32,
303}
304
305#[derive(Clone, Debug, Deserialize, Default)]
306pub struct ContextMenu {
307 #[serde(flatten)]
308 pub container: ContainerStyle,
309 pub item: Interactive<ContextMenuItem>,
310 pub keystroke_margin: f32,
311 pub separator: ContainerStyle,
312}
313
314#[derive(Clone, Debug, Deserialize, Default)]
315pub struct ContextMenuItem {
316 #[serde(flatten)]
317 pub container: ContainerStyle,
318 pub label: TextStyle,
319 pub keystroke: ContainedText,
320 pub icon_width: f32,
321 pub icon_spacing: f32,
322}
323
324#[derive(Debug, Deserialize, Default)]
325pub struct CommandPalette {
326 pub key: Interactive<ContainedLabel>,
327 pub keystroke_spacing: f32,
328}
329
330#[derive(Deserialize, Default)]
331pub struct ContactsPanel {
332 #[serde(flatten)]
333 pub container: ContainerStyle,
334 pub user_query_editor: FieldEditor,
335 pub user_query_editor_height: f32,
336 pub add_contact_button: IconButton,
337 pub header_row: Interactive<ContainedText>,
338 pub contact_row: Interactive<ContainerStyle>,
339 pub project_row: Interactive<ProjectRow>,
340 pub row_height: f32,
341 pub contact_avatar: ImageStyle,
342 pub contact_username: ContainedText,
343 pub contact_button: Interactive<IconButton>,
344 pub contact_button_spacing: f32,
345 pub disabled_button: IconButton,
346 pub tree_branch: Interactive<TreeBranch>,
347 pub private_button: Interactive<IconButton>,
348 pub section_icon_size: f32,
349 pub invite_row: Interactive<ContainedLabel>,
350}
351
352#[derive(Deserialize, Default)]
353pub struct InviteLink {
354 #[serde(flatten)]
355 pub container: ContainerStyle,
356 #[serde(flatten)]
357 pub label: LabelStyle,
358 pub icon: Icon,
359}
360
361#[derive(Deserialize, Default, Clone, Copy)]
362pub struct TreeBranch {
363 pub width: f32,
364 pub color: Color,
365}
366
367#[derive(Deserialize, Default)]
368pub struct ContactFinder {
369 pub row_height: f32,
370 pub contact_avatar: ImageStyle,
371 pub contact_username: ContainerStyle,
372 pub contact_button: IconButton,
373 pub disabled_contact_button: IconButton,
374}
375
376#[derive(Deserialize, Default)]
377pub struct Icon {
378 #[serde(flatten)]
379 pub container: ContainerStyle,
380 pub color: Color,
381 pub width: f32,
382 pub path: String,
383}
384
385#[derive(Deserialize, Clone, Copy, Default)]
386pub struct IconButton {
387 #[serde(flatten)]
388 pub container: ContainerStyle,
389 pub color: Color,
390 pub icon_width: f32,
391 pub button_width: f32,
392}
393
394#[derive(Deserialize, Default)]
395pub struct ProjectRow {
396 #[serde(flatten)]
397 pub container: ContainerStyle,
398 pub name: ContainedText,
399 pub guests: ContainerStyle,
400 pub guest_avatar: ImageStyle,
401 pub guest_avatar_spacing: f32,
402}
403
404#[derive(Deserialize, Default)]
405pub struct ChatMessage {
406 #[serde(flatten)]
407 pub container: ContainerStyle,
408 pub body: TextStyle,
409 pub sender: ContainedText,
410 pub timestamp: ContainedText,
411}
412
413#[derive(Deserialize, Default)]
414pub struct ChannelSelect {
415 #[serde(flatten)]
416 pub container: ContainerStyle,
417 pub header: ChannelName,
418 pub item: ChannelName,
419 pub active_item: ChannelName,
420 pub hovered_item: ChannelName,
421 pub hovered_active_item: ChannelName,
422 pub menu: ContainerStyle,
423}
424
425#[derive(Deserialize, Default)]
426pub struct ChannelName {
427 #[serde(flatten)]
428 pub container: ContainerStyle,
429 pub hash: ContainedText,
430 pub name: TextStyle,
431}
432
433#[derive(Deserialize, Default)]
434pub struct Picker {
435 #[serde(flatten)]
436 pub container: ContainerStyle,
437 pub empty: ContainedLabel,
438 pub input_editor: FieldEditor,
439 pub item: Interactive<ContainedLabel>,
440}
441
442#[derive(Clone, Debug, Deserialize, Default)]
443pub struct ContainedText {
444 #[serde(flatten)]
445 pub container: ContainerStyle,
446 #[serde(flatten)]
447 pub text: TextStyle,
448}
449
450#[derive(Clone, Debug, Deserialize, Default)]
451pub struct ContainedLabel {
452 #[serde(flatten)]
453 pub container: ContainerStyle,
454 #[serde(flatten)]
455 pub label: LabelStyle,
456}
457
458#[derive(Clone, Deserialize, Default)]
459pub struct ProjectDiagnostics {
460 #[serde(flatten)]
461 pub container: ContainerStyle,
462 pub empty_message: TextStyle,
463 pub tab_icon_width: f32,
464 pub tab_icon_spacing: f32,
465 pub tab_summary_spacing: f32,
466}
467
468#[derive(Deserialize, Default)]
469pub struct ContactNotification {
470 pub header_avatar: ImageStyle,
471 pub header_message: ContainedText,
472 pub header_height: f32,
473 pub body_message: ContainedText,
474 pub button: Interactive<ContainedText>,
475 pub dismiss_button: Interactive<IconButton>,
476}
477
478#[derive(Deserialize, Default)]
479pub struct UpdateNotification {
480 pub message: ContainedText,
481 pub action_message: Interactive<ContainedText>,
482 pub dismiss_button: Interactive<IconButton>,
483}
484
485#[derive(Deserialize, Default)]
486pub struct ProjectSharedNotification {
487 pub owner_avatar: ImageStyle,
488 pub message: ContainedText,
489 pub join_button: ContainedText,
490 pub dismiss_button: ContainedText,
491}
492
493#[derive(Clone, Deserialize, Default)]
494pub struct Editor {
495 pub text_color: Color,
496 #[serde(default)]
497 pub background: Color,
498 pub selection: SelectionStyle,
499 pub gutter_background: Color,
500 pub gutter_padding_factor: f32,
501 pub active_line_background: Color,
502 pub highlighted_line_background: Color,
503 pub rename_fade: f32,
504 pub document_highlight_read_background: Color,
505 pub document_highlight_write_background: Color,
506 pub diff_background_deleted: Color,
507 pub diff_background_inserted: Color,
508 pub line_number: Color,
509 pub line_number_active: Color,
510 pub guest_selections: Vec<SelectionStyle>,
511 pub syntax: Arc<SyntaxTheme>,
512 pub diagnostic_path_header: DiagnosticPathHeader,
513 pub diagnostic_header: DiagnosticHeader,
514 pub error_diagnostic: DiagnosticStyle,
515 pub invalid_error_diagnostic: DiagnosticStyle,
516 pub warning_diagnostic: DiagnosticStyle,
517 pub invalid_warning_diagnostic: DiagnosticStyle,
518 pub information_diagnostic: DiagnosticStyle,
519 pub invalid_information_diagnostic: DiagnosticStyle,
520 pub hint_diagnostic: DiagnosticStyle,
521 pub invalid_hint_diagnostic: DiagnosticStyle,
522 pub autocomplete: AutocompleteStyle,
523 pub code_actions: CodeActions,
524 pub unnecessary_code_fade: f32,
525 pub hover_popover: HoverPopover,
526 pub link_definition: HighlightStyle,
527 pub composition_mark: HighlightStyle,
528 pub jump_icon: Interactive<IconButton>,
529}
530
531#[derive(Clone, Deserialize, Default)]
532pub struct DiagnosticPathHeader {
533 #[serde(flatten)]
534 pub container: ContainerStyle,
535 pub filename: ContainedText,
536 pub path: ContainedText,
537 pub text_scale_factor: f32,
538}
539
540#[derive(Clone, Deserialize, Default)]
541pub struct DiagnosticHeader {
542 #[serde(flatten)]
543 pub container: ContainerStyle,
544 pub message: ContainedLabel,
545 pub code: ContainedText,
546 pub text_scale_factor: f32,
547 pub icon_width_factor: f32,
548}
549
550#[derive(Clone, Deserialize, Default)]
551pub struct DiagnosticStyle {
552 pub message: LabelStyle,
553 #[serde(default)]
554 pub header: ContainerStyle,
555 pub text_scale_factor: f32,
556}
557
558#[derive(Clone, Deserialize, Default)]
559pub struct AutocompleteStyle {
560 #[serde(flatten)]
561 pub container: ContainerStyle,
562 pub item: ContainerStyle,
563 pub selected_item: ContainerStyle,
564 pub hovered_item: ContainerStyle,
565 pub match_highlight: HighlightStyle,
566}
567
568#[derive(Clone, Copy, Default, Deserialize)]
569pub struct SelectionStyle {
570 pub cursor: Color,
571 pub selection: Color,
572}
573
574#[derive(Clone, Deserialize, Default)]
575pub struct FieldEditor {
576 #[serde(flatten)]
577 pub container: ContainerStyle,
578 pub text: TextStyle,
579 #[serde(default)]
580 pub placeholder_text: Option<TextStyle>,
581 pub selection: SelectionStyle,
582}
583
584#[derive(Clone, Deserialize, Default)]
585pub struct CodeActions {
586 #[serde(default)]
587 pub indicator: Color,
588 pub vertical_scale: f32,
589}
590
591#[derive(Debug, Default, Clone, Copy)]
592pub struct Interactive<T> {
593 pub default: T,
594 pub hover: Option<T>,
595 pub clicked: Option<T>,
596 pub active: Option<T>,
597 pub disabled: Option<T>,
598}
599
600impl<T> Interactive<T> {
601 pub fn style_for(&self, state: MouseState, active: bool) -> &T {
602 if active {
603 self.active.as_ref().unwrap_or(&self.default)
604 } else if state.clicked == Some(gpui::MouseButton::Left) && self.clicked.is_some() {
605 self.clicked.as_ref().unwrap()
606 } else if state.hovered {
607 self.hover.as_ref().unwrap_or(&self.default)
608 } else {
609 &self.default
610 }
611 }
612
613 pub fn disabled_style(&self) -> &T {
614 self.disabled.as_ref().unwrap_or(&self.default)
615 }
616}
617
618impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
619 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
620 where
621 D: serde::Deserializer<'de>,
622 {
623 #[derive(Deserialize)]
624 struct Helper {
625 #[serde(flatten)]
626 default: Value,
627 hover: Option<Value>,
628 clicked: Option<Value>,
629 active: Option<Value>,
630 disabled: Option<Value>,
631 }
632
633 let json = Helper::deserialize(deserializer)?;
634
635 let deserialize_state = |state_json: Option<Value>| -> Result<Option<T>, D::Error> {
636 if let Some(mut state_json) = state_json {
637 if let Value::Object(state_json) = &mut state_json {
638 if let Value::Object(default) = &json.default {
639 for (key, value) in default {
640 if !state_json.contains_key(key) {
641 state_json.insert(key.clone(), value.clone());
642 }
643 }
644 }
645 }
646 Ok(Some(
647 serde_json::from_value::<T>(state_json).map_err(serde::de::Error::custom)?,
648 ))
649 } else {
650 Ok(None)
651 }
652 };
653
654 let hover = deserialize_state(json.hover)?;
655 let clicked = deserialize_state(json.clicked)?;
656 let active = deserialize_state(json.active)?;
657 let disabled = deserialize_state(json.disabled)?;
658 let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
659
660 Ok(Interactive {
661 default,
662 hover,
663 clicked,
664 active,
665 disabled,
666 })
667 }
668}
669
670impl Editor {
671 pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle {
672 let style_ix = replica_id as usize % (self.guest_selections.len() + 1);
673 if style_ix == 0 {
674 &self.selection
675 } else {
676 &self.guest_selections[style_ix - 1]
677 }
678 }
679}
680
681#[derive(Default)]
682pub struct SyntaxTheme {
683 pub highlights: Vec<(String, HighlightStyle)>,
684}
685
686impl SyntaxTheme {
687 pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
688 Self { highlights }
689 }
690}
691
692impl<'de> Deserialize<'de> for SyntaxTheme {
693 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
694 where
695 D: serde::Deserializer<'de>,
696 {
697 let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
698
699 let mut result = Self::new(Vec::new());
700 for (key, style) in syntax_data {
701 match result
702 .highlights
703 .binary_search_by(|(needle, _)| needle.cmp(&key))
704 {
705 Ok(i) | Err(i) => {
706 result.highlights.insert(i, (key, style));
707 }
708 }
709 }
710
711 Ok(result)
712 }
713}
714
715#[derive(Clone, Deserialize, Default)]
716pub struct HoverPopover {
717 pub container: ContainerStyle,
718 pub info_container: ContainerStyle,
719 pub warning_container: ContainerStyle,
720 pub error_container: ContainerStyle,
721 pub block_style: ContainerStyle,
722 pub prose: TextStyle,
723 pub highlight: Color,
724}
725
726#[derive(Clone, Deserialize, Default)]
727pub struct TerminalStyle {
728 pub colors: TerminalColors,
729 pub modal_container: ContainerStyle,
730}
731
732#[derive(Clone, Deserialize, Default)]
733pub struct TerminalColors {
734 pub black: Color,
735 pub red: Color,
736 pub green: Color,
737 pub yellow: Color,
738 pub blue: Color,
739 pub magenta: Color,
740 pub cyan: Color,
741 pub white: Color,
742 pub bright_black: Color,
743 pub bright_red: Color,
744 pub bright_green: Color,
745 pub bright_yellow: Color,
746 pub bright_blue: Color,
747 pub bright_magenta: Color,
748 pub bright_cyan: Color,
749 pub bright_white: Color,
750 pub foreground: Color,
751 pub background: Color,
752 pub modal_background: Color,
753 pub cursor: Color,
754 pub dim_black: Color,
755 pub dim_red: Color,
756 pub dim_green: Color,
757 pub dim_yellow: Color,
758 pub dim_blue: Color,
759 pub dim_magenta: Color,
760 pub dim_cyan: Color,
761 pub dim_white: Color,
762 pub bright_foreground: Color,
763 pub dim_foreground: Color,
764}