1use crate::fallback_themes::zed_default_dark;
2use crate::{
3 Appearance, IconTheme, IconThemeNotFoundError, SyntaxTheme, Theme, ThemeNotFoundError,
4 ThemeRegistry, ThemeStyleContent, DEFAULT_ICON_THEME_NAME,
5};
6use anyhow::Result;
7use derive_more::{Deref, DerefMut};
8use gpui::{
9 px, App, Context, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels,
10 Subscription, Window,
11};
12use refineable::Refineable;
13use schemars::{
14 gen::SchemaGenerator,
15 schema::{InstanceType, Schema, SchemaObject},
16 JsonSchema,
17};
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20use settings::{add_references_to_properties, Settings, SettingsJsonSchemaParams, SettingsSources};
21use std::sync::Arc;
22use util::ResultExt as _;
23
24const MIN_FONT_SIZE: Pixels = px(6.0);
25const MIN_LINE_HEIGHT: f32 = 1.0;
26
27#[derive(
28 Debug,
29 Default,
30 PartialEq,
31 Eq,
32 PartialOrd,
33 Ord,
34 Hash,
35 Clone,
36 Copy,
37 Serialize,
38 Deserialize,
39 JsonSchema,
40)]
41
42/// Specifies the density of the UI.
43/// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
44#[serde(rename_all = "snake_case")]
45pub enum UiDensity {
46 /// A denser UI with tighter spacing and smaller elements.
47 #[serde(alias = "compact")]
48 Compact,
49 #[default]
50 #[serde(alias = "default")]
51 /// The default UI density.
52 Default,
53 #[serde(alias = "comfortable")]
54 /// A looser UI with more spacing and larger elements.
55 Comfortable,
56}
57
58impl UiDensity {
59 /// The spacing ratio of a given density.
60 /// TODO: Standardize usage throughout the app or remove
61 pub fn spacing_ratio(self) -> f32 {
62 match self {
63 UiDensity::Compact => 0.75,
64 UiDensity::Default => 1.0,
65 UiDensity::Comfortable => 1.25,
66 }
67 }
68}
69
70impl From<String> for UiDensity {
71 fn from(s: String) -> Self {
72 match s.as_str() {
73 "compact" => Self::Compact,
74 "default" => Self::Default,
75 "comfortable" => Self::Comfortable,
76 _ => Self::default(),
77 }
78 }
79}
80
81impl From<UiDensity> for String {
82 fn from(val: UiDensity) -> Self {
83 match val {
84 UiDensity::Compact => "compact".to_string(),
85 UiDensity::Default => "default".to_string(),
86 UiDensity::Comfortable => "comfortable".to_string(),
87 }
88 }
89}
90
91/// Customizable settings for the UI and theme system.
92#[derive(Clone, PartialEq)]
93pub struct ThemeSettings {
94 /// The UI font size. Determines the size of text in the UI,
95 /// as well as the size of a [gpui::Rems] unit.
96 ///
97 /// Changing this will impact the size of all UI elements.
98 pub ui_font_size: Pixels,
99 /// The font used for UI elements.
100 pub ui_font: Font,
101 /// The font size used for buffers, and the terminal.
102 ///
103 /// The terminal font size can be overridden using it's own setting.
104 pub buffer_font_size: Pixels,
105 /// The font used for buffers, and the terminal.
106 ///
107 /// The terminal font family can be overridden using it's own setting.
108 pub buffer_font: Font,
109 /// The line height for buffers, and the terminal.
110 ///
111 /// Changing this may affect the spacing of some UI elements.
112 ///
113 /// The terminal font family can be overridden using it's own setting.
114 pub buffer_line_height: BufferLineHeight,
115 /// The current theme selection.
116 pub theme_selection: Option<ThemeSelection>,
117 /// The active theme.
118 pub active_theme: Arc<Theme>,
119 /// Manual overrides for the active theme.
120 ///
121 /// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
122 pub theme_overrides: Option<ThemeStyleContent>,
123 /// The current icon theme selection.
124 pub icon_theme_selection: Option<IconThemeSelection>,
125 /// The active icon theme.
126 pub active_icon_theme: Arc<IconTheme>,
127 /// The density of the UI.
128 /// Note: This setting is still experimental. See [this tracking issue](
129 pub ui_density: UiDensity,
130 /// The amount of fading applied to unnecessary code.
131 pub unnecessary_code_fade: f32,
132}
133
134impl ThemeSettings {
135 const DEFAULT_LIGHT_THEME: &'static str = "One Light";
136 const DEFAULT_DARK_THEME: &'static str = "One Dark";
137
138 /// Returns the name of the default theme for the given [`Appearance`].
139 pub fn default_theme(appearance: Appearance) -> &'static str {
140 match appearance {
141 Appearance::Light => Self::DEFAULT_LIGHT_THEME,
142 Appearance::Dark => Self::DEFAULT_DARK_THEME,
143 }
144 }
145
146 /// Reloads the current theme.
147 ///
148 /// Reads the [`ThemeSettings`] to know which theme should be loaded,
149 /// taking into account the current [`SystemAppearance`].
150 pub fn reload_current_theme(cx: &mut App) {
151 let mut theme_settings = ThemeSettings::get_global(cx).clone();
152 let system_appearance = SystemAppearance::global(cx);
153
154 if let Some(theme_selection) = theme_settings.theme_selection.clone() {
155 let mut theme_name = theme_selection.theme(*system_appearance);
156
157 // If the selected theme doesn't exist, fall back to a default theme
158 // based on the system appearance.
159 let theme_registry = ThemeRegistry::global(cx);
160 if theme_registry.get(theme_name).ok().is_none() {
161 theme_name = Self::default_theme(*system_appearance);
162 };
163
164 if let Some(_theme) = theme_settings.switch_theme(theme_name, cx) {
165 ThemeSettings::override_global(theme_settings, cx);
166 }
167 }
168 }
169
170 /// Reloads the current icon theme.
171 ///
172 /// Reads the [`ThemeSettings`] to know which icon theme should be loaded,
173 /// taking into account the current [`SystemAppearance`].
174 pub fn reload_current_icon_theme(cx: &mut App) {
175 let mut theme_settings = ThemeSettings::get_global(cx).clone();
176 let system_appearance = SystemAppearance::global(cx);
177
178 if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.clone() {
179 let mut icon_theme_name = icon_theme_selection.icon_theme(*system_appearance);
180
181 // If the selected icon theme doesn't exist, fall back to the default theme.
182 let theme_registry = ThemeRegistry::global(cx);
183 if theme_registry
184 .get_icon_theme(icon_theme_name)
185 .ok()
186 .is_none()
187 {
188 icon_theme_name = DEFAULT_ICON_THEME_NAME;
189 };
190
191 if let Some(_theme) = theme_settings.switch_icon_theme(icon_theme_name, cx) {
192 ThemeSettings::override_global(theme_settings, cx);
193 }
194 }
195 }
196}
197
198/// The appearance of the system.
199#[derive(Debug, Clone, Copy, Deref)]
200pub struct SystemAppearance(pub Appearance);
201
202impl Default for SystemAppearance {
203 fn default() -> Self {
204 Self(Appearance::Dark)
205 }
206}
207
208#[derive(Deref, DerefMut, Default)]
209struct GlobalSystemAppearance(SystemAppearance);
210
211impl Global for GlobalSystemAppearance {}
212
213impl SystemAppearance {
214 /// Initializes the [`SystemAppearance`] for the application.
215 pub fn init(cx: &mut App) {
216 *cx.default_global::<GlobalSystemAppearance>() =
217 GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
218 }
219
220 /// Returns the global [`SystemAppearance`].
221 ///
222 /// Inserts a default [`SystemAppearance`] if one does not yet exist.
223 pub(crate) fn default_global(cx: &mut App) -> Self {
224 cx.default_global::<GlobalSystemAppearance>().0
225 }
226
227 /// Returns the global [`SystemAppearance`].
228 pub fn global(cx: &App) -> Self {
229 cx.global::<GlobalSystemAppearance>().0
230 }
231
232 /// Returns a mutable reference to the global [`SystemAppearance`].
233 pub fn global_mut(cx: &mut App) -> &mut Self {
234 cx.global_mut::<GlobalSystemAppearance>()
235 }
236}
237
238#[derive(Default)]
239pub(crate) struct AdjustedBufferFontSize(Pixels);
240
241impl Global for AdjustedBufferFontSize {}
242
243#[derive(Default)]
244pub(crate) struct AdjustedUiFontSize(Pixels);
245
246impl Global for AdjustedUiFontSize {}
247
248/// Represents the selection of a theme, which can be either static or dynamic.
249#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
250#[serde(untagged)]
251pub enum ThemeSelection {
252 /// A static theme selection, represented by a single theme name.
253 Static(#[schemars(schema_with = "theme_name_ref")] String),
254 /// A dynamic theme selection, which can change based the [ThemeMode].
255 Dynamic {
256 /// The mode used to determine which theme to use.
257 #[serde(default)]
258 mode: ThemeMode,
259 /// The theme to use for light mode.
260 #[schemars(schema_with = "theme_name_ref")]
261 light: String,
262 /// The theme to use for dark mode.
263 #[schemars(schema_with = "theme_name_ref")]
264 dark: String,
265 },
266}
267
268fn theme_name_ref(_: &mut SchemaGenerator) -> Schema {
269 Schema::new_ref("#/definitions/ThemeName".into())
270}
271
272// TODO: Rename ThemeMode -> ThemeAppearanceMode
273/// The mode use to select a theme.
274///
275/// `Light` and `Dark` will select their respective themes.
276///
277/// `System` will select the theme based on the system's appearance.
278#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
279#[serde(rename_all = "snake_case")]
280pub enum ThemeMode {
281 /// Use the specified `light` theme.
282 Light,
283
284 /// Use the specified `dark` theme.
285 Dark,
286
287 /// Use the theme based on the system's appearance.
288 #[default]
289 System,
290}
291
292impl ThemeSelection {
293 /// Returns the theme name for the selected [ThemeMode].
294 pub fn theme(&self, system_appearance: Appearance) -> &str {
295 match self {
296 Self::Static(theme) => theme,
297 Self::Dynamic { mode, light, dark } => match mode {
298 ThemeMode::Light => light,
299 ThemeMode::Dark => dark,
300 ThemeMode::System => match system_appearance {
301 Appearance::Light => light,
302 Appearance::Dark => dark,
303 },
304 },
305 }
306 }
307
308 /// Returns the [ThemeMode] for the [ThemeSelection].
309 pub fn mode(&self) -> Option<ThemeMode> {
310 match self {
311 ThemeSelection::Static(_) => None,
312 ThemeSelection::Dynamic { mode, .. } => Some(*mode),
313 }
314 }
315}
316
317fn icon_theme_name_ref(_: &mut SchemaGenerator) -> Schema {
318 Schema::new_ref("#/definitions/IconThemeName".into())
319}
320
321/// Represents the selection of an icon theme, which can be either static or dynamic.
322#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
323#[serde(untagged)]
324pub enum IconThemeSelection {
325 /// A static icon theme selection, represented by a single icon theme name.
326 Static(#[schemars(schema_with = "icon_theme_name_ref")] String),
327 /// A dynamic icon theme selection, which can change based on the [`ThemeMode`].
328 Dynamic {
329 /// The mode used to determine which theme to use.
330 #[serde(default)]
331 mode: ThemeMode,
332 /// The icon theme to use for light mode.
333 #[schemars(schema_with = "icon_theme_name_ref")]
334 light: String,
335 /// The icon theme to use for dark mode.
336 #[schemars(schema_with = "icon_theme_name_ref")]
337 dark: String,
338 },
339}
340
341impl IconThemeSelection {
342 /// Returns the icon theme name based on the given [`Appearance`].
343 pub fn icon_theme(&self, system_appearance: Appearance) -> &str {
344 match self {
345 Self::Static(theme) => theme,
346 Self::Dynamic { mode, light, dark } => match mode {
347 ThemeMode::Light => light,
348 ThemeMode::Dark => dark,
349 ThemeMode::System => match system_appearance {
350 Appearance::Light => light,
351 Appearance::Dark => dark,
352 },
353 },
354 }
355 }
356
357 /// Returns the [`ThemeMode`] for the [`IconThemeSelection`].
358 pub fn mode(&self) -> Option<ThemeMode> {
359 match self {
360 IconThemeSelection::Static(_) => None,
361 IconThemeSelection::Dynamic { mode, .. } => Some(*mode),
362 }
363 }
364}
365
366/// Settings for rendering text in UI and text buffers.
367#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
368pub struct ThemeSettingsContent {
369 /// The default font size for text in the UI.
370 #[serde(default)]
371 pub ui_font_size: Option<f32>,
372 /// The name of a font to use for rendering in the UI.
373 #[serde(default)]
374 pub ui_font_family: Option<String>,
375 /// The font fallbacks to use for rendering in the UI.
376 #[serde(default)]
377 #[schemars(default = "default_font_fallbacks")]
378 pub ui_font_fallbacks: Option<Vec<String>>,
379 /// The OpenType features to enable for text in the UI.
380 #[serde(default)]
381 #[schemars(default = "default_font_features")]
382 pub ui_font_features: Option<FontFeatures>,
383 /// The weight of the UI font in CSS units from 100 to 900.
384 #[serde(default)]
385 pub ui_font_weight: Option<f32>,
386 /// The name of a font to use for rendering in text buffers.
387 #[serde(default)]
388 pub buffer_font_family: Option<String>,
389 /// The font fallbacks to use for rendering in text buffers.
390 #[serde(default)]
391 #[schemars(default = "default_font_fallbacks")]
392 pub buffer_font_fallbacks: Option<Vec<String>>,
393 /// The default font size for rendering in text buffers.
394 #[serde(default)]
395 pub buffer_font_size: Option<f32>,
396 /// The weight of the editor font in CSS units from 100 to 900.
397 #[serde(default)]
398 pub buffer_font_weight: Option<f32>,
399 /// The buffer's line height.
400 #[serde(default)]
401 pub buffer_line_height: Option<BufferLineHeight>,
402 /// The OpenType features to enable for rendering in text buffers.
403 #[serde(default)]
404 #[schemars(default = "default_font_features")]
405 pub buffer_font_features: Option<FontFeatures>,
406 /// The name of the Zed theme to use.
407 #[serde(default)]
408 pub theme: Option<ThemeSelection>,
409 /// The name of the icon theme to use.
410 #[serde(default)]
411 pub icon_theme: Option<IconThemeSelection>,
412
413 /// UNSTABLE: Expect many elements to be broken.
414 ///
415 // Controls the density of the UI.
416 #[serde(rename = "unstable.ui_density", default)]
417 pub ui_density: Option<UiDensity>,
418
419 /// How much to fade out unused code.
420 #[serde(default)]
421 pub unnecessary_code_fade: Option<f32>,
422
423 /// EXPERIMENTAL: Overrides for the current theme.
424 ///
425 /// These values will override the ones on the current theme specified in `theme`.
426 #[serde(rename = "experimental.theme_overrides", default)]
427 pub theme_overrides: Option<ThemeStyleContent>,
428}
429
430fn default_font_features() -> Option<FontFeatures> {
431 Some(FontFeatures::default())
432}
433
434fn default_font_fallbacks() -> Option<FontFallbacks> {
435 Some(FontFallbacks::default())
436}
437
438impl ThemeSettingsContent {
439 /// Sets the theme for the given appearance to the theme with the specified name.
440 pub fn set_theme(&mut self, theme_name: String, appearance: Appearance) {
441 if let Some(selection) = self.theme.as_mut() {
442 let theme_to_update = match selection {
443 ThemeSelection::Static(theme) => theme,
444 ThemeSelection::Dynamic { mode, light, dark } => match mode {
445 ThemeMode::Light => light,
446 ThemeMode::Dark => dark,
447 ThemeMode::System => match appearance {
448 Appearance::Light => light,
449 Appearance::Dark => dark,
450 },
451 },
452 };
453
454 *theme_to_update = theme_name.to_string();
455 } else {
456 self.theme = Some(ThemeSelection::Static(theme_name.to_string()));
457 }
458 }
459
460 /// Sets the icon theme for the given appearance to the icon theme with the specified name.
461 pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
462 if let Some(selection) = self.icon_theme.as_mut() {
463 let icon_theme_to_update = match selection {
464 IconThemeSelection::Static(theme) => theme,
465 IconThemeSelection::Dynamic { mode, light, dark } => match mode {
466 ThemeMode::Light => light,
467 ThemeMode::Dark => dark,
468 ThemeMode::System => match appearance {
469 Appearance::Light => light,
470 Appearance::Dark => dark,
471 },
472 },
473 };
474
475 *icon_theme_to_update = icon_theme_name.to_string();
476 } else {
477 self.icon_theme = Some(IconThemeSelection::Static(icon_theme_name.to_string()));
478 }
479 }
480
481 /// Sets the mode for the theme.
482 pub fn set_mode(&mut self, mode: ThemeMode) {
483 if let Some(selection) = self.theme.as_mut() {
484 match selection {
485 ThemeSelection::Static(theme) => {
486 // If the theme was previously set to a single static theme,
487 // we don't know whether it was a light or dark theme, so we
488 // just use it for both.
489 self.theme = Some(ThemeSelection::Dynamic {
490 mode,
491 light: theme.clone(),
492 dark: theme.clone(),
493 });
494 }
495 ThemeSelection::Dynamic {
496 mode: mode_to_update,
497 ..
498 } => *mode_to_update = mode,
499 }
500 } else {
501 self.theme = Some(ThemeSelection::Dynamic {
502 mode,
503 light: ThemeSettings::DEFAULT_LIGHT_THEME.into(),
504 dark: ThemeSettings::DEFAULT_DARK_THEME.into(),
505 });
506 }
507
508 if let Some(selection) = self.icon_theme.as_mut() {
509 match selection {
510 IconThemeSelection::Static(icon_theme) => {
511 // If the icon theme was previously set to a single static
512 // theme, we don't know whether it was a light or dark
513 // theme, so we just use it for both.
514 self.icon_theme = Some(IconThemeSelection::Dynamic {
515 mode,
516 light: icon_theme.clone(),
517 dark: icon_theme.clone(),
518 });
519 }
520 IconThemeSelection::Dynamic {
521 mode: mode_to_update,
522 ..
523 } => *mode_to_update = mode,
524 }
525 } else {
526 self.icon_theme = Some(IconThemeSelection::Static(DEFAULT_ICON_THEME_NAME.into()));
527 }
528 }
529}
530
531/// The buffer's line height.
532#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
533#[serde(rename_all = "snake_case")]
534pub enum BufferLineHeight {
535 /// A less dense line height.
536 #[default]
537 Comfortable,
538 /// The default line height.
539 Standard,
540 /// A custom line height.
541 ///
542 /// A line height of 1.0 is the height of the buffer's font size.
543 Custom(f32),
544}
545
546impl BufferLineHeight {
547 /// Returns the value of the line height.
548 pub fn value(&self) -> f32 {
549 match self {
550 BufferLineHeight::Comfortable => 1.618,
551 BufferLineHeight::Standard => 1.3,
552 BufferLineHeight::Custom(line_height) => *line_height,
553 }
554 }
555}
556
557impl ThemeSettings {
558 /// Returns the buffer font size.
559 pub fn buffer_font_size(&self, cx: &App) -> Pixels {
560 let font_size = cx
561 .try_global::<AdjustedBufferFontSize>()
562 .map_or(self.buffer_font_size, |size| size.0);
563 clamp_font_size(font_size)
564 }
565
566 // TODO: Rename: `line_height` -> `buffer_line_height`
567 /// Returns the buffer's line height.
568 pub fn line_height(&self) -> f32 {
569 f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
570 }
571
572 /// Switches to the theme with the given name, if it exists.
573 ///
574 /// Returns a `Some` containing the new theme if it was successful.
575 /// Returns `None` otherwise.
576 pub fn switch_theme(&mut self, theme: &str, cx: &mut App) -> Option<Arc<Theme>> {
577 let themes = ThemeRegistry::default_global(cx);
578
579 let mut new_theme = None;
580
581 match themes.get(theme) {
582 Ok(theme) => {
583 self.active_theme = theme.clone();
584 new_theme = Some(theme);
585 }
586 Err(err @ ThemeNotFoundError(_)) => {
587 log::error!("{err}");
588 }
589 }
590
591 self.apply_theme_overrides();
592
593 new_theme
594 }
595
596 /// Applies the theme overrides, if there are any, to the current theme.
597 pub fn apply_theme_overrides(&mut self) {
598 if let Some(theme_overrides) = &self.theme_overrides {
599 let mut base_theme = (*self.active_theme).clone();
600
601 if let Some(window_background_appearance) = theme_overrides.window_background_appearance
602 {
603 base_theme.styles.window_background_appearance =
604 window_background_appearance.into();
605 }
606
607 base_theme
608 .styles
609 .colors
610 .refine(&theme_overrides.theme_colors_refinement());
611 base_theme
612 .styles
613 .status
614 .refine(&theme_overrides.status_colors_refinement());
615 base_theme.styles.player.merge(&theme_overrides.players);
616 base_theme.styles.accents.merge(&theme_overrides.accents);
617 base_theme.styles.syntax =
618 SyntaxTheme::merge(base_theme.styles.syntax, theme_overrides.syntax_overrides());
619
620 self.active_theme = Arc::new(base_theme);
621 }
622 }
623
624 /// Switches to the icon theme with the given name, if it exists.
625 ///
626 /// Returns a `Some` containing the new icon theme if it was successful.
627 /// Returns `None` otherwise.
628 pub fn switch_icon_theme(&mut self, icon_theme: &str, cx: &mut App) -> Option<Arc<IconTheme>> {
629 let themes = ThemeRegistry::default_global(cx);
630
631 let mut new_icon_theme = None;
632
633 if let Some(icon_theme) = themes.get_icon_theme(icon_theme).log_err() {
634 self.active_icon_theme = icon_theme.clone();
635 new_icon_theme = Some(icon_theme);
636 cx.refresh_windows();
637 }
638
639 new_icon_theme
640 }
641}
642
643/// Observe changes to the adjusted buffer font size.
644pub fn observe_buffer_font_size_adjustment<V: 'static>(
645 cx: &mut Context<V>,
646 f: impl 'static + Fn(&mut V, &mut Context<V>),
647) -> Subscription {
648 cx.observe_global::<AdjustedBufferFontSize>(f)
649}
650
651/// Sets the adjusted buffer font size.
652pub fn adjusted_font_size(size: Pixels, cx: &App) -> Pixels {
653 let adjusted_font_size = if let Some(AdjustedBufferFontSize(adjusted_size)) =
654 cx.try_global::<AdjustedBufferFontSize>()
655 {
656 let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
657 let delta = *adjusted_size - buffer_font_size;
658 size + delta
659 } else {
660 size
661 };
662 clamp_font_size(adjusted_font_size)
663}
664
665/// Returns the adjusted buffer font size.
666pub fn get_buffer_font_size(cx: &App) -> Pixels {
667 let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
668 cx.try_global::<AdjustedBufferFontSize>()
669 .map_or(buffer_font_size, |adjusted_size| adjusted_size.0)
670}
671
672/// Adjusts the buffer font size.
673pub fn adjust_buffer_font_size(cx: &mut App, mut f: impl FnMut(&mut Pixels)) {
674 let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
675 let mut adjusted_size = cx
676 .try_global::<AdjustedBufferFontSize>()
677 .map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
678
679 f(&mut adjusted_size);
680 cx.set_global(AdjustedBufferFontSize(clamp_font_size(adjusted_size)));
681 cx.refresh_windows();
682}
683
684/// Returns whether the buffer font size has been adjusted.
685pub fn has_adjusted_buffer_font_size(cx: &App) -> bool {
686 cx.has_global::<AdjustedBufferFontSize>()
687}
688
689/// Resets the buffer font size to the default value.
690pub fn reset_buffer_font_size(cx: &mut App) {
691 if cx.has_global::<AdjustedBufferFontSize>() {
692 cx.remove_global::<AdjustedBufferFontSize>();
693 cx.refresh_windows();
694 }
695}
696
697// TODO: Make private, change usages to use `get_ui_font_size` instead.
698#[allow(missing_docs)]
699pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
700 let (ui_font, ui_font_size) = {
701 let theme_settings = ThemeSettings::get_global(cx);
702 let font = theme_settings.ui_font.clone();
703 (font, get_ui_font_size(cx))
704 };
705
706 window.set_rem_size(ui_font_size);
707 ui_font
708}
709
710/// Gets the adjusted UI font size.
711pub fn get_ui_font_size(cx: &App) -> Pixels {
712 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
713 cx.try_global::<AdjustedUiFontSize>()
714 .map_or(ui_font_size, |adjusted_size| adjusted_size.0)
715}
716
717/// Sets the adjusted UI font size.
718pub fn adjust_ui_font_size(cx: &mut App, mut f: impl FnMut(&mut Pixels)) {
719 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
720 let mut adjusted_size = cx
721 .try_global::<AdjustedUiFontSize>()
722 .map_or(ui_font_size, |adjusted_size| adjusted_size.0);
723
724 f(&mut adjusted_size);
725 cx.set_global(AdjustedUiFontSize(clamp_font_size(adjusted_size)));
726 cx.refresh_windows();
727}
728
729/// Returns whether the UI font size has been adjusted.
730pub fn has_adjusted_ui_font_size(cx: &App) -> bool {
731 cx.has_global::<AdjustedUiFontSize>()
732}
733
734/// Resets the UI font size to the default value.
735pub fn reset_ui_font_size(cx: &mut App) {
736 if cx.has_global::<AdjustedUiFontSize>() {
737 cx.remove_global::<AdjustedUiFontSize>();
738 cx.refresh_windows();
739 }
740}
741
742/// Ensures font size is within the valid range.
743pub fn clamp_font_size(size: Pixels) -> Pixels {
744 size.max(MIN_FONT_SIZE)
745}
746
747fn clamp_font_weight(weight: f32) -> FontWeight {
748 FontWeight(weight.clamp(100., 950.))
749}
750
751impl settings::Settings for ThemeSettings {
752 const KEY: Option<&'static str> = None;
753
754 type FileContent = ThemeSettingsContent;
755
756 fn load(sources: SettingsSources<Self::FileContent>, cx: &mut App) -> Result<Self> {
757 let themes = ThemeRegistry::default_global(cx);
758 let system_appearance = SystemAppearance::default_global(cx);
759
760 let defaults = sources.default;
761 let mut this = Self {
762 ui_font_size: defaults.ui_font_size.unwrap().into(),
763 ui_font: Font {
764 family: defaults.ui_font_family.as_ref().unwrap().clone().into(),
765 features: defaults.ui_font_features.clone().unwrap(),
766 fallbacks: defaults
767 .ui_font_fallbacks
768 .as_ref()
769 .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
770 weight: defaults.ui_font_weight.map(FontWeight).unwrap(),
771 style: Default::default(),
772 },
773 buffer_font: Font {
774 family: defaults.buffer_font_family.as_ref().unwrap().clone().into(),
775 features: defaults.buffer_font_features.clone().unwrap(),
776 fallbacks: defaults
777 .buffer_font_fallbacks
778 .as_ref()
779 .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
780 weight: defaults.buffer_font_weight.map(FontWeight).unwrap(),
781 style: FontStyle::default(),
782 },
783 buffer_font_size: defaults.buffer_font_size.unwrap().into(),
784 buffer_line_height: defaults.buffer_line_height.unwrap(),
785 theme_selection: defaults.theme.clone(),
786 active_theme: themes
787 .get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
788 .or(themes.get(&zed_default_dark().name))
789 .unwrap(),
790 theme_overrides: None,
791 icon_theme_selection: defaults.icon_theme.clone(),
792 active_icon_theme: defaults
793 .icon_theme
794 .as_ref()
795 .and_then(|selection| {
796 themes
797 .get_icon_theme(selection.icon_theme(*system_appearance))
798 .ok()
799 })
800 .unwrap_or_else(|| themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()),
801 ui_density: defaults.ui_density.unwrap_or(UiDensity::Default),
802 unnecessary_code_fade: defaults.unnecessary_code_fade.unwrap_or(0.0),
803 };
804
805 for value in sources
806 .user
807 .into_iter()
808 .chain(sources.release_channel)
809 .chain(sources.server)
810 {
811 if let Some(value) = value.ui_density {
812 this.ui_density = value;
813 }
814
815 if let Some(value) = value.buffer_font_family.clone() {
816 this.buffer_font.family = value.into();
817 }
818 if let Some(value) = value.buffer_font_features.clone() {
819 this.buffer_font.features = value;
820 }
821 if let Some(value) = value.buffer_font_fallbacks.clone() {
822 this.buffer_font.fallbacks = Some(FontFallbacks::from_fonts(value));
823 }
824 if let Some(value) = value.buffer_font_weight {
825 this.buffer_font.weight = clamp_font_weight(value);
826 }
827
828 if let Some(value) = value.ui_font_family.clone() {
829 this.ui_font.family = value.into();
830 }
831 if let Some(value) = value.ui_font_features.clone() {
832 this.ui_font.features = value;
833 }
834 if let Some(value) = value.ui_font_fallbacks.clone() {
835 this.ui_font.fallbacks = Some(FontFallbacks::from_fonts(value));
836 }
837 if let Some(value) = value.ui_font_weight {
838 this.ui_font.weight = clamp_font_weight(value);
839 }
840
841 if let Some(value) = &value.theme {
842 this.theme_selection = Some(value.clone());
843
844 let theme_name = value.theme(*system_appearance);
845
846 match themes.get(theme_name) {
847 Ok(theme) => {
848 this.active_theme = theme;
849 }
850 Err(err @ ThemeNotFoundError(_)) => {
851 log::error!("{err}");
852 }
853 }
854 }
855
856 this.theme_overrides.clone_from(&value.theme_overrides);
857 this.apply_theme_overrides();
858
859 if let Some(value) = &value.icon_theme {
860 this.icon_theme_selection = Some(value.clone());
861
862 let icon_theme_name = value.icon_theme(*system_appearance);
863
864 match themes.get_icon_theme(icon_theme_name) {
865 Ok(icon_theme) => {
866 this.active_icon_theme = icon_theme;
867 }
868 Err(err @ IconThemeNotFoundError(_)) => {
869 log::error!("{err}");
870 }
871 }
872 }
873
874 merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
875 this.ui_font_size = this.ui_font_size.clamp(px(6.), px(100.));
876
877 merge(
878 &mut this.buffer_font_size,
879 value.buffer_font_size.map(Into::into),
880 );
881 this.buffer_font_size = this.buffer_font_size.clamp(px(6.), px(100.));
882
883 merge(&mut this.buffer_line_height, value.buffer_line_height);
884
885 // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
886 merge(&mut this.unnecessary_code_fade, value.unnecessary_code_fade);
887 this.unnecessary_code_fade = this.unnecessary_code_fade.clamp(0.0, 0.9);
888 }
889
890 Ok(this)
891 }
892
893 fn json_schema(
894 generator: &mut SchemaGenerator,
895 params: &SettingsJsonSchemaParams,
896 cx: &App,
897 ) -> schemars::schema::RootSchema {
898 let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
899 let theme_names = ThemeRegistry::global(cx)
900 .list_names()
901 .into_iter()
902 .map(|theme_name| Value::String(theme_name.to_string()))
903 .collect();
904
905 let theme_name_schema = SchemaObject {
906 instance_type: Some(InstanceType::String.into()),
907 enum_values: Some(theme_names),
908 ..Default::default()
909 };
910
911 let icon_theme_names = ThemeRegistry::global(cx)
912 .list_icon_themes()
913 .into_iter()
914 .map(|icon_theme| Value::String(icon_theme.name.to_string()))
915 .collect();
916
917 let icon_theme_name_schema = SchemaObject {
918 instance_type: Some(InstanceType::String.into()),
919 enum_values: Some(icon_theme_names),
920 ..Default::default()
921 };
922
923 root_schema.definitions.extend([
924 ("ThemeName".into(), theme_name_schema.into()),
925 ("IconThemeName".into(), icon_theme_name_schema.into()),
926 ("FontFamilies".into(), params.font_family_schema()),
927 ("FontFallbacks".into(), params.font_fallback_schema()),
928 ]);
929
930 add_references_to_properties(
931 &mut root_schema,
932 &[
933 ("buffer_font_family", "#/definitions/FontFamilies"),
934 ("buffer_font_fallbacks", "#/definitions/FontFallbacks"),
935 ("ui_font_family", "#/definitions/FontFamilies"),
936 ("ui_font_fallbacks", "#/definitions/FontFallbacks"),
937 ],
938 );
939
940 root_schema
941 }
942}
943
944fn merge<T: Copy>(target: &mut T, value: Option<T>) {
945 if let Some(value) = value {
946 *target = value;
947 }
948}