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}