1mod theme_registry;
2
3use gpui::{
4 color::Color,
5 elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, TooltipStyle},
6 fonts::{HighlightStyle, TextStyle},
7 Border, MouseState,
8};
9use serde::{de::DeserializeOwned, Deserialize};
10use serde_json::Value;
11use std::{collections::HashMap, sync::Arc};
12use ui::{ButtonStyle, CheckboxStyle, ModalStyle, SvgStyle};
13
14pub mod ui;
15
16pub use theme_registry::*;
17
18#[derive(Deserialize, Default)]
19pub struct Theme {
20 #[serde(default)]
21 pub meta: ThemeMeta,
22 pub workspace: Workspace,
23 pub context_menu: ContextMenu,
24 pub contacts_popover: ContactsPopover,
25 pub contact_list: ContactList,
26 pub copilot: Copilot,
27 pub contact_finder: ContactFinder,
28 pub project_panel: ProjectPanel,
29 pub command_palette: CommandPalette,
30 pub picker: Picker,
31 pub editor: Editor,
32 pub search: Search,
33 pub project_diagnostics: ProjectDiagnostics,
34 pub breadcrumbs: ContainedText,
35 pub shared_screen: ContainerStyle,
36 pub contact_notification: ContactNotification,
37 pub update_notification: UpdateNotification,
38 pub simple_message_notification: MessageNotification,
39 pub project_shared_notification: ProjectSharedNotification,
40 pub incoming_call_notification: IncomingCallNotification,
41 pub tooltip: TooltipStyle,
42 pub terminal: TerminalStyle,
43 pub feedback: FeedbackStyle,
44 pub welcome: WelcomeStyle,
45 pub color_scheme: ColorScheme,
46}
47
48#[derive(Deserialize, Default, Clone)]
49pub struct ThemeMeta {
50 pub name: String,
51 pub is_light: bool,
52}
53
54#[derive(Deserialize, Default)]
55pub struct Workspace {
56 pub background: Color,
57 pub blank_pane: BlankPaneStyle,
58 pub titlebar: Titlebar,
59 pub tab_bar: TabBar,
60 pub pane_divider: Border,
61 pub leader_border_opacity: f32,
62 pub leader_border_width: f32,
63 pub sidebar: Sidebar,
64 pub status_bar: StatusBar,
65 pub toolbar: Toolbar,
66 pub disconnected_overlay: ContainedText,
67 pub modal: ContainerStyle,
68 pub notification: ContainerStyle,
69 pub notifications: Notifications,
70 pub joining_project_avatar: ImageStyle,
71 pub joining_project_message: ContainedText,
72 pub external_location_message: ContainedText,
73 pub dock: Dock,
74 pub drop_target_overlay_color: Color,
75}
76
77#[derive(Clone, Deserialize, Default)]
78pub struct BlankPaneStyle {
79 pub logo: SvgStyle,
80 pub logo_shadow: SvgStyle,
81 pub logo_container: ContainerStyle,
82 pub keyboard_hints: ContainerStyle,
83 pub keyboard_hint: Interactive<ContainedText>,
84 pub keyboard_hint_width: f32,
85}
86
87#[derive(Clone, Deserialize, Default)]
88pub struct Titlebar {
89 #[serde(flatten)]
90 pub container: ContainerStyle,
91 pub height: f32,
92 pub title: TextStyle,
93 pub item_spacing: f32,
94 pub face_pile_spacing: f32,
95 pub avatar_ribbon: AvatarRibbon,
96 pub follower_avatar_overlap: f32,
97 pub leader_selection: ContainerStyle,
98 pub offline_icon: OfflineIcon,
99 pub leader_avatar: AvatarStyle,
100 pub follower_avatar: AvatarStyle,
101 pub inactive_avatar_grayscale: bool,
102 pub sign_in_prompt: Interactive<ContainedText>,
103 pub outdated_warning: ContainedText,
104 pub share_button: Interactive<ContainedText>,
105 pub call_control: Interactive<IconButton>,
106 pub toggle_contacts_button: Interactive<IconButton>,
107 pub user_menu_button: Interactive<IconButton>,
108 pub toggle_contacts_badge: ContainerStyle,
109}
110
111#[derive(Copy, Clone, Deserialize, Default)]
112pub struct AvatarStyle {
113 #[serde(flatten)]
114 pub image: ImageStyle,
115 pub outer_width: f32,
116 pub outer_corner_radius: f32,
117}
118
119#[derive(Deserialize, Default, Clone)]
120pub struct Copilot {
121 pub modal: ModalStyle,
122 pub auth: CopilotAuth,
123}
124
125#[derive(Deserialize, Default, Clone)]
126pub struct CopilotAuth {
127 pub instruction_text: TextStyle,
128 pub cta_button: ButtonStyle,
129 pub content_width: f32,
130 pub copilot_icon: SvgStyle,
131 pub plus_icon: SvgStyle,
132 pub zed_icon: SvgStyle,
133 pub header_text: TextStyle,
134 pub device_code_group: ContainerStyle,
135 pub github_group: ContainerStyle,
136 pub header_group: ContainerStyle,
137 pub device_code: TextStyle,
138 pub device_code_cta: ButtonStyle,
139 pub device_code_left: f32,
140 pub device_code_left_container: ContainerStyle,
141 pub device_code_right: f32,
142 pub device_code_right_container: ContainerStyle,
143 pub device_code_seperator_height: f32,
144}
145
146#[derive(Deserialize, Default)]
147pub struct ContactsPopover {
148 #[serde(flatten)]
149 pub container: ContainerStyle,
150 pub height: f32,
151 pub width: f32,
152}
153
154#[derive(Deserialize, Default)]
155pub struct ContactList {
156 pub user_query_editor: FieldEditor,
157 pub user_query_editor_height: f32,
158 pub add_contact_button: IconButton,
159 pub header_row: Interactive<ContainedText>,
160 pub leave_call: Interactive<ContainedText>,
161 pub contact_row: Interactive<ContainerStyle>,
162 pub row_height: f32,
163 pub project_row: Interactive<ProjectRow>,
164 pub tree_branch: Interactive<TreeBranch>,
165 pub contact_avatar: ImageStyle,
166 pub contact_status_free: ContainerStyle,
167 pub contact_status_busy: ContainerStyle,
168 pub contact_username: ContainedText,
169 pub contact_button: Interactive<IconButton>,
170 pub contact_button_spacing: f32,
171 pub disabled_button: IconButton,
172 pub section_icon_size: f32,
173 pub calling_indicator: ContainedText,
174}
175
176#[derive(Deserialize, Default)]
177pub struct ProjectRow {
178 #[serde(flatten)]
179 pub container: ContainerStyle,
180 pub icon: Icon,
181 pub name: ContainedText,
182}
183
184#[derive(Deserialize, Default, Clone, Copy)]
185pub struct TreeBranch {
186 pub width: f32,
187 pub color: Color,
188}
189
190#[derive(Deserialize, Default)]
191pub struct ContactFinder {
192 pub picker: Picker,
193 pub row_height: f32,
194 pub contact_avatar: ImageStyle,
195 pub contact_username: ContainerStyle,
196 pub contact_button: IconButton,
197 pub disabled_contact_button: IconButton,
198}
199
200#[derive(Clone, Deserialize, Default)]
201pub struct TabBar {
202 #[serde(flatten)]
203 pub container: ContainerStyle,
204 pub pane_button: Interactive<IconButton>,
205 pub pane_button_container: ContainerStyle,
206 pub active_pane: TabStyles,
207 pub inactive_pane: TabStyles,
208 pub dragged_tab: Tab,
209 pub height: f32,
210}
211
212impl TabBar {
213 pub fn tab_style(&self, pane_active: bool, tab_active: bool) -> &Tab {
214 let tabs = if pane_active {
215 &self.active_pane
216 } else {
217 &self.inactive_pane
218 };
219
220 if tab_active {
221 &tabs.active_tab
222 } else {
223 &tabs.inactive_tab
224 }
225 }
226}
227
228#[derive(Clone, Deserialize, Default)]
229pub struct TabStyles {
230 pub active_tab: Tab,
231 pub inactive_tab: Tab,
232}
233
234#[derive(Clone, Deserialize, Default)]
235pub struct AvatarRibbon {
236 #[serde(flatten)]
237 pub container: ContainerStyle,
238 pub width: f32,
239 pub height: f32,
240}
241
242#[derive(Clone, Deserialize, Default)]
243pub struct OfflineIcon {
244 #[serde(flatten)]
245 pub container: ContainerStyle,
246 pub width: f32,
247 pub color: Color,
248}
249
250#[derive(Clone, Deserialize, Default)]
251pub struct Tab {
252 pub height: f32,
253 #[serde(flatten)]
254 pub container: ContainerStyle,
255 #[serde(flatten)]
256 pub label: LabelStyle,
257 pub description: ContainedText,
258 pub spacing: f32,
259 pub close_icon_width: f32,
260 pub type_icon_width: f32,
261 pub icon_close: Color,
262 pub icon_close_active: Color,
263 pub icon_dirty: Color,
264 pub icon_conflict: Color,
265}
266
267#[derive(Clone, Deserialize, Default)]
268pub struct Toolbar {
269 #[serde(flatten)]
270 pub container: ContainerStyle,
271 pub height: f32,
272 pub item_spacing: f32,
273 pub nav_button: Interactive<IconButton>,
274}
275
276#[derive(Clone, Deserialize, Default)]
277pub struct Dock {
278 pub initial_size_right: f32,
279 pub initial_size_bottom: f32,
280 pub wash_color: Color,
281 pub panel: ContainerStyle,
282 pub maximized: ContainerStyle,
283}
284
285#[derive(Clone, Deserialize, Default)]
286pub struct Notifications {
287 #[serde(flatten)]
288 pub container: ContainerStyle,
289 pub width: f32,
290}
291
292#[derive(Clone, Deserialize, Default)]
293pub struct Search {
294 #[serde(flatten)]
295 pub container: ContainerStyle,
296 pub editor: FindEditor,
297 pub invalid_editor: ContainerStyle,
298 pub option_button_group: ContainerStyle,
299 pub option_button: Interactive<ContainedText>,
300 pub match_background: Color,
301 pub match_index: ContainedText,
302 pub results_status: TextStyle,
303 pub dismiss_button: Interactive<IconButton>,
304}
305
306#[derive(Clone, Deserialize, Default)]
307pub struct FindEditor {
308 #[serde(flatten)]
309 pub input: FieldEditor,
310 pub min_width: f32,
311 pub max_width: f32,
312}
313
314#[derive(Deserialize, Default)]
315pub struct StatusBar {
316 #[serde(flatten)]
317 pub container: ContainerStyle,
318 pub height: f32,
319 pub item_spacing: f32,
320 pub cursor_position: TextStyle,
321 pub active_language: Interactive<ContainedText>,
322 pub auto_update_progress_message: TextStyle,
323 pub auto_update_done_message: TextStyle,
324 pub lsp_status: Interactive<StatusBarLspStatus>,
325 pub sidebar_buttons: StatusBarSidebarButtons,
326 pub diagnostic_summary: Interactive<StatusBarDiagnosticSummary>,
327 pub diagnostic_message: Interactive<ContainedText>,
328}
329
330#[derive(Deserialize, Default)]
331pub struct StatusBarSidebarButtons {
332 pub group_left: ContainerStyle,
333 pub group_right: ContainerStyle,
334 pub item: Interactive<SidebarItem>,
335 pub badge: ContainerStyle,
336}
337
338#[derive(Deserialize, Default)]
339pub struct StatusBarDiagnosticSummary {
340 pub container_ok: ContainerStyle,
341 pub container_warning: ContainerStyle,
342 pub container_error: ContainerStyle,
343 pub text: TextStyle,
344 pub icon_color_ok: Color,
345 pub icon_color_warning: Color,
346 pub icon_color_error: Color,
347 pub height: f32,
348 pub icon_width: f32,
349 pub icon_spacing: f32,
350 pub summary_spacing: f32,
351}
352
353#[derive(Deserialize, Default)]
354pub struct StatusBarLspStatus {
355 #[serde(flatten)]
356 pub container: ContainerStyle,
357 pub height: f32,
358 pub icon_spacing: f32,
359 pub icon_color: Color,
360 pub icon_width: f32,
361 pub message: TextStyle,
362}
363
364#[derive(Deserialize, Default)]
365pub struct Sidebar {
366 pub initial_size: f32,
367 #[serde(flatten)]
368 pub container: ContainerStyle,
369}
370
371#[derive(Clone, Deserialize, Default)]
372pub struct SidebarItem {
373 #[serde(flatten)]
374 pub container: ContainerStyle,
375 pub icon_color: Color,
376 pub icon_size: f32,
377 pub label: ContainedText,
378}
379
380#[derive(Deserialize, Default)]
381pub struct ProjectPanel {
382 #[serde(flatten)]
383 pub container: ContainerStyle,
384 pub entry: Interactive<ProjectPanelEntry>,
385 pub dragged_entry: ProjectPanelEntry,
386 pub ignored_entry: Interactive<ProjectPanelEntry>,
387 pub cut_entry: Interactive<ProjectPanelEntry>,
388 pub filename_editor: FieldEditor,
389 pub indent_width: f32,
390 pub open_project_button: Interactive<ContainedText>,
391}
392
393#[derive(Clone, Debug, Deserialize, Default)]
394pub struct ProjectPanelEntry {
395 pub height: f32,
396 #[serde(flatten)]
397 pub container: ContainerStyle,
398 pub text: TextStyle,
399 pub icon_color: Color,
400 pub icon_size: f32,
401 pub icon_spacing: f32,
402}
403
404#[derive(Clone, Debug, Deserialize, Default)]
405pub struct ContextMenu {
406 #[serde(flatten)]
407 pub container: ContainerStyle,
408 pub item: Interactive<ContextMenuItem>,
409 pub keystroke_margin: f32,
410 pub separator: ContainerStyle,
411}
412
413#[derive(Clone, Debug, Deserialize, Default)]
414pub struct ContextMenuItem {
415 #[serde(flatten)]
416 pub container: ContainerStyle,
417 pub label: TextStyle,
418 pub keystroke: ContainedText,
419 pub icon_width: f32,
420 pub icon_spacing: f32,
421}
422
423#[derive(Debug, Deserialize, Default)]
424pub struct CommandPalette {
425 pub key: Interactive<ContainedLabel>,
426 pub keystroke_spacing: f32,
427}
428
429#[derive(Deserialize, Default)]
430pub struct InviteLink {
431 #[serde(flatten)]
432 pub container: ContainerStyle,
433 #[serde(flatten)]
434 pub label: LabelStyle,
435 pub icon: Icon,
436}
437
438#[derive(Deserialize, Clone, Copy, Default)]
439pub struct Icon {
440 #[serde(flatten)]
441 pub container: ContainerStyle,
442 pub color: Color,
443 pub width: f32,
444}
445
446#[derive(Deserialize, Clone, Copy, Default)]
447pub struct IconButton {
448 #[serde(flatten)]
449 pub container: ContainerStyle,
450 pub color: Color,
451 pub icon_width: f32,
452 pub button_width: f32,
453}
454
455#[derive(Deserialize, Default)]
456pub struct ChatMessage {
457 #[serde(flatten)]
458 pub container: ContainerStyle,
459 pub body: TextStyle,
460 pub sender: ContainedText,
461 pub timestamp: ContainedText,
462}
463
464#[derive(Deserialize, Default)]
465pub struct ChannelSelect {
466 #[serde(flatten)]
467 pub container: ContainerStyle,
468 pub header: ChannelName,
469 pub item: ChannelName,
470 pub active_item: ChannelName,
471 pub hovered_item: ChannelName,
472 pub hovered_active_item: ChannelName,
473 pub menu: ContainerStyle,
474}
475
476#[derive(Deserialize, Default)]
477pub struct ChannelName {
478 #[serde(flatten)]
479 pub container: ContainerStyle,
480 pub hash: ContainedText,
481 pub name: TextStyle,
482}
483
484#[derive(Clone, Deserialize, Default)]
485pub struct Picker {
486 #[serde(flatten)]
487 pub container: ContainerStyle,
488 pub empty_container: ContainerStyle,
489 pub input_editor: FieldEditor,
490 pub empty_input_editor: FieldEditor,
491 pub no_matches: ContainedLabel,
492 pub item: Interactive<ContainedLabel>,
493}
494
495#[derive(Clone, Debug, Deserialize, Default)]
496pub struct ContainedText {
497 #[serde(flatten)]
498 pub container: ContainerStyle,
499 #[serde(flatten)]
500 pub text: TextStyle,
501}
502
503#[derive(Clone, Debug, Deserialize, Default)]
504pub struct ContainedLabel {
505 #[serde(flatten)]
506 pub container: ContainerStyle,
507 #[serde(flatten)]
508 pub label: LabelStyle,
509}
510
511#[derive(Clone, Deserialize, Default)]
512pub struct ProjectDiagnostics {
513 #[serde(flatten)]
514 pub container: ContainerStyle,
515 pub empty_message: TextStyle,
516 pub tab_icon_width: f32,
517 pub tab_icon_spacing: f32,
518 pub tab_summary_spacing: f32,
519}
520
521#[derive(Deserialize, Default)]
522pub struct ContactNotification {
523 pub header_avatar: ImageStyle,
524 pub header_message: ContainedText,
525 pub header_height: f32,
526 pub body_message: ContainedText,
527 pub button: Interactive<ContainedText>,
528 pub dismiss_button: Interactive<IconButton>,
529}
530
531#[derive(Deserialize, Default)]
532pub struct UpdateNotification {
533 pub message: ContainedText,
534 pub action_message: Interactive<ContainedText>,
535 pub dismiss_button: Interactive<IconButton>,
536}
537
538#[derive(Deserialize, Default)]
539pub struct MessageNotification {
540 pub message: ContainedText,
541 pub action_message: Interactive<ContainedText>,
542 pub dismiss_button: Interactive<IconButton>,
543}
544
545#[derive(Deserialize, Default)]
546pub struct ProjectSharedNotification {
547 pub window_height: f32,
548 pub window_width: f32,
549 #[serde(default)]
550 pub background: Color,
551 pub owner_container: ContainerStyle,
552 pub owner_avatar: ImageStyle,
553 pub owner_metadata: ContainerStyle,
554 pub owner_username: ContainedText,
555 pub message: ContainedText,
556 pub worktree_roots: ContainedText,
557 pub button_width: f32,
558 pub open_button: ContainedText,
559 pub dismiss_button: ContainedText,
560}
561
562#[derive(Deserialize, Default)]
563pub struct IncomingCallNotification {
564 pub window_height: f32,
565 pub window_width: f32,
566 #[serde(default)]
567 pub background: Color,
568 pub caller_container: ContainerStyle,
569 pub caller_avatar: ImageStyle,
570 pub caller_metadata: ContainerStyle,
571 pub caller_username: ContainedText,
572 pub caller_message: ContainedText,
573 pub worktree_roots: ContainedText,
574 pub button_width: f32,
575 pub accept_button: ContainedText,
576 pub decline_button: ContainedText,
577}
578
579#[derive(Clone, Deserialize, Default)]
580pub struct Editor {
581 pub text_color: Color,
582 #[serde(default)]
583 pub background: Color,
584 pub selection: SelectionStyle,
585 pub gutter_background: Color,
586 pub gutter_padding_factor: f32,
587 pub active_line_background: Color,
588 pub highlighted_line_background: Color,
589 pub rename_fade: f32,
590 pub document_highlight_read_background: Color,
591 pub document_highlight_write_background: Color,
592 pub diff: DiffStyle,
593 pub line_number: Color,
594 pub line_number_active: Color,
595 pub guest_selections: Vec<SelectionStyle>,
596 pub syntax: Arc<SyntaxTheme>,
597 pub suggestion: HighlightStyle,
598 pub diagnostic_path_header: DiagnosticPathHeader,
599 pub diagnostic_header: DiagnosticHeader,
600 pub error_diagnostic: DiagnosticStyle,
601 pub invalid_error_diagnostic: DiagnosticStyle,
602 pub warning_diagnostic: DiagnosticStyle,
603 pub invalid_warning_diagnostic: DiagnosticStyle,
604 pub information_diagnostic: DiagnosticStyle,
605 pub invalid_information_diagnostic: DiagnosticStyle,
606 pub hint_diagnostic: DiagnosticStyle,
607 pub invalid_hint_diagnostic: DiagnosticStyle,
608 pub autocomplete: AutocompleteStyle,
609 pub code_actions: CodeActions,
610 pub folds: Folds,
611 pub unnecessary_code_fade: f32,
612 pub hover_popover: HoverPopover,
613 pub link_definition: HighlightStyle,
614 pub composition_mark: HighlightStyle,
615 pub jump_icon: Interactive<IconButton>,
616 pub scrollbar: Scrollbar,
617}
618
619#[derive(Clone, Deserialize, Default)]
620pub struct Scrollbar {
621 pub track: ContainerStyle,
622 pub thumb: ContainerStyle,
623 pub width: f32,
624 pub min_height_factor: f32,
625}
626
627#[derive(Clone, Deserialize, Default)]
628pub struct DiagnosticPathHeader {
629 #[serde(flatten)]
630 pub container: ContainerStyle,
631 pub filename: ContainedText,
632 pub path: ContainedText,
633 pub text_scale_factor: f32,
634}
635
636#[derive(Clone, Deserialize, Default)]
637pub struct DiagnosticHeader {
638 #[serde(flatten)]
639 pub container: ContainerStyle,
640 pub message: ContainedLabel,
641 pub code: ContainedText,
642 pub text_scale_factor: f32,
643 pub icon_width_factor: f32,
644}
645
646#[derive(Clone, Deserialize, Default)]
647pub struct DiagnosticStyle {
648 pub message: LabelStyle,
649 #[serde(default)]
650 pub header: ContainerStyle,
651 pub text_scale_factor: f32,
652}
653
654#[derive(Clone, Deserialize, Default)]
655pub struct AutocompleteStyle {
656 #[serde(flatten)]
657 pub container: ContainerStyle,
658 pub item: ContainerStyle,
659 pub selected_item: ContainerStyle,
660 pub hovered_item: ContainerStyle,
661 pub match_highlight: HighlightStyle,
662}
663
664#[derive(Clone, Copy, Default, Deserialize)]
665pub struct SelectionStyle {
666 pub cursor: Color,
667 pub selection: Color,
668}
669
670#[derive(Clone, Deserialize, Default)]
671pub struct FieldEditor {
672 #[serde(flatten)]
673 pub container: ContainerStyle,
674 pub text: TextStyle,
675 #[serde(default)]
676 pub placeholder_text: Option<TextStyle>,
677 pub selection: SelectionStyle,
678}
679
680#[derive(Clone, Deserialize, Default)]
681pub struct InteractiveColor {
682 pub color: Color,
683}
684
685#[derive(Clone, Deserialize, Default)]
686pub struct CodeActions {
687 #[serde(default)]
688 pub indicator: Interactive<InteractiveColor>,
689 pub vertical_scale: f32,
690}
691
692#[derive(Clone, Deserialize, Default)]
693pub struct Folds {
694 pub indicator: Interactive<InteractiveColor>,
695 pub ellipses: FoldEllipses,
696 pub fold_background: Color,
697 pub icon_margin_scale: f32,
698 pub folded_icon: String,
699 pub foldable_icon: String,
700}
701
702#[derive(Clone, Deserialize, Default)]
703pub struct FoldEllipses {
704 pub text_color: Color,
705 pub background: Interactive<InteractiveColor>,
706 pub corner_radius_factor: f32,
707}
708
709#[derive(Clone, Deserialize, Default)]
710pub struct DiffStyle {
711 pub inserted: Color,
712 pub modified: Color,
713 pub deleted: Color,
714 pub removed_width_em: f32,
715 pub width_em: f32,
716 pub corner_radius: f32,
717}
718
719#[derive(Debug, Default, Clone, Copy)]
720pub struct Interactive<T> {
721 pub default: T,
722 pub hover: Option<T>,
723 pub clicked: Option<T>,
724 pub active: Option<T>,
725 pub disabled: Option<T>,
726}
727
728impl<T> Interactive<T> {
729 pub fn style_for(&self, state: &mut MouseState, active: bool) -> &T {
730 if active {
731 self.active.as_ref().unwrap_or(&self.default)
732 } else if state.clicked() == Some(gpui::MouseButton::Left) && self.clicked.is_some() {
733 self.clicked.as_ref().unwrap()
734 } else if state.hovered() {
735 self.hover.as_ref().unwrap_or(&self.default)
736 } else {
737 &self.default
738 }
739 }
740
741 pub fn disabled_style(&self) -> &T {
742 self.disabled.as_ref().unwrap_or(&self.default)
743 }
744}
745
746impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
747 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
748 where
749 D: serde::Deserializer<'de>,
750 {
751 #[derive(Deserialize)]
752 struct Helper {
753 #[serde(flatten)]
754 default: Value,
755 hover: Option<Value>,
756 clicked: Option<Value>,
757 active: Option<Value>,
758 disabled: Option<Value>,
759 }
760
761 let json = Helper::deserialize(deserializer)?;
762
763 let deserialize_state = |state_json: Option<Value>| -> Result<Option<T>, D::Error> {
764 if let Some(mut state_json) = state_json {
765 if let Value::Object(state_json) = &mut state_json {
766 if let Value::Object(default) = &json.default {
767 for (key, value) in default {
768 if !state_json.contains_key(key) {
769 state_json.insert(key.clone(), value.clone());
770 }
771 }
772 }
773 }
774 Ok(Some(
775 serde_json::from_value::<T>(state_json).map_err(serde::de::Error::custom)?,
776 ))
777 } else {
778 Ok(None)
779 }
780 };
781
782 let hover = deserialize_state(json.hover)?;
783 let clicked = deserialize_state(json.clicked)?;
784 let active = deserialize_state(json.active)?;
785 let disabled = deserialize_state(json.disabled)?;
786 let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
787
788 Ok(Interactive {
789 default,
790 hover,
791 clicked,
792 active,
793 disabled,
794 })
795 }
796}
797
798impl Editor {
799 pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle {
800 let style_ix = replica_id as usize % (self.guest_selections.len() + 1);
801 if style_ix == 0 {
802 &self.selection
803 } else {
804 &self.guest_selections[style_ix - 1]
805 }
806 }
807}
808
809#[derive(Default)]
810pub struct SyntaxTheme {
811 pub highlights: Vec<(String, HighlightStyle)>,
812}
813
814impl SyntaxTheme {
815 pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
816 Self { highlights }
817 }
818}
819
820impl<'de> Deserialize<'de> for SyntaxTheme {
821 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
822 where
823 D: serde::Deserializer<'de>,
824 {
825 let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
826
827 let mut result = Self::new(Vec::new());
828 for (key, style) in syntax_data {
829 match result
830 .highlights
831 .binary_search_by(|(needle, _)| needle.cmp(&key))
832 {
833 Ok(i) | Err(i) => {
834 result.highlights.insert(i, (key, style));
835 }
836 }
837 }
838
839 Ok(result)
840 }
841}
842
843#[derive(Clone, Deserialize, Default)]
844pub struct HoverPopover {
845 pub container: ContainerStyle,
846 pub info_container: ContainerStyle,
847 pub warning_container: ContainerStyle,
848 pub error_container: ContainerStyle,
849 pub block_style: ContainerStyle,
850 pub prose: TextStyle,
851 pub highlight: Color,
852}
853
854#[derive(Clone, Deserialize, Default)]
855pub struct TerminalStyle {
856 pub black: Color,
857 pub red: Color,
858 pub green: Color,
859 pub yellow: Color,
860 pub blue: Color,
861 pub magenta: Color,
862 pub cyan: Color,
863 pub white: Color,
864 pub bright_black: Color,
865 pub bright_red: Color,
866 pub bright_green: Color,
867 pub bright_yellow: Color,
868 pub bright_blue: Color,
869 pub bright_magenta: Color,
870 pub bright_cyan: Color,
871 pub bright_white: Color,
872 pub foreground: Color,
873 pub background: Color,
874 pub modal_background: Color,
875 pub cursor: Color,
876 pub dim_black: Color,
877 pub dim_red: Color,
878 pub dim_green: Color,
879 pub dim_yellow: Color,
880 pub dim_blue: Color,
881 pub dim_magenta: Color,
882 pub dim_cyan: Color,
883 pub dim_white: Color,
884 pub bright_foreground: Color,
885 pub dim_foreground: Color,
886}
887
888#[derive(Clone, Deserialize, Default)]
889pub struct FeedbackStyle {
890 pub submit_button: Interactive<ContainedText>,
891 pub button_margin: f32,
892 pub info_text_default: ContainedText,
893 pub link_text_default: ContainedText,
894 pub link_text_hover: ContainedText,
895}
896
897#[derive(Clone, Deserialize, Default)]
898pub struct WelcomeStyle {
899 pub page_width: f32,
900 pub logo: SvgStyle,
901 pub logo_subheading: ContainedText,
902 pub usage_note: ContainedText,
903 pub checkbox: CheckboxStyle,
904 pub checkbox_container: ContainerStyle,
905 pub button: Interactive<ContainedText>,
906 pub button_group: ContainerStyle,
907 pub heading_group: ContainerStyle,
908 pub checkbox_group: ContainerStyle,
909}
910
911#[derive(Clone, Deserialize, Default)]
912pub struct ColorScheme {
913 pub name: String,
914 pub is_light: bool,
915 pub ramps: RampSet,
916 pub lowest: Layer,
917 pub middle: Layer,
918 pub highest: Layer,
919
920 pub popover_shadow: Shadow,
921 pub modal_shadow: Shadow,
922
923 pub players: Vec<Player>,
924}
925
926#[derive(Clone, Deserialize, Default)]
927pub struct Player {
928 pub cursor: Color,
929 pub selection: Color,
930}
931
932#[derive(Clone, Deserialize, Default)]
933pub struct RampSet {
934 pub neutral: Vec<Color>,
935 pub red: Vec<Color>,
936 pub orange: Vec<Color>,
937 pub yellow: Vec<Color>,
938 pub green: Vec<Color>,
939 pub cyan: Vec<Color>,
940 pub blue: Vec<Color>,
941 pub violet: Vec<Color>,
942 pub magenta: Vec<Color>,
943}
944
945#[derive(Clone, Deserialize, Default)]
946pub struct Layer {
947 pub base: StyleSet,
948 pub variant: StyleSet,
949 pub on: StyleSet,
950 pub accent: StyleSet,
951 pub positive: StyleSet,
952 pub warning: StyleSet,
953 pub negative: StyleSet,
954}
955
956#[derive(Clone, Deserialize, Default)]
957pub struct StyleSet {
958 pub default: Style,
959 pub active: Style,
960 pub disabled: Style,
961 pub hovered: Style,
962 pub pressed: Style,
963 pub inverted: Style,
964}
965
966#[derive(Clone, Deserialize, Default)]
967pub struct Style {
968 pub background: Color,
969 pub border: Color,
970 pub foreground: Color,
971}