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, kbd::Kbd, text::Text, ActiveTheme, StyledExt};
7
8enum TooltipContext {
9 Text(Text),
10 Element(Box<dyn Fn(&mut Window, &mut App) -> AnyElement>),
11}
12
13pub 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 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 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 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 pub fn key_binding(mut self, key_binding: Option<Kbd>) -> Self {
57 self.key_binding = key_binding;
58 self
59 }
60
61 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 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}