gpui_component/
tooltip.rs

1use gpui::{
2    div, prelude::FluentBuilder, px, Action, AnyElement, AnyView, App, AppContext, Context,
3    IntoElement, ParentElement, Render, SharedString, StyleRefinement, Styled, Window,
4};
5
6use crate::{h_flex, kbd::Kbd, text::Text, ActiveTheme, StyledExt};
7
8enum TooltipContext {
9    Text(Text),
10    Element(Box<dyn Fn(&mut Window, &mut App) -> AnyElement>),
11}
12
13/// A Tooltip element that can display text or custom content,
14/// with optional key binding information.
15pub struct Tooltip {
16    style: StyleRefinement,
17    content: TooltipContext,
18    key_binding: Option<Kbd>,
19    action: Option<(Box<dyn Action>, Option<SharedString>)>,
20}
21
22impl Tooltip {
23    /// Create a Tooltip with a text content.
24    pub fn new(text: impl Into<Text>) -> Self {
25        Self {
26            style: StyleRefinement::default(),
27            content: TooltipContext::Text(text.into()),
28            key_binding: None,
29            action: None,
30        }
31    }
32
33    /// Create a Tooltip with a custom element.
34    pub fn element<E, F>(builder: F) -> Self
35    where
36        E: IntoElement,
37        F: Fn(&mut Window, &mut App) -> E + 'static,
38    {
39        Self {
40            style: StyleRefinement::default(),
41            key_binding: None,
42            action: None,
43            content: TooltipContext::Element(Box::new(move |window, cx| {
44                builder(window, cx).into_any_element()
45            })),
46        }
47    }
48
49    /// Set Action to display key binding information for the tooltip if it exists.
50    pub fn action(mut self, action: &dyn Action, context: Option<&str>) -> Self {
51        self.action = Some((action.boxed_clone(), context.map(SharedString::new)));
52        self
53    }
54
55    /// Set KeyBinding information for the tooltip.
56    pub fn key_binding(mut self, key_binding: Option<Kbd>) -> Self {
57        self.key_binding = key_binding;
58        self
59    }
60
61    /// Build the tooltip and return it as an `AnyView`.
62    pub fn build(self, _: &mut Window, cx: &mut App) -> AnyView {
63        cx.new(|_| self).into()
64    }
65}
66
67impl FluentBuilder for Tooltip {}
68impl Styled for Tooltip {
69    fn style(&mut self) -> &mut StyleRefinement {
70        &mut self.style
71    }
72}
73impl Render for Tooltip {
74    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
75        let key_binding = if let Some(key_binding) = &self.key_binding {
76            Some(key_binding.clone())
77        } else {
78            if let Some((action, context)) = &self.action {
79                Kbd::binding_for_action(
80                    action.as_ref(),
81                    context.as_ref().map(|s| s.as_ref()),
82                    window,
83                )
84            } else {
85                None
86            }
87        };
88
89        div().child(
90            // Wrap in a child, to ensure the left margin is applied to the tooltip
91            h_flex()
92                .font_family(".SystemUIFont")
93                .m_3()
94                .bg(cx.theme().popover)
95                .text_color(cx.theme().popover_foreground)
96                .bg(cx.theme().popover)
97                .border_1()
98                .border_color(cx.theme().border)
99                .shadow_md()
100                .rounded(px(6.))
101                .justify_between()
102                .py_0p5()
103                .px_2()
104                .text_sm()
105                .gap_3()
106                .refine_style(&self.style)
107                .map(|this| {
108                    this.child(div().map(|this| match self.content {
109                        TooltipContext::Text(ref text) => this.child(text.clone()),
110                        TooltipContext::Element(ref builder) => this.child(builder(window, cx)),
111                    }))
112                })
113                .when_some(key_binding, |this, kbd| {
114                    this.child(
115                        div()
116                            .text_xs()
117                            .flex_shrink_0()
118                            .text_color(cx.theme().muted_foreground)
119                            .child(kbd.appearance(false)),
120                    )
121                }),
122        )
123    }
124}