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 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 cx.remove_global::<AdjustedBufferFontSize>();
79 cx.refresh_windows();
80}
81
82impl settings::Setting for ThemeSettings {
83 const KEY: Option<&'static str> = None;
84
85 type FileContent = ThemeSettingsContent;
86
87 fn load(
88 defaults: &Self::FileContent,
89 user_values: &[&Self::FileContent],
90 cx: &AppContext,
91 ) -> Result<Self> {
92 let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
93 let themes = cx.global::<Arc<ThemeRegistry>>();
94
95 let mut this = Self {
96 buffer_font_family: cx
97 .font_cache()
98 .load_family(
99 &[defaults.buffer_font_family.as_ref().unwrap()],
100 &buffer_font_features,
101 )
102 .unwrap(),
103 buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
104 buffer_font_features,
105 buffer_font_size: defaults.buffer_font_size.unwrap(),
106 theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
107 };
108
109 for value in user_values.into_iter().copied().cloned() {
110 let font_cache = cx.font_cache();
111 let mut family_changed = false;
112 if let Some(value) = value.buffer_font_family {
113 this.buffer_font_family_name = value;
114 family_changed = true;
115 }
116 if let Some(value) = value.buffer_font_features {
117 this.buffer_font_features = value;
118 family_changed = true;
119 }
120 if family_changed {
121 if let Some(id) = font_cache
122 .load_family(&[&this.buffer_font_family_name], &this.buffer_font_features)
123 .log_err()
124 {
125 this.buffer_font_family = id;
126 }
127 }
128
129 if let Some(value) = &value.theme {
130 if let Some(theme) = themes.get(value).log_err() {
131 this.theme = theme;
132 }
133 }
134
135 merge(&mut this.buffer_font_size, value.buffer_font_size);
136 }
137
138 Ok(this)
139 }
140
141 fn json_schema(
142 generator: &mut SchemaGenerator,
143 params: &SettingsJsonSchemaParams,
144 cx: &AppContext,
145 ) -> schemars::schema::RootSchema {
146 let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
147 let theme_names = cx
148 .global::<Arc<ThemeRegistry>>()
149 .list(params.staff_mode)
150 .map(|theme| Value::String(theme.name.clone()))
151 .collect();
152
153 let theme_name_schema = SchemaObject {
154 instance_type: Some(InstanceType::String.into()),
155 enum_values: Some(theme_names),
156 ..Default::default()
157 };
158
159 root_schema
160 .definitions
161 .extend([("ThemeName".into(), theme_name_schema.into())]);
162
163 root_schema
164 .schema
165 .object
166 .as_mut()
167 .unwrap()
168 .properties
169 .extend([(
170 "theme".to_owned(),
171 Schema::new_ref("#/definitions/ThemeName".into()),
172 )]);
173
174 root_schema
175 }
176}
177
178fn merge<T: Copy>(target: &mut T, value: Option<T>) {
179 if let Some(value) = value {
180 *target = value;
181 }
182}