1use crate::{Theme, ThemeRegistry};
2use anyhow::Result;
3use gpui::{font_cache::FamilyId, fonts, AppContext};
4use schemars::{
5 gen::SchemaGenerator,
6 schema::{InstanceType, Schema, SchemaObject},
7 JsonSchema,
8};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use settings::SettingsJsonSchemaParams;
12use std::sync::Arc;
13use util::ResultExt as _;
14
15const MIN_FONT_SIZE: f32 = 6.0;
16
17#[derive(Clone)]
18pub struct ThemeSettings {
19 pub buffer_font_family_name: String,
20 pub buffer_font_features: fonts::Features,
21 pub buffer_font_family: FamilyId,
22 pub(crate) buffer_font_size: f32,
23 pub theme: Arc<Theme>,
24}
25
26pub struct AdjustedBufferFontSize(pub f32);
27
28#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
29pub struct ThemeSettingsContent {
30 #[serde(default)]
31 pub buffer_font_family: Option<String>,
32 #[serde(default)]
33 pub buffer_font_size: Option<f32>,
34 #[serde(default)]
35 pub buffer_font_features: Option<fonts::Features>,
36 #[serde(default)]
37 pub theme: Option<String>,
38}
39
40impl ThemeSettings {
41 pub fn buffer_font_size(&self, cx: &AppContext) -> f32 {
42 if cx.has_global::<AdjustedBufferFontSize>() {
43 cx.global::<AdjustedBufferFontSize>().0
44 } else {
45 self.buffer_font_size
46 }
47 .max(MIN_FONT_SIZE)
48 }
49}
50
51pub fn adjusted_font_size(size: f32, cx: &AppContext) -> f32 {
52 if cx.has_global::<AdjustedBufferFontSize>() {
53 let buffer_font_size = settings::get::<ThemeSettings>(cx).buffer_font_size;
54 let delta = cx.global::<AdjustedBufferFontSize>().0 - buffer_font_size;
55 size + delta
56 } else {
57 size
58 }
59 .max(MIN_FONT_SIZE)
60}
61
62pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut f32)) {
63 if !cx.has_global::<AdjustedBufferFontSize>() {
64 let buffer_font_size = settings::get::<ThemeSettings>(cx).buffer_font_size;
65 cx.set_global(AdjustedBufferFontSize(buffer_font_size));
66 }
67
68 cx.update_global::<AdjustedBufferFontSize, _, _>(|delta, cx| {
69 f(&mut delta.0);
70 delta.0 = delta
71 .0
72 .max(MIN_FONT_SIZE - settings::get::<ThemeSettings>(cx).buffer_font_size);
73 });
74 cx.refresh_windows();
75}
76
77pub fn reset_font_size(cx: &mut AppContext) {
78 if cx.has_global::<AdjustedBufferFontSize>() {
79 cx.remove_global::<AdjustedBufferFontSize>();
80 cx.refresh_windows();
81 }
82}
83
84impl settings::Setting for ThemeSettings {
85 const KEY: Option<&'static str> = None;
86
87 type FileContent = ThemeSettingsContent;
88
89 fn load(
90 defaults: &Self::FileContent,
91 user_values: &[&Self::FileContent],
92 cx: &AppContext,
93 ) -> Result<Self> {
94 let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
95 let themes = cx.global::<Arc<ThemeRegistry>>();
96
97 let mut this = Self {
98 buffer_font_family: cx
99 .font_cache()
100 .load_family(
101 &[defaults.buffer_font_family.as_ref().unwrap()],
102 &buffer_font_features,
103 )
104 .unwrap(),
105 buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
106 buffer_font_features,
107 buffer_font_size: defaults.buffer_font_size.unwrap(),
108 theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
109 };
110
111 for value in user_values.into_iter().copied().cloned() {
112 let font_cache = cx.font_cache();
113 let mut family_changed = false;
114 if let Some(value) = value.buffer_font_family {
115 this.buffer_font_family_name = value;
116 family_changed = true;
117 }
118 if let Some(value) = value.buffer_font_features {
119 this.buffer_font_features = value;
120 family_changed = true;
121 }
122 if family_changed {
123 if let Some(id) = font_cache
124 .load_family(&[&this.buffer_font_family_name], &this.buffer_font_features)
125 .log_err()
126 {
127 this.buffer_font_family = id;
128 }
129 }
130
131 if let Some(value) = &value.theme {
132 if let Some(theme) = themes.get(value).log_err() {
133 this.theme = theme;
134 }
135 }
136
137 merge(&mut this.buffer_font_size, value.buffer_font_size);
138 }
139
140 Ok(this)
141 }
142
143 fn json_schema(
144 generator: &mut SchemaGenerator,
145 params: &SettingsJsonSchemaParams,
146 cx: &AppContext,
147 ) -> schemars::schema::RootSchema {
148 let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
149 let theme_names = cx
150 .global::<Arc<ThemeRegistry>>()
151 .list(params.staff_mode)
152 .map(|theme| Value::String(theme.name.clone()))
153 .collect();
154
155 let theme_name_schema = SchemaObject {
156 instance_type: Some(InstanceType::String.into()),
157 enum_values: Some(theme_names),
158 ..Default::default()
159 };
160
161 root_schema
162 .definitions
163 .extend([("ThemeName".into(), theme_name_schema.into())]);
164
165 root_schema
166 .schema
167 .object
168 .as_mut()
169 .unwrap()
170 .properties
171 .extend([(
172 "theme".to_owned(),
173 Schema::new_ref("#/definitions/ThemeName".into()),
174 )]);
175
176 root_schema
177 }
178}
179
180fn merge<T: Copy>(target: &mut T, value: Option<T>) {
181 if let Some(value) = value {
182 *target = value;
183 }
184}