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