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