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}