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