Detailed changes
@@ -10535,20 +10535,15 @@ dependencies = [
name = "onboarding"
version = "0.1.0"
dependencies = [
- "ai_onboarding",
"anyhow",
"client",
"component",
"db",
"documented",
- "editor",
"fs",
"fuzzy",
"git",
"gpui",
- "itertools 0.14.0",
- "language",
- "language_model",
"menu",
"notifications",
"picker",
@@ -1229,9 +1229,6 @@
"context": "Onboarding",
"use_key_equivalents": true,
"bindings": {
- "ctrl-1": "onboarding::ActivateBasicsPage",
- "ctrl-2": "onboarding::ActivateEditingPage",
- "ctrl-3": "onboarding::ActivateAISetupPage",
"ctrl-enter": "onboarding::Finish",
"alt-shift-l": "onboarding::SignIn",
"alt-shift-a": "onboarding::OpenAccount"
@@ -1334,10 +1334,7 @@
"context": "Onboarding",
"use_key_equivalents": true,
"bindings": {
- "cmd-1": "onboarding::ActivateBasicsPage",
- "cmd-2": "onboarding::ActivateEditingPage",
- "cmd-3": "onboarding::ActivateAISetupPage",
- "cmd-escape": "onboarding::Finish",
+ "cmd-enter": "onboarding::Finish",
"alt-tab": "onboarding::SignIn",
"alt-shift-a": "onboarding::OpenAccount"
}
@@ -1257,9 +1257,6 @@
"context": "Onboarding",
"use_key_equivalents": true,
"bindings": {
- "ctrl-1": "onboarding::ActivateBasicsPage",
- "ctrl-2": "onboarding::ActivateEditingPage",
- "ctrl-3": "onboarding::ActivateAISetupPage",
"ctrl-enter": "onboarding::Finish",
"alt-shift-l": "onboarding::SignIn",
"shift-alt-a": "onboarding::OpenAccount"
@@ -409,7 +409,7 @@ impl AgentConfiguration {
SwitchField::new(
"always-allow-tool-actions-switch",
- "Allow running commands without asking for confirmation",
+ Some("Allow running commands without asking for confirmation"),
Some(
"The agent can perform potentially destructive actions without asking for your confirmation.".into(),
),
@@ -429,7 +429,7 @@ impl AgentConfiguration {
SwitchField::new(
"single-file-review",
- "Enable single-file agent reviews",
+ Some("Enable single-file agent reviews"),
Some("Agent edits are also displayed in single-file editors for review.".into()),
single_file_review,
move |state, _window, cx| {
@@ -450,7 +450,7 @@ impl AgentConfiguration {
SwitchField::new(
"sound-notification",
- "Play sound when finished generating",
+ Some("Play sound when finished generating"),
Some(
"Hear a notification sound when the agent is done generating changes or needs your input.".into(),
),
@@ -470,7 +470,7 @@ impl AgentConfiguration {
SwitchField::new(
"modifier-send",
- "Use modifier to submit a message",
+ Some("Use modifier to submit a message"),
Some(
"Make a modifier (cmd-enter on macOS, ctrl-enter on Linux or Windows) required to send messages.".into(),
),
@@ -58,7 +58,7 @@ mod prompts;
use crate::util::atomic_incr_if_not_zero;
pub use prompts::*;
-pub(crate) const DEFAULT_WINDOW_SIZE: Size<Pixels> = size(px(1024.), px(700.));
+pub(crate) const DEFAULT_WINDOW_SIZE: Size<Pixels> = size(px(1536.), px(864.));
/// Represents the two different phases when dispatching events.
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
@@ -15,20 +15,15 @@ path = "src/onboarding.rs"
default = []
[dependencies]
-ai_onboarding.workspace = true
anyhow.workspace = true
client.workspace = true
component.workspace = true
db.workspace = true
documented.workspace = true
-editor.workspace = true
fs.workspace = true
fuzzy.workspace = true
git.workspace = true
gpui.workspace = true
-itertools.workspace = true
-language.workspace = true
-language_model.workspace = true
menu.workspace = true
notifications.workspace = true
picker.workspace = true
@@ -1,427 +0,0 @@
-use std::sync::Arc;
-
-use ai_onboarding::AiUpsellCard;
-use client::{Client, UserStore, zed_urls};
-use fs::Fs;
-use gpui::{
- Action, AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity,
- Window, prelude::*,
-};
-use itertools;
-use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
-use project::DisableAiSettings;
-use settings::{Settings, update_settings_file};
-use ui::{
- Badge, ButtonLike, Divider, KeyBinding, Modal, ModalFooter, ModalHeader, Section, SwitchField,
- ToggleState, prelude::*, tooltip_container,
-};
-use util::ResultExt;
-use workspace::{ModalView, Workspace};
-use zed_actions::agent::OpenSettings;
-
-const FEATURED_PROVIDERS: [&str; 4] = ["anthropic", "google", "openai", "ollama"];
-
-fn render_llm_provider_section(
- tab_index: &mut isize,
- workspace: WeakEntity<Workspace>,
- disabled: bool,
- window: &mut Window,
- cx: &mut App,
-) -> impl IntoElement {
- v_flex()
- .gap_4()
- .child(
- v_flex()
- .child(Label::new("Or use other LLM providers").size(LabelSize::Large))
- .child(
- Label::new("Bring your API keys to use the available providers with Zed's UI for free.")
- .color(Color::Muted),
- ),
- )
- .child(render_llm_provider_card(tab_index, workspace, disabled, window, cx))
-}
-
-fn render_privacy_card(tab_index: &mut isize, disabled: bool, cx: &mut App) -> impl IntoElement {
- let (title, description) = if disabled {
- (
- "AI is disabled across Zed",
- "Re-enable it any time in Settings.",
- )
- } else {
- (
- "Privacy is the default for Zed",
- "Any use or storage of your data is with your explicit, single-use, opt-in consent.",
- )
- };
-
- v_flex()
- .relative()
- .pt_2()
- .pb_2p5()
- .pl_3()
- .pr_2()
- .border_1()
- .border_dashed()
- .border_color(cx.theme().colors().border.opacity(0.5))
- .bg(cx.theme().colors().surface_background.opacity(0.3))
- .rounded_lg()
- .overflow_hidden()
- .child(
- h_flex()
- .gap_2()
- .justify_between()
- .child(Label::new(title))
- .child(
- h_flex()
- .gap_1()
- .child(
- Badge::new("Privacy")
- .icon(IconName::ShieldCheck)
- .tooltip(move |_, cx| cx.new(|_| AiPrivacyTooltip::new()).into()),
- )
- .child(
- Button::new("learn_more", "Learn More")
- .style(ButtonStyle::Outlined)
- .label_size(LabelSize::Small)
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .on_click(|_, _, cx| {
- cx.open_url(&zed_urls::ai_privacy_and_security(cx))
- })
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- }),
- ),
- ),
- )
- .child(
- Label::new(description)
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
-}
-
-fn render_llm_provider_card(
- tab_index: &mut isize,
- workspace: WeakEntity<Workspace>,
- disabled: bool,
- _: &mut Window,
- cx: &mut App,
-) -> impl IntoElement {
- let registry = LanguageModelRegistry::read_global(cx);
-
- v_flex()
- .border_1()
- .border_color(cx.theme().colors().border)
- .bg(cx.theme().colors().surface_background.opacity(0.5))
- .rounded_lg()
- .overflow_hidden()
- .children(itertools::intersperse_with(
- FEATURED_PROVIDERS
- .into_iter()
- .flat_map(|provider_name| {
- registry.provider(&LanguageModelProviderId::new(provider_name))
- })
- .enumerate()
- .map(|(index, provider)| {
- let group_name = SharedString::new(format!("onboarding-hover-group-{}", index));
- let is_authenticated = provider.is_authenticated(cx);
-
- ButtonLike::new(("onboarding-ai-setup-buttons", index))
- .size(ButtonSize::Large)
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- })
- .child(
- h_flex()
- .group(&group_name)
- .px_0p5()
- .w_full()
- .gap_2()
- .justify_between()
- .child(
- h_flex()
- .gap_1()
- .child(
- Icon::new(provider.icon())
- .color(Color::Muted)
- .size(IconSize::XSmall),
- )
- .child(Label::new(provider.name().0)),
- )
- .child(
- h_flex()
- .gap_1()
- .when(!is_authenticated, |el| {
- el.visible_on_hover(group_name.clone())
- .child(
- Icon::new(IconName::Settings)
- .color(Color::Muted)
- .size(IconSize::XSmall),
- )
- .child(
- Label::new("Configure")
- .color(Color::Muted)
- .size(LabelSize::Small),
- )
- })
- .when(is_authenticated && !disabled, |el| {
- el.child(
- Icon::new(IconName::Check)
- .color(Color::Success)
- .size(IconSize::XSmall),
- )
- .child(
- Label::new("Configured")
- .color(Color::Muted)
- .size(LabelSize::Small),
- )
- }),
- ),
- )
- .on_click({
- let workspace = workspace.clone();
- move |_, window, cx| {
- workspace
- .update(cx, |workspace, cx| {
- workspace.toggle_modal(window, cx, |window, cx| {
- telemetry::event!(
- "Welcome AI Modal Opened",
- provider = provider.name().0,
- );
-
- let modal = AiConfigurationModal::new(
- provider.clone(),
- window,
- cx,
- );
- window.focus(&modal.focus_handle(cx));
- modal
- });
- })
- .log_err();
- }
- })
- .into_any_element()
- }),
- || Divider::horizontal().into_any_element(),
- ))
- .child(Divider::horizontal())
- .child(
- Button::new("agent_settings", "Add Many Others")
- .size(ButtonSize::Large)
- .icon(IconName::Plus)
- .icon_position(IconPosition::Start)
- .icon_color(Color::Muted)
- .icon_size(IconSize::XSmall)
- .on_click(|_event, window, cx| {
- window.dispatch_action(OpenSettings.boxed_clone(), cx)
- })
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- }),
- )
-}
-
-pub(crate) fn render_ai_setup_page(
- workspace: WeakEntity<Workspace>,
- user_store: Entity<UserStore>,
- client: Arc<Client>,
- window: &mut Window,
- cx: &mut App,
-) -> impl IntoElement {
- let mut tab_index = 0;
- let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
-
- v_flex()
- .gap_2()
- .child(
- SwitchField::new(
- "enable_ai",
- "Enable AI features",
- None,
- if is_ai_disabled {
- ToggleState::Unselected
- } else {
- ToggleState::Selected
- },
- |&toggle_state, _, cx| {
- let enabled = match toggle_state {
- ToggleState::Indeterminate => {
- return;
- }
- ToggleState::Unselected => true,
- ToggleState::Selected => false,
- };
-
- telemetry::event!(
- "Welcome AI Enabled",
- toggle = if enabled { "on" } else { "off" },
- );
-
- let fs = <dyn Fs>::global(cx);
- update_settings_file(fs, cx, move |settings, _| {
- settings.disable_ai = Some(enabled.into());
- });
- },
- )
- .tab_index({
- tab_index += 1;
- tab_index - 1
- }),
- )
- .child(render_privacy_card(&mut tab_index, is_ai_disabled, cx))
- .child(
- v_flex()
- .mt_2()
- .gap_6()
- .child(
- AiUpsellCard::new(client, &user_store, user_store.read(cx).plan(), cx)
- .tab_index(Some({
- tab_index += 1;
- tab_index - 1
- })),
- )
- .child(render_llm_provider_section(
- &mut tab_index,
- workspace,
- is_ai_disabled,
- window,
- cx,
- ))
- .when(is_ai_disabled, |this| {
- this.child(
- div()
- .id("backdrop")
- .size_full()
- .absolute()
- .inset_0()
- .bg(cx.theme().colors().editor_background)
- .opacity(0.8)
- .block_mouse_except_scroll(),
- )
- }),
- )
-}
-
-struct AiConfigurationModal {
- focus_handle: FocusHandle,
- selected_provider: Arc<dyn LanguageModelProvider>,
- configuration_view: AnyView,
-}
-
-impl AiConfigurationModal {
- fn new(
- selected_provider: Arc<dyn LanguageModelProvider>,
- window: &mut Window,
- cx: &mut Context<Self>,
- ) -> Self {
- let focus_handle = cx.focus_handle();
- let configuration_view = selected_provider.configuration_view(
- language_model::ConfigurationViewTargetAgent::ZedAgent,
- window,
- cx,
- );
-
- Self {
- focus_handle,
- configuration_view,
- selected_provider,
- }
- }
-
- fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context<Self>) {
- cx.emit(DismissEvent);
- }
-}
-
-impl ModalView for AiConfigurationModal {}
-
-impl EventEmitter<DismissEvent> for AiConfigurationModal {}
-
-impl Focusable for AiConfigurationModal {
- fn focus_handle(&self, _cx: &App) -> FocusHandle {
- self.focus_handle.clone()
- }
-}
-
-impl Render for AiConfigurationModal {
- fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- v_flex()
- .key_context("OnboardingAiConfigurationModal")
- .w(rems(34.))
- .elevation_3(cx)
- .track_focus(&self.focus_handle)
- .on_action(
- cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(&menu::Cancel, cx)),
- )
- .child(
- Modal::new("onboarding-ai-setup-modal", None)
- .header(
- ModalHeader::new()
- .icon(
- Icon::new(self.selected_provider.icon())
- .color(Color::Muted)
- .size(IconSize::Small),
- )
- .headline(self.selected_provider.name().0),
- )
- .section(Section::new().child(self.configuration_view.clone()))
- .footer(
- ModalFooter::new().end_slot(
- Button::new("ai-onb-modal-Done", "Done")
- .key_binding(
- KeyBinding::for_action_in(
- &menu::Cancel,
- &self.focus_handle.clone(),
- window,
- cx,
- )
- .map(|kb| kb.size(rems_from_px(12.))),
- )
- .on_click(cx.listener(|this, _event, _window, cx| {
- this.cancel(&menu::Cancel, cx)
- })),
- ),
- ),
- )
- }
-}
-
-pub struct AiPrivacyTooltip {}
-
-impl AiPrivacyTooltip {
- pub fn new() -> Self {
- Self {}
- }
-}
-
-impl Render for AiPrivacyTooltip {
- fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- const DESCRIPTION: &str = "We believe in opt-in data sharing as the default for building AI products, rather than opt-out. We'll only use or store your data if you affirmatively send it to us. ";
-
- tooltip_container(cx, move |this, _| {
- this.child(
- h_flex()
- .gap_1()
- .child(
- Icon::new(IconName::ShieldCheck)
- .size(IconSize::Small)
- .color(Color::Muted),
- )
- .child(Label::new("Privacy First")),
- )
- .child(
- div().max_w_64().child(
- Label::new(DESCRIPTION)
- .size(LabelSize::Small)
- .color(Color::Muted),
- ),
- )
- })
- }
-}
@@ -2,19 +2,23 @@ use std::sync::Arc;
use client::TelemetrySettings;
use fs::Fs;
-use gpui::{App, IntoElement};
+use gpui::{Action, App, IntoElement};
use settings::{BaseKeymap, Settings, update_settings_file};
use theme::{
Appearance, SystemAppearance, ThemeMode, ThemeName, ThemeRegistry, ThemeSelection,
ThemeSettings,
};
use ui::{
- ParentElement as _, StatefulInteractiveElement, SwitchField, ToggleButtonGroup,
- ToggleButtonSimple, ToggleButtonWithIcon, prelude::*, rems_from_px,
+ ButtonLike, ParentElement as _, StatefulInteractiveElement, SwitchField, TintColor,
+ ToggleButtonGroup, ToggleButtonGroupSize, ToggleButtonSimple, ToggleButtonWithIcon, prelude::*,
+ rems_from_px,
};
use vim_mode_setting::VimModeSetting;
-use crate::theme_preview::{ThemePreviewStyle, ThemePreviewTile};
+use crate::{
+ ImportCursorSettings, ImportVsCodeSettings, SettingsImportState,
+ theme_preview::{ThemePreviewStyle, ThemePreviewTile},
+};
const LIGHT_THEMES: [&str; 3] = ["One Light", "Ayu Light", "Gruvbox Light"];
const DARK_THEMES: [&str; 3] = ["One Dark", "Ayu Dark", "Gruvbox Dark"];
@@ -78,6 +82,7 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement
)
}),
)
+ .size(ToggleButtonGroupSize::Medium)
.tab_index(tab_index)
.selected_index(theme_mode as usize)
.style(ui::ToggleButtonGroupStyle::Outlined)
@@ -228,91 +233,87 @@ fn render_telemetry_section(tab_index: &mut isize, cx: &App) -> impl IntoElement
.gap_4()
.border_t_1()
.border_color(cx.theme().colors().border_variant.opacity(0.5))
- .child(Label::new("Telemetry").size(LabelSize::Large))
- .child(SwitchField::new(
- "onboarding-telemetry-metrics",
- "Help Improve Zed",
- Some("Anonymous usage data helps us build the right features and improve your experience.".into()),
- if TelemetrySettings::get_global(cx).metrics {
- ui::ToggleState::Selected
- } else {
- ui::ToggleState::Unselected
- },
- {
- let fs = fs.clone();
- move |selection, _, cx| {
- let enabled = match selection {
- ToggleState::Selected => true,
- ToggleState::Unselected => false,
- ToggleState::Indeterminate => { return; },
- };
-
- update_settings_file(
- fs.clone(),
- cx,
- move |setting, _| {
- setting.telemetry.get_or_insert_default().metrics = Some(enabled);
- }
- ,
- );
-
- // This telemetry event shouldn't fire when it's off. If it does we'll be alerted
- // and can fix it in a timely manner to respect a user's choice.
- telemetry::event!("Welcome Page Telemetry Metrics Toggled",
- options = if enabled {
- "on"
- } else {
- "off"
+ .child(
+ SwitchField::new(
+ "onboarding-telemetry-metrics",
+ None::<&str>,
+ Some("Help improve Zed by sending anonymous usage data".into()),
+ if TelemetrySettings::get_global(cx).metrics {
+ ui::ToggleState::Selected
+ } else {
+ ui::ToggleState::Unselected
+ },
+ {
+ let fs = fs.clone();
+ move |selection, _, cx| {
+ let enabled = match selection {
+ ToggleState::Selected => true,
+ ToggleState::Unselected => false,
+ ToggleState::Indeterminate => {
+ return;
+ }
+ };
+
+ update_settings_file(fs.clone(), cx, move |setting, _| {
+ setting.telemetry.get_or_insert_default().metrics = Some(enabled);
+ });
+
+ // This telemetry event shouldn't fire when it's off. If it does we'll be alerted
+ // and can fix it in a timely manner to respect a user's choice.
+ telemetry::event!(
+ "Welcome Page Telemetry Metrics Toggled",
+ options = if enabled { "on" } else { "off" }
+ );
}
- );
+ },
+ )
+ .tab_index({
+ *tab_index += 1;
+ *tab_index
+ }),
+ )
+ .child(
+ SwitchField::new(
+ "onboarding-telemetry-crash-reports",
+ None::<&str>,
+ Some(
+ "Help fix Zed by sending crash reports so we can fix critical issues fast"
+ .into(),
+ ),
+ if TelemetrySettings::get_global(cx).diagnostics {
+ ui::ToggleState::Selected
+ } else {
+ ui::ToggleState::Unselected
+ },
+ {
+ let fs = fs.clone();
+ move |selection, _, cx| {
+ let enabled = match selection {
+ ToggleState::Selected => true,
+ ToggleState::Unselected => false,
+ ToggleState::Indeterminate => {
+ return;
+ }
+ };
- }},
- ).tab_index({
- *tab_index += 1;
- *tab_index
- }))
- .child(SwitchField::new(
- "onboarding-telemetry-crash-reports",
- "Help Fix Zed",
- Some("Send crash reports so we can fix critical issues fast.".into()),
- if TelemetrySettings::get_global(cx).diagnostics {
- ui::ToggleState::Selected
- } else {
- ui::ToggleState::Unselected
- },
- {
- let fs = fs.clone();
- move |selection, _, cx| {
- let enabled = match selection {
- ToggleState::Selected => true,
- ToggleState::Unselected => false,
- ToggleState::Indeterminate => { return; },
- };
-
- update_settings_file(
- fs.clone(),
- cx,
- move |setting, _| {
+ update_settings_file(fs.clone(), cx, move |setting, _| {
setting.telemetry.get_or_insert_default().diagnostics = Some(enabled);
- },
-
- );
-
- // This telemetry event shouldn't fire when it's off. If it does we'll be alerted
- // and can fix it in a timely manner to respect a user's choice.
- telemetry::event!("Welcome Page Telemetry Diagnostics Toggled",
- options = if enabled {
- "on"
- } else {
- "off"
- }
- );
- }
- }
- ).tab_index({
- *tab_index += 1;
- *tab_index
- }))
+ });
+
+ // This telemetry event shouldn't fire when it's off. If it does we'll be alerted
+ // and can fix it in a timely manner to respect a user's choice.
+ telemetry::event!(
+ "Welcome Page Telemetry Diagnostics Toggled",
+ options = if enabled { "on" } else { "off" }
+ );
+ }
+ },
+ )
+ .tab_index({
+ *tab_index += 1;
+ *tab_index
+ }),
+ )
}
fn render_base_keymap_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement {
@@ -380,8 +381,8 @@ fn render_vim_mode_switch(tab_index: &mut isize, cx: &mut App) -> impl IntoEleme
};
SwitchField::new(
"onboarding-vim-mode",
- "Vim Mode",
- Some("Coming from Neovim? Use our first-class implementation of Vim Mode.".into()),
+ Some("Vim Mode"),
+ Some("Coming from Neovim? Use our first-class implementation of Vim Mode".into()),
toggle_state,
{
let fs = <dyn Fs>::global(cx);
@@ -410,12 +411,79 @@ fn render_vim_mode_switch(tab_index: &mut isize, cx: &mut App) -> impl IntoEleme
})
}
+fn render_setting_import_button(
+ tab_index: isize,
+ label: SharedString,
+ action: &dyn Action,
+ imported: bool,
+) -> impl IntoElement + 'static {
+ let action = action.boxed_clone();
+ h_flex().w_full().child(
+ ButtonLike::new(label.clone())
+ .style(ButtonStyle::OutlinedTransparent)
+ .selected_style(ButtonStyle::Tinted(TintColor::Accent))
+ .toggle_state(imported)
+ .size(ButtonSize::Medium)
+ .tab_index(tab_index)
+ .child(
+ h_flex()
+ .w_full()
+ .justify_between()
+ .when(imported, |this| {
+ this.child(Icon::new(IconName::Check).color(Color::Success))
+ })
+ .child(Label::new(label.clone()).mx_2().size(LabelSize::Small)),
+ )
+ .on_click(move |_, window, cx| {
+ telemetry::event!("Welcome Import Settings", import_source = label,);
+ window.dispatch_action(action.boxed_clone(), cx);
+ }),
+ )
+}
+
+fn render_import_settings_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement {
+ let import_state = SettingsImportState::global(cx);
+ let imports: [(SharedString, &dyn Action, bool); 2] = [
+ (
+ "VS Code".into(),
+ &ImportVsCodeSettings { skip_prompt: false },
+ import_state.vscode,
+ ),
+ (
+ "Cursor".into(),
+ &ImportCursorSettings { skip_prompt: false },
+ import_state.cursor,
+ ),
+ ];
+
+ let [vscode, cursor] = imports.map(|(label, action, imported)| {
+ *tab_index += 1;
+ render_setting_import_button(*tab_index - 1, label, action, imported)
+ });
+
+ h_flex()
+ .child(
+ v_flex()
+ .gap_0p5()
+ .max_w_5_6()
+ .child(Label::new("Import Settings"))
+ .child(
+ Label::new("Automatically pull your settings from other editors")
+ .color(Color::Muted),
+ ),
+ )
+ .child(div().w_full())
+ .child(h_flex().gap_1().child(vscode).child(cursor))
+}
+
pub(crate) fn render_basics_page(cx: &mut App) -> impl IntoElement {
let mut tab_index = 0;
v_flex()
+ .id("basics-page")
.gap_6()
.child(render_theme_section(&mut tab_index, cx))
.child(render_base_keymap_section(&mut tab_index, cx))
+ .child(render_import_settings_section(&mut tab_index, cx))
.child(render_vim_mode_switch(&mut tab_index, cx))
.child(render_telemetry_section(&mut tab_index, cx))
}
@@ -1,611 +0,0 @@
-use std::sync::Arc;
-
-use editor::{EditorSettings, ShowMinimap};
-use fs::Fs;
-use gpui::{Action, App, FontFeatures, IntoElement, Pixels, SharedString, Window};
-use language::language_settings::{AllLanguageSettings, FormatOnSave};
-use project::project_settings::ProjectSettings;
-use settings::{Settings as _, update_settings_file};
-use theme::{FontFamilyName, ThemeSettings};
-use ui::{
- ButtonLike, PopoverMenu, SwitchField, ToggleButtonGroup, ToggleButtonGroupStyle,
- ToggleButtonSimple, ToggleState, Tooltip, prelude::*,
-};
-use ui_input::{NumberField, font_picker};
-
-use crate::{ImportCursorSettings, ImportVsCodeSettings, SettingsImportState};
-
-fn read_show_mini_map(cx: &App) -> ShowMinimap {
- editor::EditorSettings::get_global(cx).minimap.show
-}
-
-fn write_show_mini_map(show: ShowMinimap, cx: &mut App) {
- let fs = <dyn Fs>::global(cx);
-
- // This is used to speed up the UI
- // the UI reads the current values to get what toggle state to show on buttons
- // there's a slight delay if we just call update_settings_file so we manually set
- // the value here then call update_settings file to get around the delay
- let mut curr_settings = EditorSettings::get_global(cx).clone();
- curr_settings.minimap.show = show;
- EditorSettings::override_global(curr_settings, cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- telemetry::event!(
- "Welcome Minimap Clicked",
- from = settings.editor.minimap.clone().unwrap_or_default(),
- to = show
- );
- settings.editor.minimap.get_or_insert_default().show = Some(show);
- });
-}
-
-fn read_inlay_hints(cx: &App) -> bool {
- AllLanguageSettings::get_global(cx)
- .defaults
- .inlay_hints
- .enabled
-}
-
-fn write_inlay_hints(enabled: bool, cx: &mut App) {
- let fs = <dyn Fs>::global(cx);
-
- let mut curr_settings = AllLanguageSettings::get_global(cx).clone();
- curr_settings.defaults.inlay_hints.enabled = enabled;
- AllLanguageSettings::override_global(curr_settings, cx);
-
- update_settings_file(fs, cx, move |settings, _cx| {
- settings
- .project
- .all_languages
- .defaults
- .inlay_hints
- .get_or_insert_default()
- .enabled = Some(enabled);
- });
-}
-
-fn read_git_blame(cx: &App) -> bool {
- ProjectSettings::get_global(cx).git.inline_blame.enabled
-}
-
-fn write_git_blame(enabled: bool, cx: &mut App) {
- let fs = <dyn Fs>::global(cx);
-
- let mut curr_settings = ProjectSettings::get_global(cx).clone();
- curr_settings.git.inline_blame.enabled = enabled;
- ProjectSettings::override_global(curr_settings, cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- settings
- .git
- .get_or_insert_default()
- .inline_blame
- .get_or_insert_default()
- .enabled = Some(enabled);
- });
-}
-
-fn write_ui_font_family(font: SharedString, cx: &mut App) {
- let fs = <dyn Fs>::global(cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- telemetry::event!(
- "Welcome Font Changed",
- type = "ui font",
- old = settings.theme.ui_font_family,
- new = font
- );
- settings.theme.ui_font_family = Some(FontFamilyName(font.into()));
- });
-}
-
-fn write_ui_font_size(size: Pixels, cx: &mut App) {
- let fs = <dyn Fs>::global(cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- settings.theme.ui_font_size = Some(size.into());
- });
-}
-
-fn write_buffer_font_size(size: Pixels, cx: &mut App) {
- let fs = <dyn Fs>::global(cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- settings.theme.buffer_font_size = Some(size.into());
- });
-}
-
-fn write_buffer_font_family(font_family: SharedString, cx: &mut App) {
- let fs = <dyn Fs>::global(cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- telemetry::event!(
- "Welcome Font Changed",
- type = "editor font",
- old = settings.theme.buffer_font_family,
- new = font_family
- );
-
- settings.theme.buffer_font_family = Some(FontFamilyName(font_family.into()));
- });
-}
-
-fn read_font_ligatures(cx: &App) -> bool {
- ThemeSettings::get_global(cx)
- .buffer_font
- .features
- .is_calt_enabled()
- .unwrap_or(true)
-}
-
-fn write_font_ligatures(enabled: bool, cx: &mut App) {
- let fs = <dyn Fs>::global(cx);
- let bit = if enabled { 1 } else { 0 };
-
- update_settings_file(fs, cx, move |settings, _| {
- let mut features = settings
- .theme
- .buffer_font_features
- .as_mut()
- .map(|features| features.tag_value_list().to_vec())
- .unwrap_or_default();
-
- if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
- features[calt_index].1 = bit;
- } else {
- features.push(("calt".into(), bit));
- }
-
- settings.theme.buffer_font_features = Some(FontFeatures(Arc::new(features)));
- });
-}
-
-fn read_format_on_save(cx: &App) -> bool {
- match AllLanguageSettings::get_global(cx).defaults.format_on_save {
- FormatOnSave::On => true,
- FormatOnSave::Off => false,
- }
-}
-
-fn write_format_on_save(format_on_save: bool, cx: &mut App) {
- let fs = <dyn Fs>::global(cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- settings.project.all_languages.defaults.format_on_save = Some(match format_on_save {
- true => FormatOnSave::On,
- false => FormatOnSave::Off,
- });
- });
-}
-
-fn render_setting_import_button(
- tab_index: isize,
- label: SharedString,
- icon_name: IconName,
- action: &dyn Action,
- imported: bool,
-) -> impl IntoElement {
- let action = action.boxed_clone();
- h_flex().w_full().child(
- ButtonLike::new(label.clone())
- .full_width()
- .style(ButtonStyle::Outlined)
- .size(ButtonSize::Large)
- .tab_index(tab_index)
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .child(
- h_flex()
- .gap_1p5()
- .px_1()
- .child(
- Icon::new(icon_name)
- .color(Color::Muted)
- .size(IconSize::XSmall),
- )
- .child(Label::new(label.clone())),
- )
- .when(imported, |this| {
- this.child(
- h_flex()
- .gap_1p5()
- .child(
- Icon::new(IconName::Check)
- .color(Color::Success)
- .size(IconSize::XSmall),
- )
- .child(Label::new("Imported").size(LabelSize::Small)),
- )
- }),
- )
- .on_click(move |_, window, cx| {
- telemetry::event!("Welcome Import Settings", import_source = label,);
- window.dispatch_action(action.boxed_clone(), cx);
- }),
- )
-}
-
-fn render_import_settings_section(tab_index: &mut isize, cx: &App) -> impl IntoElement {
- let import_state = SettingsImportState::global(cx);
- let imports: [(SharedString, IconName, &dyn Action, bool); 2] = [
- (
- "VS Code".into(),
- IconName::EditorVsCode,
- &ImportVsCodeSettings { skip_prompt: false },
- import_state.vscode,
- ),
- (
- "Cursor".into(),
- IconName::EditorCursor,
- &ImportCursorSettings { skip_prompt: false },
- import_state.cursor,
- ),
- ];
-
- let [vscode, cursor] = imports.map(|(label, icon_name, action, imported)| {
- *tab_index += 1;
- render_setting_import_button(*tab_index - 1, label, icon_name, action, imported)
- });
-
- v_flex()
- .gap_4()
- .child(
- v_flex()
- .child(Label::new("Import Settings").size(LabelSize::Large))
- .child(
- Label::new("Automatically pull your settings from other editors.")
- .color(Color::Muted),
- ),
- )
- .child(h_flex().w_full().gap_4().child(vscode).child(cursor))
-}
-
-fn render_font_customization_section(
- tab_index: &mut isize,
- window: &mut Window,
- cx: &mut App,
-) -> impl IntoElement {
- let theme_settings = ThemeSettings::get_global(cx);
- let ui_font_size = theme_settings.ui_font_size(cx);
- let ui_font_family = theme_settings.ui_font.family.clone();
- let buffer_font_family = theme_settings.buffer_font.family.clone();
- let buffer_font_size = theme_settings.buffer_font_size(cx);
-
- let ui_font_picker =
- cx.new(|cx| font_picker(ui_font_family.clone(), write_ui_font_family, window, cx));
-
- let buffer_font_picker = cx.new(|cx| {
- font_picker(
- buffer_font_family.clone(),
- write_buffer_font_family,
- window,
- cx,
- )
- });
-
- let ui_font_handle = ui::PopoverMenuHandle::default();
- let buffer_font_handle = ui::PopoverMenuHandle::default();
-
- h_flex()
- .w_full()
- .gap_4()
- .child(
- v_flex()
- .w_full()
- .gap_1()
- .child(Label::new("UI Font"))
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .gap_2()
- .child(
- PopoverMenu::new("ui-font-picker")
- .menu({
- let ui_font_picker = ui_font_picker;
- move |_window, _cx| Some(ui_font_picker.clone())
- })
- .trigger(
- ButtonLike::new("ui-font-family-button")
- .style(ButtonStyle::Outlined)
- .size(ButtonSize::Medium)
- .full_width()
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- })
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .child(Label::new(ui_font_family))
- .child(
- Icon::new(IconName::ChevronUpDown)
- .color(Color::Muted)
- .size(IconSize::XSmall),
- ),
- ),
- )
- .full_width(true)
- .anchor(gpui::Corner::TopLeft)
- .offset(gpui::Point {
- x: px(0.0),
- y: px(4.0),
- })
- .with_handle(ui_font_handle),
- )
- .child(font_picker_stepper(
- "ui-font-size",
- &ui_font_size,
- tab_index,
- write_ui_font_size,
- window,
- cx,
- )),
- ),
- )
- .child(
- v_flex()
- .w_full()
- .gap_1()
- .child(Label::new("Editor Font"))
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .gap_2()
- .child(
- PopoverMenu::new("buffer-font-picker")
- .menu({
- let buffer_font_picker = buffer_font_picker;
- move |_window, _cx| Some(buffer_font_picker.clone())
- })
- .trigger(
- ButtonLike::new("buffer-font-family-button")
- .style(ButtonStyle::Outlined)
- .size(ButtonSize::Medium)
- .full_width()
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- })
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .child(Label::new(buffer_font_family))
- .child(
- Icon::new(IconName::ChevronUpDown)
- .color(Color::Muted)
- .size(IconSize::XSmall),
- ),
- ),
- )
- .full_width(true)
- .anchor(gpui::Corner::TopLeft)
- .offset(gpui::Point {
- x: px(0.0),
- y: px(4.0),
- })
- .with_handle(buffer_font_handle),
- )
- .child(font_picker_stepper(
- "buffer-font-size",
- &buffer_font_size,
- tab_index,
- write_buffer_font_size,
- window,
- cx,
- )),
- ),
- )
-}
-
-fn font_picker_stepper(
- id: &'static str,
- font_size: &Pixels,
- tab_index: &mut isize,
- write_font_size: fn(Pixels, &mut App),
- window: &mut Window,
- cx: &mut App,
-) -> NumberField<u32> {
- window.with_id(id, |window| {
- let optimistic_font_size: gpui::Entity<Option<u32>> = window.use_state(cx, |_, _| None);
- optimistic_font_size.update(cx, |optimistic_font_size, _| {
- if let Some(optimistic_font_size_val) = optimistic_font_size {
- if *optimistic_font_size_val == u32::from(font_size) {
- *optimistic_font_size = None;
- }
- }
- });
-
- let stepper_font_size = optimistic_font_size
- .read(cx)
- .unwrap_or_else(|| font_size.into());
-
- NumberField::new(
- SharedString::new(format!("{}-stepper", id)),
- stepper_font_size,
- window,
- cx,
- )
- .on_change(move |new_value, _, cx| {
- optimistic_font_size.write(cx, Some(*new_value));
- write_font_size(Pixels::from(*new_value), cx);
- })
- .format(|value| format!("{value}px"))
- .tab_index({
- *tab_index += 2;
- *tab_index - 2
- })
- .min(6)
- .max(32)
- })
-}
-
-fn render_popular_settings_section(
- tab_index: &mut isize,
- window: &mut Window,
- cx: &mut App,
-) -> impl IntoElement {
- const LIGATURE_TOOLTIP: &str =
- "Font ligatures combine two characters into one. For example, turning != into ≠.";
-
- v_flex()
- .pt_6()
- .gap_4()
- .border_t_1()
- .border_color(cx.theme().colors().border_variant.opacity(0.5))
- .child(Label::new("Popular Settings").size(LabelSize::Large))
- .child(render_font_customization_section(tab_index, window, cx))
- .child(
- SwitchField::new(
- "onboarding-font-ligatures",
- "Font Ligatures",
- Some("Combine text characters into their associated symbols.".into()),
- if read_font_ligatures(cx) {
- ui::ToggleState::Selected
- } else {
- ui::ToggleState::Unselected
- },
- |toggle_state, _, cx| {
- let enabled = toggle_state == &ToggleState::Selected;
- telemetry::event!(
- "Welcome Font Ligature",
- options = if enabled { "on" } else { "off" },
- );
-
- write_font_ligatures(enabled, cx);
- },
- )
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- })
- .tooltip(Tooltip::text(LIGATURE_TOOLTIP)),
- )
- .child(
- SwitchField::new(
- "onboarding-format-on-save",
- "Format on Save",
- Some("Format code automatically when saving.".into()),
- if read_format_on_save(cx) {
- ui::ToggleState::Selected
- } else {
- ui::ToggleState::Unselected
- },
- |toggle_state, _, cx| {
- let enabled = toggle_state == &ToggleState::Selected;
- telemetry::event!(
- "Welcome Format On Save Changed",
- options = if enabled { "on" } else { "off" },
- );
-
- write_format_on_save(enabled, cx);
- },
- )
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- }),
- )
- .child(
- SwitchField::new(
- "onboarding-enable-inlay-hints",
- "Inlay Hints",
- Some("See parameter names for function and method calls inline.".into()),
- if read_inlay_hints(cx) {
- ui::ToggleState::Selected
- } else {
- ui::ToggleState::Unselected
- },
- |toggle_state, _, cx| {
- let enabled = toggle_state == &ToggleState::Selected;
- telemetry::event!(
- "Welcome Inlay Hints Changed",
- options = if enabled { "on" } else { "off" },
- );
-
- write_inlay_hints(enabled, cx);
- },
- )
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- }),
- )
- .child(
- SwitchField::new(
- "onboarding-git-blame-switch",
- "Inline Git Blame",
- Some("See who committed each line on a given file.".into()),
- if read_git_blame(cx) {
- ui::ToggleState::Selected
- } else {
- ui::ToggleState::Unselected
- },
- |toggle_state, _, cx| {
- let enabled = toggle_state == &ToggleState::Selected;
- telemetry::event!(
- "Welcome Git Blame Changed",
- options = if enabled { "on" } else { "off" },
- );
-
- write_git_blame(enabled, cx);
- },
- )
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- }),
- )
- .child(
- h_flex()
- .items_start()
- .justify_between()
- .child(
- v_flex().child(Label::new("Minimap")).child(
- Label::new("See a high-level overview of your source code.")
- .color(Color::Muted),
- ),
- )
- .child(
- ToggleButtonGroup::single_row(
- "onboarding-show-mini-map",
- [
- ToggleButtonSimple::new("Auto", |_, _, cx| {
- write_show_mini_map(ShowMinimap::Auto, cx);
- })
- .tooltip(Tooltip::text(
- "Show the minimap if the editor's scrollbar is visible.",
- )),
- ToggleButtonSimple::new("Always", |_, _, cx| {
- write_show_mini_map(ShowMinimap::Always, cx);
- }),
- ToggleButtonSimple::new("Never", |_, _, cx| {
- write_show_mini_map(ShowMinimap::Never, cx);
- }),
- ],
- )
- .selected_index(match read_show_mini_map(cx) {
- ShowMinimap::Auto => 0,
- ShowMinimap::Always => 1,
- ShowMinimap::Never => 2,
- })
- .tab_index(tab_index)
- .style(ToggleButtonGroupStyle::Outlined)
- .width(ui::rems_from_px(3. * 64.)),
- ),
- )
-}
-
-pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
- let mut tab_index = 0;
- v_flex()
- .gap_6()
- .child(render_import_settings_section(&mut tab_index, cx))
- .child(render_popular_settings_section(&mut tab_index, window, cx))
-}
@@ -14,8 +14,8 @@ use serde::Deserialize;
use settings::{SettingsStore, VsCodeSettingsSource};
use std::sync::Arc;
use ui::{
- Avatar, ButtonLike, FluentBuilder, Headline, KeyBinding, ParentElement as _,
- StatefulInteractiveElement, Vector, VectorName, WithScrollbar, prelude::*, rems_from_px,
+ KeyBinding, ParentElement as _, StatefulInteractiveElement, Vector, VectorName,
+ WithScrollbar as _, prelude::*, rems_from_px,
};
pub use ui_input::font_picker;
use workspace::{
@@ -26,10 +26,8 @@ use workspace::{
open_new, register_serializable_item, with_active_or_new_workspace,
};
-mod ai_setup_page;
mod base_keymap_picker;
mod basics_page;
-mod editing_page;
pub mod multibuffer_hint;
mod theme_preview;
mod welcome;
@@ -66,12 +64,6 @@ actions!(
actions!(
onboarding,
[
- /// Activates the Basics page.
- ActivateBasicsPage,
- /// Activates the Editing page.
- ActivateEditingPage,
- /// Activates the AI Setup page.
- ActivateAISetupPage,
/// Finish the onboarding process.
Finish,
/// Sign in while in the onboarding flow.
@@ -216,27 +208,9 @@ pub fn show_onboarding_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyh
)
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum SelectedPage {
- Basics,
- Editing,
- AiSetup,
-}
-
-impl SelectedPage {
- fn name(&self) -> &'static str {
- match self {
- SelectedPage::Basics => "Basics",
- SelectedPage::Editing => "Editing",
- SelectedPage::AiSetup => "AI Setup",
- }
- }
-}
-
struct Onboarding {
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
- selected_page: SelectedPage,
user_store: Entity<UserStore>,
scroll_handle: ScrollHandle,
_settings_subscription: Subscription,
@@ -259,7 +233,6 @@ impl Onboarding {
workspace: workspace.weak_handle(),
focus_handle: cx.focus_handle(),
scroll_handle: ScrollHandle::new(),
- selected_page: SelectedPage::Basics,
user_store: workspace.user_store().clone(),
_settings_subscription: cx
.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
@@ -267,228 +240,8 @@ impl Onboarding {
})
}
- fn set_page(
- &mut self,
- page: SelectedPage,
- clicked: Option<&'static str>,
- cx: &mut Context<Self>,
- ) {
- if let Some(click) = clicked {
- telemetry::event!(
- "Welcome Tab Clicked",
- from = self.selected_page.name(),
- to = page.name(),
- clicked = click,
- );
- }
-
- self.selected_page = page;
- self.scroll_handle.set_offset(Default::default());
- cx.notify();
- cx.emit(ItemEvent::UpdateTab);
- }
-
- fn render_nav_buttons(
- &mut self,
- window: &mut Window,
- cx: &mut Context<Self>,
- ) -> [impl IntoElement; 3] {
- let pages = [
- SelectedPage::Basics,
- SelectedPage::Editing,
- SelectedPage::AiSetup,
- ];
-
- let text = ["Basics", "Editing", "AI Setup"];
-
- let actions: [&dyn Action; 3] = [
- &ActivateBasicsPage,
- &ActivateEditingPage,
- &ActivateAISetupPage,
- ];
-
- let mut binding = actions.map(|action| {
- KeyBinding::for_action_in(action, &self.focus_handle, window, cx)
- .map(|kb| kb.size(rems_from_px(12.)))
- });
-
- pages.map(|page| {
- let i = page as usize;
- let selected = self.selected_page == page;
- h_flex()
- .id(text[i])
- .relative()
- .w_full()
- .gap_2()
- .px_2()
- .py_0p5()
- .justify_between()
- .rounded_sm()
- .when(selected, |this| {
- this.child(
- div()
- .h_4()
- .w_px()
- .bg(cx.theme().colors().text_accent)
- .absolute()
- .left_0(),
- )
- })
- .hover(|style| style.bg(cx.theme().colors().element_hover))
- .child(Label::new(text[i]).map(|this| {
- if selected {
- this.color(Color::Default)
- } else {
- this.color(Color::Muted)
- }
- }))
- .child(binding[i].take().map_or(
- gpui::Empty.into_any_element(),
- IntoElement::into_any_element,
- ))
- .on_click(cx.listener(move |this, click_event, _, cx| {
- let click = match click_event {
- gpui::ClickEvent::Mouse(_) => "mouse",
- gpui::ClickEvent::Keyboard(_) => "keyboard",
- };
-
- this.set_page(page, Some(click), cx);
- }))
- })
- }
-
- fn render_nav(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- v_flex()
- .h_full()
- .w(rems_from_px(220.))
- .flex_shrink_0()
- .gap_4()
- .justify_between()
- .child(
- v_flex()
- .gap_6()
- .child(
- h_flex()
- .px_2()
- .gap_4()
- .child(Vector::square(VectorName::ZedLogo, rems(2.5)))
- .child(
- v_flex()
- .child(
- Headline::new("Welcome to Zed").size(HeadlineSize::Small),
- )
- .child(
- Label::new("The editor for what's next")
- .color(Color::Muted)
- .size(LabelSize::Small)
- .italic(),
- ),
- ),
- )
- .child(
- v_flex()
- .gap_4()
- .child(
- v_flex()
- .py_4()
- .border_y_1()
- .border_color(cx.theme().colors().border_variant.opacity(0.5))
- .gap_1()
- .children(self.render_nav_buttons(window, cx)),
- )
- .map(|this| {
- if let Some(user) = self.user_store.read(cx).current_user() {
- this.child(
- v_flex()
- .gap_1()
- .child(
- h_flex()
- .ml_2()
- .gap_2()
- .max_w_full()
- .w_full()
- .child(Avatar::new(user.avatar_uri.clone()))
- .child(
- Label::new(user.github_login.clone())
- .truncate(),
- ),
- )
- .child(
- ButtonLike::new("open_account")
- .size(ButtonSize::Medium)
- .child(
- h_flex()
- .ml_1()
- .w_full()
- .justify_between()
- .child(Label::new("Open Account"))
- .children(
- KeyBinding::for_action_in(
- &OpenAccount,
- &self.focus_handle,
- window,
- cx,
- )
- .map(|kb| {
- kb.size(rems_from_px(12.))
- }),
- ),
- )
- .on_click(|_, window, cx| {
- window.dispatch_action(
- OpenAccount.boxed_clone(),
- cx,
- );
- }),
- ),
- )
- } else {
- this.child(
- ButtonLike::new("sign_in")
- .size(ButtonSize::Medium)
- .child(
- h_flex()
- .ml_1()
- .w_full()
- .justify_between()
- .child(Label::new("Sign In"))
- .children(
- KeyBinding::for_action_in(
- &SignIn,
- &self.focus_handle,
- window,
- cx,
- )
- .map(|kb| kb.size(rems_from_px(12.))),
- ),
- )
- .on_click(|_, window, cx| {
- telemetry::event!("Welcome Sign In Clicked");
- window.dispatch_action(SignIn.boxed_clone(), cx);
- }),
- )
- }
- }),
- ),
- )
- .child({
- Button::new("start_building", "Start Building")
- .full_width()
- .style(ButtonStyle::Outlined)
- .size(ButtonSize::Medium)
- .key_binding(
- KeyBinding::for_action_in(&Finish, &self.focus_handle, window, cx)
- .map(|kb| kb.size(rems_from_px(12.))),
- )
- .on_click(|_, window, cx| {
- telemetry::event!("Welcome Start Building Clicked");
- window.dispatch_action(Finish.boxed_clone(), cx);
- })
- })
- }
-
fn on_finish(_: &Finish, _: &mut Window, cx: &mut App) {
- telemetry::event!("Welcome Skip Clicked");
+ telemetry::event!("Finish Setup");
go_to_welcome_page(cx);
}
@@ -509,29 +262,14 @@ impl Onboarding {
cx.open_url(&zed_urls::account_url(cx))
}
- fn render_page(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
- let client = Client::global(cx);
-
- match self.selected_page {
- SelectedPage::Basics => crate::basics_page::render_basics_page(cx).into_any_element(),
- SelectedPage::Editing => {
- crate::editing_page::render_editing_page(window, cx).into_any_element()
- }
- SelectedPage::AiSetup => crate::ai_setup_page::render_ai_setup_page(
- self.workspace.clone(),
- self.user_store.clone(),
- client,
- window,
- cx,
- )
- .into_any_element(),
- }
+ fn render_page(&mut self, cx: &mut Context<Self>) -> AnyElement {
+ crate::basics_page::render_basics_page(cx).into_any_element()
}
}
impl Render for Onboarding {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- h_flex()
+ div()
.image_cache(gpui::retain_all("onboarding-page"))
.key_context({
let mut ctx = KeyContext::new_with_defaults();
@@ -545,15 +283,6 @@ impl Render for Onboarding {
.on_action(Self::on_finish)
.on_action(Self::handle_sign_in)
.on_action(Self::handle_open_account)
- .on_action(cx.listener(|this, _: &ActivateBasicsPage, _, cx| {
- this.set_page(SelectedPage::Basics, Some("action"), cx);
- }))
- .on_action(cx.listener(|this, _: &ActivateEditingPage, _, cx| {
- this.set_page(SelectedPage::Editing, Some("action"), cx);
- }))
- .on_action(cx.listener(|this, _: &ActivateAISetupPage, _, cx| {
- this.set_page(SelectedPage::AiSetup, Some("action"), cx);
- }))
.on_action(cx.listener(|_, _: &menu::SelectNext, window, cx| {
window.focus_next();
cx.notify();
@@ -563,35 +292,68 @@ impl Render for Onboarding {
cx.notify();
}))
.child(
- h_flex()
- .max_w(rems_from_px(1100.))
- .max_h(rems_from_px(850.))
+ div()
+ .max_w(Rems(48.0))
+ .w_full()
+ .mx_auto()
.size_full()
- .m_auto()
- .py_20()
- .px_12()
- .items_start()
- .gap_12()
- .child(self.render_nav(window, cx))
+ .gap_6()
.child(
- div()
+ v_flex()
+ .m_auto()
+ .id("page-content")
+ .gap_6()
.size_full()
- .pr_6()
+ .max_w_full()
+ .min_w_0()
+ .p_12()
+ .border_color(cx.theme().colors().border_variant.opacity(0.5))
+ .overflow_y_scroll()
.child(
- v_flex()
- .id("page-content")
- .size_full()
- .max_w_full()
- .min_w_0()
- .pl_12()
- .border_l_1()
- .border_color(cx.theme().colors().border_variant.opacity(0.5))
- .overflow_y_scroll()
- .child(self.render_page(window, cx))
- .track_scroll(&self.scroll_handle),
+ h_flex()
+ .w_full()
+ .gap_4()
+ .child(Vector::square(VectorName::ZedLogo, rems(2.5)))
+ .child(
+ v_flex()
+ .child(
+ Headline::new("Welcome to Zed")
+ .size(HeadlineSize::Small),
+ )
+ .child(
+ Label::new("The editor for what's next")
+ .color(Color::Muted)
+ .size(LabelSize::Small)
+ .italic(),
+ ),
+ )
+ .child(div().w_full())
+ .child({
+ Button::new("finish_setup", "Finish Setup")
+ .style(ButtonStyle::Filled)
+ .size(ButtonSize::Large)
+ .width(Rems(12.0))
+ .key_binding(
+ KeyBinding::for_action_in(
+ &Finish,
+ &self.focus_handle,
+ window,
+ cx,
+ )
+ .map(|kb| kb.size(rems_from_px(12.))),
+ )
+ .on_click(|_, window, cx| {
+ window.dispatch_action(Finish.boxed_clone(), cx);
+ })
+ })
+ .pb_6()
+ .border_b_1()
+ .border_color(cx.theme().colors().border_variant.opacity(0.5)),
)
- .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
- ),
+ .child(self.render_page(cx))
+ .track_scroll(&self.scroll_handle),
+ )
+ .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
)
}
}
@@ -628,7 +390,6 @@ impl Item for Onboarding {
Some(cx.new(|cx| Onboarding {
workspace: self.workspace.clone(),
user_store: self.user_store.clone(),
- selected_page: self.selected_page,
scroll_handle: ScrollHandle::new(),
focus_handle: cx.focus_handle(),
_settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
@@ -814,25 +575,10 @@ impl workspace::SerializableItem for Onboarding {
cx: &mut App,
) -> gpui::Task<gpui::Result<Entity<Self>>> {
window.spawn(cx, async move |cx| {
- if let Some(page_number) =
+ if let Some(_) =
persistence::ONBOARDING_PAGES.get_onboarding_page(item_id, workspace_id)?
{
- let page = match page_number {
- 0 => Some(SelectedPage::Basics),
- 1 => Some(SelectedPage::Editing),
- 2 => Some(SelectedPage::AiSetup),
- _ => None,
- };
- workspace.update(cx, |workspace, cx| {
- let onboarding_page = Onboarding::new(workspace, cx);
- if let Some(page) = page {
- zlog::info!("Onboarding page {page:?} loaded");
- onboarding_page.update(cx, |onboarding_page, cx| {
- onboarding_page.set_page(page, None, cx);
- })
- }
- onboarding_page
- })
+ workspace.update(cx, |workspace, cx| Onboarding::new(workspace, cx))
} else {
Err(anyhow::anyhow!("No onboarding page to deserialize"))
}
@@ -848,10 +594,10 @@ impl workspace::SerializableItem for Onboarding {
cx: &mut ui::Context<Self>,
) -> Option<gpui::Task<gpui::Result<()>>> {
let workspace_id = workspace.database_id()?;
- let page_number = self.selected_page as u16;
+
Some(cx.background_spawn(async move {
persistence::ONBOARDING_PAGES
- .save_onboarding_page(item_id, workspace_id, page_number)
+ .save_onboarding_page(item_id, workspace_id)
.await
}))
}
@@ -874,17 +620,32 @@ mod persistence {
impl Domain for OnboardingPagesDb {
const NAME: &str = stringify!(OnboardingPagesDb);
- const MIGRATIONS: &[&str] = &[sql!(
- CREATE TABLE onboarding_pages (
- workspace_id INTEGER,
- item_id INTEGER UNIQUE,
- page_number INTEGER,
-
- PRIMARY KEY(workspace_id, item_id),
- FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
- ON DELETE CASCADE
- ) STRICT;
- )];
+ const MIGRATIONS: &[&str] = &[
+ sql!(
+ CREATE TABLE onboarding_pages (
+ workspace_id INTEGER,
+ item_id INTEGER UNIQUE,
+ page_number INTEGER,
+
+ PRIMARY KEY(workspace_id, item_id),
+ FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+ ON DELETE CASCADE
+ ) STRICT;
+ ),
+ sql!(
+ CREATE TABLE onboarding_pages_2 (
+ workspace_id INTEGER,
+ item_id INTEGER UNIQUE,
+
+ PRIMARY KEY(workspace_id, item_id),
+ FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+ ON DELETE CASCADE
+ ) STRICT;
+ INSERT INTO onboarding_pages_2 SELECT workspace_id, item_id FROM onboarding_pages;
+ DROP TABLE onboarding_pages;
+ ALTER TABLE onboarding_pages_2 RENAME TO onboarding_pages;
+ ),
+ ];
}
db::static_connection!(ONBOARDING_PAGES, OnboardingPagesDb, [WorkspaceDb]);
@@ -893,11 +654,10 @@ mod persistence {
query! {
pub async fn save_onboarding_page(
item_id: workspace::ItemId,
- workspace_id: workspace::WorkspaceId,
- page_number: u16
+ workspace_id: workspace::WorkspaceId
) -> Result<()> {
- INSERT OR REPLACE INTO onboarding_pages(item_id, workspace_id, page_number)
- VALUES (?, ?, ?)
+ INSERT OR REPLACE INTO onboarding_pages(item_id, workspace_id)
+ VALUES (?, ?)
}
}
@@ -905,8 +665,8 @@ mod persistence {
pub fn get_onboarding_page(
item_id: workspace::ItemId,
workspace_id: workspace::WorkspaceId
- ) -> Result<Option<u16>> {
- SELECT page_number
+ ) -> Result<Option<workspace::ItemId>> {
+ SELECT item_id
FROM onboarding_pages
WHERE item_id = ? AND workspace_id = ?
}
@@ -151,6 +151,7 @@ impl SectionEntry {
}
pub struct WelcomePage {
+ first_paint: bool,
focus_handle: FocusHandle,
}
@@ -168,6 +169,10 @@ impl WelcomePage {
impl Render for WelcomePage {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ if self.first_paint {
+ window.request_animation_frame();
+ self.first_paint = false;
+ }
let (first_section, second_section) = CONTENT;
let first_section_entries = first_section.entries.len();
let last_index = first_section_entries + second_section.entries.len();
@@ -311,7 +316,10 @@ impl WelcomePage {
cx.on_focus(&focus_handle, window, |_, _, cx| cx.notify())
.detach();
- WelcomePage { focus_handle }
+ WelcomePage {
+ first_paint: true,
+ focus_handle,
+ }
})
}
}
@@ -135,6 +135,9 @@ pub enum ButtonStyle {
/// a fully transparent button.
Outlined,
+ /// Transparent button that always has an outline.
+ OutlinedTransparent,
+
/// A more de-emphasized version of the outlined button.
OutlinedGhost,
@@ -149,11 +152,38 @@ pub enum ButtonStyle {
Transparent,
}
+/// Rounding for a button that may have straight edges.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
-pub(crate) enum ButtonLikeRounding {
- All,
- Left,
- Right,
+pub(crate) struct ButtonLikeRounding {
+ /// Top-left corner rounding
+ pub top_left: bool,
+ /// Top-right corner rounding
+ pub top_right: bool,
+ /// Bottom-right corner rounding
+ pub bottom_right: bool,
+ /// Bottom-left corner rounding
+ pub bottom_left: bool,
+}
+
+impl ButtonLikeRounding {
+ pub const ALL: Self = Self {
+ top_left: true,
+ top_right: true,
+ bottom_right: true,
+ bottom_left: true,
+ };
+ pub const LEFT: Self = Self {
+ top_left: true,
+ top_right: false,
+ bottom_right: false,
+ bottom_left: true,
+ };
+ pub const RIGHT: Self = Self {
+ top_left: false,
+ top_right: true,
+ bottom_right: true,
+ bottom_left: false,
+ };
}
#[derive(Debug, Clone)]
@@ -198,6 +228,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
+ ButtonStyle::OutlinedTransparent => ButtonLikeStyles {
+ background: cx.theme().colors().ghost_element_background,
+ border_color: cx.theme().colors().border_variant,
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
ButtonStyle::OutlinedGhost => ButtonLikeStyles {
background: transparent_black(),
border_color: cx.theme().colors().border_variant,
@@ -249,6 +285,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
+ ButtonStyle::OutlinedTransparent => ButtonLikeStyles {
+ background: cx.theme().colors().ghost_element_hover,
+ border_color: cx.theme().colors().border,
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
ButtonStyle::OutlinedGhost => ButtonLikeStyles {
background: transparent_black(),
border_color: cx.theme().colors().border,
@@ -293,6 +335,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
+ ButtonStyle::OutlinedTransparent => ButtonLikeStyles {
+ background: cx.theme().colors().ghost_element_active,
+ border_color: cx.theme().colors().border_variant,
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
ButtonStyle::OutlinedGhost => ButtonLikeStyles {
background: transparent_black(),
border_color: cx.theme().colors().border_variant,
@@ -332,6 +380,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
+ ButtonStyle::OutlinedTransparent => ButtonLikeStyles {
+ background: cx.theme().colors().ghost_element_background,
+ border_color: cx.theme().colors().border,
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
ButtonStyle::OutlinedGhost => ButtonLikeStyles {
background: transparent_black(),
border_color: cx.theme().colors().border,
@@ -374,6 +428,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
+ ButtonStyle::OutlinedTransparent => ButtonLikeStyles {
+ background: cx.theme().colors().ghost_element_disabled,
+ border_color: cx.theme().colors().border_disabled,
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
ButtonStyle::OutlinedGhost => ButtonLikeStyles {
background: transparent_black(),
border_color: cx.theme().colors().border_disabled,
@@ -455,7 +515,7 @@ impl ButtonLike {
width: None,
height: None,
size: ButtonSize::Default,
- rounding: Some(ButtonLikeRounding::All),
+ rounding: Some(ButtonLikeRounding::ALL),
tooltip: None,
hoverable_tooltip: None,
children: SmallVec::new(),
@@ -469,15 +529,15 @@ impl ButtonLike {
}
pub fn new_rounded_left(id: impl Into<ElementId>) -> Self {
- Self::new(id).rounding(ButtonLikeRounding::Left)
+ Self::new(id).rounding(ButtonLikeRounding::LEFT)
}
pub fn new_rounded_right(id: impl Into<ElementId>) -> Self {
- Self::new(id).rounding(ButtonLikeRounding::Right)
+ Self::new(id).rounding(ButtonLikeRounding::RIGHT)
}
pub fn new_rounded_all(id: impl Into<ElementId>) -> Self {
- Self::new(id).rounding(ButtonLikeRounding::All)
+ Self::new(id).rounding(ButtonLikeRounding::ALL)
}
pub fn opacity(mut self, opacity: f32) -> Self {
@@ -630,14 +690,17 @@ impl RenderOnce for ButtonLike {
.when(
matches!(
self.style,
- ButtonStyle::Outlined | ButtonStyle::OutlinedGhost
+ ButtonStyle::Outlined
+ | ButtonStyle::OutlinedTransparent
+ | ButtonStyle::OutlinedGhost
),
|this| this.border_1(),
)
- .when_some(self.rounding, |this, rounding| match rounding {
- ButtonLikeRounding::All => this.rounded_sm(),
- ButtonLikeRounding::Left => this.rounded_l_sm(),
- ButtonLikeRounding::Right => this.rounded_r_sm(),
+ .when_some(self.rounding, |this, rounding| {
+ this.when(rounding.top_left, |this| this.rounded_tl_sm())
+ .when(rounding.top_right, |this| this.rounded_tr_sm())
+ .when(rounding.bottom_right, |this| this.rounded_br_sm())
+ .when(rounding.bottom_left, |this| this.rounded_bl_sm())
})
.gap(DynamicSpacing::Base04.rems(cx))
.map(|this| match self.size {
@@ -6,15 +6,41 @@ use crate::{ButtonLike, ButtonLikeRounding, ElevationIndex, TintColor, Tooltip,
/// The position of a [`ToggleButton`] within a group of buttons.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum ToggleButtonPosition {
- /// The toggle button is first in the group.
- First,
-
- /// The toggle button is in the middle of the group (i.e., it is not the first or last toggle button).
- Middle,
+pub struct ToggleButtonPosition {
+ /// The toggle button is one of the leftmost of the group.
+ leftmost: bool,
+ /// The toggle button is one of the rightmost of the group.
+ rightmost: bool,
+ /// The toggle button is one of the topmost of the group.
+ topmost: bool,
+ /// The toggle button is one of the bottommost of the group.
+ bottommost: bool,
+}
- /// The toggle button is last in the group.
- Last,
+impl ToggleButtonPosition {
+ pub const HORIZONTAL_FIRST: Self = Self {
+ leftmost: true,
+ ..Self::HORIZONTAL_MIDDLE
+ };
+ pub const HORIZONTAL_MIDDLE: Self = Self {
+ leftmost: false,
+ rightmost: false,
+ topmost: true,
+ bottommost: true,
+ };
+ pub const HORIZONTAL_LAST: Self = Self {
+ rightmost: true,
+ ..Self::HORIZONTAL_MIDDLE
+ };
+
+ pub(crate) fn to_rounding(self) -> ButtonLikeRounding {
+ ButtonLikeRounding {
+ top_left: self.topmost && self.leftmost,
+ top_right: self.topmost && self.rightmost,
+ bottom_right: self.bottommost && self.rightmost,
+ bottom_left: self.bottommost && self.leftmost,
+ }
+ }
}
#[derive(IntoElement, RegisterComponent)]
@@ -46,15 +72,15 @@ impl ToggleButton {
}
pub fn first(self) -> Self {
- self.position_in_group(ToggleButtonPosition::First)
+ self.position_in_group(ToggleButtonPosition::HORIZONTAL_FIRST)
}
pub fn middle(self) -> Self {
- self.position_in_group(ToggleButtonPosition::Middle)
+ self.position_in_group(ToggleButtonPosition::HORIZONTAL_MIDDLE)
}
pub fn last(self) -> Self {
- self.position_in_group(ToggleButtonPosition::Last)
+ self.position_in_group(ToggleButtonPosition::HORIZONTAL_LAST)
}
}
@@ -153,10 +179,8 @@ impl RenderOnce for ToggleButton {
};
self.base
- .when_some(self.position_in_group, |this, position| match position {
- ToggleButtonPosition::First => this.rounding(ButtonLikeRounding::Left),
- ToggleButtonPosition::Middle => this.rounding(None),
- ToggleButtonPosition::Last => this.rounding(ButtonLikeRounding::Right),
+ .when_some(self.position_in_group, |this, position| {
+ this.rounding(position.to_rounding())
})
.child(
Label::new(self.label)
@@ -535,7 +559,15 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
ButtonLike::new((group_name.clone(), entry_index))
.full_width()
- .rounding(None)
+ .rounding(Some(
+ ToggleButtonPosition {
+ leftmost: col_index == 0,
+ rightmost: col_index == COLS - 1,
+ topmost: row_index == 0,
+ bottommost: row_index == ROWS - 1,
+ }
+ .to_rounding(),
+ ))
.when_some(self.tab_index, |this, tab_index| {
this.tab_index(tab_index + entry_index as isize)
})
@@ -585,7 +585,7 @@ impl RenderOnce for Switch {
///
/// let switch_field = SwitchField::new(
/// "feature-toggle",
-/// "Enable feature",
+/// Some("Enable feature"),
/// Some("This feature adds new functionality to the app.".into()),
/// ToggleState::Unselected,
/// |state, window, cx| {
@@ -596,7 +596,7 @@ impl RenderOnce for Switch {
#[derive(IntoElement, RegisterComponent)]
pub struct SwitchField {
id: ElementId,
- label: SharedString,
+ label: Option<SharedString>,
description: Option<SharedString>,
toggle_state: ToggleState,
on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
@@ -609,14 +609,14 @@ pub struct SwitchField {
impl SwitchField {
pub fn new(
id: impl Into<ElementId>,
- label: impl Into<SharedString>,
+ label: Option<impl Into<SharedString>>,
description: Option<SharedString>,
toggle_state: impl Into<ToggleState>,
on_click: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
- label: label.into(),
+ label: label.map(Into::into),
description,
toggle_state: toggle_state.into(),
on_click: Arc::new(on_click),
@@ -657,11 +657,11 @@ impl SwitchField {
impl RenderOnce for SwitchField {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
- let tooltip = self.tooltip.map(|tooltip_fn| {
- h_flex()
- .gap_0p5()
- .child(Label::new(self.label.clone()))
- .child(
+ let tooltip = self
+ .tooltip
+ .zip(self.label.clone())
+ .map(|(tooltip_fn, label)| {
+ h_flex().gap_0p5().child(Label::new(label)).child(
IconButton::new("tooltip_button", IconName::Info)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
@@ -673,7 +673,7 @@ impl RenderOnce for SwitchField {
})
.on_click(|_, _, _| {}), // Intentional empty on click handler so that clicking on the info tooltip icon doesn't trigger the switch toggle
)
- });
+ });
h_flex()
.id((self.id.clone(), "container"))
@@ -694,11 +694,17 @@ impl RenderOnce for SwitchField {
(Some(description), None) => v_flex()
.gap_0p5()
.max_w_5_6()
- .child(Label::new(self.label.clone()))
+ .when_some(self.label, |this, label| this.child(Label::new(label)))
.child(Label::new(description.clone()).color(Color::Muted))
.into_any_element(),
(None, Some(tooltip)) => tooltip.into_any_element(),
- (None, None) => Label::new(self.label.clone()).into_any_element(),
+ (None, None) => {
+ if let Some(label) = self.label.clone() {
+ Label::new(label).into_any_element()
+ } else {
+ gpui::Empty.into_any_element()
+ }
+ }
})
.child(
Switch::new((self.id.clone(), "switch"), self.toggle_state)
@@ -748,7 +754,7 @@ impl Component for SwitchField {
"Unselected",
SwitchField::new(
"switch_field_unselected",
- "Enable notifications",
+ Some("Enable notifications"),
Some("Receive notifications when new messages arrive.".into()),
ToggleState::Unselected,
|_, _, _| {},
@@ -759,7 +765,7 @@ impl Component for SwitchField {
"Selected",
SwitchField::new(
"switch_field_selected",
- "Enable notifications",
+ Some("Enable notifications"),
Some("Receive notifications when new messages arrive.".into()),
ToggleState::Selected,
|_, _, _| {},
@@ -775,7 +781,7 @@ impl Component for SwitchField {
"Default",
SwitchField::new(
"switch_field_default",
- "Default color",
+ Some("Default color"),
Some("This uses the default switch color.".into()),
ToggleState::Selected,
|_, _, _| {},
@@ -786,7 +792,7 @@ impl Component for SwitchField {
"Accent",
SwitchField::new(
"switch_field_accent",
- "Accent color",
+ Some("Accent color"),
Some("This uses the accent color scheme.".into()),
ToggleState::Selected,
|_, _, _| {},
@@ -802,7 +808,7 @@ impl Component for SwitchField {
"Disabled",
SwitchField::new(
"switch_field_disabled",
- "Disabled field",
+ Some("Disabled field"),
Some("This field is disabled and cannot be toggled.".into()),
ToggleState::Selected,
|_, _, _| {},
@@ -817,7 +823,7 @@ impl Component for SwitchField {
"No Description",
SwitchField::new(
"switch_field_disabled",
- "Disabled field",
+ Some("Disabled field"),
None,
ToggleState::Selected,
|_, _, _| {},
@@ -832,7 +838,7 @@ impl Component for SwitchField {
"Tooltip with Description",
SwitchField::new(
"switch_field_tooltip_with_desc",
- "Nice Feature",
+ Some("Nice Feature"),
Some("Enable advanced configuration options.".into()),
ToggleState::Unselected,
|_, _, _| {},
@@ -844,7 +850,7 @@ impl Component for SwitchField {
"Tooltip without Description",
SwitchField::new(
"switch_field_tooltip_no_desc",
- "Nice Feature",
+ Some("Nice Feature"),
None,
ToggleState::Selected,
|_, _, _| {},