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 diagnostic_path_header: DiagnosticPathHeader,
577 pub diagnostic_header: DiagnosticHeader,
578 pub error_diagnostic: DiagnosticStyle,
579 pub invalid_error_diagnostic: DiagnosticStyle,
580 pub warning_diagnostic: DiagnosticStyle,
581 pub invalid_warning_diagnostic: DiagnosticStyle,
582 pub information_diagnostic: DiagnosticStyle,
583 pub invalid_information_diagnostic: DiagnosticStyle,
584 pub hint_diagnostic: DiagnosticStyle,
585 pub invalid_hint_diagnostic: DiagnosticStyle,
586 pub autocomplete: AutocompleteStyle,
587 pub code_actions: CodeActions,
588 pub folds: Folds,
589 pub unnecessary_code_fade: f32,
590 pub hover_popover: HoverPopover,
591 pub link_definition: HighlightStyle,
592 pub composition_mark: HighlightStyle,
593 pub jump_icon: Interactive<IconButton>,
594 pub scrollbar: Scrollbar,
595}
596
597#[derive(Clone, Deserialize, Default)]
598pub struct Scrollbar {
599 pub track: ContainerStyle,
600 pub thumb: ContainerStyle,
601 pub width: f32,
602 pub min_height_factor: f32,
603}
604
605#[derive(Clone, Deserialize, Default)]
606pub struct DiagnosticPathHeader {
607 #[serde(flatten)]
608 pub container: ContainerStyle,
609 pub filename: ContainedText,
610 pub path: ContainedText,
611 pub text_scale_factor: f32,
612}
613
614#[derive(Clone, Deserialize, Default)]
615pub struct DiagnosticHeader {
616 #[serde(flatten)]
617 pub container: ContainerStyle,
618 pub message: ContainedLabel,
619 pub code: ContainedText,
620 pub text_scale_factor: f32,
621 pub icon_width_factor: f32,
622}
623
624#[derive(Clone, Deserialize, Default)]
625pub struct DiagnosticStyle {
626 pub message: LabelStyle,
627 #[serde(default)]
628 pub header: ContainerStyle,
629 pub text_scale_factor: f32,
630}
631
632#[derive(Clone, Deserialize, Default)]
633pub struct AutocompleteStyle {
634 #[serde(flatten)]
635 pub container: ContainerStyle,
636 pub item: ContainerStyle,
637 pub selected_item: ContainerStyle,
638 pub hovered_item: ContainerStyle,
639 pub match_highlight: HighlightStyle,
640}
641
642#[derive(Clone, Copy, Default, Deserialize)]
643pub struct SelectionStyle {
644 pub cursor: Color,
645 pub selection: Color,
646}
647
648#[derive(Clone, Deserialize, Default)]
649pub struct FieldEditor {
650 #[serde(flatten)]
651 pub container: ContainerStyle,
652 pub text: TextStyle,
653 #[serde(default)]
654 pub placeholder_text: Option<TextStyle>,
655 pub selection: SelectionStyle,
656}
657
658#[derive(Clone, Deserialize, Default)]
659pub struct InteractiveColor {
660 pub color: Color,
661}
662
663#[derive(Clone, Deserialize, Default)]
664pub struct CodeActions {
665 #[serde(default)]
666 pub indicator: Interactive<InteractiveColor>,
667 pub vertical_scale: f32,
668}
669
670#[derive(Clone, Deserialize, Default)]
671pub struct Folds {
672 pub indicator: Interactive<InteractiveColor>,
673 pub ellipses: FoldEllipses,
674 pub fold_background: Color,
675 pub icon_margin_scale: f32,
676 pub folded_icon: String,
677 pub foldable_icon: String,
678}
679
680#[derive(Clone, Deserialize, Default)]
681pub struct FoldEllipses {
682 pub text_color: Color,
683 pub background: Interactive<InteractiveColor>,
684 pub corner_radius_factor: f32,
685}
686
687#[derive(Clone, Deserialize, Default)]
688pub struct DiffStyle {
689 pub inserted: Color,
690 pub modified: Color,
691 pub deleted: Color,
692 pub removed_width_em: f32,
693 pub width_em: f32,
694 pub corner_radius: f32,
695}
696
697#[derive(Debug, Default, Clone, Copy)]
698pub struct Interactive<T> {
699 pub default: T,
700 pub hover: Option<T>,
701 pub clicked: Option<T>,
702 pub active: Option<T>,
703 pub disabled: Option<T>,
704}
705
706impl<T> Interactive<T> {
707 pub fn style_for(&self, state: &mut MouseState, active: bool) -> &T {
708 if active {
709 self.active.as_ref().unwrap_or(&self.default)
710 } else if state.clicked() == Some(gpui::MouseButton::Left) && self.clicked.is_some() {
711 self.clicked.as_ref().unwrap()
712 } else if state.hovered() {
713 self.hover.as_ref().unwrap_or(&self.default)
714 } else {
715 &self.default
716 }
717 }
718
719 pub fn disabled_style(&self) -> &T {
720 self.disabled.as_ref().unwrap_or(&self.default)
721 }
722}
723
724impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
725 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
726 where
727 D: serde::Deserializer<'de>,
728 {
729 #[derive(Deserialize)]
730 struct Helper {
731 #[serde(flatten)]
732 default: Value,
733 hover: Option<Value>,
734 clicked: Option<Value>,
735 active: Option<Value>,
736 disabled: Option<Value>,
737 }
738
739 let json = Helper::deserialize(deserializer)?;
740
741 let deserialize_state = |state_json: Option<Value>| -> Result<Option<T>, D::Error> {
742 if let Some(mut state_json) = state_json {
743 if let Value::Object(state_json) = &mut state_json {
744 if let Value::Object(default) = &json.default {
745 for (key, value) in default {
746 if !state_json.contains_key(key) {
747 state_json.insert(key.clone(), value.clone());
748 }
749 }
750 }
751 }
752 Ok(Some(
753 serde_json::from_value::<T>(state_json).map_err(serde::de::Error::custom)?,
754 ))
755 } else {
756 Ok(None)
757 }
758 };
759
760 let hover = deserialize_state(json.hover)?;
761 let clicked = deserialize_state(json.clicked)?;
762 let active = deserialize_state(json.active)?;
763 let disabled = deserialize_state(json.disabled)?;
764 let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
765
766 Ok(Interactive {
767 default,
768 hover,
769 clicked,
770 active,
771 disabled,
772 })
773 }
774}
775
776impl Editor {
777 pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle {
778 let style_ix = replica_id as usize % (self.guest_selections.len() + 1);
779 if style_ix == 0 {
780 &self.selection
781 } else {
782 &self.guest_selections[style_ix - 1]
783 }
784 }
785}
786
787#[derive(Default)]
788pub struct SyntaxTheme {
789 pub highlights: Vec<(String, HighlightStyle)>,
790}
791
792impl SyntaxTheme {
793 pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
794 Self { highlights }
795 }
796}
797
798impl<'de> Deserialize<'de> for SyntaxTheme {
799 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
800 where
801 D: serde::Deserializer<'de>,
802 {
803 let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
804
805 let mut result = Self::new(Vec::new());
806 for (key, style) in syntax_data {
807 match result
808 .highlights
809 .binary_search_by(|(needle, _)| needle.cmp(&key))
810 {
811 Ok(i) | Err(i) => {
812 result.highlights.insert(i, (key, style));
813 }
814 }
815 }
816
817 Ok(result)
818 }
819}
820
821#[derive(Clone, Deserialize, Default)]
822pub struct HoverPopover {
823 pub container: ContainerStyle,
824 pub info_container: ContainerStyle,
825 pub warning_container: ContainerStyle,
826 pub error_container: ContainerStyle,
827 pub block_style: ContainerStyle,
828 pub prose: TextStyle,
829 pub highlight: Color,
830}
831
832#[derive(Clone, Deserialize, Default)]
833pub struct TerminalStyle {
834 pub black: Color,
835 pub red: Color,
836 pub green: Color,
837 pub yellow: Color,
838 pub blue: Color,
839 pub magenta: Color,
840 pub cyan: Color,
841 pub white: Color,
842 pub bright_black: Color,
843 pub bright_red: Color,
844 pub bright_green: Color,
845 pub bright_yellow: Color,
846 pub bright_blue: Color,
847 pub bright_magenta: Color,
848 pub bright_cyan: Color,
849 pub bright_white: Color,
850 pub foreground: Color,
851 pub background: Color,
852 pub modal_background: Color,
853 pub cursor: Color,
854 pub dim_black: Color,
855 pub dim_red: Color,
856 pub dim_green: Color,
857 pub dim_yellow: Color,
858 pub dim_blue: Color,
859 pub dim_magenta: Color,
860 pub dim_cyan: Color,
861 pub dim_white: Color,
862 pub bright_foreground: Color,
863 pub dim_foreground: Color,
864}
865
866#[derive(Clone, Deserialize, Default)]
867pub struct FeedbackStyle {
868 pub submit_button: Interactive<ContainedText>,
869 pub button_margin: f32,
870 pub info_text_default: ContainedText,
871 pub link_text_default: ContainedText,
872 pub link_text_hover: ContainedText,
873}
874
875#[derive(Clone, Deserialize, Default)]
876pub struct WelcomeStyle {
877 pub page_width: f32,
878 pub logo: IconStyle,
879 pub logo_subheading: ContainedText,
880 pub usage_note: ContainedText,
881 pub checkbox: CheckboxStyle,
882 pub checkbox_container: ContainerStyle,
883 pub button: Interactive<ContainedText>,
884 pub button_group: ContainerStyle,
885 pub heading_group: ContainerStyle,
886 pub checkbox_group: ContainerStyle,
887}
888
889#[derive(Clone, Deserialize, Default)]
890pub struct ColorScheme {
891 pub name: String,
892 pub is_light: bool,
893 pub ramps: RampSet,
894 pub lowest: Layer,
895 pub middle: Layer,
896 pub highest: Layer,
897
898 pub popover_shadow: Shadow,
899 pub modal_shadow: Shadow,
900
901 pub players: Vec<Player>,
902}
903
904#[derive(Clone, Deserialize, Default)]
905pub struct Player {
906 pub cursor: Color,
907 pub selection: Color,
908}
909
910#[derive(Clone, Deserialize, Default)]
911pub struct RampSet {
912 pub neutral: Vec<Color>,
913 pub red: Vec<Color>,
914 pub orange: Vec<Color>,
915 pub yellow: Vec<Color>,
916 pub green: Vec<Color>,
917 pub cyan: Vec<Color>,
918 pub blue: Vec<Color>,
919 pub violet: Vec<Color>,
920 pub magenta: Vec<Color>,
921}
922
923#[derive(Clone, Deserialize, Default)]
924pub struct Layer {
925 pub base: StyleSet,
926 pub variant: StyleSet,
927 pub on: StyleSet,
928 pub accent: StyleSet,
929 pub positive: StyleSet,
930 pub warning: StyleSet,
931 pub negative: StyleSet,
932}
933
934#[derive(Clone, Deserialize, Default)]
935pub struct StyleSet {
936 pub default: Style,
937 pub active: Style,
938 pub disabled: Style,
939 pub hovered: Style,
940 pub pressed: Style,
941 pub inverted: Style,
942}
943
944#[derive(Clone, Deserialize, Default)]
945pub struct Style {
946 pub background: Color,
947 pub border: Color,
948 pub foreground: Color,
949}