theme.rs

  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}