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().unwrap_or_default();
 52
 53    // let theme_mode = theme_selection.mode();
 54    // TODO: Clean this up once the "System" button inside the
 55    // toggle button group is done
 56
 57    let selected_index = match appearance {
 58        Appearance::Light => 0,
 59        Appearance::Dark => 1,
 60    };
 61
 62    let theme_seed = 0xBEEF as f32;
 63
 64    const LIGHT_THEMES: [&'static str; 3] = ["One Light", "Ayu Light", "Gruvbox Light"];
 65    const DARK_THEMES: [&'static str; 3] = ["One Dark", "Ayu Dark", "Gruvbox Dark"];
 66
 67    let theme_names = match appearance {
 68        Appearance::Light => LIGHT_THEMES,
 69        Appearance::Dark => DARK_THEMES,
 70    };
 71    let themes = theme_names
 72        .map(|theme_name| theme_registry.get(theme_name))
 73        .map(Result::unwrap);
 74
 75    let theme_previews = themes.map(|theme| {
 76        let is_selected = theme.name == current_theme_name;
 77        let name = theme.name.clone();
 78        let colors = cx.theme().colors();
 79
 80        v_flex()
 81            .id(name.clone())
 82            .w_full()
 83            .items_center()
 84            .gap_1()
 85            .child(
 86                div()
 87                    .w_full()
 88                    .border_2()
 89                    .border_color(colors.border_transparent)
 90                    .rounded(ThemePreviewTile::CORNER_RADIUS)
 91                    .map(|this| {
 92                        if is_selected {
 93                            this.border_color(colors.border_selected)
 94                        } else {
 95                            this.opacity(0.8).hover(|s| s.border_color(colors.border))
 96                        }
 97                    })
 98                    .child(ThemePreviewTile::new(theme.clone(), theme_seed)),
 99            )
100            .child(Label::new(name).color(Color::Muted).size(LabelSize::Small))
101            .on_click({
102                let theme_name = theme.name.clone();
103                move |_, _, cx| {
104                    let fs = <dyn Fs>::global(cx);
105                    let theme_name = theme_name.clone();
106                    update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
107                        settings.set_theme(theme_name, appearance);
108                    });
109                }
110            })
111    });
112
113    return v_flex()
114        .gap_2()
115        .child(
116            h_flex().justify_between().child(Label::new("Theme")).child(
117                ToggleButtonGroup::single_row(
118                    "theme-selector-onboarding-dark-light",
119                    [
120                        ToggleButtonSimple::new("Light", {
121                            let appearance_state = appearance_state.clone();
122                            move |_, _, cx| {
123                                write_appearance_change(&appearance_state, Appearance::Light, cx);
124                            }
125                        }),
126                        ToggleButtonSimple::new("Dark", {
127                            let appearance_state = appearance_state.clone();
128                            move |_, _, cx| {
129                                write_appearance_change(&appearance_state, Appearance::Dark, cx);
130                            }
131                        }),
132                        // TODO: Properly put the System back as a button within this group
133                        // Currently, given "System" is not an option in the Appearance enum,
134                        // this button doesn't get selected
135                        ToggleButtonSimple::new("System", {
136                            let theme = theme_selection.clone();
137                            move |_, _, cx| {
138                                toggle_system_theme_mode(theme.clone(), appearance, cx);
139                            }
140                        })
141                        .selected(theme_mode == ThemeMode::System),
142                    ],
143                )
144                .selected_index(selected_index)
145                .style(ui::ToggleButtonGroupStyle::Outlined)
146                .button_width(rems_from_px(64.)),
147            ),
148        )
149        .child(h_flex().gap_4().justify_between().children(theme_previews));
150
151    fn write_appearance_change(
152        appearance_state: &Entity<Appearance>,
153        new_appearance: Appearance,
154        cx: &mut App,
155    ) {
156        let fs = <dyn Fs>::global(cx);
157        appearance_state.write(cx, new_appearance);
158
159        update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
160            if settings.theme.as_ref().and_then(ThemeSelection::mode) == Some(ThemeMode::System) {
161                return;
162            }
163            let new_mode = match new_appearance {
164                Appearance::Light => ThemeMode::Light,
165                Appearance::Dark => ThemeMode::Dark,
166            };
167            settings.set_mode(new_mode);
168        });
169    }
170
171    fn toggle_system_theme_mode(
172        theme_selection: ThemeSelection,
173        appearance: Appearance,
174        cx: &mut App,
175    ) {
176        let fs = <dyn Fs>::global(cx);
177
178        update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
179            settings.theme = Some(match theme_selection {
180                ThemeSelection::Static(theme_name) => ThemeSelection::Dynamic {
181                    mode: ThemeMode::System,
182                    light: theme_name.clone(),
183                    dark: theme_name.clone(),
184                },
185                ThemeSelection::Dynamic {
186                    mode: ThemeMode::System,
187                    light,
188                    dark,
189                } => {
190                    let mode = match appearance {
191                        Appearance::Light => ThemeMode::Light,
192                        Appearance::Dark => ThemeMode::Dark,
193                    };
194                    ThemeSelection::Dynamic { mode, light, dark }
195                }
196                ThemeSelection::Dynamic {
197                    mode: _,
198                    light,
199                    dark,
200                } => ThemeSelection::Dynamic {
201                    mode: ThemeMode::System,
202                    light,
203                    dark,
204                },
205            });
206        });
207    }
208}
209
210fn write_keymap_base(keymap_base: BaseKeymap, cx: &App) {
211    let fs = <dyn Fs>::global(cx);
212
213    update_settings_file::<BaseKeymap>(fs, cx, move |setting, _| {
214        *setting = Some(keymap_base);
215    });
216}
217
218fn render_telemetry_section(cx: &App) -> impl IntoElement {
219    let fs = <dyn Fs>::global(cx);
220
221    v_flex()
222        .gap_4()
223        .child(Label::new("Telemetry").size(LabelSize::Large))
224        .child(SwitchField::new(
225            "onboarding-telemetry-metrics",
226            "Help Improve Zed",
227            Some("Sending anonymous usage data helps us build the right features and create the best experience.".into()),
228            if TelemetrySettings::get_global(cx).metrics {
229                ui::ToggleState::Selected
230            } else {
231                ui::ToggleState::Unselected
232            },
233            {
234            let fs = fs.clone();
235            move |selection, _, cx| {
236                let enabled = match selection {
237                    ToggleState::Selected => true,
238                    ToggleState::Unselected => false,
239                    ToggleState::Indeterminate => { return; },
240                };
241
242                update_settings_file::<TelemetrySettings>(
243                    fs.clone(),
244                    cx,
245                    move |setting, _| setting.metrics = Some(enabled),
246                );
247            }},
248        ))
249        .child(SwitchField::new(
250            "onboarding-telemetry-crash-reports",
251            "Help Fix Zed",
252            Some("Send crash reports so we can fix critical issues fast.".into()),
253            if TelemetrySettings::get_global(cx).diagnostics {
254                ui::ToggleState::Selected
255            } else {
256                ui::ToggleState::Unselected
257            },
258            {
259                let fs = fs.clone();
260                move |selection, _, cx| {
261                    let enabled = match selection {
262                        ToggleState::Selected => true,
263                        ToggleState::Unselected => false,
264                        ToggleState::Indeterminate => { return; },
265                    };
266
267                    update_settings_file::<TelemetrySettings>(
268                        fs.clone(),
269                        cx,
270                        move |setting, _| setting.diagnostics = Some(enabled),
271                    );
272                }
273            }
274        ))
275}
276
277pub(crate) fn render_basics_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
278    let base_keymap = match BaseKeymap::get_global(cx) {
279        BaseKeymap::VSCode => Some(0),
280        BaseKeymap::JetBrains => Some(1),
281        BaseKeymap::SublimeText => Some(2),
282        BaseKeymap::Atom => Some(3),
283        BaseKeymap::Emacs => Some(4),
284        BaseKeymap::Cursor => Some(5),
285        BaseKeymap::TextMate | BaseKeymap::None => None,
286    };
287
288    v_flex()
289        .gap_6()
290         .child(render_theme_section(window, cx))
291        .child(
292            v_flex().gap_2().child(Label::new("Base Keymap")).child(
293                ToggleButtonGroup::two_rows(
294                    "multiple_row_test",
295                    [
296                        ToggleButtonWithIcon::new("VS Code", IconName::EditorVsCode, |_, _, cx| {
297                            write_keymap_base(BaseKeymap::VSCode, cx);
298                        }),
299                        ToggleButtonWithIcon::new("Jetbrains", IconName::EditorJetBrains, |_, _, cx| {
300                            write_keymap_base(BaseKeymap::JetBrains, cx);
301                        }),
302                        ToggleButtonWithIcon::new("Sublime Text", IconName::EditorSublime, |_, _, cx| {
303                            write_keymap_base(BaseKeymap::SublimeText, cx);
304                        }),
305                    ],
306                    [
307                        ToggleButtonWithIcon::new("Atom", IconName::EditorAtom, |_, _, cx| {
308                            write_keymap_base(BaseKeymap::Atom, cx);
309                        }),
310                        ToggleButtonWithIcon::new("Emacs", IconName::EditorEmacs, |_, _, cx| {
311                            write_keymap_base(BaseKeymap::Emacs, cx);
312                        }),
313                        ToggleButtonWithIcon::new("Cursor (Beta)", IconName::EditorCursor, |_, _, cx| {
314                            write_keymap_base(BaseKeymap::Cursor, cx);
315                        }),
316                    ],
317                )
318                .when_some(base_keymap, |this, base_keymap| this.selected_index(base_keymap))
319                .button_width(rems_from_px(216.))
320                .size(ui::ToggleButtonGroupSize::Medium)
321                .style(ui::ToggleButtonGroupStyle::Outlined)
322            ),
323        )
324        .child(SwitchField::new(
325            "onboarding-vim-mode",
326            "Vim Mode",
327            Some("Coming from Neovim? Zed's first-class implementation of Vim Mode has got your back.".into()),
328            if VimModeSetting::get_global(cx).0 {
329                ui::ToggleState::Selected
330            } else {
331                ui::ToggleState::Unselected
332            },
333            {
334                let fs = <dyn Fs>::global(cx);
335                move |selection, _, cx| {
336                    let enabled = match selection {
337                        ToggleState::Selected => true,
338                        ToggleState::Unselected => false,
339                        ToggleState::Indeterminate => { return; },
340                    };
341
342                    update_settings_file::<VimModeSetting>(
343                        fs.clone(),
344                        cx,
345                        move |setting, _| *setting = Some(enabled),
346                    );
347                }
348            },
349        ))
350        .child(render_telemetry_section(cx))
351}