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