1#![allow(missing_docs)]
2
3use gpui::{Action, AnyView, AppContext as _, FocusHandle, IntoElement, Render};
4use settings::Settings;
5use theme::ThemeSettings;
6
7use crate::prelude::*;
8use crate::{h_flex, v_flex, Color, KeyBinding, Label, LabelSize, StyledExt};
9
10pub struct Tooltip {
11 title: SharedString,
12 meta: Option<SharedString>,
13 key_binding: Option<KeyBinding>,
14}
15
16impl Tooltip {
17 pub fn simple(title: impl Into<SharedString>, cx: &mut App) -> AnyView {
18 cx.new(|_| Self {
19 title: title.into(),
20 meta: None,
21 key_binding: None,
22 })
23 .into()
24 }
25
26 pub fn text(title: impl Into<SharedString>) -> impl Fn(&mut Window, &mut App) -> AnyView {
27 let title = title.into();
28 move |_, cx| {
29 cx.new(|_| Self {
30 title: title.clone(),
31 meta: None,
32 key_binding: None,
33 })
34 .into()
35 }
36 }
37
38 pub fn for_action(
39 title: impl Into<SharedString>,
40 action: &dyn Action,
41 window: &mut Window,
42 cx: &mut App,
43 ) -> AnyView {
44 cx.new(|_| Self {
45 title: title.into(),
46 meta: None,
47 key_binding: KeyBinding::for_action(action, window),
48 })
49 .into()
50 }
51
52 pub fn for_action_in(
53 title: impl Into<SharedString>,
54 action: &dyn Action,
55 focus_handle: &FocusHandle,
56 window: &mut Window,
57 cx: &mut App,
58 ) -> AnyView {
59 cx.new(|_| Self {
60 title: title.into(),
61 meta: None,
62 key_binding: KeyBinding::for_action_in(action, focus_handle, window),
63 })
64 .into()
65 }
66
67 pub fn with_meta(
68 title: impl Into<SharedString>,
69 action: Option<&dyn Action>,
70 meta: impl Into<SharedString>,
71 window: &mut Window,
72 cx: &mut App,
73 ) -> AnyView {
74 cx.new(|_| Self {
75 title: title.into(),
76 meta: Some(meta.into()),
77 key_binding: action.and_then(|action| KeyBinding::for_action(action, window)),
78 })
79 .into()
80 }
81
82 pub fn with_meta_in(
83 title: impl Into<SharedString>,
84 action: Option<&dyn Action>,
85 meta: impl Into<SharedString>,
86 focus_handle: &FocusHandle,
87 window: &mut Window,
88 cx: &mut App,
89 ) -> AnyView {
90 cx.new(|_| Self {
91 title: title.into(),
92 meta: Some(meta.into()),
93 key_binding: action
94 .and_then(|action| KeyBinding::for_action_in(action, focus_handle, window)),
95 })
96 .into()
97 }
98
99 pub fn new(title: impl Into<SharedString>) -> Self {
100 Self {
101 title: title.into(),
102 meta: None,
103 key_binding: None,
104 }
105 }
106
107 pub fn meta(mut self, meta: impl Into<SharedString>) -> Self {
108 self.meta = Some(meta.into());
109 self
110 }
111
112 pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
113 self.key_binding = key_binding.into();
114 self
115 }
116}
117
118impl Render for Tooltip {
119 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
120 tooltip_container(window, cx, |el, _, _| {
121 el.child(
122 h_flex()
123 .gap_4()
124 .child(div().max_w_72().child(self.title.clone()))
125 .when_some(self.key_binding.clone(), |this, key_binding| {
126 this.justify_between().child(key_binding)
127 }),
128 )
129 .when_some(self.meta.clone(), |this, meta| {
130 this.child(Label::new(meta).size(LabelSize::Small).color(Color::Muted))
131 })
132 })
133 }
134}
135
136pub fn tooltip_container<V>(
137 window: &mut Window,
138 cx: &mut Context<V>,
139 f: impl FnOnce(Div, &mut Window, &mut Context<V>) -> Div,
140) -> impl IntoElement {
141 let ui_font = ThemeSettings::get_global(cx).ui_font.clone();
142
143 // padding to avoid tooltip appearing right below the mouse cursor
144 div().pl_2().pt_2p5().child(
145 v_flex()
146 .elevation_2(cx)
147 .font(ui_font)
148 .text_ui(cx)
149 .text_color(cx.theme().colors().text)
150 .py_1()
151 .px_2()
152 .map(|el| f(el, window, cx)),
153 )
154}
155
156pub struct LinkPreview {
157 link: SharedString,
158}
159
160impl LinkPreview {
161 pub fn new(url: &str, cx: &mut App) -> AnyView {
162 let mut wrapped_url = String::new();
163 for (i, ch) in url.chars().enumerate() {
164 if i == 500 {
165 wrapped_url.push('…');
166 break;
167 }
168 if i % 100 == 0 && i != 0 {
169 wrapped_url.push('\n');
170 }
171 wrapped_url.push(ch);
172 }
173 cx.new(|_| LinkPreview {
174 link: wrapped_url.into(),
175 })
176 .into()
177 }
178}
179
180impl Render for LinkPreview {
181 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
182 tooltip_container(window, cx, |el, _, _| {
183 el.child(
184 Label::new(self.link.clone())
185 .size(LabelSize::XSmall)
186 .color(Color::Muted),
187 )
188 })
189 }
190}