Skip to main content

ui/
tooltip.rs

1//! [`Tooltip`] — the small hover label.
2//!
3//! An entity rather than a function because gpui's `.tooltip(..)` takes a
4//! builder returning an `AnyView`: the tooltip is mounted in its own layer,
5//! after the hover delay, so it cannot be an inline element.
6//!
7//! ```ignore
8//! use ui::tooltip::Tooltip;
9//!
10//! div()
11//!     .id("copy")
12//!     .tooltip(|window, cx| Tooltip::text("Copy path", window, cx))
13//!     .child("⌘C")
14//! ```
15
16use gpui::{Action, AnyView, App, Context, IntoElement, SharedString, Window, div, prelude::*, px};
17
18use theme::{TextStyle, Theme, Typeset};
19
20use crate::{keys, popover, surface::Surfaced as _};
21
22pub struct Tooltip {
23    text: SharedString,
24    /// Optional keystroke shown right-aligned, e.g. `⌘C`.
25    keystroke: Option<SharedString>,
26}
27
28impl Tooltip {
29    /// A plain text tooltip, built for `.tooltip(..)`.
30    pub fn text(text: impl Into<SharedString>, _window: &mut Window, cx: &mut App) -> AnyView {
31        let text = text.into();
32        cx.new(|_| Self {
33            text,
34            keystroke: None,
35        })
36        .into()
37    }
38
39    /// A tooltip that also names the shortcut — the pairing that keeps
40    /// keyboard affordances discoverable without a menu.
41    pub fn with_keystroke(
42        text: impl Into<SharedString>,
43        keystroke: impl Into<SharedString>,
44        _window: &mut Window,
45        cx: &mut App,
46    ) -> AnyView {
47        let (text, keystroke) = (text.into(), keystroke.into());
48        cx.new(|_| Self {
49            text,
50            keystroke: Some(keystroke),
51        })
52        .into()
53    }
54
55    /// The same pairing with the chord read off the keymap rather than typed
56    /// in, so rebinding the action moves the hint with it. Falls back to plain
57    /// text when nothing is bound.
58    ///
59    /// A tooltip is built on hover, while the surface it describes still holds
60    /// focus, so [`keys::shortcut`] is the right lookup — use
61    /// [`Tooltip::for_action_in`] where it is not, such as a button that moves
62    /// focus to itself.
63    pub fn for_action(
64        text: impl Into<SharedString>,
65        action: &dyn Action,
66        window: &mut Window,
67        cx: &mut App,
68    ) -> AnyView {
69        let text = text.into();
70        let keystroke = keys::shortcut(action, window);
71        cx.new(|_| Self { text, keystroke }).into()
72    }
73
74    /// [`Tooltip::for_action`] against a named key context instead of whatever
75    /// holds focus.
76    pub fn for_action_in(
77        text: impl Into<SharedString>,
78        action: &dyn Action,
79        context: &str,
80        window: &mut Window,
81        cx: &mut App,
82    ) -> AnyView {
83        let text = text.into();
84        let keystroke = keys::shortcut_in(action, context, window);
85        cx.new(|_| Self { text, keystroke }).into()
86    }
87}
88
89impl Tooltip {
90    /// The card a tooltip paints, for a hover drawn inside another view
91    /// rather than mounted by `.tooltip(..)`.
92    pub fn card(
93        text: impl Into<SharedString>,
94        keystroke: Option<SharedString>,
95        cx: &App,
96    ) -> gpui::AnyElement {
97        let theme = Theme::of(cx).clone();
98        // Tooltips are small and frequent, so this is a tighter card than
99        // `popover_card`: less padding, no menu rhythm.
100        popover::popover_card(&theme)
101            .px(px(8.0))
102            .py(px(5.0))
103            .flex()
104            .flex_row()
105            .items_center()
106            .gap(px(Theme::SPACE))
107            .text_style(TextStyle::Callout)
108            .text_color(theme.text)
109            .child(text.into())
110            .when_some(keystroke, |card, keystroke| {
111                card.child(
112                    div()
113                        .text_style(TextStyle::Subheadline)
114                        .text_color(theme.text_faint)
115                        .child(keystroke),
116                )
117            })
118            .surface(&theme, theme.popover_surface)
119            .into_any_element()
120    }
121}
122
123impl Render for Tooltip {
124    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
125        Self::card(self.text.clone(), self.keystroke.clone(), cx)
126    }
127}