appearance_settings_controls.rs

  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}