1use crate::one_themes::one_dark;
2use crate::{Theme, ThemeRegistry};
3use anyhow::Result;
4use gpui::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels};
5use schemars::{
6 gen::SchemaGenerator,
7 schema::{InstanceType, Schema, SchemaObject},
8 JsonSchema,
9};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use settings::{Settings, SettingsJsonSchemaParams};
13use std::sync::Arc;
14use util::ResultExt as _;
15
16const MIN_FONT_SIZE: Pixels = px(6.0);
17const MIN_LINE_HEIGHT: f32 = 1.0;
18
19#[derive(Clone)]
20pub struct ThemeSettings {
21 pub ui_font_size: Pixels,
22 pub ui_font: Font,
23 pub buffer_font: Font,
24 pub buffer_font_size: Pixels,
25 pub buffer_line_height: BufferLineHeight,
26 pub active_theme: Arc<Theme>,
27}
28
29#[derive(Default)]
30pub struct AdjustedBufferFontSize(Option<Pixels>);
31
32#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
33pub struct ThemeSettingsContent {
34 #[serde(default)]
35 pub ui_font_size: Option<f32>,
36 #[serde(default)]
37 pub ui_font_family: Option<String>,
38 #[serde(default)]
39 pub ui_font_features: Option<FontFeatures>,
40 #[serde(default)]
41 pub buffer_font_family: Option<String>,
42 #[serde(default)]
43 pub buffer_font_size: Option<f32>,
44 #[serde(default)]
45 pub buffer_line_height: Option<BufferLineHeight>,
46 #[serde(default)]
47 pub buffer_font_features: Option<FontFeatures>,
48 #[serde(default)]
49 pub theme: Option<String>,
50}
51
52#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
53#[serde(rename_all = "snake_case")]
54pub enum BufferLineHeight {
55 #[default]
56 Comfortable,
57 Standard,
58 Custom(f32),
59}
60
61impl BufferLineHeight {
62 pub fn value(&self) -> f32 {
63 match self {
64 BufferLineHeight::Comfortable => 1.618,
65 BufferLineHeight::Standard => 1.3,
66 BufferLineHeight::Custom(line_height) => *line_height,
67 }
68 }
69}
70
71impl ThemeSettings {
72 pub fn buffer_font_size(&self, cx: &mut AppContext) -> Pixels {
73 let font_size = *cx
74 .default_global::<AdjustedBufferFontSize>()
75 .0
76 .get_or_insert(self.buffer_font_size.into());
77 font_size.max(MIN_FONT_SIZE)
78 }
79
80 pub fn line_height(&self) -> f32 {
81 f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
82 }
83}
84
85pub fn adjusted_font_size(size: Pixels, cx: &mut AppContext) -> Pixels {
86 if let Some(adjusted_size) = cx.default_global::<AdjustedBufferFontSize>().0 {
87 let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
88 let delta = adjusted_size - buffer_font_size;
89 size + delta
90 } else {
91 size
92 }
93 .max(MIN_FONT_SIZE)
94}
95
96pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
97 let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
98 let adjusted_size = cx
99 .default_global::<AdjustedBufferFontSize>()
100 .0
101 .get_or_insert(buffer_font_size);
102 f(adjusted_size);
103 *adjusted_size = (*adjusted_size).max(MIN_FONT_SIZE - buffer_font_size);
104 cx.refresh();
105}
106
107pub fn reset_font_size(cx: &mut AppContext) {
108 if cx.has_global::<AdjustedBufferFontSize>() {
109 cx.global_mut::<AdjustedBufferFontSize>().0 = None;
110 cx.refresh();
111 }
112}
113
114impl settings::Settings for ThemeSettings {
115 const KEY: Option<&'static str> = None;
116
117 type FileContent = ThemeSettingsContent;
118
119 fn load(
120 defaults: &Self::FileContent,
121 user_values: &[&Self::FileContent],
122 cx: &mut AppContext,
123 ) -> Result<Self> {
124 let themes = cx.default_global::<ThemeRegistry>();
125
126 let mut this = Self {
127 ui_font_size: defaults.ui_font_size.unwrap().into(),
128 ui_font: Font {
129 family: defaults.ui_font_family.clone().unwrap().into(),
130 features: defaults.ui_font_features.clone().unwrap(),
131 weight: Default::default(),
132 style: Default::default(),
133 },
134 buffer_font: Font {
135 family: defaults.buffer_font_family.clone().unwrap().into(),
136 features: defaults.buffer_font_features.clone().unwrap(),
137 weight: FontWeight::default(),
138 style: FontStyle::default(),
139 },
140 buffer_font_size: defaults.buffer_font_size.unwrap().into(),
141 buffer_line_height: defaults.buffer_line_height.unwrap(),
142 active_theme: themes
143 .get(defaults.theme.as_ref().unwrap())
144 .or(themes.get(&one_dark().name))
145 .unwrap(),
146 };
147
148 for value in user_values.into_iter().copied().cloned() {
149 if let Some(value) = value.buffer_font_family {
150 this.buffer_font.family = value.into();
151 }
152 if let Some(value) = value.buffer_font_features {
153 this.buffer_font.features = value;
154 }
155
156 if let Some(value) = value.ui_font_family {
157 this.ui_font.family = value.into();
158 }
159 if let Some(value) = value.ui_font_features {
160 this.ui_font.features = value;
161 }
162
163 if let Some(value) = &value.theme {
164 if let Some(theme) = themes.get(value).log_err() {
165 this.active_theme = theme;
166 }
167 }
168
169 merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
170 merge(
171 &mut this.buffer_font_size,
172 value.buffer_font_size.map(Into::into),
173 );
174 merge(&mut this.buffer_line_height, value.buffer_line_height);
175 }
176
177 Ok(this)
178 }
179
180 fn json_schema(
181 generator: &mut SchemaGenerator,
182 params: &SettingsJsonSchemaParams,
183 cx: &AppContext,
184 ) -> schemars::schema::RootSchema {
185 let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
186 let theme_names = cx
187 .global::<ThemeRegistry>()
188 .list_names(params.staff_mode)
189 .map(|theme_name| Value::String(theme_name.to_string()))
190 .collect();
191
192 let theme_name_schema = SchemaObject {
193 instance_type: Some(InstanceType::String.into()),
194 enum_values: Some(theme_names),
195 ..Default::default()
196 };
197
198 root_schema
199 .definitions
200 .extend([("ThemeName".into(), theme_name_schema.into())]);
201
202 root_schema
203 .schema
204 .object
205 .as_mut()
206 .unwrap()
207 .properties
208 .extend([(
209 "theme".to_owned(),
210 Schema::new_ref("#/definitions/ThemeName".into()),
211 )]);
212
213 root_schema
214 }
215}
216
217fn merge<T: Copy>(target: &mut T, value: Option<T>) {
218 if let Some(value) = value {
219 *target = value;
220 }
221}