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 pub status: EntryStatus,
450}
451
452#[derive(Clone, Debug, Deserialize, Default)]
453pub struct EntryStatus {
454 pub git: GitProjectStatus,
455}
456
457#[derive(Clone, Debug, Deserialize, Default)]
458pub struct GitProjectStatus {
459 pub modified: Color,
460 pub inserted: Color,
461 pub conflict: Color,
462}
463
464#[derive(Clone, Debug, Deserialize, Default)]
465pub struct ContextMenu {
466 #[serde(flatten)]
467 pub container: ContainerStyle,
468 pub item: Interactive<ContextMenuItem>,
469 pub keystroke_margin: f32,
470 pub separator: ContainerStyle,
471}
472
473#[derive(Clone, Debug, Deserialize, Default)]
474pub struct ContextMenuItem {
475 #[serde(flatten)]
476 pub container: ContainerStyle,
477 pub label: TextStyle,
478 pub keystroke: ContainedText,
479 pub icon_width: f32,
480 pub icon_spacing: f32,
481}
482
483#[derive(Debug, Deserialize, Default)]
484pub struct CommandPalette {
485 pub key: Interactive<ContainedLabel>,
486 pub keystroke_spacing: f32,
487}
488
489#[derive(Deserialize, Default)]
490pub struct InviteLink {
491 #[serde(flatten)]
492 pub container: ContainerStyle,
493 #[serde(flatten)]
494 pub label: LabelStyle,
495 pub icon: Icon,
496}
497
498#[derive(Deserialize, Clone, Copy, Default)]
499pub struct Icon {
500 #[serde(flatten)]
501 pub container: ContainerStyle,
502 pub color: Color,
503 pub width: f32,
504}
505
506#[derive(Deserialize, Clone, Copy, Default)]
507pub struct IconButton {
508 #[serde(flatten)]
509 pub container: ContainerStyle,
510 pub color: Color,
511 pub icon_width: f32,
512 pub button_width: f32,
513}
514
515#[derive(Deserialize, Default)]
516pub struct ChatMessage {
517 #[serde(flatten)]
518 pub container: ContainerStyle,
519 pub body: TextStyle,
520 pub sender: ContainedText,
521 pub timestamp: ContainedText,
522}
523
524#[derive(Deserialize, Default)]
525pub struct ChannelSelect {
526 #[serde(flatten)]
527 pub container: ContainerStyle,
528 pub header: ChannelName,
529 pub item: ChannelName,
530 pub active_item: ChannelName,
531 pub hovered_item: ChannelName,
532 pub hovered_active_item: ChannelName,
533 pub menu: ContainerStyle,
534}
535
536#[derive(Deserialize, Default)]
537pub struct ChannelName {
538 #[serde(flatten)]
539 pub container: ContainerStyle,
540 pub hash: ContainedText,
541 pub name: TextStyle,
542}
543
544#[derive(Clone, Deserialize, Default)]
545pub struct Picker {
546 #[serde(flatten)]
547 pub container: ContainerStyle,
548 pub empty_container: ContainerStyle,
549 pub input_editor: FieldEditor,
550 pub empty_input_editor: FieldEditor,
551 pub no_matches: ContainedLabel,
552 pub item: Interactive<ContainedLabel>,
553}
554
555#[derive(Clone, Debug, Deserialize, Default)]
556pub struct ContainedText {
557 #[serde(flatten)]
558 pub container: ContainerStyle,
559 #[serde(flatten)]
560 pub text: TextStyle,
561}
562
563#[derive(Clone, Debug, Deserialize, Default)]
564pub struct ContainedLabel {
565 #[serde(flatten)]
566 pub container: ContainerStyle,
567 #[serde(flatten)]
568 pub label: LabelStyle,
569}
570
571#[derive(Clone, Deserialize, Default)]
572pub struct ProjectDiagnostics {
573 #[serde(flatten)]
574 pub container: ContainerStyle,
575 pub empty_message: TextStyle,
576 pub tab_icon_width: f32,
577 pub tab_icon_spacing: f32,
578 pub tab_summary_spacing: f32,
579}
580
581#[derive(Deserialize, Default)]
582pub struct ContactNotification {
583 pub header_avatar: ImageStyle,
584 pub header_message: ContainedText,
585 pub header_height: f32,
586 pub body_message: ContainedText,
587 pub button: Interactive<ContainedText>,
588 pub dismiss_button: Interactive<IconButton>,
589}
590
591#[derive(Deserialize, Default)]
592pub struct UpdateNotification {
593 pub message: ContainedText,
594 pub action_message: Interactive<ContainedText>,
595 pub dismiss_button: Interactive<IconButton>,
596}
597
598#[derive(Deserialize, Default)]
599pub struct MessageNotification {
600 pub message: ContainedText,
601 pub action_message: Interactive<ContainedText>,
602 pub dismiss_button: Interactive<IconButton>,
603}
604
605#[derive(Deserialize, Default)]
606pub struct ProjectSharedNotification {
607 pub window_height: f32,
608 pub window_width: f32,
609 #[serde(default)]
610 pub background: Color,
611 pub owner_container: ContainerStyle,
612 pub owner_avatar: ImageStyle,
613 pub owner_metadata: ContainerStyle,
614 pub owner_username: ContainedText,
615 pub message: ContainedText,
616 pub worktree_roots: ContainedText,
617 pub button_width: f32,
618 pub open_button: ContainedText,
619 pub dismiss_button: ContainedText,
620}
621
622#[derive(Deserialize, Default)]
623pub struct IncomingCallNotification {
624 pub window_height: f32,
625 pub window_width: f32,
626 #[serde(default)]
627 pub background: Color,
628 pub caller_container: ContainerStyle,
629 pub caller_avatar: ImageStyle,
630 pub caller_metadata: ContainerStyle,
631 pub caller_username: ContainedText,
632 pub caller_message: ContainedText,
633 pub worktree_roots: ContainedText,
634 pub button_width: f32,
635 pub accept_button: ContainedText,
636 pub decline_button: ContainedText,
637}
638
639#[derive(Clone, Deserialize, Default)]
640pub struct Editor {
641 pub text_color: Color,
642 #[serde(default)]
643 pub background: Color,
644 pub selection: SelectionStyle,
645 pub gutter_background: Color,
646 pub gutter_padding_factor: f32,
647 pub active_line_background: Color,
648 pub highlighted_line_background: Color,
649 pub rename_fade: f32,
650 pub document_highlight_read_background: Color,
651 pub document_highlight_write_background: Color,
652 pub diff: DiffStyle,
653 pub line_number: Color,
654 pub line_number_active: Color,
655 pub guest_selections: Vec<SelectionStyle>,
656 pub syntax: Arc<SyntaxTheme>,
657 pub suggestion: HighlightStyle,
658 pub diagnostic_path_header: DiagnosticPathHeader,
659 pub diagnostic_header: DiagnosticHeader,
660 pub error_diagnostic: DiagnosticStyle,
661 pub invalid_error_diagnostic: DiagnosticStyle,
662 pub warning_diagnostic: DiagnosticStyle,
663 pub invalid_warning_diagnostic: DiagnosticStyle,
664 pub information_diagnostic: DiagnosticStyle,
665 pub invalid_information_diagnostic: DiagnosticStyle,
666 pub hint_diagnostic: DiagnosticStyle,
667 pub invalid_hint_diagnostic: DiagnosticStyle,
668 pub autocomplete: AutocompleteStyle,
669 pub code_actions: CodeActions,
670 pub folds: Folds,
671 pub unnecessary_code_fade: f32,
672 pub hover_popover: HoverPopover,
673 pub link_definition: HighlightStyle,
674 pub composition_mark: HighlightStyle,
675 pub jump_icon: Interactive<IconButton>,
676 pub scrollbar: Scrollbar,
677 pub whitespace: Color,
678}
679
680#[derive(Clone, Deserialize, Default)]
681pub struct Scrollbar {
682 pub track: ContainerStyle,
683 pub thumb: ContainerStyle,
684 pub width: f32,
685 pub min_height_factor: f32,
686 pub git: GitDiffColors,
687}
688
689#[derive(Clone, Deserialize, Default)]
690pub struct GitDiffColors {
691 pub inserted: Color,
692 pub modified: Color,
693 pub deleted: Color,
694}
695
696#[derive(Clone, Deserialize, Default)]
697pub struct DiagnosticPathHeader {
698 #[serde(flatten)]
699 pub container: ContainerStyle,
700 pub filename: ContainedText,
701 pub path: ContainedText,
702 pub text_scale_factor: f32,
703}
704
705#[derive(Clone, Deserialize, Default)]
706pub struct DiagnosticHeader {
707 #[serde(flatten)]
708 pub container: ContainerStyle,
709 pub source: ContainedLabel,
710 pub message: ContainedLabel,
711 pub code: ContainedText,
712 pub text_scale_factor: f32,
713 pub icon_width_factor: f32,
714}
715
716#[derive(Clone, Deserialize, Default)]
717pub struct DiagnosticStyle {
718 pub message: LabelStyle,
719 #[serde(default)]
720 pub header: ContainerStyle,
721 pub text_scale_factor: f32,
722}
723
724#[derive(Clone, Deserialize, Default)]
725pub struct AutocompleteStyle {
726 #[serde(flatten)]
727 pub container: ContainerStyle,
728 pub item: ContainerStyle,
729 pub selected_item: ContainerStyle,
730 pub hovered_item: ContainerStyle,
731 pub match_highlight: HighlightStyle,
732}
733
734#[derive(Clone, Copy, Default, Deserialize)]
735pub struct SelectionStyle {
736 pub cursor: Color,
737 pub selection: Color,
738}
739
740#[derive(Clone, Deserialize, Default)]
741pub struct FieldEditor {
742 #[serde(flatten)]
743 pub container: ContainerStyle,
744 pub text: TextStyle,
745 #[serde(default)]
746 pub placeholder_text: Option<TextStyle>,
747 pub selection: SelectionStyle,
748}
749
750#[derive(Clone, Deserialize, Default)]
751pub struct InteractiveColor {
752 pub color: Color,
753}
754
755#[derive(Clone, Deserialize, Default)]
756pub struct CodeActions {
757 #[serde(default)]
758 pub indicator: Interactive<InteractiveColor>,
759 pub vertical_scale: f32,
760}
761
762#[derive(Clone, Deserialize, Default)]
763pub struct Folds {
764 pub indicator: Interactive<InteractiveColor>,
765 pub ellipses: FoldEllipses,
766 pub fold_background: Color,
767 pub icon_margin_scale: f32,
768 pub folded_icon: String,
769 pub foldable_icon: String,
770}
771
772#[derive(Clone, Deserialize, Default)]
773pub struct FoldEllipses {
774 pub text_color: Color,
775 pub background: Interactive<InteractiveColor>,
776 pub corner_radius_factor: f32,
777}
778
779#[derive(Clone, Deserialize, Default)]
780pub struct DiffStyle {
781 pub inserted: Color,
782 pub modified: Color,
783 pub deleted: Color,
784 pub removed_width_em: f32,
785 pub width_em: f32,
786 pub corner_radius: f32,
787}
788
789#[derive(Debug, Default, Clone, Copy)]
790pub struct Interactive<T> {
791 pub default: T,
792 pub hover: Option<T>,
793 pub hover_and_active: Option<T>,
794 pub clicked: Option<T>,
795 pub click_and_active: Option<T>,
796 pub active: Option<T>,
797 pub disabled: Option<T>,
798}
799
800impl<T> Interactive<T> {
801 pub fn style_for(&self, state: &mut MouseState, active: bool) -> &T {
802 if active {
803 if state.hovered() {
804 self.hover_and_active
805 .as_ref()
806 .unwrap_or(self.active.as_ref().unwrap_or(&self.default))
807 } else if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some()
808 {
809 self.click_and_active
810 .as_ref()
811 .unwrap_or(self.active.as_ref().unwrap_or(&self.default))
812 } else {
813 self.active.as_ref().unwrap_or(&self.default)
814 }
815 } else if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some() {
816 self.clicked.as_ref().unwrap()
817 } else if state.hovered() {
818 self.hover.as_ref().unwrap_or(&self.default)
819 } else {
820 &self.default
821 }
822 }
823
824 pub fn disabled_style(&self) -> &T {
825 self.disabled.as_ref().unwrap_or(&self.default)
826 }
827}
828
829impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
830 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
831 where
832 D: serde::Deserializer<'de>,
833 {
834 #[derive(Deserialize)]
835 struct Helper {
836 #[serde(flatten)]
837 default: Value,
838 hover: Option<Value>,
839 hover_and_active: Option<Value>,
840 clicked: Option<Value>,
841 click_and_active: Option<Value>,
842 active: Option<Value>,
843 disabled: Option<Value>,
844 }
845
846 let json = Helper::deserialize(deserializer)?;
847
848 let deserialize_state = |state_json: Option<Value>| -> Result<Option<T>, D::Error> {
849 if let Some(mut state_json) = state_json {
850 if let Value::Object(state_json) = &mut state_json {
851 if let Value::Object(default) = &json.default {
852 for (key, value) in default {
853 if !state_json.contains_key(key) {
854 state_json.insert(key.clone(), value.clone());
855 }
856 }
857 }
858 }
859 Ok(Some(
860 serde_json::from_value::<T>(state_json).map_err(serde::de::Error::custom)?,
861 ))
862 } else {
863 Ok(None)
864 }
865 };
866
867 let hover = deserialize_state(json.hover)?;
868 let hover_and_active = deserialize_state(json.hover_and_active)?;
869 let clicked = deserialize_state(json.clicked)?;
870 let click_and_active = deserialize_state(json.click_and_active)?;
871 let active = deserialize_state(json.active)?;
872 let disabled = deserialize_state(json.disabled)?;
873 let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
874
875 Ok(Interactive {
876 default,
877 hover,
878 hover_and_active,
879 clicked,
880 click_and_active,
881 active,
882 disabled,
883 })
884 }
885}
886
887impl Editor {
888 pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle {
889 let style_ix = replica_id as usize % (self.guest_selections.len() + 1);
890 if style_ix == 0 {
891 &self.selection
892 } else {
893 &self.guest_selections[style_ix - 1]
894 }
895 }
896}
897
898#[derive(Default)]
899pub struct SyntaxTheme {
900 pub highlights: Vec<(String, HighlightStyle)>,
901}
902
903impl SyntaxTheme {
904 pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
905 Self { highlights }
906 }
907}
908
909impl<'de> Deserialize<'de> for SyntaxTheme {
910 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
911 where
912 D: serde::Deserializer<'de>,
913 {
914 let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
915
916 let mut result = Self::new(Vec::new());
917 for (key, style) in syntax_data {
918 match result
919 .highlights
920 .binary_search_by(|(needle, _)| needle.cmp(&key))
921 {
922 Ok(i) | Err(i) => {
923 result.highlights.insert(i, (key, style));
924 }
925 }
926 }
927
928 Ok(result)
929 }
930}
931
932#[derive(Clone, Deserialize, Default)]
933pub struct HoverPopover {
934 pub container: ContainerStyle,
935 pub info_container: ContainerStyle,
936 pub warning_container: ContainerStyle,
937 pub error_container: ContainerStyle,
938 pub block_style: ContainerStyle,
939 pub prose: TextStyle,
940 pub diagnostic_source_highlight: HighlightStyle,
941 pub highlight: Color,
942}
943
944#[derive(Clone, Deserialize, Default)]
945pub struct TerminalStyle {
946 pub black: Color,
947 pub red: Color,
948 pub green: Color,
949 pub yellow: Color,
950 pub blue: Color,
951 pub magenta: Color,
952 pub cyan: Color,
953 pub white: Color,
954 pub bright_black: Color,
955 pub bright_red: Color,
956 pub bright_green: Color,
957 pub bright_yellow: Color,
958 pub bright_blue: Color,
959 pub bright_magenta: Color,
960 pub bright_cyan: Color,
961 pub bright_white: Color,
962 pub foreground: Color,
963 pub background: Color,
964 pub modal_background: Color,
965 pub cursor: Color,
966 pub dim_black: Color,
967 pub dim_red: Color,
968 pub dim_green: Color,
969 pub dim_yellow: Color,
970 pub dim_blue: Color,
971 pub dim_magenta: Color,
972 pub dim_cyan: Color,
973 pub dim_white: Color,
974 pub bright_foreground: Color,
975 pub dim_foreground: Color,
976}
977
978#[derive(Clone, Deserialize, Default)]
979pub struct FeedbackStyle {
980 pub submit_button: Interactive<ContainedText>,
981 pub button_margin: f32,
982 pub info_text_default: ContainedText,
983 pub link_text_default: ContainedText,
984 pub link_text_hover: ContainedText,
985}
986
987#[derive(Clone, Deserialize, Default)]
988pub struct WelcomeStyle {
989 pub page_width: f32,
990 pub logo: SvgStyle,
991 pub logo_subheading: ContainedText,
992 pub usage_note: ContainedText,
993 pub checkbox: CheckboxStyle,
994 pub checkbox_container: ContainerStyle,
995 pub button: Interactive<ContainedText>,
996 pub button_group: ContainerStyle,
997 pub heading_group: ContainerStyle,
998 pub checkbox_group: ContainerStyle,
999}
1000
1001#[derive(Clone, Deserialize, Default)]
1002pub struct ColorScheme {
1003 pub name: String,
1004 pub is_light: bool,
1005 pub ramps: RampSet,
1006 pub lowest: Layer,
1007 pub middle: Layer,
1008 pub highest: Layer,
1009
1010 pub popover_shadow: Shadow,
1011 pub modal_shadow: Shadow,
1012
1013 pub players: Vec<Player>,
1014}
1015
1016#[derive(Clone, Deserialize, Default)]
1017pub struct Player {
1018 pub cursor: Color,
1019 pub selection: Color,
1020}
1021
1022#[derive(Clone, Deserialize, Default)]
1023pub struct RampSet {
1024 pub neutral: Vec<Color>,
1025 pub red: Vec<Color>,
1026 pub orange: Vec<Color>,
1027 pub yellow: Vec<Color>,
1028 pub green: Vec<Color>,
1029 pub cyan: Vec<Color>,
1030 pub blue: Vec<Color>,
1031 pub violet: Vec<Color>,
1032 pub magenta: Vec<Color>,
1033}
1034
1035#[derive(Clone, Deserialize, Default)]
1036pub struct Layer {
1037 pub base: StyleSet,
1038 pub variant: StyleSet,
1039 pub on: StyleSet,
1040 pub accent: StyleSet,
1041 pub positive: StyleSet,
1042 pub warning: StyleSet,
1043 pub negative: StyleSet,
1044}
1045
1046#[derive(Clone, Deserialize, Default)]
1047pub struct StyleSet {
1048 pub default: Style,
1049 pub active: Style,
1050 pub disabled: Style,
1051 pub hovered: Style,
1052 pub pressed: Style,
1053 pub inverted: Style,
1054}
1055
1056#[derive(Clone, Deserialize, Default)]
1057pub struct Style {
1058 pub background: Color,
1059 pub border: Color,
1060 pub foreground: Color,
1061}