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