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