theme.rs

   1#![allow(unused)]
   2
   3use std::borrow::Cow;
   4use std::collections::HashMap;
   5use std::fmt;
   6use std::ops::{Deref, DerefMut};
   7use std::sync::Arc;
   8
   9use pathfinder_color::ColorU;
  10use serde::de::{self, DeserializeOwned, Unexpected};
  11use serde::{Deserialize, Deserializer};
  12use serde_json::{self, Value};
  13
  14#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
  15#[repr(transparent)]
  16pub struct Color(pub ColorU);
  17
  18impl Color {
  19    pub fn from_u32(rgba: u32) -> Self {
  20        Self(ColorU::from_u32(rgba))
  21    }
  22}
  23
  24impl Deref for Color {
  25    type Target = ColorU;
  26    fn deref(&self) -> &Self::Target {
  27        &self.0
  28    }
  29}
  30
  31impl DerefMut for Color {
  32    fn deref_mut(&mut self) -> &mut Self::Target {
  33        &mut self.0
  34    }
  35}
  36
  37impl fmt::Debug for Color {
  38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  39        self.0.fmt(f)
  40    }
  41}
  42
  43impl<'de> Deserialize<'de> for Color {
  44    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  45    where
  46        D: Deserializer<'de>,
  47    {
  48        let literal: Cow<str> = Deserialize::deserialize(deserializer)?;
  49        if let Some(digits) = literal.strip_prefix('#') {
  50            if let Ok(value) = u32::from_str_radix(digits, 16) {
  51                if digits.len() == 6 {
  52                    return Ok(Color::from_u32((value << 8) | 0xFF));
  53                } else if digits.len() == 8 {
  54                    return Ok(Color::from_u32(value));
  55                }
  56            }
  57        }
  58        Err(de::Error::invalid_value(
  59            Unexpected::Str(literal.as_ref()),
  60            &"#RRGGBB[AA]",
  61        ))
  62    }
  63}
  64
  65#[derive(Clone, Debug, Default, Deserialize)]
  66pub struct TextStyle {
  67    pub color: Color,
  68}
  69
  70#[derive(Clone, Debug, Default)]
  71pub struct HighlightStyle {
  72    pub color: Option<Color>,
  73    pub weight: Option<Weight>,
  74    pub italic: Option<bool>,
  75}
  76
  77impl<'de> Deserialize<'de> for HighlightStyle {
  78    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  79    where
  80        D: serde::Deserializer<'de>,
  81    {
  82        #[derive(Deserialize)]
  83        struct HighlightStyleJson {
  84            color: Option<Color>,
  85            weight: Option<Weight>,
  86            italic: Option<bool>,
  87        }
  88
  89        let json = serde_json::Value::deserialize(deserializer)?;
  90        if json.is_object() {
  91            let style: HighlightStyleJson =
  92                serde_json::from_value(json).map_err(de::Error::custom)?;
  93
  94            Ok(Self {
  95                color: style.color,
  96                weight: style.weight,
  97                italic: style.italic,
  98            })
  99        } else {
 100            Ok(Self {
 101                color: serde_json::from_value(json).map_err(de::Error::custom)?,
 102                ..Default::default()
 103            })
 104        }
 105    }
 106}
 107
 108#[allow(non_camel_case_types)]
 109#[derive(Clone, Debug, Deserialize)]
 110pub enum Weight {
 111    thin,
 112    extra_light,
 113    light,
 114    normal,
 115    medium,
 116    semibold,
 117    bold,
 118    extra_bold,
 119    black,
 120}
 121
 122#[derive(Clone, Copy, Debug, Default, Deserialize)]
 123pub struct ContainerStyle {
 124    #[serde(rename = "background")]
 125    pub background_color: Option<Color>,
 126    #[serde(rename = "overlay")]
 127    pub overlay_color: Option<Color>,
 128    #[serde(default)]
 129    pub border: Border,
 130}
 131
 132#[derive(Clone, Deserialize, Default)]
 133pub struct SvgStyle {
 134    pub color: Color,
 135}
 136
 137#[derive(Clone, Deserialize, Default)]
 138pub struct IconStyle {
 139    pub icon: SvgStyle,
 140    pub container: ContainerStyle,
 141}
 142
 143#[derive(Copy, Clone, Debug, Default)]
 144pub struct Border {
 145    pub color: Color,
 146    pub width: f32,
 147    pub overlay: bool,
 148    pub top: bool,
 149    pub bottom: bool,
 150    pub left: bool,
 151    pub right: bool,
 152}
 153
 154impl<'de> Deserialize<'de> for Border {
 155    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 156    where
 157        D: serde::Deserializer<'de>,
 158    {
 159        #[derive(Deserialize)]
 160        struct BorderData {
 161            pub width: f32,
 162            pub color: Color,
 163            #[serde(default)]
 164            pub overlay: bool,
 165            #[serde(default)]
 166            pub top: bool,
 167            #[serde(default)]
 168            pub right: bool,
 169            #[serde(default)]
 170            pub bottom: bool,
 171            #[serde(default)]
 172            pub left: bool,
 173        }
 174
 175        let data = BorderData::deserialize(deserializer)?;
 176        let mut border = Border {
 177            width: data.width,
 178            color: data.color,
 179            overlay: data.overlay,
 180            top: data.top,
 181            bottom: data.bottom,
 182            left: data.left,
 183            right: data.right,
 184        };
 185        if !border.top && !border.bottom && !border.left && !border.right {
 186            border.top = true;
 187            border.bottom = true;
 188            border.left = true;
 189            border.right = true;
 190        }
 191        Ok(border)
 192    }
 193}
 194
 195#[derive(Clone, Deserialize, Default)]
 196pub struct TooltipStyle {
 197    #[serde(flatten)]
 198    pub container: ContainerStyle,
 199    pub text: TextStyle,
 200    keystroke: KeystrokeStyle,
 201    pub max_text_width: Option<f32>,
 202}
 203
 204#[derive(Clone, Deserialize, Default)]
 205pub struct KeystrokeStyle {
 206    #[serde(flatten)]
 207    container: ContainerStyle,
 208    #[serde(flatten)]
 209    text: TextStyle,
 210}
 211
 212#[derive(Copy, Clone, Default, Deserialize)]
 213pub struct ImageStyle {
 214    #[serde(default)]
 215    pub border: Border,
 216    #[serde(default)]
 217    pub corner_radius: f32,
 218    #[serde(default)]
 219    pub height: Option<f32>,
 220    #[serde(default)]
 221    pub width: Option<f32>,
 222    #[serde(default)]
 223    pub grayscale: bool,
 224}
 225
 226#[derive(Deserialize, Default)]
 227pub struct Theme {
 228    #[serde(default)]
 229    pub meta: ThemeMeta,
 230    pub workspace: Workspace,
 231    pub context_menu: ContextMenu,
 232    pub toolbar_dropdown_menu: DropdownMenu,
 233    pub copilot: Copilot,
 234    pub collab_panel: CollabPanel,
 235    pub project_panel: ProjectPanel,
 236    pub chat_panel: ChatPanel,
 237    pub notification_panel: NotificationPanel,
 238    pub command_palette: CommandPalette,
 239    pub picker: Picker,
 240    pub editor: Editor,
 241    pub search: Search,
 242    pub project_diagnostics: ProjectDiagnostics,
 243    pub shared_screen: ContainerStyle,
 244    pub contact_notification: ContactNotification,
 245    pub update_notification: UpdateNotification,
 246    pub simple_message_notification: MessageNotification,
 247    pub project_shared_notification: ProjectSharedNotification,
 248    pub incoming_call_notification: IncomingCallNotification,
 249    pub tooltip: TooltipStyle,
 250    pub terminal: TerminalStyle,
 251    pub assistant: AssistantStyle,
 252    pub feedback: FeedbackStyle,
 253    pub welcome: WelcomeStyle,
 254    pub titlebar: Titlebar,
 255    // Nathan: New elements are styled in Rust, directly from the base theme.
 256    // We store it on the legacy theme so we can mix both kinds of elements during the transition.
 257    pub base_theme: serde_json::Value,
 258}
 259
 260#[derive(Deserialize, Default, Clone)]
 261pub struct ThemeMeta {
 262    #[serde(skip_deserializing)]
 263    pub id: usize,
 264    pub name: String,
 265    pub is_light: bool,
 266}
 267
 268#[derive(Deserialize, Default)]
 269pub struct Workspace {
 270    pub background: Color,
 271    pub blank_pane: BlankPaneStyle,
 272    pub tab_bar: TabBar,
 273    pub pane_divider: Border,
 274    pub leader_border_opacity: f32,
 275    pub leader_border_width: f32,
 276    pub dock: Dock,
 277    pub status_bar: StatusBar,
 278    pub toolbar: Toolbar,
 279    pub disconnected_overlay: ContainedText,
 280    pub modal: ContainerStyle,
 281    pub zoomed_panel_foreground: ContainerStyle,
 282    pub zoomed_pane_foreground: ContainerStyle,
 283    pub zoomed_background: ContainerStyle,
 284    pub notification: ContainerStyle,
 285    pub notifications: Notifications,
 286    pub joining_project_avatar: ImageStyle,
 287    pub joining_project_message: ContainedText,
 288    pub external_location_message: ContainedText,
 289    pub drop_target_overlay_color: Color,
 290}
 291
 292#[derive(Clone, Deserialize, Default)]
 293pub struct BlankPaneStyle {
 294    pub logo: SvgStyle,
 295    pub logo_shadow: SvgStyle,
 296    pub logo_container: ContainerStyle,
 297    pub keyboard_hints: ContainerStyle,
 298    pub keyboard_hint: Interactive<ContainedText>,
 299    pub keyboard_hint_width: f32,
 300}
 301
 302#[derive(Clone, Deserialize, Default)]
 303pub struct Titlebar {
 304    #[serde(flatten)]
 305    pub container: ContainerStyle,
 306    pub height: f32,
 307    pub menu: TitlebarMenu,
 308    pub project_menu_button: Toggleable<Interactive<ContainedText>>,
 309    pub git_menu_button: Toggleable<Interactive<ContainedText>>,
 310    pub project_host: Interactive<ContainedText>,
 311    pub item_spacing: f32,
 312    pub face_pile_spacing: f32,
 313    pub avatar_ribbon: AvatarRibbon,
 314    pub follower_avatar_overlap: f32,
 315    pub leader_selection: ContainerStyle,
 316    pub offline_icon: OfflineIcon,
 317    pub leader_avatar: AvatarStyle,
 318    pub follower_avatar: AvatarStyle,
 319    pub inactive_avatar_grayscale: bool,
 320    pub sign_in_button: Toggleable<Interactive<ContainedText>>,
 321    pub outdated_warning: ContainedText,
 322    pub share_button: Toggleable<Interactive<ContainedText>>,
 323    pub muted: Color,
 324    pub speaking: Color,
 325    pub screen_share_button: Toggleable<Interactive<IconButton>>,
 326    pub toggle_contacts_button: Toggleable<Interactive<IconButton>>,
 327    pub toggle_microphone_button: Toggleable<Interactive<IconButton>>,
 328    pub toggle_speakers_button: Toggleable<Interactive<IconButton>>,
 329    pub leave_call_button: Interactive<IconButton>,
 330    pub toggle_contacts_badge: ContainerStyle,
 331    pub user_menu: UserMenu,
 332}
 333
 334#[derive(Clone, Deserialize, Default)]
 335pub struct TitlebarMenu {
 336    pub width: f32,
 337    pub height: f32,
 338}
 339
 340#[derive(Clone, Deserialize, Default)]
 341pub struct UserMenu {
 342    pub user_menu_button_online: UserMenuButton,
 343    pub user_menu_button_offline: UserMenuButton,
 344}
 345
 346#[derive(Clone, Deserialize, Default)]
 347pub struct UserMenuButton {
 348    pub user_menu: Toggleable<Interactive<Icon>>,
 349    pub avatar: AvatarStyle,
 350    pub icon: Icon,
 351}
 352
 353#[derive(Copy, Clone, Deserialize, Default)]
 354pub struct AvatarStyle {
 355    #[serde(flatten)]
 356    pub image: ImageStyle,
 357    pub outer_width: f32,
 358    pub outer_corner_radius: f32,
 359}
 360
 361#[derive(Deserialize, Default, Clone)]
 362pub struct Copilot {
 363    pub out_link_icon: Interactive<IconStyle>,
 364    // pub modal: ModalStyle,
 365    pub auth: CopilotAuth,
 366}
 367
 368pub type CopilotCTAButton = Interactive<ContainedText>;
 369
 370#[derive(Deserialize, Default, Clone)]
 371pub struct CopilotAuth {
 372    pub content_width: f32,
 373    pub prompting: CopilotAuthPrompting,
 374    pub not_authorized: CopilotAuthNotAuthorized,
 375    pub authorized: CopilotAuthAuthorized,
 376    pub cta_button: CopilotCTAButton,
 377    pub header: IconStyle,
 378}
 379
 380#[derive(Deserialize, Default, Clone)]
 381pub struct CopilotAuthPrompting {
 382    pub subheading: ContainedText,
 383    pub hint: ContainedText,
 384    pub device_code: DeviceCode,
 385}
 386
 387#[derive(Deserialize, Default, Clone)]
 388pub struct DeviceCode {
 389    pub text: TextStyle,
 390    pub cta: CopilotCTAButton,
 391    pub left: f32,
 392    pub left_container: ContainerStyle,
 393    pub right: f32,
 394    pub right_container: Interactive<ContainerStyle>,
 395}
 396
 397#[derive(Deserialize, Default, Clone)]
 398pub struct CopilotAuthNotAuthorized {
 399    pub subheading: ContainedText,
 400    pub warning: ContainedText,
 401}
 402
 403#[derive(Deserialize, Default, Clone)]
 404pub struct CopilotAuthAuthorized {
 405    pub subheading: ContainedText,
 406    pub hint: ContainedText,
 407}
 408
 409#[derive(Deserialize, Default)]
 410pub struct CollabPanel {
 411    #[serde(flatten)]
 412    pub container: ContainerStyle,
 413    // pub disclosure: DisclosureStyle<()>,
 414    pub list_empty_state: Toggleable<Interactive<ContainedText>>,
 415    pub list_empty_icon: Icon,
 416    pub list_empty_label_container: ContainerStyle,
 417    pub log_in_button: Interactive<ContainedText>,
 418    pub channel_editor: ContainerStyle,
 419    pub channel_hash: Icon,
 420    pub channel_note_active_color: Color,
 421    pub tabbed_modal: TabbedModal,
 422    pub contact_finder: ContactFinder,
 423    pub channel_modal: ChannelModal,
 424    pub user_query_editor: FieldEditor,
 425    pub user_query_editor_height: f32,
 426    pub leave_call_button: Toggleable<Interactive<IconButton>>,
 427    pub add_contact_button: Toggleable<Interactive<IconButton>>,
 428    pub add_channel_button: Toggleable<Interactive<IconButton>>,
 429    pub header_row: ContainedText,
 430    pub dragged_over_header: ContainerStyle,
 431    pub subheader_row: Toggleable<Interactive<ContainedText>>,
 432    pub leave_call: Interactive<ContainedText>,
 433    pub contact_row: Toggleable<Interactive<ContainerStyle>>,
 434    pub channel_row: Toggleable<Interactive<ContainerStyle>>,
 435    pub channel_name: Toggleable<ContainedText>,
 436    pub row_height: f32,
 437    pub project_row: Toggleable<Interactive<ProjectRow>>,
 438    pub tree_branch: Toggleable<Interactive<TreeBranch>>,
 439    pub contact_avatar: ImageStyle,
 440    pub channel_avatar: ImageStyle,
 441    pub extra_participant_label: ContainedText,
 442    pub contact_status_free: ContainerStyle,
 443    pub contact_status_busy: ContainerStyle,
 444    pub contact_username: ContainedText,
 445    pub contact_button: Interactive<IconButton>,
 446    pub contact_button_spacing: f32,
 447    pub channel_indent: f32,
 448    pub disabled_button: IconButton,
 449    pub section_icon_size: f32,
 450    pub calling_indicator: ContainedText,
 451    pub face_overlap: f32,
 452}
 453
 454#[derive(Deserialize, Default)]
 455pub struct TabbedModal {
 456    pub tab_button: Toggleable<Interactive<ContainedText>>,
 457    pub modal: ContainerStyle,
 458    pub header: ContainerStyle,
 459    pub body: ContainerStyle,
 460    pub title: ContainedText,
 461    pub visibility_toggle: Interactive<ContainedText>,
 462    pub channel_link: Interactive<ContainedText>,
 463    pub picker: Picker,
 464    pub max_height: f32,
 465    pub max_width: f32,
 466    pub row_height: f32,
 467}
 468
 469#[derive(Deserialize, Default)]
 470pub struct ChannelModal {
 471    pub contact_avatar: ImageStyle,
 472    pub contact_username: ContainerStyle,
 473    pub remove_member_button: ContainedText,
 474    pub cancel_invite_button: ContainedText,
 475    pub member_icon: IconButton,
 476    pub invitee_icon: IconButton,
 477    pub member_tag: ContainedText,
 478}
 479
 480#[derive(Deserialize, Default)]
 481pub struct ProjectRow {
 482    #[serde(flatten)]
 483    pub container: ContainerStyle,
 484    pub icon: Icon,
 485    pub name: ContainedText,
 486}
 487
 488#[derive(Deserialize, Default, Clone, Copy)]
 489pub struct TreeBranch {
 490    pub width: f32,
 491    pub color: Color,
 492}
 493
 494#[derive(Deserialize, Default)]
 495pub struct ContactFinder {
 496    pub contact_avatar: ImageStyle,
 497    pub contact_username: ContainerStyle,
 498    pub contact_button: IconButton,
 499    pub disabled_contact_button: IconButton,
 500}
 501
 502#[derive(Deserialize, Default)]
 503pub struct DropdownMenu {
 504    #[serde(flatten)]
 505    pub container: ContainerStyle,
 506    pub header: Interactive<DropdownMenuItem>,
 507    pub section_header: ContainedText,
 508    pub item: Toggleable<Interactive<DropdownMenuItem>>,
 509    pub row_height: f32,
 510}
 511
 512#[derive(Deserialize, Default)]
 513pub struct DropdownMenuItem {
 514    #[serde(flatten)]
 515    pub container: ContainerStyle,
 516    #[serde(flatten)]
 517    pub text: TextStyle,
 518    pub secondary_text: Option<TextStyle>,
 519    #[serde(default)]
 520    pub secondary_text_spacing: f32,
 521}
 522
 523#[derive(Clone, Deserialize, Default)]
 524pub struct TabBar {
 525    #[serde(flatten)]
 526    pub container: ContainerStyle,
 527    pub pane_button: Toggleable<Interactive<IconButton>>,
 528    pub pane_button_container: ContainerStyle,
 529    pub active_pane: TabStyles,
 530    pub inactive_pane: TabStyles,
 531    pub dragged_tab: Tab,
 532    pub height: f32,
 533    pub nav_button: Interactive<IconButton>,
 534}
 535
 536impl TabBar {
 537    pub fn tab_style(&self, pane_active: bool, tab_active: bool) -> &Tab {
 538        let tabs = if pane_active {
 539            &self.active_pane
 540        } else {
 541            &self.inactive_pane
 542        };
 543
 544        if tab_active {
 545            &tabs.active_tab
 546        } else {
 547            &tabs.inactive_tab
 548        }
 549    }
 550}
 551
 552#[derive(Clone, Deserialize, Default)]
 553pub struct TabStyles {
 554    pub active_tab: Tab,
 555    pub inactive_tab: Tab,
 556}
 557
 558#[derive(Clone, Deserialize, Default)]
 559pub struct AvatarRibbon {
 560    #[serde(flatten)]
 561    pub container: ContainerStyle,
 562    pub width: f32,
 563    pub height: f32,
 564}
 565
 566#[derive(Clone, Deserialize, Default)]
 567pub struct OfflineIcon {
 568    #[serde(flatten)]
 569    pub container: ContainerStyle,
 570    pub width: f32,
 571    pub color: Color,
 572}
 573
 574#[derive(Clone, Debug, Deserialize, Default)]
 575pub struct LabelStyle {
 576    pub text: TextStyle,
 577    pub highlight_text: Option<TextStyle>,
 578}
 579
 580#[derive(Clone, Deserialize, Default)]
 581pub struct Tab {
 582    pub height: f32,
 583    #[serde(flatten)]
 584    pub container: ContainerStyle,
 585    #[serde(flatten)]
 586    pub label: LabelStyle,
 587    pub description: ContainedText,
 588    pub spacing: f32,
 589    pub close_icon_width: f32,
 590    pub type_icon_width: f32,
 591    pub icon_close: Color,
 592    pub icon_close_active: Color,
 593    pub icon_dirty: Color,
 594    pub icon_conflict: Color,
 595    pub git: GitProjectStatus,
 596}
 597
 598#[derive(Clone, Deserialize, Default)]
 599pub struct Toolbar {
 600    #[serde(flatten)]
 601    pub container: ContainerStyle,
 602    pub height: f32,
 603    pub item_spacing: f32,
 604    pub toggleable_tool: Toggleable<Interactive<IconButton>>,
 605    pub toggleable_text_tool: Toggleable<Interactive<ContainedText>>,
 606    pub breadcrumb_height: f32,
 607    pub breadcrumbs: Interactive<ContainedText>,
 608}
 609
 610#[derive(Clone, Deserialize, Default)]
 611pub struct Notifications {
 612    #[serde(flatten)]
 613    pub container: ContainerStyle,
 614    pub width: f32,
 615}
 616
 617#[derive(Clone, Deserialize, Default)]
 618pub struct Search {
 619    #[serde(flatten)]
 620    pub container: ContainerStyle,
 621    pub editor: FindEditor,
 622    pub invalid_editor: ContainerStyle,
 623    pub option_button_group: ContainerStyle,
 624    pub include_exclude_editor: FindEditor,
 625    pub invalid_include_exclude_editor: ContainerStyle,
 626    pub include_exclude_inputs: ContainedText,
 627    pub option_button_component: ToggleIconButtonStyle,
 628    pub match_background: Color,
 629    pub match_index: ContainedText,
 630    pub major_results_status: TextStyle,
 631    pub minor_results_status: TextStyle,
 632    pub editor_icon: IconStyle,
 633    pub mode_button: Toggleable<Interactive<ContainedText>>,
 634    pub nav_button: Toggleable<Interactive<ContainedLabel>>,
 635    pub search_bar_row_height: f32,
 636    pub search_row_spacing: f32,
 637    pub option_button_height: f32,
 638    pub modes_container: ContainerStyle,
 639    pub replace_icon: IconStyle,
 640    // Used for filters and replace
 641    pub option_button: Toggleable<Interactive<IconButton>>,
 642    pub action_button: IconButtonStyle,
 643}
 644
 645#[derive(Clone, Deserialize, Default)]
 646pub struct FindEditor {
 647    #[serde(flatten)]
 648    pub input: FieldEditor,
 649    pub min_width: f32,
 650    pub max_width: f32,
 651}
 652
 653#[derive(Deserialize, Default)]
 654pub struct StatusBar {
 655    #[serde(flatten)]
 656    pub container: ContainerStyle,
 657    pub height: f32,
 658    pub item_spacing: f32,
 659    pub cursor_position: TextStyle,
 660    pub vim_mode_indicator: ContainedText,
 661    pub active_language: Interactive<ContainedText>,
 662    pub auto_update_progress_message: TextStyle,
 663    pub auto_update_done_message: TextStyle,
 664    pub lsp_status: Interactive<StatusBarLspStatus>,
 665    pub panel_buttons: StatusBarPanelButtons,
 666    pub diagnostic_summary: Interactive<StatusBarDiagnosticSummary>,
 667    pub diagnostic_message: Interactive<ContainedText>,
 668}
 669
 670#[derive(Deserialize, Default)]
 671pub struct StatusBarPanelButtons {
 672    pub group_left: ContainerStyle,
 673    pub group_bottom: ContainerStyle,
 674    pub group_right: ContainerStyle,
 675    pub button: Toggleable<Interactive<PanelButton>>,
 676}
 677
 678#[derive(Deserialize, Default)]
 679pub struct StatusBarDiagnosticSummary {
 680    pub container_ok: ContainerStyle,
 681    pub container_warning: ContainerStyle,
 682    pub container_error: ContainerStyle,
 683    pub text: TextStyle,
 684    pub icon_color_ok: Color,
 685    pub icon_color_warning: Color,
 686    pub icon_color_error: Color,
 687    pub height: f32,
 688    pub icon_width: f32,
 689    pub icon_spacing: f32,
 690    pub summary_spacing: f32,
 691}
 692
 693#[derive(Deserialize, Default)]
 694pub struct StatusBarLspStatus {
 695    #[serde(flatten)]
 696    pub container: ContainerStyle,
 697    pub height: f32,
 698    pub icon_spacing: f32,
 699    pub icon_color: Color,
 700    pub icon_width: f32,
 701    pub message: TextStyle,
 702}
 703
 704#[derive(Deserialize, Default)]
 705pub struct Dock {
 706    pub left: ContainerStyle,
 707    pub bottom: ContainerStyle,
 708    pub right: ContainerStyle,
 709}
 710
 711#[derive(Clone, Deserialize, Default)]
 712pub struct PanelButton {
 713    #[serde(flatten)]
 714    pub container: ContainerStyle,
 715    pub icon_color: Color,
 716    pub icon_size: f32,
 717    pub label: ContainedText,
 718}
 719
 720#[derive(Deserialize, Default)]
 721pub struct ProjectPanel {
 722    #[serde(flatten)]
 723    pub container: ContainerStyle,
 724    pub entry: Toggleable<Interactive<ProjectPanelEntry>>,
 725    pub dragged_entry: ProjectPanelEntry,
 726    pub ignored_entry: Toggleable<Interactive<ProjectPanelEntry>>,
 727    pub cut_entry: Toggleable<Interactive<ProjectPanelEntry>>,
 728    pub filename_editor: FieldEditor,
 729    pub indent_width: f32,
 730    pub open_project_button: Interactive<ContainedText>,
 731}
 732
 733#[derive(Clone, Debug, Deserialize, Default)]
 734pub struct ProjectPanelEntry {
 735    pub height: f32,
 736    #[serde(flatten)]
 737    pub container: ContainerStyle,
 738    pub text: TextStyle,
 739    pub icon_size: f32,
 740    pub icon_color: Color,
 741    pub chevron_color: Color,
 742    pub chevron_size: f32,
 743    pub icon_spacing: f32,
 744    pub status: EntryStatus,
 745}
 746
 747#[derive(Clone, Debug, Deserialize, Default)]
 748pub struct EntryStatus {
 749    pub git: GitProjectStatus,
 750}
 751
 752#[derive(Clone, Debug, Deserialize, Default)]
 753pub struct GitProjectStatus {
 754    pub modified: Color,
 755    pub inserted: Color,
 756    pub conflict: Color,
 757}
 758
 759#[derive(Clone, Debug, Deserialize, Default)]
 760pub struct ContextMenu {
 761    #[serde(flatten)]
 762    pub container: ContainerStyle,
 763    pub item: Toggleable<Interactive<ContextMenuItem>>,
 764    pub keystroke_margin: f32,
 765    pub separator: ContainerStyle,
 766}
 767
 768#[derive(Clone, Debug, Deserialize, Default)]
 769pub struct ContextMenuItem {
 770    #[serde(flatten)]
 771    pub container: ContainerStyle,
 772    pub label: TextStyle,
 773    pub keystroke: ContainedText,
 774    pub icon_width: f32,
 775    pub icon_spacing: f32,
 776}
 777
 778#[derive(Debug, Deserialize, Default)]
 779pub struct CommandPalette {
 780    pub key: Toggleable<ContainedLabel>,
 781    pub keystroke_spacing: f32,
 782}
 783
 784#[derive(Deserialize, Default)]
 785pub struct InviteLink {
 786    #[serde(flatten)]
 787    pub container: ContainerStyle,
 788    #[serde(flatten)]
 789    pub label: LabelStyle,
 790    pub icon: Icon,
 791}
 792
 793#[derive(Deserialize, Clone, Copy, Default)]
 794pub struct Icon {
 795    #[serde(flatten)]
 796    pub container: ContainerStyle,
 797    pub color: Color,
 798    pub width: f32,
 799}
 800
 801#[derive(Deserialize, Clone, Copy, Default)]
 802pub struct IconButton {
 803    #[serde(flatten)]
 804    pub container: ContainerStyle,
 805    pub color: Color,
 806    pub icon_width: f32,
 807    pub button_width: f32,
 808}
 809
 810#[derive(Deserialize, Default)]
 811pub struct ChatPanel {
 812    #[serde(flatten)]
 813    pub container: ContainerStyle,
 814    pub list: ContainerStyle,
 815    pub channel_select: ChannelSelect,
 816    pub input_editor: FieldEditor,
 817    pub avatar: AvatarStyle,
 818    pub avatar_container: ContainerStyle,
 819    pub rich_text: RichTextStyle,
 820    pub message_sender: ContainedText,
 821    pub message_timestamp: ContainedText,
 822    pub message: Interactive<ContainerStyle>,
 823    pub continuation_message: Interactive<ContainerStyle>,
 824    pub pending_message: Interactive<ContainerStyle>,
 825    pub last_message_bottom_spacing: f32,
 826    pub sign_in_prompt: Interactive<TextStyle>,
 827    pub icon_button: Interactive<IconButton>,
 828}
 829
 830#[derive(Clone, Deserialize, Default)]
 831pub struct RichTextStyle {
 832    pub text: TextStyle,
 833    pub mention_highlight: HighlightStyle,
 834    pub mention_background: Option<Color>,
 835    pub self_mention_highlight: HighlightStyle,
 836    pub self_mention_background: Option<Color>,
 837    pub code_background: Option<Color>,
 838}
 839
 840#[derive(Deserialize, Default)]
 841pub struct NotificationPanel {
 842    #[serde(flatten)]
 843    pub container: ContainerStyle,
 844    pub title: ContainedText,
 845    pub title_icon: SvgStyle,
 846    pub title_height: f32,
 847    pub list: ContainerStyle,
 848    pub avatar: AvatarStyle,
 849    pub avatar_container: ContainerStyle,
 850    pub sign_in_prompt: Interactive<TextStyle>,
 851    pub icon_button: Interactive<IconButton>,
 852    pub unread_text: ContainedText,
 853    pub read_text: ContainedText,
 854    pub timestamp: ContainedText,
 855    pub button: Interactive<ContainedText>,
 856}
 857
 858#[derive(Deserialize, Default)]
 859pub struct ChannelSelect {
 860    #[serde(flatten)]
 861    pub container: ContainerStyle,
 862    pub header: ChannelName,
 863    pub item: ChannelName,
 864    pub active_item: ChannelName,
 865    pub hovered_item: ChannelName,
 866    pub menu: ContainerStyle,
 867}
 868
 869#[derive(Deserialize, Default)]
 870pub struct ChannelName {
 871    #[serde(flatten)]
 872    pub container: ContainerStyle,
 873    pub hash: ContainedText,
 874    pub name: TextStyle,
 875}
 876
 877#[derive(Clone, Deserialize, Default)]
 878pub struct Picker {
 879    #[serde(flatten)]
 880    pub container: ContainerStyle,
 881    pub empty_container: ContainerStyle,
 882    pub input_editor: FieldEditor,
 883    pub empty_input_editor: FieldEditor,
 884    pub no_matches: ContainedLabel,
 885    pub item: Toggleable<Interactive<ContainedLabel>>,
 886    pub header: ContainedLabel,
 887    pub footer: Interactive<ContainedLabel>,
 888}
 889
 890#[derive(Clone, Debug, Deserialize, Default)]
 891pub struct ContainedText {
 892    #[serde(flatten)]
 893    pub container: ContainerStyle,
 894    #[serde(flatten)]
 895    pub text: TextStyle,
 896}
 897
 898#[derive(Clone, Debug, Deserialize, Default)]
 899pub struct ContainedLabel {
 900    #[serde(flatten)]
 901    pub container: ContainerStyle,
 902    #[serde(flatten)]
 903    pub label: LabelStyle,
 904}
 905
 906#[derive(Clone, Deserialize, Default)]
 907pub struct ProjectDiagnostics {
 908    #[serde(flatten)]
 909    pub container: ContainerStyle,
 910    pub empty_message: TextStyle,
 911    pub tab_icon_width: f32,
 912    pub tab_icon_spacing: f32,
 913    pub tab_summary_spacing: f32,
 914}
 915
 916#[derive(Deserialize, Default)]
 917pub struct ContactNotification {
 918    pub header_avatar: ImageStyle,
 919    pub header_message: ContainedText,
 920    pub header_height: f32,
 921    pub body_message: ContainedText,
 922    pub button: Interactive<ContainedText>,
 923    pub dismiss_button: Interactive<IconButton>,
 924}
 925
 926#[derive(Deserialize, Default)]
 927pub struct UpdateNotification {
 928    pub message: ContainedText,
 929    pub action_message: Interactive<ContainedText>,
 930    pub dismiss_button: Interactive<IconButton>,
 931}
 932
 933#[derive(Deserialize, Default)]
 934pub struct MessageNotification {
 935    pub message: ContainedText,
 936    pub action_message: Interactive<ContainedText>,
 937    pub dismiss_button: Interactive<IconButton>,
 938}
 939
 940#[derive(Deserialize, Default)]
 941pub struct ProjectSharedNotification {
 942    pub window_height: f32,
 943    pub window_width: f32,
 944    #[serde(default)]
 945    pub background: Color,
 946    pub owner_container: ContainerStyle,
 947    pub owner_avatar: ImageStyle,
 948    pub owner_metadata: ContainerStyle,
 949    pub owner_username: ContainedText,
 950    pub message: ContainedText,
 951    pub worktree_roots: ContainedText,
 952    pub button_width: f32,
 953    pub open_button: ContainedText,
 954    pub dismiss_button: ContainedText,
 955}
 956
 957#[derive(Deserialize, Default)]
 958pub struct IncomingCallNotification {
 959    pub window_height: f32,
 960    pub window_width: f32,
 961    #[serde(default)]
 962    pub background: Color,
 963    pub caller_container: ContainerStyle,
 964    pub caller_avatar: ImageStyle,
 965    pub caller_metadata: ContainerStyle,
 966    pub caller_username: ContainedText,
 967    pub caller_message: ContainedText,
 968    pub worktree_roots: ContainedText,
 969    pub button_width: f32,
 970    pub accept_button: ContainedText,
 971    pub decline_button: ContainedText,
 972}
 973
 974#[derive(Clone, Deserialize, Default)]
 975pub struct Editor {
 976    pub text_color: Color,
 977    #[serde(default)]
 978    pub background: Color,
 979    pub selection: SelectionStyle,
 980    pub gutter_background: Color,
 981    pub gutter_padding_factor: f32,
 982    pub active_line_background: Color,
 983    pub highlighted_line_background: Color,
 984    pub rename_fade: f32,
 985    pub document_highlight_read_background: Color,
 986    pub document_highlight_write_background: Color,
 987    pub diff: DiffStyle,
 988    pub wrap_guide: Color,
 989    pub active_wrap_guide: Color,
 990    pub line_number: Color,
 991    pub line_number_active: Color,
 992    pub guest_selections: Vec<SelectionStyle>,
 993    pub absent_selection: SelectionStyle,
 994    pub syntax: Arc<SyntaxTheme>,
 995    pub hint: HighlightStyle,
 996    pub suggestion: HighlightStyle,
 997    pub diagnostic_path_header: DiagnosticPathHeader,
 998    pub diagnostic_header: DiagnosticHeader,
 999    pub error_diagnostic: DiagnosticStyle,
1000    pub invalid_error_diagnostic: DiagnosticStyle,
1001    pub warning_diagnostic: DiagnosticStyle,
1002    pub invalid_warning_diagnostic: DiagnosticStyle,
1003    pub information_diagnostic: DiagnosticStyle,
1004    pub invalid_information_diagnostic: DiagnosticStyle,
1005    pub hint_diagnostic: DiagnosticStyle,
1006    pub invalid_hint_diagnostic: DiagnosticStyle,
1007    pub autocomplete: AutocompleteStyle,
1008    pub code_actions: CodeActions,
1009    pub folds: Folds,
1010    pub unnecessary_code_fade: f32,
1011    pub hover_popover: HoverPopover,
1012    pub link_definition: HighlightStyle,
1013    pub composition_mark: HighlightStyle,
1014    pub jump_icon: Interactive<IconButton>,
1015    pub scrollbar: Scrollbar,
1016    pub whitespace: Color,
1017}
1018
1019#[derive(Clone, Deserialize, Default)]
1020pub struct Scrollbar {
1021    pub track: ContainerStyle,
1022    pub thumb: ContainerStyle,
1023    pub width: f32,
1024    pub min_height_factor: f32,
1025    pub git: BufferGitDiffColors,
1026    pub selections: Color,
1027}
1028
1029#[derive(Clone, Deserialize, Default)]
1030pub struct BufferGitDiffColors {
1031    pub inserted: Color,
1032    pub modified: Color,
1033    pub deleted: Color,
1034}
1035
1036#[derive(Clone, Deserialize, Default)]
1037pub struct DiagnosticPathHeader {
1038    #[serde(flatten)]
1039    pub container: ContainerStyle,
1040    pub filename: ContainedText,
1041    pub path: ContainedText,
1042    pub text_scale_factor: f32,
1043}
1044
1045#[derive(Clone, Deserialize, Default)]
1046pub struct DiagnosticHeader {
1047    #[serde(flatten)]
1048    pub container: ContainerStyle,
1049    pub source: ContainedLabel,
1050    pub message: ContainedLabel,
1051    pub code: ContainedText,
1052    pub text_scale_factor: f32,
1053    pub icon_width_factor: f32,
1054}
1055
1056#[derive(Clone, Deserialize, Default)]
1057pub struct DiagnosticStyle {
1058    pub message: LabelStyle,
1059    #[serde(default)]
1060    pub header: ContainerStyle,
1061    pub text_scale_factor: f32,
1062}
1063
1064#[derive(Clone, Deserialize, Default)]
1065pub struct AutocompleteStyle {
1066    #[serde(flatten)]
1067    pub container: ContainerStyle,
1068    pub item: ContainerStyle,
1069    pub selected_item: ContainerStyle,
1070    pub hovered_item: ContainerStyle,
1071    pub match_highlight: HighlightStyle,
1072    pub completion_min_width: f32,
1073    pub completion_max_width: f32,
1074    pub inline_docs_container: ContainerStyle,
1075    pub inline_docs_color: Color,
1076    pub inline_docs_size_percent: f32,
1077    pub alongside_docs_max_width: f32,
1078    pub alongside_docs_container: ContainerStyle,
1079}
1080
1081#[derive(Clone, Copy, Default, Deserialize)]
1082pub struct SelectionStyle {
1083    pub cursor: Color,
1084    pub selection: Color,
1085}
1086
1087#[derive(Clone, Deserialize, Default)]
1088pub struct FieldEditor {
1089    #[serde(flatten)]
1090    pub container: ContainerStyle,
1091    pub text: TextStyle,
1092    #[serde(default)]
1093    pub placeholder_text: Option<TextStyle>,
1094    pub selection: SelectionStyle,
1095}
1096
1097#[derive(Clone, Deserialize, Default)]
1098pub struct InteractiveColor {
1099    pub color: Color,
1100}
1101
1102#[derive(Clone, Deserialize, Default)]
1103pub struct CodeActions {
1104    #[serde(default)]
1105    pub indicator: Toggleable<Interactive<InteractiveColor>>,
1106    pub vertical_scale: f32,
1107}
1108
1109#[derive(Clone, Deserialize, Default)]
1110pub struct Folds {
1111    pub indicator: Toggleable<Interactive<InteractiveColor>>,
1112    pub ellipses: FoldEllipses,
1113    pub fold_background: Color,
1114    pub icon_margin_scale: f32,
1115    pub folded_icon: String,
1116    pub foldable_icon: String,
1117}
1118
1119#[derive(Clone, Deserialize, Default)]
1120pub struct FoldEllipses {
1121    pub text_color: Color,
1122    pub background: Interactive<InteractiveColor>,
1123    pub corner_radius_factor: f32,
1124}
1125
1126#[derive(Clone, Deserialize, Default)]
1127pub struct DiffStyle {
1128    pub inserted: Color,
1129    pub modified: Color,
1130    pub deleted: Color,
1131    pub removed_width_em: f32,
1132    pub width_em: f32,
1133    pub corner_radius: f32,
1134}
1135
1136#[derive(Debug, Default, Clone, Copy)]
1137pub struct Interactive<T> {
1138    pub default: T,
1139    pub hovered: Option<T>,
1140    pub clicked: Option<T>,
1141    pub disabled: Option<T>,
1142}
1143
1144impl<T> Deref for Interactive<T> {
1145    type Target = T;
1146
1147    fn deref(&self) -> &Self::Target {
1148        &self.default
1149    }
1150}
1151
1152impl Interactive<()> {
1153    pub fn new_blank() -> Self {
1154        Self {
1155            default: (),
1156            hovered: None,
1157            clicked: None,
1158            disabled: None,
1159        }
1160    }
1161}
1162
1163#[derive(Clone, Copy, Debug, Default, Deserialize)]
1164pub struct Toggleable<T> {
1165    active: T,
1166    inactive: T,
1167}
1168
1169impl<T> Deref for Toggleable<T> {
1170    type Target = T;
1171
1172    fn deref(&self) -> &Self::Target {
1173        &self.inactive
1174    }
1175}
1176
1177impl Toggleable<()> {
1178    pub fn new_blank() -> Self {
1179        Self {
1180            active: (),
1181            inactive: (),
1182        }
1183    }
1184}
1185
1186impl<T> Toggleable<T> {
1187    pub fn new(active: T, inactive: T) -> Self {
1188        Self { active, inactive }
1189    }
1190    pub fn in_state(&self, active: bool) -> &T {
1191        if active {
1192            &self.active
1193        } else {
1194            &self.inactive
1195        }
1196    }
1197    pub fn active_state(&self) -> &T {
1198        self.in_state(true)
1199    }
1200
1201    pub fn inactive_state(&self) -> &T {
1202        self.in_state(false)
1203    }
1204}
1205
1206impl<T> Interactive<T> {
1207    pub fn disabled_style(&self) -> &T {
1208        self.disabled.as_ref().unwrap_or(&self.default)
1209    }
1210}
1211
1212impl<T> Toggleable<Interactive<T>> {
1213    pub fn default_style(&self) -> &T {
1214        &self.inactive.default
1215    }
1216}
1217
1218impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
1219    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1220    where
1221        D: serde::Deserializer<'de>,
1222    {
1223        #[derive(Deserialize)]
1224        struct Helper {
1225            default: Value,
1226            hovered: Option<Value>,
1227            clicked: Option<Value>,
1228            disabled: Option<Value>,
1229        }
1230
1231        let json = Helper::deserialize(deserializer)?;
1232
1233        let deserialize_state = |state_json: Option<Value>| -> Result<Option<T>, D::Error> {
1234            if let Some(mut state_json) = state_json {
1235                if let Value::Object(state_json) = &mut state_json {
1236                    if let Value::Object(default) = &json.default {
1237                        for (key, value) in default {
1238                            if !state_json.contains_key(key) {
1239                                state_json.insert(key.clone(), value.clone());
1240                            }
1241                        }
1242                    }
1243                }
1244                Ok(Some(
1245                    serde_json::from_value::<T>(state_json).map_err(serde::de::Error::custom)?,
1246                ))
1247            } else {
1248                Ok(None)
1249            }
1250        };
1251
1252        let hovered = deserialize_state(json.hovered)?;
1253        let clicked = deserialize_state(json.clicked)?;
1254        let disabled = deserialize_state(json.disabled)?;
1255        let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
1256
1257        Ok(Interactive {
1258            default,
1259            hovered,
1260            clicked,
1261            disabled,
1262        })
1263    }
1264}
1265
1266impl Editor {
1267    pub fn selection_style_for_room_participant(&self, participant_index: u32) -> SelectionStyle {
1268        if self.guest_selections.is_empty() {
1269            return SelectionStyle::default();
1270        }
1271        let style_ix = participant_index as usize % self.guest_selections.len();
1272        self.guest_selections[style_ix]
1273    }
1274}
1275
1276#[derive(Default)]
1277pub struct SyntaxTheme {
1278    pub highlights: Vec<(String, HighlightStyle)>,
1279}
1280
1281impl SyntaxTheme {
1282    pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
1283        Self { highlights }
1284    }
1285}
1286
1287impl<'de> Deserialize<'de> for SyntaxTheme {
1288    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1289    where
1290        D: serde::Deserializer<'de>,
1291    {
1292        let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
1293
1294        let mut result = Self::new(Vec::new());
1295        for (key, style) in syntax_data {
1296            match result
1297                .highlights
1298                .binary_search_by(|(needle, _)| needle.cmp(&key))
1299            {
1300                Ok(i) | Err(i) => {
1301                    result.highlights.insert(i, (key, style));
1302                }
1303            }
1304        }
1305
1306        Ok(result)
1307    }
1308}
1309
1310#[derive(Clone, Deserialize, Default)]
1311pub struct HoverPopover {
1312    pub container: ContainerStyle,
1313    pub info_container: ContainerStyle,
1314    pub warning_container: ContainerStyle,
1315    pub error_container: ContainerStyle,
1316    pub block_style: ContainerStyle,
1317    pub prose: TextStyle,
1318    pub diagnostic_source_highlight: HighlightStyle,
1319    pub highlight: Color,
1320}
1321
1322#[derive(Clone, Deserialize, Default)]
1323pub struct TerminalStyle {
1324    pub black: Color,
1325    pub red: Color,
1326    pub green: Color,
1327    pub yellow: Color,
1328    pub blue: Color,
1329    pub magenta: Color,
1330    pub cyan: Color,
1331    pub white: Color,
1332    pub bright_black: Color,
1333    pub bright_red: Color,
1334    pub bright_green: Color,
1335    pub bright_yellow: Color,
1336    pub bright_blue: Color,
1337    pub bright_magenta: Color,
1338    pub bright_cyan: Color,
1339    pub bright_white: Color,
1340    pub foreground: Color,
1341    pub background: Color,
1342    pub modal_background: Color,
1343    pub cursor: Color,
1344    pub dim_black: Color,
1345    pub dim_red: Color,
1346    pub dim_green: Color,
1347    pub dim_yellow: Color,
1348    pub dim_blue: Color,
1349    pub dim_magenta: Color,
1350    pub dim_cyan: Color,
1351    pub dim_white: Color,
1352    pub bright_foreground: Color,
1353    pub dim_foreground: Color,
1354}
1355
1356#[derive(Clone, Deserialize, Default)]
1357pub struct AssistantStyle {
1358    pub container: ContainerStyle,
1359    pub hamburger_button: Interactive<IconStyle>,
1360    pub split_button: Interactive<IconStyle>,
1361    pub assist_button: Interactive<IconStyle>,
1362    pub quote_button: Interactive<IconStyle>,
1363    pub zoom_in_button: Interactive<IconStyle>,
1364    pub zoom_out_button: Interactive<IconStyle>,
1365    pub plus_button: Interactive<IconStyle>,
1366    pub title: ContainedText,
1367    pub message_header: ContainerStyle,
1368    pub sent_at: ContainedText,
1369    pub user_sender: Interactive<ContainedText>,
1370    pub assistant_sender: Interactive<ContainedText>,
1371    pub system_sender: Interactive<ContainedText>,
1372    pub model: Interactive<ContainedText>,
1373    pub remaining_tokens: ContainedText,
1374    pub low_remaining_tokens: ContainedText,
1375    pub no_remaining_tokens: ContainedText,
1376    pub error_icon: Icon,
1377    pub api_key_editor: FieldEditor,
1378    pub api_key_prompt: ContainedText,
1379    pub saved_conversation: SavedConversation,
1380    pub inline: InlineAssistantStyle,
1381}
1382
1383#[derive(Clone, Deserialize, Default)]
1384pub struct ButtonStyle<C> {
1385    #[serde(flatten)]
1386    pub container: ContainerStyle,
1387    // TODO: These are incorrect for the intended usage of the buttons.
1388    // The size should be constant, but putting them here duplicates them
1389    // across the states the buttons can be in
1390    pub button_width: Option<f32>,
1391    pub button_height: Option<f32>,
1392    #[serde(flatten)]
1393    contents: C,
1394}
1395
1396pub type IconButtonStyle = Interactive<ButtonStyle<SvgStyle>>;
1397pub type ToggleIconButtonStyle = Toggleable<IconButtonStyle>;
1398
1399#[derive(Clone, Deserialize, Default)]
1400pub struct InlineAssistantStyle {
1401    #[serde(flatten)]
1402    pub container: ContainerStyle,
1403    pub editor: FieldEditor,
1404    pub disabled_editor: FieldEditor,
1405    pub pending_edit_background: Color,
1406    pub include_conversation: ToggleIconButtonStyle,
1407    pub retrieve_context: ToggleIconButtonStyle,
1408    pub context_status: ContextStatusStyle,
1409}
1410
1411#[derive(Clone, Deserialize, Default)]
1412pub struct ContextStatusStyle {
1413    pub error_icon: Icon,
1414    pub in_progress_icon: Icon,
1415    pub complete_icon: Icon,
1416}
1417
1418#[derive(Clone, Deserialize, Default)]
1419pub struct Contained<T> {
1420    container: ContainerStyle,
1421    contained: T,
1422}
1423
1424#[derive(Clone, Deserialize, Default)]
1425pub struct SavedConversation {
1426    pub container: Interactive<ContainerStyle>,
1427    pub saved_at: ContainedText,
1428    pub title: ContainedText,
1429}
1430
1431#[derive(Clone, Deserialize, Default)]
1432pub struct FeedbackStyle {
1433    pub submit_button: Interactive<ContainedText>,
1434    pub button_margin: f32,
1435    pub info_text_default: ContainedText,
1436    pub link_text_default: ContainedText,
1437    pub link_text_hover: ContainedText,
1438}
1439
1440#[derive(Clone, Deserialize, Default)]
1441pub struct CheckboxStyle {
1442    pub icon: SvgStyle,
1443    pub label: ContainedText,
1444    pub default: ContainerStyle,
1445    pub checked: ContainerStyle,
1446    pub hovered: ContainerStyle,
1447    pub hovered_and_checked: ContainerStyle,
1448}
1449
1450#[derive(Clone, Deserialize, Default)]
1451pub struct WelcomeStyle {
1452    pub page_width: f32,
1453    pub logo: SvgStyle,
1454    pub logo_subheading: ContainedText,
1455    pub usage_note: ContainedText,
1456    pub checkbox: CheckboxStyle,
1457    pub checkbox_container: ContainerStyle,
1458    pub button: Interactive<ContainedText>,
1459    pub button_group: ContainerStyle,
1460    pub heading_group: ContainerStyle,
1461    pub checkbox_group: ContainerStyle,
1462}
1463
1464#[derive(Clone, Deserialize, Default)]
1465pub struct ColorScheme {
1466    pub name: String,
1467    pub is_light: bool,
1468    pub lowest: Layer,
1469    pub middle: Layer,
1470    pub highest: Layer,
1471}
1472
1473#[derive(Clone, Deserialize, Default)]
1474pub struct Player {
1475    pub cursor: Color,
1476    pub selection: Color,
1477}
1478
1479#[derive(Clone, Deserialize, Default)]
1480pub struct RampSet {
1481    pub neutral: Vec<Color>,
1482    pub red: Vec<Color>,
1483    pub orange: Vec<Color>,
1484    pub yellow: Vec<Color>,
1485    pub green: Vec<Color>,
1486    pub cyan: Vec<Color>,
1487    pub blue: Vec<Color>,
1488    pub violet: Vec<Color>,
1489    pub magenta: Vec<Color>,
1490}
1491
1492#[derive(Clone, Deserialize, Default)]
1493pub struct Layer {
1494    pub base: StyleSet,
1495    pub variant: StyleSet,
1496    pub on: StyleSet,
1497    pub accent: StyleSet,
1498    pub positive: StyleSet,
1499    pub warning: StyleSet,
1500    pub negative: StyleSet,
1501}
1502
1503#[derive(Clone, Deserialize, Default)]
1504pub struct StyleSet {
1505    pub default: Style,
1506    pub active: Style,
1507    pub disabled: Style,
1508    pub hovered: Style,
1509    pub pressed: Style,
1510    pub inverted: Style,
1511}
1512
1513#[derive(Clone, Deserialize, Default)]
1514pub struct Style {
1515    pub background: Color,
1516    pub border: Color,
1517    pub foreground: Color,
1518}