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