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