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