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