theme.rs

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