theme.rs

  1#![deny(missing_docs)]
  2
  3//! # Theme
  4//!
  5//! This crate provides the theme system for Zed.
  6//!
  7//! ## Overview
  8//!
  9//! A theme is a collection of colors used to build a consistent appearance for UI components across the application.
 10
 11mod default_colors;
 12mod fallback_themes;
 13mod font_family_cache;
 14mod icon_theme;
 15mod icon_theme_schema;
 16mod registry;
 17mod scale;
 18mod schema;
 19mod styles;
 20
 21use std::sync::Arc;
 22
 23use derive_more::{Deref, DerefMut};
 24use gpui::BorrowAppContext;
 25use gpui::Global;
 26use gpui::{
 27    App, AssetSource, Hsla, Pixels, SharedString, WindowAppearance, WindowBackgroundAppearance, px,
 28};
 29use serde::Deserialize;
 30
 31pub use crate::default_colors::*;
 32pub use crate::fallback_themes::{apply_status_color_defaults, apply_theme_color_defaults};
 33pub use crate::font_family_cache::*;
 34pub use crate::icon_theme::*;
 35pub use crate::icon_theme_schema::*;
 36pub use crate::registry::*;
 37pub use crate::scale::*;
 38pub use crate::schema::*;
 39pub use crate::styles::*;
 40
 41/// The name of the default dark theme.
 42pub const DEFAULT_DARK_THEME: &str = "One Dark";
 43
 44/// Defines window border radius for platforms that use client side decorations.
 45pub const CLIENT_SIDE_DECORATION_ROUNDING: Pixels = px(10.0);
 46/// Defines window shadow size for platforms that use client side decorations.
 47pub const CLIENT_SIDE_DECORATION_SHADOW: Pixels = px(10.0);
 48
 49/// The appearance of the theme.
 50#[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
 51pub enum Appearance {
 52    /// A light appearance.
 53    Light,
 54    /// A dark appearance.
 55    Dark,
 56}
 57
 58impl Appearance {
 59    /// Returns whether the appearance is light.
 60    pub fn is_light(&self) -> bool {
 61        match self {
 62            Self::Light => true,
 63            Self::Dark => false,
 64        }
 65    }
 66}
 67
 68impl From<WindowAppearance> for Appearance {
 69    fn from(value: WindowAppearance) -> Self {
 70        match value {
 71            WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
 72            WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
 73        }
 74    }
 75}
 76
 77/// Which themes should be loaded. This is used primarily for testing.
 78pub enum LoadThemes {
 79    /// Only load the base theme.
 80    ///
 81    /// No user themes will be loaded.
 82    JustBase,
 83
 84    /// Load all of the built-in themes.
 85    All(Box<dyn AssetSource>),
 86}
 87
 88/// Initialize the theme system with default themes.
 89///
 90/// This sets up the [`ThemeRegistry`], [`FontFamilyCache`], [`SystemAppearance`],
 91/// and [`GlobalTheme`] with the default dark theme. It does NOT load bundled
 92/// themes from JSON or integrate with settings — use `theme_settings::init` for that.
 93pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
 94    SystemAppearance::init(cx);
 95    let assets = match themes_to_load {
 96        LoadThemes::JustBase => Box::new(()) as Box<dyn AssetSource>,
 97        LoadThemes::All(assets) => assets,
 98    };
 99    ThemeRegistry::set_global(assets, cx);
100    FontFamilyCache::init_global(cx);
101
102    let themes = ThemeRegistry::default_global(cx);
103    let theme = themes.get(DEFAULT_DARK_THEME).unwrap_or_else(|_| {
104        themes
105            .list()
106            .into_iter()
107            .next()
108            .map(|m| themes.get(&m.name).unwrap())
109            .unwrap()
110    });
111    let icon_theme = themes.default_icon_theme().unwrap();
112    cx.set_global(GlobalTheme { theme, icon_theme });
113}
114
115/// Implementing this trait allows accessing the active theme.
116pub trait ActiveTheme {
117    /// Returns the active theme.
118    fn theme(&self) -> &Arc<Theme>;
119}
120
121impl ActiveTheme for App {
122    fn theme(&self) -> &Arc<Theme> {
123        GlobalTheme::theme(self)
124    }
125}
126
127/// The appearance of the system.
128#[derive(Debug, Clone, Copy, Deref)]
129pub struct SystemAppearance(pub Appearance);
130
131impl Default for SystemAppearance {
132    fn default() -> Self {
133        Self(Appearance::Dark)
134    }
135}
136
137#[derive(Deref, DerefMut, Default)]
138struct GlobalSystemAppearance(SystemAppearance);
139
140impl Global for GlobalSystemAppearance {}
141
142impl SystemAppearance {
143    /// Initializes the [`SystemAppearance`] for the application.
144    pub fn init(cx: &mut App) {
145        *cx.default_global::<GlobalSystemAppearance>() =
146            GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
147    }
148
149    /// Returns the global [`SystemAppearance`].
150    pub fn global(cx: &App) -> Self {
151        cx.global::<GlobalSystemAppearance>().0
152    }
153
154    /// Returns a mutable reference to the global [`SystemAppearance`].
155    pub fn global_mut(cx: &mut App) -> &mut Self {
156        cx.global_mut::<GlobalSystemAppearance>()
157    }
158}
159
160/// A theme family is a grouping of themes under a single name.
161///
162/// For example, the "One" theme family contains the "One Light" and "One Dark" themes.
163///
164/// It can also be used to package themes with many variants.
165///
166/// For example, the "Atelier" theme family contains "Cave", "Dune", "Estuary", "Forest", "Heath", etc.
167pub struct ThemeFamily {
168    /// The unique identifier for the theme family.
169    pub id: String,
170    /// The name of the theme family. This will be displayed in the UI, such as when adding or removing a theme family.
171    pub name: SharedString,
172    /// The author of the theme family.
173    pub author: SharedString,
174    /// The [Theme]s in the family.
175    pub themes: Vec<Theme>,
176    /// The color scales used by the themes in the family.
177    /// Note: This will be removed in the future.
178    pub scales: ColorScales,
179}
180
181/// A theme is the primary mechanism for defining the appearance of the UI.
182#[derive(Clone, Debug, PartialEq)]
183pub struct Theme {
184    /// The unique identifier for the theme.
185    pub id: String,
186    /// The name of the theme.
187    pub name: SharedString,
188    /// The appearance of the theme (light or dark).
189    pub appearance: Appearance,
190    /// The colors and other styles for the theme.
191    pub styles: ThemeStyles,
192}
193
194impl Theme {
195    /// Returns the [`SystemColors`] for the theme.
196    #[inline(always)]
197    pub fn system(&self) -> &SystemColors {
198        &self.styles.system
199    }
200
201    /// Returns the [`AccentColors`] for the theme.
202    #[inline(always)]
203    pub fn accents(&self) -> &AccentColors {
204        &self.styles.accents
205    }
206
207    /// Returns the [`PlayerColors`] for the theme.
208    #[inline(always)]
209    pub fn players(&self) -> &PlayerColors {
210        &self.styles.player
211    }
212
213    /// Returns the [`ThemeColors`] for the theme.
214    #[inline(always)]
215    pub fn colors(&self) -> &ThemeColors {
216        &self.styles.colors
217    }
218
219    /// Returns the [`SyntaxTheme`] for the theme.
220    #[inline(always)]
221    pub fn syntax(&self) -> &Arc<SyntaxTheme> {
222        &self.styles.syntax
223    }
224
225    /// Returns the [`StatusColors`] for the theme.
226    #[inline(always)]
227    pub fn status(&self) -> &StatusColors {
228        &self.styles.status
229    }
230
231    /// Returns the [`Appearance`] for the theme.
232    #[inline(always)]
233    pub fn appearance(&self) -> Appearance {
234        self.appearance
235    }
236
237    /// Returns the [`WindowBackgroundAppearance`] for the theme.
238    #[inline(always)]
239    pub fn window_background_appearance(&self) -> WindowBackgroundAppearance {
240        self.styles.window_background_appearance
241    }
242
243    /// Darkens the color by reducing its lightness.
244    /// The resulting lightness is clamped to ensure it doesn't go below 0.0.
245    ///
246    /// The first value darkens light appearance mode, the second darkens appearance dark mode.
247    ///
248    /// Note: This is a tentative solution and may be replaced with a more robust color system.
249    pub fn darken(&self, color: Hsla, light_amount: f32, dark_amount: f32) -> Hsla {
250        let amount = match self.appearance {
251            Appearance::Light => light_amount,
252            Appearance::Dark => dark_amount,
253        };
254        let mut hsla = color;
255        hsla.l = (hsla.l - amount).max(0.0);
256        hsla
257    }
258}
259
260/// Deserializes an icon theme from the given bytes.
261pub fn deserialize_icon_theme(bytes: &[u8]) -> anyhow::Result<IconThemeFamilyContent> {
262    let icon_theme_family: IconThemeFamilyContent = serde_json_lenient::from_slice(bytes)?;
263
264    Ok(icon_theme_family)
265}
266
267/// The active theme.
268pub struct GlobalTheme {
269    theme: Arc<Theme>,
270    icon_theme: Arc<IconTheme>,
271}
272impl Global for GlobalTheme {}
273
274impl GlobalTheme {
275    /// Creates a new [`GlobalTheme`] with the given theme and icon theme.
276    pub fn new(theme: Arc<Theme>, icon_theme: Arc<IconTheme>) -> Self {
277        Self { theme, icon_theme }
278    }
279
280    /// Updates the active theme.
281    pub fn update_theme(cx: &mut App, theme: Arc<Theme>) {
282        cx.update_global::<Self, _>(|this, _| this.theme = theme);
283    }
284
285    /// Updates the active icon theme.
286    pub fn update_icon_theme(cx: &mut App, icon_theme: Arc<IconTheme>) {
287        cx.update_global::<Self, _>(|this, _| this.icon_theme = icon_theme);
288    }
289
290    /// Returns the active theme.
291    pub fn theme(cx: &App) -> &Arc<Theme> {
292        &cx.global::<Self>().theme
293    }
294
295    /// Returns the active icon theme.
296    pub fn icon_theme(cx: &App) -> &Arc<IconTheme> {
297        &cx.global::<Self>().icon_theme
298    }
299}