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