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