gpui_component/
tooltip.rs1use 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 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 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 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 pub fn key_binding(mut self, key_binding: Option<Kbd>) -> Self {
55 self.key_binding = key_binding;
56 self
57 }
58
59 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 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}