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