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(crate) struct AdjustedBufferFontSize(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: &AppContext) -> Pixels {
73 cx.try_global::<AdjustedBufferFontSize>()
74 .map_or(self.buffer_font_size, |size| size.0)
75 .max(MIN_FONT_SIZE)
76 }
77
78 pub fn line_height(&self) -> f32 {
79 f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
80 }
81}
82
83pub fn adjusted_font_size(size: Pixels, cx: &mut AppContext) -> Pixels {
84 if let Some(AdjustedBufferFontSize(adjusted_size)) = cx.try_global::<AdjustedBufferFontSize>() {
85 let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
86 let delta = *adjusted_size - buffer_font_size;
87 size + delta
88 } else {
89 size
90 }
91 .max(MIN_FONT_SIZE)
92}
93
94pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
95 let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
96 let mut adjusted_size = cx
97 .try_global::<AdjustedBufferFontSize>()
98 .map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
99
100 f(&mut adjusted_size);
101 adjusted_size = adjusted_size.max(MIN_FONT_SIZE);
102 cx.set_global(AdjustedBufferFontSize(adjusted_size));
103 cx.refresh();
104}
105
106pub fn reset_font_size(cx: &mut AppContext) {
107 if cx.has_global::<AdjustedBufferFontSize>() {
108 cx.remove_global::<AdjustedBufferFontSize>();
109 cx.refresh();
110 }
111}
112
113impl settings::Settings for ThemeSettings {
114 const KEY: Option<&'static str> = None;
115
116 type FileContent = ThemeSettingsContent;
117
118 fn load(
119 defaults: &Self::FileContent,
120 user_values: &[&Self::FileContent],
121 cx: &mut AppContext,
122 ) -> Result<Self> {
123 let themes = cx.default_global::<ThemeRegistry>();
124
125 let mut this = Self {
126 ui_font_size: defaults.ui_font_size.unwrap().into(),
127 ui_font: Font {
128 family: defaults.ui_font_family.clone().unwrap().into(),
129 features: defaults.ui_font_features.clone().unwrap(),
130 weight: Default::default(),
131 style: Default::default(),
132 },
133 buffer_font: Font {
134 family: defaults.buffer_font_family.clone().unwrap().into(),
135 features: defaults.buffer_font_features.clone().unwrap(),
136 weight: FontWeight::default(),
137 style: FontStyle::default(),
138 },
139 buffer_font_size: defaults.buffer_font_size.unwrap().into(),
140 buffer_line_height: defaults.buffer_line_height.unwrap(),
141 active_theme: themes
142 .get(defaults.theme.as_ref().unwrap())
143 .or(themes.get(&one_dark().name))
144 .unwrap(),
145 };
146
147 for value in user_values.into_iter().copied().cloned() {
148 if let Some(value) = value.buffer_font_family {
149 this.buffer_font.family = value.into();
150 }
151 if let Some(value) = value.buffer_font_features {
152 this.buffer_font.features = value;
153 }
154
155 if let Some(value) = value.ui_font_family {
156 this.ui_font.family = value.into();
157 }
158 if let Some(value) = value.ui_font_features {
159 this.ui_font.features = value;
160 }
161
162 if let Some(value) = &value.theme {
163 if let Some(theme) = themes.get(value).log_err() {
164 this.active_theme = theme;
165 }
166 }
167
168 merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
169 merge(
170 &mut this.buffer_font_size,
171 value.buffer_font_size.map(Into::into),
172 );
173 merge(&mut this.buffer_line_height, value.buffer_line_height);
174 }
175
176 Ok(this)
177 }
178
179 fn json_schema(
180 generator: &mut SchemaGenerator,
181 params: &SettingsJsonSchemaParams,
182 cx: &AppContext,
183 ) -> schemars::schema::RootSchema {
184 let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
185 let theme_names = cx
186 .global::<ThemeRegistry>()
187 .list_names(params.staff_mode)
188 .map(|theme_name| Value::String(theme_name.to_string()))
189 .collect();
190
191 let theme_name_schema = SchemaObject {
192 instance_type: Some(InstanceType::String.into()),
193 enum_values: Some(theme_names),
194 ..Default::default()
195 };
196
197 root_schema
198 .definitions
199 .extend([("ThemeName".into(), theme_name_schema.into())]);
200
201 root_schema
202 .schema
203 .object
204 .as_mut()
205 .unwrap()
206 .properties
207 .extend([(
208 "theme".to_owned(),
209 Schema::new_ref("#/definitions/ThemeName".into()),
210 )]);
211
212 root_schema
213 }
214}
215
216fn merge<T: Copy>(target: &mut T, value: Option<T>) {
217 if let Some(value) = value {
218 *target = value;
219 }
220}