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