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