onboarding: Add explainer tooltips for the editing and AI section (#35619)
Includes the ability to add a tooltip for both the badge and switch field components. Release Notes: - N/A
This commit is contained in:
parent
afc4f50300
commit
e1d0e3fc34
4 changed files with 191 additions and 48 deletions
|
@ -11,7 +11,7 @@ use project::DisableAiSettings;
|
||||||
use settings::{Settings, update_settings_file};
|
use settings::{Settings, update_settings_file};
|
||||||
use ui::{
|
use ui::{
|
||||||
Badge, ButtonLike, Divider, Modal, ModalFooter, ModalHeader, Section, SwitchField, ToggleState,
|
Badge, ButtonLike, Divider, Modal, ModalFooter, ModalHeader, Section, SwitchField, ToggleState,
|
||||||
prelude::*,
|
prelude::*, tooltip_container,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::ModalView;
|
use workspace::ModalView;
|
||||||
|
@ -41,7 +41,11 @@ fn render_llm_provider_section(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_privacy_card(disabled: bool, cx: &mut App) -> impl IntoElement {
|
fn render_privacy_card(disabled: bool, cx: &mut App) -> impl IntoElement {
|
||||||
let privacy_badge = || Badge::new("Privacy").icon(IconName::ShieldCheck);
|
let privacy_badge = || {
|
||||||
|
Badge::new("Privacy")
|
||||||
|
.icon(IconName::ShieldCheck)
|
||||||
|
.tooltip(move |_, cx| cx.new(|_| AiPrivacyTooltip::new()).into())
|
||||||
|
};
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.relative()
|
.relative()
|
||||||
|
@ -355,3 +359,37 @@ impl Render for AiConfigurationModal {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AiPrivacyTooltip {}
|
||||||
|
|
||||||
|
impl AiPrivacyTooltip {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for AiPrivacyTooltip {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
const DESCRIPTION: &'static str = "One of Zed's most important principles is transparency. This is why we are and value open-source so much. And it wouldn't be any different with AI.";
|
||||||
|
|
||||||
|
tooltip_container(window, cx, move |this, _, _| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::ShieldCheck)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(Label::new("Privacy Principle")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div().max_w_64().child(
|
||||||
|
Label::new(DESCRIPTION)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use settings::{Settings as _, update_settings_file};
|
||||||
use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
|
use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
|
||||||
use ui::{
|
use ui::{
|
||||||
ButtonLike, ContextMenu, DropdownMenu, NumericStepper, SwitchField, ToggleButtonGroup,
|
ButtonLike, ContextMenu, DropdownMenu, NumericStepper, SwitchField, ToggleButtonGroup,
|
||||||
ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, prelude::*,
|
ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, Tooltip, prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{ImportCursorSettings, ImportVsCodeSettings};
|
use crate::{ImportCursorSettings, ImportVsCodeSettings};
|
||||||
|
@ -357,11 +357,14 @@ fn render_font_customization_section(window: &mut Window, cx: &mut App) -> impl
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_popular_settings_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render_popular_settings_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
const LIGATURE_TOOLTIP: &'static str = "Ligatures are when a font creates a special character out of combining two characters into one. For example, with ligatures turned on, =/= would become ≠.";
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_5()
|
.gap_5()
|
||||||
.child(Label::new("Popular Settings").size(LabelSize::Large).mt_8())
|
.child(Label::new("Popular Settings").size(LabelSize::Large).mt_8())
|
||||||
.child(render_font_customization_section(window, cx))
|
.child(render_font_customization_section(window, cx))
|
||||||
.child(SwitchField::new(
|
.child(
|
||||||
|
SwitchField::new(
|
||||||
"onboarding-font-ligatures",
|
"onboarding-font-ligatures",
|
||||||
"Font Ligatures",
|
"Font Ligatures",
|
||||||
Some("Combine text characters into their associated symbols.".into()),
|
Some("Combine text characters into their associated symbols.".into()),
|
||||||
|
@ -373,7 +376,9 @@ fn render_popular_settings_section(window: &mut Window, cx: &mut App) -> impl In
|
||||||
|toggle_state, _, cx| {
|
|toggle_state, _, cx| {
|
||||||
write_font_ligatures(toggle_state == &ToggleState::Selected, cx);
|
write_font_ligatures(toggle_state == &ToggleState::Selected, cx);
|
||||||
},
|
},
|
||||||
))
|
)
|
||||||
|
.tooltip(Tooltip::text(LIGATURE_TOOLTIP)),
|
||||||
|
)
|
||||||
.child(SwitchField::new(
|
.child(SwitchField::new(
|
||||||
"onboarding-format-on-save",
|
"onboarding-format-on-save",
|
||||||
"Format on Save",
|
"Format on Save",
|
||||||
|
@ -387,6 +392,32 @@ fn render_popular_settings_section(window: &mut Window, cx: &mut App) -> impl In
|
||||||
write_format_on_save(toggle_state == &ToggleState::Selected, cx);
|
write_format_on_save(toggle_state == &ToggleState::Selected, cx);
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
.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| {
|
||||||
|
write_inlay_hints(toggle_state == &ToggleState::Selected, cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.child(SwitchField::new(
|
||||||
|
"onboarding-git-blame-switch",
|
||||||
|
"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| {
|
||||||
|
set_git_blame(toggle_state == &ToggleState::Selected, cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.items_start()
|
.items_start()
|
||||||
|
@ -421,32 +452,6 @@ fn render_popular_settings_section(window: &mut Window, cx: &mut App) -> impl In
|
||||||
.button_width(ui::rems_from_px(64.)),
|
.button_width(ui::rems_from_px(64.)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.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| {
|
|
||||||
write_inlay_hints(toggle_state == &ToggleState::Selected, cx);
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.child(SwitchField::new(
|
|
||||||
"onboarding-git-blame-switch",
|
|
||||||
"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| {
|
|
||||||
set_git_blame(toggle_state == &ToggleState::Selected, cx);
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
|
pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::Divider;
|
use crate::Divider;
|
||||||
use crate::DividerColor;
|
use crate::DividerColor;
|
||||||
|
use crate::Tooltip;
|
||||||
use crate::component_prelude::*;
|
use crate::component_prelude::*;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use gpui::AnyView;
|
||||||
use gpui::{AnyElement, IntoElement, SharedString, Window};
|
use gpui::{AnyElement, IntoElement, SharedString, Window};
|
||||||
|
|
||||||
#[derive(IntoElement, RegisterComponent)]
|
#[derive(IntoElement, RegisterComponent)]
|
||||||
pub struct Badge {
|
pub struct Badge {
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
|
tooltip: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyView>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Badge {
|
impl Badge {
|
||||||
|
@ -15,6 +20,7 @@ impl Badge {
|
||||||
Self {
|
Self {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
icon: IconName::Check,
|
icon: IconName::Check,
|
||||||
|
tooltip: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,11 +28,19 @@ impl Badge {
|
||||||
self.icon = icon;
|
self.icon = icon;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
|
||||||
|
self.tooltip = Some(Rc::new(tooltip));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for Badge {
|
impl RenderOnce for Badge {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let tooltip = self.tooltip;
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.id(self.label.clone())
|
||||||
.h_full()
|
.h_full()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.pl_1()
|
.pl_1()
|
||||||
|
@ -43,6 +57,9 @@ impl RenderOnce for Badge {
|
||||||
)
|
)
|
||||||
.child(Divider::vertical().color(DividerColor::Border))
|
.child(Divider::vertical().color(DividerColor::Border))
|
||||||
.child(Label::new(self.label.clone()).size(LabelSize::Small).ml_1())
|
.child(Label::new(self.label.clone()).size(LabelSize::Small).ml_1())
|
||||||
|
.when_some(tooltip, |this, tooltip| {
|
||||||
|
this.tooltip(move |window, cx| tooltip(window, cx))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +76,18 @@ impl Component for Badge {
|
||||||
|
|
||||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||||
Some(
|
Some(
|
||||||
single_example("Basic Badge", Badge::new("Default").into_any_element())
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.child(single_example(
|
||||||
|
"Basic Badge",
|
||||||
|
Badge::new("Default").into_any_element(),
|
||||||
|
))
|
||||||
|
.child(single_example(
|
||||||
|
"With Tooltip",
|
||||||
|
Badge::new("Tooltip")
|
||||||
|
.tooltip(Tooltip::text("This is a tooltip."))
|
||||||
|
.into_any_element(),
|
||||||
|
))
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ use gpui::{
|
||||||
AnyElement, AnyView, ClickEvent, ElementId, Hsla, IntoElement, Styled, Window, div, hsla,
|
AnyElement, AnyView, ClickEvent, ElementId, Hsla, IntoElement, Styled, Window, div, hsla,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::{rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use crate::utils::is_light;
|
use crate::utils::is_light;
|
||||||
use crate::{Color, Icon, IconName, ToggleState};
|
use crate::{Color, Icon, IconName, ToggleState, Tooltip};
|
||||||
use crate::{ElevationIndex, KeyBinding, prelude::*};
|
use crate::{ElevationIndex, KeyBinding, prelude::*};
|
||||||
|
|
||||||
// TODO: Checkbox, CheckboxWithLabel, and Switch could all be
|
// TODO: Checkbox, CheckboxWithLabel, and Switch could all be
|
||||||
|
@ -571,6 +571,7 @@ pub struct SwitchField {
|
||||||
on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
|
on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
color: SwitchColor,
|
color: SwitchColor,
|
||||||
|
tooltip: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyView>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SwitchField {
|
impl SwitchField {
|
||||||
|
@ -589,6 +590,7 @@ impl SwitchField {
|
||||||
on_click: Arc::new(on_click),
|
on_click: Arc::new(on_click),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
color: SwitchColor::Accent,
|
color: SwitchColor::Accent,
|
||||||
|
tooltip: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,10 +610,17 @@ impl SwitchField {
|
||||||
self.color = color;
|
self.color = color;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
|
||||||
|
self.tooltip = Some(Rc::new(tooltip));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for SwitchField {
|
impl RenderOnce for SwitchField {
|
||||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||||
|
let tooltip = self.tooltip;
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(SharedString::from(format!("{}-container", self.id)))
|
.id(SharedString::from(format!("{}-container", self.id)))
|
||||||
.when(!self.disabled, |this| {
|
.when(!self.disabled, |this| {
|
||||||
|
@ -621,14 +630,48 @@ impl RenderOnce for SwitchField {
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.flex_wrap()
|
.flex_wrap()
|
||||||
.child(match &self.description {
|
.child(match (&self.description, &tooltip) {
|
||||||
Some(description) => v_flex()
|
(Some(description), Some(tooltip)) => v_flex()
|
||||||
|
.gap_0p5()
|
||||||
|
.max_w_5_6()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_0p5()
|
||||||
|
.child(Label::new(self.label.clone()))
|
||||||
|
.child(
|
||||||
|
IconButton::new("tooltip_button", IconName::Info)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.shape(crate::IconButtonShape::Square)
|
||||||
|
.tooltip({
|
||||||
|
let tooltip = tooltip.clone();
|
||||||
|
move |window, cx| tooltip(window, cx)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(Label::new(description.clone()).color(Color::Muted))
|
||||||
|
.into_any_element(),
|
||||||
|
(Some(description), None) => v_flex()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
.max_w_5_6()
|
.max_w_5_6()
|
||||||
.child(Label::new(self.label.clone()))
|
.child(Label::new(self.label.clone()))
|
||||||
.child(Label::new(description.clone()).color(Color::Muted))
|
.child(Label::new(description.clone()).color(Color::Muted))
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
None => Label::new(self.label.clone()).into_any_element(),
|
(None, Some(tooltip)) => h_flex()
|
||||||
|
.gap_0p5()
|
||||||
|
.child(Label::new(self.label.clone()))
|
||||||
|
.child(
|
||||||
|
IconButton::new("tooltip_button", IconName::Info)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.shape(crate::IconButtonShape::Square)
|
||||||
|
.tooltip({
|
||||||
|
let tooltip = tooltip.clone();
|
||||||
|
move |window, cx| tooltip(window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
(None, None) => Label::new(self.label.clone()).into_any_element(),
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
Switch::new(
|
Switch::new(
|
||||||
|
@ -754,6 +797,35 @@ impl Component for SwitchField {
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
)],
|
)],
|
||||||
),
|
),
|
||||||
|
example_group_with_title(
|
||||||
|
"With Tooltip",
|
||||||
|
vec![
|
||||||
|
single_example(
|
||||||
|
"Tooltip with Description",
|
||||||
|
SwitchField::new(
|
||||||
|
"switch_field_tooltip_with_desc",
|
||||||
|
"Nice Feature",
|
||||||
|
Some("Enable advanced configuration options.".into()),
|
||||||
|
ToggleState::Unselected,
|
||||||
|
|_, _, _| {},
|
||||||
|
)
|
||||||
|
.tooltip(Tooltip::text("This is content for this tooltip!"))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Tooltip without Description",
|
||||||
|
SwitchField::new(
|
||||||
|
"switch_field_tooltip_no_desc",
|
||||||
|
"Nice Feature",
|
||||||
|
None,
|
||||||
|
ToggleState::Selected,
|
||||||
|
|_, _, _| {},
|
||||||
|
)
|
||||||
|
.tooltip(Tooltip::text("This is content for this tooltip!"))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
])
|
])
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue