basics_page.rs

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