theme.rs

   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}