use std::sync::Arc; use anyhow::Result; use gpui::{Hsla, SharedString}; use palette::FromColor as _; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ highlighter::{HighlightTheme, HighlightThemeStyle}, Colorize, Theme, ThemeColor, ThemeMode, }; /// Represents a theme configuration. #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] #[serde(default)] pub struct ThemeSet { /// The name of the theme set. pub name: SharedString, /// The author of the theme. pub author: Option, /// The URL of the theme. pub url: Option, /// The base font size, default is 16. #[serde(rename = "font.size")] pub font_size: Option, #[serde(rename = "themes")] pub themes: Vec, } #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] #[serde(default)] pub struct ThemeConfig { /// The name of the theme. pub name: SharedString, /// The mode of the theme, default is light. pub mode: ThemeMode, /// The colors of the theme. pub colors: ThemeConfigColors, /// The highlight theme, this part is combilbility with `style` section in Zed theme. /// /// https://github.com/zed-industries/zed/blob/f50041779dcfd7a76c8aec293361c60c53f02d51/assets/themes/ayu/ayu.json#L9 pub highlight: Option, } #[derive(Debug, Default, Clone, JsonSchema, Serialize, Deserialize)] pub struct ThemeConfigColors { /// Used for accents such as hover background on MenuItem, ListItem, etc. #[serde(rename = "accent.background")] pub accent: Option, /// Used for accent text color. #[serde(rename = "accent.foreground")] pub accent_foreground: Option, /// Accordion background color. #[serde(rename = "accordion.background")] pub accordion: Option, /// Accordion active background color. #[serde(rename = "accordion.active.background")] pub accordion_active: Option, /// Accordion hover background color. #[serde(rename = "accordion.hover.background")] pub accordion_hover: Option, /// Default background color. #[serde(rename = "background")] pub background: Option, /// Default border color #[serde(rename = "border")] pub border: Option, /// Background color for Card. #[serde(rename = "card.background")] pub card: Option, /// Text color for Card. #[serde(rename = "card.foreground")] pub card_foreground: Option, /// Input caret color (Blinking cursor). #[serde(rename = "caret")] pub caret: Option, /// Chart 1 color. #[serde(rename = "chart.1")] pub chart_1: Option, /// Chart 2 color. #[serde(rename = "chart.2")] pub chart_2: Option, /// Chart 3 color. #[serde(rename = "chart.3")] pub chart_3: Option, /// Chart 4 color. #[serde(rename = "chart.4")] pub chart_4: Option, /// Chart 5 color. #[serde(rename = "chart.5")] pub chart_5: Option, /// Danger background color. #[serde(rename = "danger.background")] pub danger: Option, /// Danger active background color. #[serde(rename = "danger.active.background")] pub danger_active: Option, /// Danger text color. #[serde(rename = "danger.foreground")] pub danger_foreground: Option, /// Danger hover background color. #[serde(rename = "danger.hover.background")] pub danger_hover: Option, /// Description List label background color. #[serde(rename = "description_list.label.background")] pub description_list_label: Option, /// Description List label foreground color. #[serde(rename = "description_list.label.foreground")] pub description_list_label_foreground: Option, /// Drag border color. #[serde(rename = "drag.border")] pub drag_border: Option, /// Drop target background color. #[serde(rename = "drop_target.background")] pub drop_target: Option, /// Default text color. #[serde(rename = "foreground")] pub foreground: Option, /// Info background color. #[serde(rename = "info.background")] pub info: Option, /// Info active background color. #[serde(rename = "info.active.background")] pub info_active: Option, /// Info text color. #[serde(rename = "info.foreground")] pub info_foreground: Option, /// Info hover background color. #[serde(rename = "info.hover.background")] pub info_hover: Option, /// Border color for inputs such as Input, Dropdown, etc. #[serde(rename = "input.border")] pub input: Option, /// Link text color. #[serde(rename = "link")] pub link: Option, /// Active link text color. #[serde(rename = "link.active")] pub link_active: Option, /// Hover link text color. #[serde(rename = "link.hover")] pub link_hover: Option, /// Background color for List and ListItem. #[serde(rename = "list.background")] pub list: Option, /// Background color for active ListItem. #[serde(rename = "list.active.background")] pub list_active: Option, /// Border color for active ListItem. #[serde(rename = "list.active.border")] pub list_active_border: Option, /// Stripe background color for even ListItem. #[serde(rename = "list.even.background")] pub list_even: Option, /// Background color for List header. #[serde(rename = "list.head.background")] pub list_head: Option, /// Hover background color for ListItem. #[serde(rename = "list.hover.background")] pub list_hover: Option, /// Muted backgrounds such as Skeleton and Switch. #[serde(rename = "muted.background")] pub muted: Option, /// Muted text color, as used in disabled text. #[serde(rename = "muted.foreground")] pub muted_foreground: Option, /// Background color for Popover. #[serde(rename = "popover.background")] pub popover: Option, /// Text color for Popover. #[serde(rename = "popover.foreground")] pub popover_foreground: Option, /// Primary background color. #[serde(rename = "primary.background")] pub primary: Option, /// Active primary background color. #[serde(rename = "primary.active.background")] pub primary_active: Option, /// Primary text color. #[serde(rename = "primary.foreground")] pub primary_foreground: Option, /// Hover primary background color. #[serde(rename = "primary.hover.background")] pub primary_hover: Option, /// Progress bar background color. #[serde(rename = "progress.bar.background")] pub progress_bar: Option, /// Used for focus ring. #[serde(rename = "ring")] pub ring: Option, /// Scrollbar background color. #[serde(rename = "scrollbar.background")] pub scrollbar: Option, /// Scrollbar thumb background color. #[serde(rename = "scrollbar.thumb.background")] pub scrollbar_thumb: Option, /// Scrollbar thumb hover background color. #[serde(rename = "scrollbar.thumb.hover.background")] pub scrollbar_thumb_hover: Option, /// Secondary background color. #[serde(rename = "secondary.background")] pub secondary: Option, /// Active secondary background color. #[serde(rename = "secondary.active.background")] pub secondary_active: Option, /// Secondary text color, used for secondary Button text color or secondary text. #[serde(rename = "secondary.foreground")] pub secondary_foreground: Option, /// Hover secondary background color. #[serde(rename = "secondary.hover.background")] pub secondary_hover: Option, /// Input selection background color. #[serde(rename = "selection.background")] pub selection: Option, /// Sidebar background color. #[serde(rename = "sidebar.background")] pub sidebar: Option, /// Sidebar accent background color. #[serde(rename = "sidebar.accent.background")] pub sidebar_accent: Option, /// Sidebar accent text color. #[serde(rename = "sidebar.accent.foreground")] pub sidebar_accent_foreground: Option, /// Sidebar border color. #[serde(rename = "sidebar.border")] pub sidebar_border: Option, /// Sidebar text color. #[serde(rename = "sidebar.foreground")] pub sidebar_foreground: Option, /// Sidebar primary background color. #[serde(rename = "sidebar.primary.background")] pub sidebar_primary: Option, /// Sidebar primary text color. #[serde(rename = "sidebar.primary.foreground")] pub sidebar_primary_foreground: Option, /// Skeleton background color. #[serde(rename = "skeleton.background")] pub skeleton: Option, /// Slider bar background color. #[serde(rename = "slider.background")] pub slider_bar: Option, /// Slider thumb background color. #[serde(rename = "slider.thumb.background")] pub slider_thumb: Option, /// Success background color. #[serde(rename = "success.background")] pub success: Option, /// Success text color. #[serde(rename = "success.foreground")] pub success_foreground: Option, /// Success hover background color. #[serde(rename = "success.hover.background")] pub success_hover: Option, /// Success active background color. #[serde(rename = "success.active.background")] pub success_active: Option, /// Switch background color. #[serde(rename = "switch.background")] pub switch: Option, /// Tab background color. #[serde(rename = "tab.background")] pub tab: Option, /// Tab active background color. #[serde(rename = "tab.active.background")] pub tab_active: Option, /// Tab active text color. #[serde(rename = "tab.active.foreground")] pub tab_active_foreground: Option, /// TabBar background color. #[serde(rename = "tab_bar.background")] pub tab_bar: Option, /// TabBar segmented background color. #[serde(rename = "tab_bar.segmented.background")] pub tab_bar_segmented: Option, /// Tab text color. #[serde(rename = "tab.foreground")] pub tab_foreground: Option, /// Table background color. #[serde(rename = "table.background")] pub table: Option, /// Table active item background color. #[serde(rename = "table.active.background")] pub table_active: Option, /// Table active item border color. #[serde(rename = "table.active.border")] pub table_active_border: Option, /// Stripe background color for even TableRow. #[serde(rename = "table.even.background")] pub table_even: Option, /// Table head background color. #[serde(rename = "table.head.background")] pub table_head: Option, /// Table head text color. #[serde(rename = "table.head.foreground")] pub table_head_foreground: Option, /// Table item hover background color. #[serde(rename = "table.hover.background")] pub table_hover: Option, /// Table row border color. #[serde(rename = "table.row.border")] pub table_row_border: Option, /// TitleBar background color, use for Window title bar. #[serde(rename = "title_bar.background")] pub title_bar: Option, /// TitleBar border color. #[serde(rename = "title_bar.border")] pub title_bar_border: Option, /// Background color for Tiles. #[serde(rename = "tiles.background")] pub tiles: Option, /// Warning background color. #[serde(rename = "warning.background")] pub warning: Option, /// Warning active background color. #[serde(rename = "warning.active.background")] pub warning_active: Option, /// Warning hover background color. #[serde(rename = "warning.hover.background")] pub warning_hover: Option, /// Warning foreground color. #[serde(rename = "warning.foreground")] pub warning_foreground: Option, /// Overlay background color. #[serde(rename = "overlay")] pub overlay: Option, /// Window border color. /// /// # Platform specific: /// /// This is only works on Linux, other platforms we can't change the window border color. #[serde(rename = "window.border")] pub window_border: Option, /// Base blue color. #[serde(rename = "base.blue")] blue: Option, /// Base light blue color. #[serde(rename = "base.blue.light")] blue_light: Option, /// Base cyan color. #[serde(rename = "base.cyan")] cyan: Option, /// Base light cyan color. #[serde(rename = "base.cyan.light")] cyan_light: Option, /// Base green color. #[serde(rename = "base.green")] green: Option, /// Base light green color. #[serde(rename = "base.green.light")] green_light: Option, /// Base magenta color. #[serde(rename = "base.magenta")] magenta: Option, #[serde(rename = "base.magenta.light")] magenta_light: Option, /// Base red color. #[serde(rename = "base.red")] red: Option, /// Base light red color. #[serde(rename = "base.red.light")] red_light: Option, /// Base yellow color. #[serde(rename = "base.yellow")] yellow: Option, /// Base light yellow color. #[serde(rename = "base.yellow.light")] yellow_light: Option, } /// Try to parse HEX color, `#RRGGBB` or `#RRGGBBAA` fn try_parse_color(color: &str) -> Result { let rgba = gpui::Rgba::try_from(color)?; let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a)); let hsla = palette::Hsla::from_color(rgba); let hsla = gpui::hsla( hsla.hue.into_positive_degrees() / 360., hsla.saturation, hsla.lightness, hsla.alpha, ); Ok(hsla) } impl Theme { /// Apply the given theme configuration to the current theme. pub fn apply_config(&mut self, config: &ThemeConfig) { let colors = config.colors.clone(); let default_theme = if config.mode.is_dark() { ThemeColor::dark() } else { ThemeColor::light() }; macro_rules! apply_color { ($config_field:ident) => { if let Some(value) = colors.$config_field { if let Ok(color) = try_parse_color(&value) { self.$config_field = color; } else { self.$config_field = default_theme.$config_field; } } else { self.$config_field = default_theme.$config_field; } }; // With fallback ($config_field:ident, fallback = $fallback:expr) => { if let Some(value) = colors.$config_field { if let Ok(color) = try_parse_color(&value) { self.$config_field = color; } } else { self.$config_field = $fallback; } }; } self.mode = config.mode; // Base colors for fallback apply_color!(red); apply_color!(red_light, fallback = self.red.opacity(0.8)); apply_color!(green); apply_color!(green_light, fallback = self.green.opacity(0.8)); apply_color!(blue); apply_color!(blue_light, fallback = self.blue.opacity(0.8)); apply_color!(magenta); apply_color!(magenta_light, fallback = self.magenta.opacity(0.8)); apply_color!(yellow); apply_color!(yellow_light, fallback = self.yellow.opacity(0.8)); apply_color!(cyan); apply_color!(cyan_light, fallback = self.cyan.opacity(0.8)); apply_color!(background); apply_color!(border); apply_color!(foreground); apply_color!(muted); apply_color!(muted_foreground, fallback = self.foreground.opacity(0.7)); apply_color!(primary); apply_color!(primary_active, fallback = self.primary.darken(0.1)); apply_color!(primary_foreground, fallback = self.foreground); apply_color!(primary_hover, fallback = self.primary.opacity(0.9)); apply_color!(secondary); apply_color!(secondary_active, fallback = self.secondary.darken(0.1)); apply_color!(secondary_foreground, fallback = self.foreground); apply_color!(secondary_hover, fallback = self.secondary.opacity(0.9)); // Other colors apply_color!(accent, fallback = self.secondary); apply_color!(accent_foreground, fallback = self.secondary_foreground); apply_color!(accordion, fallback = self.background); apply_color!(accordion_active, fallback = self.accordion); apply_color!(accordion_hover, fallback = self.accordion); apply_color!(card, fallback = self.background); apply_color!(card_foreground, fallback = self.foreground); apply_color!(caret, fallback = self.primary); apply_color!(chart_1, fallback = self.blue.lighten(0.4)); apply_color!(chart_2, fallback = self.blue.lighten(0.2)); apply_color!(chart_3, fallback = self.blue); apply_color!(chart_4, fallback = self.blue.darken(0.2)); apply_color!(chart_5, fallback = self.blue.darken(0.4)); apply_color!(danger, fallback = self.red); apply_color!(danger_active, fallback = self.danger.darken(0.1)); apply_color!(danger_foreground, fallback = self.primary_foreground); apply_color!(danger_hover, fallback = self.danger.opacity(0.9)); apply_color!(description_list_label, fallback = self.border.opacity(0.2)); apply_color!( description_list_label_foreground, fallback = self.secondary_foreground ); apply_color!(drag_border, fallback = self.primary.opacity(0.65)); apply_color!(drop_target, fallback = self.primary.opacity(0.2)); apply_color!(info, fallback = self.cyan); apply_color!(info_active, fallback = self.info.darken(0.1)); apply_color!(info_foreground, fallback = self.primary_foreground); apply_color!(info_hover, fallback = self.info.opacity(0.9)); apply_color!(input, fallback = self.border); apply_color!(link, fallback = self.primary); apply_color!(link_active, fallback = self.link); apply_color!(link_hover, fallback = self.link); apply_color!(list, fallback = self.background); apply_color!(list_active, fallback = self.primary.opacity(0.1)); apply_color!(list_active_border, fallback = self.primary.opacity(0.6)); apply_color!(list_even, fallback = self.list); apply_color!(list_head, fallback = self.list); apply_color!(list_hover, fallback = self.secondary_hover); apply_color!(popover, fallback = self.background); apply_color!(popover_foreground, fallback = self.foreground); apply_color!(progress_bar, fallback = self.primary); apply_color!(ring, fallback = self.primary); apply_color!(scrollbar, fallback = self.background); apply_color!(scrollbar_thumb, fallback = self.secondary); apply_color!(scrollbar_thumb_hover, fallback = self.secondary_hover); apply_color!(selection, fallback = self.primary.opacity(0.5)); apply_color!(sidebar, fallback = self.background); apply_color!(sidebar_accent, fallback = self.accent); apply_color!(sidebar_accent_foreground, fallback = self.accent_foreground); apply_color!(sidebar_border, fallback = self.border); apply_color!(sidebar_foreground, fallback = self.foreground); apply_color!(sidebar_primary, fallback = self.primary); apply_color!( sidebar_primary_foreground, fallback = self.primary_foreground ); apply_color!(skeleton, fallback = self.secondary); apply_color!(slider_bar, fallback = self.primary); apply_color!(slider_thumb, fallback = self.primary_foreground); apply_color!(success, fallback = self.green); apply_color!(success_foreground, fallback = self.primary_foreground); apply_color!(success_hover, fallback = self.success.opacity(0.9)); apply_color!(success_active, fallback = self.success.darken(0.1)); apply_color!(switch, fallback = self.secondary); apply_color!(tab, fallback = self.background); apply_color!(tab_active, fallback = self.background); apply_color!(tab_active_foreground, fallback = self.foreground); apply_color!(tab_bar, fallback = self.background); apply_color!(tab_bar_segmented, fallback = self.background); apply_color!(tab_foreground, fallback = self.secondary_foreground); apply_color!(table, fallback = self.list); apply_color!(table_active, fallback = self.list_active); apply_color!(table_active_border, fallback = self.list_active_border); apply_color!(table_even, fallback = self.list_even); apply_color!(table_head, fallback = self.list_head); apply_color!(table_head_foreground, fallback = self.muted_foreground); apply_color!(table_hover, fallback = self.list_hover); apply_color!(table_row_border, fallback = self.border); apply_color!(title_bar, fallback = self.background); apply_color!(title_bar_border, fallback = self.border); apply_color!(tiles, fallback = self.background); apply_color!(warning, fallback = self.yellow); apply_color!(warning_active, fallback = self.warning.darken(0.1)); apply_color!(warning_hover, fallback = self.warning.opacity(0.9)); apply_color!(warning_foreground, fallback = self.primary_foreground); apply_color!(overlay); apply_color!(window_border, fallback = self.border); // TODO: Apply default fallback colors to highlight. // Ensure opacity for list_active, table_active self.colors.list_active = self.colors.list_active.alpha(0.2); self.colors.table_active = self.colors.table_active.alpha(0.2); if config.mode.is_dark() { self.dark_theme = self.colors; } else { self.light_theme = self.colors; } if let Some(style) = &config.highlight { let highlight_theme = Arc::new(HighlightTheme { name: config.name.to_string(), appearance: self.mode, style: style.clone(), }); self.highlight_theme = highlight_theme.clone(); if config.mode.is_dark() { self.dark_highlight_theme = highlight_theme; } else { self.light_highlight_theme = highlight_theme; } } } } #[cfg(test)] mod tests { use super::try_parse_color; use gpui::hsla; #[test] fn test_try_parse_color() { assert_eq!( try_parse_color("#F2F200").ok(), Some(hsla(0.16666667, 1., 0.4745098, 1.0)) ); assert_eq!( try_parse_color("#00f21888").ok(), Some(hsla(0.34986225, 1.0, 0.4745098, 0.53333336)) ); } }