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