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