tooltip: Add action, key_binding method to Tooltip. (#787)

This commit is contained in:
Jason Lee 2025-04-14 17:38:21 +08:00 committed by GitHub
parent 8ef4e2eb17
commit 9a6a0f4421
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 63 additions and 21 deletions

View file

@ -1,6 +1,6 @@
use gpui::{ use gpui::{
actions, div, App, AppContext, Context, Entity, Focusable, InteractiveElement, KeyBinding, actions, div, App, AppContext, Context, Entity, Focusable, InteractiveElement, KeyBinding,
ParentElement, Render, StatefulInteractiveElement, Styled, Window, Keystroke, ParentElement, Render, StatefulInteractiveElement, Styled, Window,
}; };
use gpui_component::{ use gpui_component::{
@ -10,7 +10,7 @@ use gpui_component::{
h_flex, h_flex,
label::Label, label::Label,
tooltip::Tooltip, tooltip::Tooltip,
v_flex, ActiveTheme, IconName, v_flex, ActiveTheme, IconName, Kbd,
}; };
actions!(tooltip, [Info]); actions!(tooltip, [Info]);
@ -56,7 +56,7 @@ impl Focusable for TooltipStory {
impl Render for TooltipStory { impl Render for TooltipStory {
fn render( fn render(
&mut self, &mut self,
window: &mut gpui::Window, _: &mut gpui::Window,
_cx: &mut gpui::Context<Self>, _cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement { ) -> impl gpui::IntoElement {
v_flex() v_flex()
@ -75,7 +75,6 @@ impl Render for TooltipStory {
"This is a tooltip with Action for display keybinding.", "This is a tooltip with Action for display keybinding.",
&Info, &Info,
Some("Tooltip"), Some("Tooltip"),
window,
)), )),
) )
.child( .child(
@ -83,13 +82,21 @@ impl Render for TooltipStory {
.justify_center() .justify_center()
.child(Label::new("Hover me")) .child(Label::new("Hover me"))
.id("tooltip-2") .id("tooltip-2")
.tooltip(|window, cx| Tooltip::new("This is a Label").build(window, cx)), .tooltip(|window, cx| {
Tooltip::new("This is a Label")
.action(&Info, Some("Tooltip"))
.build(window, cx)
}),
) )
.child( .child(
div() div()
.child(Checkbox::new("check").label("Remember me").checked(true)) .child(Checkbox::new("check").label("Remember me").checked(true))
.id("tooltip-3") .id("tooltip-3")
.tooltip(|window, cx| Tooltip::new("Checked!").build(window, cx)), .tooltip(|window, cx| {
Tooltip::new("Checked!")
.key_binding(Some(Kbd::new(Keystroke::parse("cmd-shift-u").unwrap())))
.build(window, cx)
}),
) )
.child( .child(
div() div()

View file

@ -1,6 +1,8 @@
use std::rc::Rc;
use crate::{ use crate::{
h_flex, indicator::Indicator, tooltip::Tooltip, ActiveTheme, Colorize as _, Disableable, Icon, h_flex, indicator::Indicator, tooltip::Tooltip, ActiveTheme, Colorize as _, Disableable, Icon,
Kbd, Selectable, Sizable, Size, StyleSized, Selectable, Sizable, Size, StyleSized,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, relative, Action, AnyElement, App, ClickEvent, Corners, Div, div, prelude::FluentBuilder as _, relative, Action, AnyElement, App, ClickEvent, Corners, Div,
@ -183,8 +185,10 @@ pub struct Button {
border_edges: Edges<bool>, border_edges: Edges<bool>,
size: Size, size: Size,
compact: bool, compact: bool,
tooltip: Option<SharedString>, tooltip: Option<(
key_binding: Option<Kbd>, SharedString,
Option<(Rc<Box<dyn Action>>, Option<SharedString>)>,
)>,
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>, on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
pub(crate) stop_propagation: bool, pub(crate) stop_propagation: bool,
loading: bool, loading: bool,
@ -212,7 +216,6 @@ impl Button {
border_edges: Edges::all(true), border_edges: Edges::all(true),
size: Size::Medium, size: Size::Medium,
tooltip: None, tooltip: None,
key_binding: None,
on_click: None, on_click: None,
stop_propagation: true, stop_propagation: true,
loading: false, loading: false,
@ -261,7 +264,7 @@ impl Button {
/// Set the tooltip of the button. /// Set the tooltip of the button.
pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self { pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
self.tooltip = Some(tooltip.into()); self.tooltip = Some((tooltip.into(), None));
self self
} }
@ -270,10 +273,14 @@ impl Button {
tooltip: impl Into<SharedString>, tooltip: impl Into<SharedString>,
action: &dyn Action, action: &dyn Action,
context: Option<&str>, context: Option<&str>,
window: &Window,
) -> Self { ) -> Self {
self.tooltip = Some(tooltip.into()); self.tooltip = Some((
self.key_binding = Kbd::binding_for_action(action, context, window); tooltip.into(),
Some((
Rc::new(action.boxed_clone()),
context.map(|c| c.to_string().into()),
)),
));
self self
} }
@ -497,10 +504,15 @@ impl RenderOnce for Button {
.children(self.children) .children(self.children)
}) })
.when(self.loading, |this| this.bg(normal_style.bg.opacity(0.8))) .when(self.loading, |this| this.bg(normal_style.bg.opacity(0.8)))
.when_some(self.tooltip, |this, tooltip| { .when_some(self.tooltip, |this, (tooltip, action)| {
this.tooltip(move |window, cx| { this.tooltip(move |window, cx| {
Tooltip::new(tooltip.clone()) Tooltip::new(tooltip.clone())
.key_binding(self.key_binding.clone()) .when_some(action.clone(), |this, (action, context)| {
this.action(
action.boxed_clone().as_ref(),
context.as_ref().map(|c| c.as_ref()),
)
})
.build(window, cx) .build(window, cx)
}) })
}) })

View file

@ -1,6 +1,6 @@
use gpui::{ use gpui::{
div, prelude::FluentBuilder, px, AnyElement, AnyView, App, AppContext, Context, IntoElement, div, prelude::FluentBuilder, px, Action, AnyElement, AnyView, App, AppContext, Context,
ParentElement, Render, Styled, Window, IntoElement, ParentElement, Render, SharedString, Styled, Window,
}; };
use crate::{h_flex, text::Text, ActiveTheme, Kbd}; use crate::{h_flex, text::Text, ActiveTheme, Kbd};
@ -13,6 +13,7 @@ enum TooltipContext {
pub struct Tooltip { pub struct Tooltip {
content: TooltipContext, content: TooltipContext,
key_binding: Option<Kbd>, key_binding: Option<Kbd>,
action: Option<(Box<dyn Action>, Option<SharedString>)>,
} }
impl Tooltip { impl Tooltip {
@ -21,6 +22,7 @@ impl Tooltip {
Self { Self {
content: TooltipContext::Text(text.into()), content: TooltipContext::Text(text.into()),
key_binding: None, key_binding: None,
action: None,
} }
} }
@ -32,15 +34,22 @@ impl Tooltip {
{ {
Self { Self {
key_binding: None, key_binding: None,
action: None,
content: TooltipContext::Element(Box::new(move |window, cx| { content: TooltipContext::Element(Box::new(move |window, cx| {
builder(window, cx).into_any_element() builder(window, cx).into_any_element()
})), })),
} }
} }
/// Set Action to display key binding information for the tooltip if it exists.
pub fn action(mut self, action: &dyn Action, context: Option<&str>) -> Self {
self.action = Some((action.boxed_clone(), context.map(SharedString::new)));
self
}
/// Set KeyBinding information for the tooltip. /// Set KeyBinding information for the tooltip.
pub(crate) fn key_binding(mut self, kbd: Option<impl Into<Kbd>>) -> Self { pub fn key_binding(mut self, key_binding: Option<Kbd>) -> Self {
self.key_binding = kbd.map(Into::into); self.key_binding = key_binding;
self self
} }
@ -54,6 +63,20 @@ impl FluentBuilder for Tooltip {}
impl Render for Tooltip { impl Render for Tooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let key_binding = if let Some(key_binding) = &self.key_binding {
Some(key_binding.clone())
} else {
if let Some((action, context)) = &self.action {
Kbd::binding_for_action(
action.as_ref(),
context.as_ref().map(|s| s.as_ref()),
window,
)
} else {
None
}
};
div().child( div().child(
// Wrap in a child, to ensure the left margin is applied to the tooltip // Wrap in a child, to ensure the left margin is applied to the tooltip
h_flex() h_flex()
@ -77,7 +100,7 @@ impl Render for Tooltip {
TooltipContext::Element(ref builder) => this.child(builder(window, cx)), TooltipContext::Element(ref builder) => this.child(builder(window, cx)),
})) }))
}) })
.when_some(self.key_binding.clone(), |this, kbd| { .when_some(key_binding, |this, kbd| {
this.child( this.child(
div() div()
.text_xs() .text_xs()