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