1use gpui::{AppContext, FontWeight};
2use settings::{EditableSettingControl, Settings};
3use theme::{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(UiFontSizeControl)
33 .child(UiFontWeightControl),
34 )
35 }
36}
37
38#[derive(IntoElement)]
39struct ThemeControl;
40
41impl EditableSettingControl for ThemeControl {
42 type Value = String;
43 type Settings = ThemeSettings;
44
45 fn name(&self) -> SharedString {
46 "Theme".into()
47 }
48
49 fn read(cx: &AppContext) -> Self::Value {
50 let settings = ThemeSettings::get_global(cx);
51 let appearance = SystemAppearance::global(cx);
52 settings
53 .theme_selection
54 .as_ref()
55 .map(|selection| selection.theme(appearance.0).to_string())
56 .unwrap_or_else(|| ThemeSettings::default_theme(*appearance).to_string())
57 }
58
59 fn apply(
60 settings: &mut <Self::Settings as Settings>::FileContent,
61 value: Self::Value,
62 cx: &AppContext,
63 ) {
64 let appearance = SystemAppearance::global(cx);
65 settings.set_theme(value, appearance.0);
66 }
67}
68
69impl RenderOnce for ThemeControl {
70 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
71 let value = Self::read(cx);
72
73 DropdownMenu::new(
74 "theme",
75 value.clone(),
76 ContextMenu::build(cx, |mut menu, cx| {
77 let theme_registry = ThemeRegistry::global(cx);
78
79 for theme in theme_registry.list_names(false) {
80 menu = menu.custom_entry(
81 {
82 let theme = theme.clone();
83 move |_cx| Label::new(theme.clone()).into_any_element()
84 },
85 {
86 let theme = theme.clone();
87 move |cx| {
88 Self::write(theme.to_string(), cx);
89 }
90 },
91 )
92 }
93
94 menu
95 }),
96 )
97 .full_width(true)
98 }
99}
100
101#[derive(IntoElement)]
102struct ThemeModeControl;
103
104impl EditableSettingControl for ThemeModeControl {
105 type Value = ThemeMode;
106 type Settings = ThemeSettings;
107
108 fn name(&self) -> SharedString {
109 "Theme Mode".into()
110 }
111
112 fn read(cx: &AppContext) -> Self::Value {
113 let settings = ThemeSettings::get_global(cx);
114 settings
115 .theme_selection
116 .as_ref()
117 .and_then(|selection| selection.mode())
118 .unwrap_or_default()
119 }
120
121 fn apply(
122 settings: &mut <Self::Settings as Settings>::FileContent,
123 value: Self::Value,
124 _cx: &AppContext,
125 ) {
126 settings.set_mode(value);
127 }
128}
129
130impl RenderOnce for ThemeModeControl {
131 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
132 let value = Self::read(cx);
133
134 h_flex()
135 .child(
136 ToggleButton::new("light", "Light")
137 .style(ButtonStyle::Filled)
138 .size(ButtonSize::Large)
139 .selected(value == ThemeMode::Light)
140 .on_click(|_, cx| Self::write(ThemeMode::Light, cx))
141 .first(),
142 )
143 .child(
144 ToggleButton::new("system", "System")
145 .style(ButtonStyle::Filled)
146 .size(ButtonSize::Large)
147 .selected(value == ThemeMode::System)
148 .on_click(|_, cx| Self::write(ThemeMode::System, cx))
149 .middle(),
150 )
151 .child(
152 ToggleButton::new("dark", "Dark")
153 .style(ButtonStyle::Filled)
154 .size(ButtonSize::Large)
155 .selected(value == ThemeMode::Dark)
156 .on_click(|_, cx| Self::write(ThemeMode::Dark, cx))
157 .last(),
158 )
159 }
160}
161
162#[derive(IntoElement)]
163struct UiFontSizeControl;
164
165impl EditableSettingControl for UiFontSizeControl {
166 type Value = Pixels;
167 type Settings = ThemeSettings;
168
169 fn name(&self) -> SharedString {
170 "UI Font Size".into()
171 }
172
173 fn read(cx: &AppContext) -> Self::Value {
174 let settings = ThemeSettings::get_global(cx);
175 settings.ui_font_size
176 }
177
178 fn apply(
179 settings: &mut <Self::Settings as Settings>::FileContent,
180 value: Self::Value,
181 _cx: &AppContext,
182 ) {
183 settings.ui_font_size = Some(value.into());
184 }
185}
186
187impl RenderOnce for UiFontSizeControl {
188 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
189 let value = Self::read(cx);
190
191 h_flex()
192 .gap_2()
193 .child(Icon::new(IconName::FontSize))
194 .child(NumericStepper::new(
195 value.to_string(),
196 move |_, cx| {
197 Self::write(value - px(1.), cx);
198 },
199 move |_, cx| {
200 Self::write(value + px(1.), cx);
201 },
202 ))
203 }
204}
205
206#[derive(IntoElement)]
207struct UiFontWeightControl;
208
209impl EditableSettingControl for UiFontWeightControl {
210 type Value = FontWeight;
211 type Settings = ThemeSettings;
212
213 fn name(&self) -> SharedString {
214 "UI Font Weight".into()
215 }
216
217 fn read(cx: &AppContext) -> Self::Value {
218 let settings = ThemeSettings::get_global(cx);
219 settings.ui_font.weight
220 }
221
222 fn apply(
223 settings: &mut <Self::Settings as Settings>::FileContent,
224 value: Self::Value,
225 _cx: &AppContext,
226 ) {
227 settings.ui_font_weight = Some(value.0);
228 }
229}
230
231impl RenderOnce for UiFontWeightControl {
232 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
233 let value = Self::read(cx);
234
235 h_flex()
236 .gap_2()
237 .child(Icon::new(IconName::FontWeight))
238 .child(DropdownMenu::new(
239 "ui-font-weight",
240 value.0.to_string(),
241 ContextMenu::build(cx, |mut menu, _cx| {
242 for weight in FontWeight::ALL {
243 menu = menu.custom_entry(
244 move |_cx| Label::new(weight.0.to_string()).into_any_element(),
245 {
246 move |cx| {
247 Self::write(weight, cx);
248 }
249 },
250 )
251 }
252
253 menu
254 }),
255 ))
256 }
257}