basics_page.rs

  1use client::TelemetrySettings;
  2use fs::Fs;
  3use gpui::{App, Entity, IntoElement, Window};
  4use settings::{BaseKeymap, Settings, update_settings_file};
  5use theme::{Appearance, ThemeMode, ThemeName, ThemeRegistry, ThemeSelection, ThemeSettings};
  6use ui::{
  7    ParentElement as _, StatefulInteractiveElement, SwitchField, ToggleButtonGroup,
  8    ToggleButtonSimple, ToggleButtonWithIcon, prelude::*, rems_from_px,
  9};
 10use vim_mode_setting::VimModeSetting;
 11
 12use crate::theme_preview::ThemePreviewTile;
 13
 14/// separates theme "mode" ("dark" | "light" | "system") into two separate states
 15/// - appearance = "dark" | "light"
 16/// - "system" true/false
 17/// when system selected:
 18///  - toggling between light and dark does not change theme.mode, just which variant will be changed
 19/// when system not selected:
 20///  - toggling between light and dark does change theme.mode
 21/// selecting a theme preview will always change theme.["light" | "dark"] to the selected theme,
 22///
 23/// this allows for selecting a dark and light theme option regardless of whether the mode is set to system or not
 24/// it does not support setting theme to a static value
 25fn render_theme_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
 26    let theme_selection = ThemeSettings::get_global(cx).theme_selection.clone();
 27    let system_appearance = theme::SystemAppearance::global(cx);
 28    let appearance_state = window.use_state(cx, |_, _cx| {
 29        theme_selection
 30            .as_ref()
 31            .and_then(|selection| selection.mode())
 32            .and_then(|mode| match mode {
 33                ThemeMode::System => None,
 34                ThemeMode::Light => Some(Appearance::Light),
 35                ThemeMode::Dark => Some(Appearance::Dark),
 36            })
 37            .unwrap_or(*system_appearance)
 38    });
 39    let appearance = *appearance_state.read(cx);
 40    let theme_selection = theme_selection.unwrap_or_else(|| ThemeSelection::Dynamic {
 41        mode: match *system_appearance {
 42            Appearance::Light => ThemeMode::Light,
 43            Appearance::Dark => ThemeMode::Dark,
 44        },
 45        light: ThemeName("One Light".into()),
 46        dark: ThemeName("One Dark".into()),
 47    });
 48    let theme_registry = ThemeRegistry::global(cx);
 49
 50    let current_theme_name = theme_selection.theme(appearance);
 51    let theme_mode = theme_selection.mode();
 52
 53    let selected_index = match appearance {
 54        Appearance::Light => 0,
 55        Appearance::Dark => 1,
 56    };
 57
 58    let theme_seed = 0xBEEF as f32;
 59
 60    const LIGHT_THEMES: [&'static str; 3] = ["One Light", "Ayu Light", "Gruvbox Light"];
 61    const DARK_THEMES: [&'static str; 3] = ["One Dark", "Ayu Dark", "Gruvbox Dark"];
 62
 63    let theme_names = match appearance {
 64        Appearance::Light => LIGHT_THEMES,
 65        Appearance::Dark => DARK_THEMES,
 66    };
 67    let themes = theme_names
 68        .map(|theme_name| theme_registry.get(theme_name))
 69        .map(Result::unwrap);
 70
 71    let theme_previews = themes.map(|theme| {
 72        let is_selected = theme.name == current_theme_name;
 73        let name = theme.name.clone();
 74        let colors = cx.theme().colors();
 75        v_flex()
 76            .id(name.clone())
 77            .on_click({
 78                let theme_name = theme.name.clone();
 79                move |_, _, cx| {
 80                    let fs = <dyn Fs>::global(cx);
 81                    let theme_name = theme_name.clone();
 82                    update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
 83                        settings.set_theme(theme_name, appearance);
 84                    });
 85                }
 86            })
 87            .flex_1()
 88            .child(
 89                div()
 90                    .border_2()
 91                    .border_color(colors.border_transparent)
 92                    .rounded(ThemePreviewTile::CORNER_RADIUS)
 93                    .hover(|mut style| {
 94                        if !is_selected {
 95                            style.border_color = Some(colors.element_hover);
 96                        }
 97                        style
 98                    })
 99                    .when(is_selected, |this| {
100                        this.border_color(colors.border_selected)
101                    })
102                    .cursor_pointer()
103                    .child(ThemePreviewTile::new(theme, theme_seed)),
104            )
105            .child(
106                h_flex()
107                    .justify_center()
108                    .items_baseline()
109                    .child(Label::new(name).color(Color::Muted)),
110            )
111    });
112
113    return v_flex()
114        .child(
115            h_flex().justify_between().child(Label::new("Theme")).child(
116                h_flex()
117                    .gap_2()
118                    .child(
119                        ToggleButtonGroup::single_row(
120                            "theme-selector-onboarding-dark-light",
121                            [
122                                ToggleButtonSimple::new("Light", {
123                                    let appearance_state = appearance_state.clone();
124                                    move |_, _, cx| {
125                                        write_appearance_change(
126                                            &appearance_state,
127                                            Appearance::Light,
128                                            cx,
129                                        );
130                                    }
131                                }),
132                                ToggleButtonSimple::new("Dark", {
133                                    let appearance_state = appearance_state.clone();
134                                    move |_, _, cx| {
135                                        write_appearance_change(
136                                            &appearance_state,
137                                            Appearance::Dark,
138                                            cx,
139                                        );
140                                    }
141                                }),
142                            ],
143                        )
144                        .selected_index(selected_index)
145                        .style(ui::ToggleButtonGroupStyle::Outlined)
146                        .button_width(rems_from_px(64.)),
147                    )
148                    .child(
149                        ToggleButtonGroup::single_row(
150                            "theme-selector-onboarding-system",
151                            [ToggleButtonSimple::new("System", {
152                                let theme = theme_selection.clone();
153                                move |_, _, cx| {
154                                    toggle_system_theme_mode(theme.clone(), appearance, cx);
155                                }
156                            })],
157                        )
158                        .selected_index((theme_mode != Some(ThemeMode::System)) as usize)
159                        .style(ui::ToggleButtonGroupStyle::Outlined)
160                        .button_width(rems_from_px(64.)),
161                    ),
162            ),
163        )
164        .child(h_flex().justify_between().children(theme_previews));
165
166    fn write_appearance_change(
167        appearance_state: &Entity<Appearance>,
168        new_appearance: Appearance,
169        cx: &mut App,
170    ) {
171        appearance_state.update(cx, |appearance, _| {
172            *appearance = new_appearance;
173        });
174        let fs = <dyn Fs>::global(cx);
175
176        update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
177            if settings.theme.as_ref().and_then(ThemeSelection::mode) == Some(ThemeMode::System) {
178                return;
179            }
180            let new_mode = match new_appearance {
181                Appearance::Light => ThemeMode::Light,
182                Appearance::Dark => ThemeMode::Dark,
183            };
184            settings.set_mode(new_mode);
185        });
186    }
187
188    fn toggle_system_theme_mode(
189        theme_selection: ThemeSelection,
190        appearance: Appearance,
191        cx: &mut App,
192    ) {
193        let fs = <dyn Fs>::global(cx);
194
195        update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
196            settings.theme = Some(match theme_selection {
197                ThemeSelection::Static(theme_name) => ThemeSelection::Dynamic {
198                    mode: ThemeMode::System,
199                    light: theme_name.clone(),
200                    dark: theme_name.clone(),
201                },
202                ThemeSelection::Dynamic {
203                    mode: ThemeMode::System,
204                    light,
205                    dark,
206                } => {
207                    let mode = match appearance {
208                        Appearance::Light => ThemeMode::Light,
209                        Appearance::Dark => ThemeMode::Dark,
210                    };
211                    ThemeSelection::Dynamic { mode, light, dark }
212                }
213
214                ThemeSelection::Dynamic {
215                    mode: _,
216                    light,
217                    dark,
218                } => ThemeSelection::Dynamic {
219                    mode: ThemeMode::System,
220                    light,
221                    dark,
222                },
223            });
224        });
225    }
226}
227
228fn write_keymap_base(keymap_base: BaseKeymap, cx: &App) {
229    let fs = <dyn Fs>::global(cx);
230
231    update_settings_file::<BaseKeymap>(fs, cx, move |setting, _| {
232        *setting = Some(keymap_base);
233    });
234}
235
236fn render_telemetry_section(cx: &App) -> impl IntoElement {
237    let fs = <dyn Fs>::global(cx);
238
239    v_flex()
240        .gap_4()
241        .child(Label::new("Telemetry").size(LabelSize::Large))
242        .child(SwitchField::new(
243            "onboarding-telemetry-metrics",
244            "Help Improve Zed",
245            Some("Sending anonymous usage data helps us build the right features and create the best experience.".into()),
246            if TelemetrySettings::get_global(cx).metrics {
247                ui::ToggleState::Selected
248            } else {
249                ui::ToggleState::Unselected
250            },
251            {
252            let fs = fs.clone();
253            move |selection, _, cx| {
254                let enabled = match selection {
255                    ToggleState::Selected => true,
256                    ToggleState::Unselected => false,
257                    ToggleState::Indeterminate => { return; },
258                };
259
260                update_settings_file::<TelemetrySettings>(
261                    fs.clone(),
262                    cx,
263                    move |setting, _| setting.metrics = Some(enabled),
264                );
265            }},
266        ))
267        .child(SwitchField::new(
268            "onboarding-telemetry-crash-reports",
269            "Help Fix Zed",
270            Some("Send crash reports so we can fix critical issues fast.".into()),
271            if TelemetrySettings::get_global(cx).diagnostics {
272                ui::ToggleState::Selected
273            } else {
274                ui::ToggleState::Unselected
275            },
276            {
277                let fs = fs.clone();
278                move |selection, _, cx| {
279                    let enabled = match selection {
280                        ToggleState::Selected => true,
281                        ToggleState::Unselected => false,
282                        ToggleState::Indeterminate => { return; },
283                    };
284
285                    update_settings_file::<TelemetrySettings>(
286                        fs.clone(),
287                        cx,
288                        move |setting, _| setting.diagnostics = Some(enabled),
289                    );
290                }
291            }
292        ))
293}
294
295pub(crate) fn render_basics_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
296    let base_keymap = match BaseKeymap::get_global(cx) {
297        BaseKeymap::VSCode => Some(0),
298        BaseKeymap::JetBrains => Some(1),
299        BaseKeymap::SublimeText => Some(2),
300        BaseKeymap::Atom => Some(3),
301        BaseKeymap::Emacs => Some(4),
302        BaseKeymap::Cursor => Some(5),
303        BaseKeymap::TextMate | BaseKeymap::None => None,
304    };
305
306    v_flex()
307        .gap_6()
308         .child(render_theme_section(window, cx))
309        .child(
310            v_flex().gap_2().child(Label::new("Base Keymap")).child(
311                ToggleButtonGroup::two_rows(
312                    "multiple_row_test",
313                    [
314                        ToggleButtonWithIcon::new("VS Code", IconName::AiZed, |_, _, cx| {
315                            write_keymap_base(BaseKeymap::VSCode, cx);
316                        }),
317                        ToggleButtonWithIcon::new("Jetbrains", IconName::AiZed, |_, _, cx| {
318                            write_keymap_base(BaseKeymap::JetBrains, cx);
319                        }),
320                        ToggleButtonWithIcon::new("Sublime Text", IconName::AiZed, |_, _, cx| {
321                            write_keymap_base(BaseKeymap::SublimeText, cx);
322                        }),
323                    ],
324                    [
325                        ToggleButtonWithIcon::new("Atom", IconName::AiZed, |_, _, cx| {
326                            write_keymap_base(BaseKeymap::Atom, cx);
327                        }),
328                        ToggleButtonWithIcon::new("Emacs", IconName::AiZed, |_, _, cx| {
329                            write_keymap_base(BaseKeymap::Emacs, cx);
330                        }),
331                        ToggleButtonWithIcon::new("Cursor (Beta)", IconName::AiZed, |_, _, cx| {
332                            write_keymap_base(BaseKeymap::Cursor, cx);
333                        }),
334                    ],
335                )
336                .when_some(base_keymap, |this, base_keymap| this.selected_index(base_keymap))
337                .button_width(rems_from_px(230.))
338                .style(ui::ToggleButtonGroupStyle::Outlined)
339            ),
340        )
341        .child(SwitchField::new(
342            "onboarding-vim-mode",
343            "Vim Mode",
344            Some("Coming from Neovim? Zed's first-class implementation of Vim Mode has got your back.".into()),
345            if VimModeSetting::get_global(cx).0 {
346                ui::ToggleState::Selected
347            } else {
348                ui::ToggleState::Unselected
349            },
350            {
351                let fs = <dyn Fs>::global(cx);
352                move |selection, _, cx| {
353                    let enabled = match selection {
354                        ToggleState::Selected => true,
355                        ToggleState::Unselected => false,
356                        ToggleState::Indeterminate => { return; },
357                    };
358
359                    update_settings_file::<VimModeSetting>(
360                        fs.clone(),
361                        cx,
362                        move |setting, _| *setting = Some(enabled),
363                    );
364                }
365            },
366        ))
367        .child(render_telemetry_section(cx))
368}