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        appearance_state.update(cx, |appearance, _| {
157            *appearance = new_appearance;
158        });
159        let fs = <dyn Fs>::global(cx);
160
161        update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
162            if settings.theme.as_ref().and_then(ThemeSelection::mode) == Some(ThemeMode::System) {
163                return;
164            }
165            let new_mode = match new_appearance {
166                Appearance::Light => ThemeMode::Light,
167                Appearance::Dark => ThemeMode::Dark,
168            };
169            settings.set_mode(new_mode);
170        });
171    }
172
173    fn toggle_system_theme_mode(
174        theme_selection: ThemeSelection,
175        appearance: Appearance,
176        cx: &mut App,
177    ) {
178        let fs = <dyn Fs>::global(cx);
179
180        update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
181            settings.theme = Some(match theme_selection {
182                ThemeSelection::Static(theme_name) => ThemeSelection::Dynamic {
183                    mode: ThemeMode::System,
184                    light: theme_name.clone(),
185                    dark: theme_name.clone(),
186                },
187                ThemeSelection::Dynamic {
188                    mode: ThemeMode::System,
189                    light,
190                    dark,
191                } => {
192                    let mode = match appearance {
193                        Appearance::Light => ThemeMode::Light,
194                        Appearance::Dark => ThemeMode::Dark,
195                    };
196                    ThemeSelection::Dynamic { mode, light, dark }
197                }
198                ThemeSelection::Dynamic {
199                    mode: _,
200                    light,
201                    dark,
202                } => ThemeSelection::Dynamic {
203                    mode: ThemeMode::System,
204                    light,
205                    dark,
206                },
207            });
208        });
209    }
210}
211
212fn write_keymap_base(keymap_base: BaseKeymap, cx: &App) {
213    let fs = <dyn Fs>::global(cx);
214
215    update_settings_file::<BaseKeymap>(fs, cx, move |setting, _| {
216        *setting = Some(keymap_base);
217    });
218}
219
220fn render_telemetry_section(cx: &App) -> impl IntoElement {
221    let fs = <dyn Fs>::global(cx);
222
223    v_flex()
224        .gap_4()
225        .child(Label::new("Telemetry").size(LabelSize::Large))
226        .child(SwitchField::new(
227            "onboarding-telemetry-metrics",
228            "Help Improve Zed",
229            Some("Sending anonymous usage data helps us build the right features and create the best experience.".into()),
230            if TelemetrySettings::get_global(cx).metrics {
231                ui::ToggleState::Selected
232            } else {
233                ui::ToggleState::Unselected
234            },
235            {
236            let fs = fs.clone();
237            move |selection, _, cx| {
238                let enabled = match selection {
239                    ToggleState::Selected => true,
240                    ToggleState::Unselected => false,
241                    ToggleState::Indeterminate => { return; },
242                };
243
244                update_settings_file::<TelemetrySettings>(
245                    fs.clone(),
246                    cx,
247                    move |setting, _| setting.metrics = Some(enabled),
248                );
249            }},
250        ))
251        .child(SwitchField::new(
252            "onboarding-telemetry-crash-reports",
253            "Help Fix Zed",
254            Some("Send crash reports so we can fix critical issues fast.".into()),
255            if TelemetrySettings::get_global(cx).diagnostics {
256                ui::ToggleState::Selected
257            } else {
258                ui::ToggleState::Unselected
259            },
260            {
261                let fs = fs.clone();
262                move |selection, _, cx| {
263                    let enabled = match selection {
264                        ToggleState::Selected => true,
265                        ToggleState::Unselected => false,
266                        ToggleState::Indeterminate => { return; },
267                    };
268
269                    update_settings_file::<TelemetrySettings>(
270                        fs.clone(),
271                        cx,
272                        move |setting, _| setting.diagnostics = Some(enabled),
273                    );
274                }
275            }
276        ))
277}
278
279pub(crate) fn render_basics_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
280    let base_keymap = match BaseKeymap::get_global(cx) {
281        BaseKeymap::VSCode => Some(0),
282        BaseKeymap::JetBrains => Some(1),
283        BaseKeymap::SublimeText => Some(2),
284        BaseKeymap::Atom => Some(3),
285        BaseKeymap::Emacs => Some(4),
286        BaseKeymap::Cursor => Some(5),
287        BaseKeymap::TextMate | BaseKeymap::None => None,
288    };
289
290    v_flex()
291        .gap_6()
292         .child(render_theme_section(window, cx))
293        .child(
294            v_flex().gap_2().child(Label::new("Base Keymap")).child(
295                ToggleButtonGroup::two_rows(
296                    "multiple_row_test",
297                    [
298                        ToggleButtonWithIcon::new("VS Code", IconName::EditorVsCode, |_, _, cx| {
299                            write_keymap_base(BaseKeymap::VSCode, cx);
300                        }),
301                        ToggleButtonWithIcon::new("Jetbrains", IconName::EditorJetBrains, |_, _, cx| {
302                            write_keymap_base(BaseKeymap::JetBrains, cx);
303                        }),
304                        ToggleButtonWithIcon::new("Sublime Text", IconName::EditorSublime, |_, _, cx| {
305                            write_keymap_base(BaseKeymap::SublimeText, cx);
306                        }),
307                    ],
308                    [
309                        ToggleButtonWithIcon::new("Atom", IconName::EditorAtom, |_, _, cx| {
310                            write_keymap_base(BaseKeymap::Atom, cx);
311                        }),
312                        ToggleButtonWithIcon::new("Emacs", IconName::EditorEmacs, |_, _, cx| {
313                            write_keymap_base(BaseKeymap::Emacs, cx);
314                        }),
315                        ToggleButtonWithIcon::new("Cursor (Beta)", IconName::EditorCursor, |_, _, cx| {
316                            write_keymap_base(BaseKeymap::Cursor, cx);
317                        }),
318                    ],
319                )
320                .when_some(base_keymap, |this, base_keymap| this.selected_index(base_keymap))
321                .button_width(rems_from_px(216.))
322                .size(ui::ToggleButtonGroupSize::Medium)
323                .style(ui::ToggleButtonGroupStyle::Outlined)
324            ),
325        )
326        .child(SwitchField::new(
327            "onboarding-vim-mode",
328            "Vim Mode",
329            Some("Coming from Neovim? Zed's first-class implementation of Vim Mode has got your back.".into()),
330            if VimModeSetting::get_global(cx).0 {
331                ui::ToggleState::Selected
332            } else {
333                ui::ToggleState::Unselected
334            },
335            {
336                let fs = <dyn Fs>::global(cx);
337                move |selection, _, cx| {
338                    let enabled = match selection {
339                        ToggleState::Selected => true,
340                        ToggleState::Unselected => false,
341                        ToggleState::Indeterminate => { return; },
342                    };
343
344                    update_settings_file::<VimModeSetting>(
345                        fs.clone(),
346                        cx,
347                        move |setting, _| *setting = Some(enabled),
348                    );
349                }
350            },
351        ))
352        .child(render_telemetry_section(cx))
353}