1use gpui::{AppContext, FontWeight};
2use settings::{EditableSettingControl, Settings};
3use theme::{FontFamilyCache, SystemAppearance, ThemeMode, ThemeRegistry, ThemeSettings};
4use ui::{
5 prelude::*, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup,
6 ToggleButton,
7};
8
9#[derive(IntoElement)]
10pub struct AppearanceSettingsControls {}
11
12impl AppearanceSettingsControls {
13 pub fn new() -> Self {
14 Self {}
15 }
16}
17
18impl RenderOnce for AppearanceSettingsControls {
19 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
20 SettingsContainer::new()
21 .child(
22 SettingsGroup::new("Theme").child(
23 h_flex()
24 .gap_2()
25 .justify_between()
26 .child(ThemeControl)
27 .child(ThemeModeControl),
28 ),
29 )
30 .child(
31 SettingsGroup::new("Font")
32 .child(
33 h_flex()
34 .gap_2()
35 .justify_between()
36 .child(UiFontFamilyControl)
37 .child(UiFontWeightControl),
38 )
39 .child(UiFontSizeControl),
40 )
41 }
42}
43
44#[derive(IntoElement)]
45struct ThemeControl;
46
47impl EditableSettingControl for ThemeControl {
48 type Value = String;
49 type Settings = ThemeSettings;
50
51 fn name(&self) -> SharedString {
52 "Theme".into()
53 }
54
55 fn read(cx: &AppContext) -> Self::Value {
56 let settings = ThemeSettings::get_global(cx);
57 let appearance = SystemAppearance::global(cx);
58 settings
59 .theme_selection
60 .as_ref()
61 .map(|selection| selection.theme(appearance.0).to_string())
62 .unwrap_or_else(|| ThemeSettings::default_theme(*appearance).to_string())
63 }
64
65 fn apply(
66 settings: &mut <Self::Settings as Settings>::FileContent,
67 value: Self::Value,
68 cx: &AppContext,
69 ) {
70 let appearance = SystemAppearance::global(cx);
71 settings.set_theme(value, appearance.0);
72 }
73}
74
75impl RenderOnce for ThemeControl {
76 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
77 let value = Self::read(cx);
78
79 DropdownMenu::new(
80 "theme",
81 value.clone(),
82 ContextMenu::build(cx, |mut menu, cx| {
83 let theme_registry = ThemeRegistry::global(cx);
84
85 for theme in theme_registry.list_names(false) {
86 menu = menu.custom_entry(
87 {
88 let theme = theme.clone();
89 move |_cx| Label::new(theme.clone()).into_any_element()
90 },
91 {
92 let theme = theme.clone();
93 move |cx| {
94 Self::write(theme.to_string(), cx);
95 }
96 },
97 )
98 }
99
100 menu
101 }),
102 )
103 .full_width(true)
104 }
105}
106
107#[derive(IntoElement)]
108struct ThemeModeControl;
109
110impl EditableSettingControl for ThemeModeControl {
111 type Value = ThemeMode;
112 type Settings = ThemeSettings;
113
114 fn name(&self) -> SharedString {
115 "Theme Mode".into()
116 }
117
118 fn read(cx: &AppContext) -> Self::Value {
119 let settings = ThemeSettings::get_global(cx);
120 settings
121 .theme_selection
122 .as_ref()
123 .and_then(|selection| selection.mode())
124 .unwrap_or_default()
125 }
126
127 fn apply(
128 settings: &mut <Self::Settings as Settings>::FileContent,
129 value: Self::Value,
130 _cx: &AppContext,
131 ) {
132 settings.set_mode(value);
133 }
134}
135
136impl RenderOnce for ThemeModeControl {
137 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
138 let value = Self::read(cx);
139
140 h_flex()
141 .child(
142 ToggleButton::new("light", "Light")
143 .style(ButtonStyle::Filled)
144 .size(ButtonSize::Large)
145 .selected(value == ThemeMode::Light)
146 .on_click(|_, cx| Self::write(ThemeMode::Light, cx))
147 .first(),
148 )
149 .child(
150 ToggleButton::new("system", "System")
151 .style(ButtonStyle::Filled)
152 .size(ButtonSize::Large)
153 .selected(value == ThemeMode::System)
154 .on_click(|_, cx| Self::write(ThemeMode::System, cx))
155 .middle(),
156 )
157 .child(
158 ToggleButton::new("dark", "Dark")
159 .style(ButtonStyle::Filled)
160 .size(ButtonSize::Large)
161 .selected(value == ThemeMode::Dark)
162 .on_click(|_, cx| Self::write(ThemeMode::Dark, cx))
163 .last(),
164 )
165 }
166}
167
168#[derive(IntoElement)]
169struct UiFontFamilyControl;
170
171impl EditableSettingControl for UiFontFamilyControl {
172 type Value = SharedString;
173 type Settings = ThemeSettings;
174
175 fn name(&self) -> SharedString {
176 "UI Font Family".into()
177 }
178
179 fn read(cx: &AppContext) -> Self::Value {
180 let settings = ThemeSettings::get_global(cx);
181 settings.ui_font.family.clone()
182 }
183
184 fn apply(
185 settings: &mut <Self::Settings as Settings>::FileContent,
186 value: Self::Value,
187 _cx: &AppContext,
188 ) {
189 settings.ui_font_family = Some(value.to_string());
190 }
191}
192
193impl RenderOnce for UiFontFamilyControl {
194 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
195 let value = Self::read(cx);
196
197 h_flex()
198 .gap_2()
199 .child(Icon::new(IconName::Font))
200 .child(DropdownMenu::new(
201 "ui-font-family",
202 value.clone(),
203 ContextMenu::build(cx, |mut menu, cx| {
204 let font_family_cache = FontFamilyCache::global(cx);
205
206 for font_name in font_family_cache.list_font_families(cx) {
207 menu = menu.custom_entry(
208 {
209 let font_name = font_name.clone();
210 move |_cx| Label::new(font_name.clone()).into_any_element()
211 },
212 {
213 let font_name = font_name.clone();
214 move |cx| {
215 Self::write(font_name.clone(), cx);
216 }
217 },
218 )
219 }
220
221 menu
222 }),
223 ))
224 }
225}
226
227#[derive(IntoElement)]
228struct UiFontSizeControl;
229
230impl EditableSettingControl for UiFontSizeControl {
231 type Value = Pixels;
232 type Settings = ThemeSettings;
233
234 fn name(&self) -> SharedString {
235 "UI Font Size".into()
236 }
237
238 fn read(cx: &AppContext) -> Self::Value {
239 let settings = ThemeSettings::get_global(cx);
240 settings.ui_font_size
241 }
242
243 fn apply(
244 settings: &mut <Self::Settings as Settings>::FileContent,
245 value: Self::Value,
246 _cx: &AppContext,
247 ) {
248 settings.ui_font_size = Some(value.into());
249 }
250}
251
252impl RenderOnce for UiFontSizeControl {
253 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
254 let value = Self::read(cx);
255
256 h_flex()
257 .gap_2()
258 .child(Icon::new(IconName::FontSize))
259 .child(NumericStepper::new(
260 value.to_string(),
261 move |_, cx| {
262 Self::write(value - px(1.), cx);
263 },
264 move |_, cx| {
265 Self::write(value + px(1.), cx);
266 },
267 ))
268 }
269}
270
271#[derive(IntoElement)]
272struct UiFontWeightControl;
273
274impl EditableSettingControl for UiFontWeightControl {
275 type Value = FontWeight;
276 type Settings = ThemeSettings;
277
278 fn name(&self) -> SharedString {
279 "UI Font Weight".into()
280 }
281
282 fn read(cx: &AppContext) -> Self::Value {
283 let settings = ThemeSettings::get_global(cx);
284 settings.ui_font.weight
285 }
286
287 fn apply(
288 settings: &mut <Self::Settings as Settings>::FileContent,
289 value: Self::Value,
290 _cx: &AppContext,
291 ) {
292 settings.ui_font_weight = Some(value.0);
293 }
294}
295
296impl RenderOnce for UiFontWeightControl {
297 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
298 let value = Self::read(cx);
299
300 h_flex()
301 .gap_2()
302 .child(Icon::new(IconName::FontWeight))
303 .child(DropdownMenu::new(
304 "ui-font-weight",
305 value.0.to_string(),
306 ContextMenu::build(cx, |mut menu, _cx| {
307 for weight in FontWeight::ALL {
308 menu = menu.custom_entry(
309 move |_cx| Label::new(weight.0.to_string()).into_any_element(),
310 {
311 move |cx| {
312 Self::write(weight, cx);
313 }
314 },
315 )
316 }
317
318 menu
319 }),
320 ))
321 }
322}