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