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_title(
39 title: impl Into<SharedString>,
40 action: &dyn Action,
41 ) -> impl Fn(&mut Window, &mut App) -> AnyView {
42 let title = title.into();
43 let action = action.boxed_clone();
44 move |window, cx| {
45 cx.new(|_| Self {
46 title: title.clone(),
47 meta: None,
48 key_binding: KeyBinding::for_action(action.as_ref(), window),
49 })
50 .into()
51 }
52 }
53
54 pub fn for_action(
55 title: impl Into<SharedString>,
56 action: &dyn Action,
57 window: &mut Window,
58 cx: &mut App,
59 ) -> AnyView {
60 cx.new(|_| Self {
61 title: title.into(),
62 meta: None,
63 key_binding: KeyBinding::for_action(action, window),
64 })
65 .into()
66 }
67
68 pub fn for_action_in(
69 title: impl Into<SharedString>,
70 action: &dyn Action,
71 focus_handle: &FocusHandle,
72 window: &mut Window,
73 cx: &mut App,
74 ) -> AnyView {
75 cx.new(|_| Self {
76 title: title.into(),
77 meta: None,
78 key_binding: KeyBinding::for_action_in(action, focus_handle, window),
79 })
80 .into()
81 }
82
83 pub fn with_meta(
84 title: impl Into<SharedString>,
85 action: Option<&dyn Action>,
86 meta: impl Into<SharedString>,
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.and_then(|action| KeyBinding::for_action(action, window)),
94 })
95 .into()
96 }
97
98 pub fn with_meta_in(
99 title: impl Into<SharedString>,
100 action: Option<&dyn Action>,
101 meta: impl Into<SharedString>,
102 focus_handle: &FocusHandle,
103 window: &mut Window,
104 cx: &mut App,
105 ) -> AnyView {
106 cx.new(|_| Self {
107 title: title.into(),
108 meta: Some(meta.into()),
109 key_binding: action
110 .and_then(|action| KeyBinding::for_action_in(action, focus_handle, window)),
111 })
112 .into()
113 }
114
115 pub fn new(title: impl Into<SharedString>) -> Self {
116 Self {
117 title: title.into(),
118 meta: None,
119 key_binding: None,
120 }
121 }
122
123 pub fn meta(mut self, meta: impl Into<SharedString>) -> Self {
124 self.meta = Some(meta.into());
125 self
126 }
127
128 pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
129 self.key_binding = key_binding.into();
130 self
131 }
132}
133
134impl Render for Tooltip {
135 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
136 tooltip_container(window, cx, |el, _, _| {
137 el.child(
138 h_flex()
139 .gap_4()
140 .child(div().max_w_72().child(self.title.clone()))
141 .when_some(self.key_binding.clone(), |this, key_binding| {
142 this.justify_between().child(key_binding)
143 }),
144 )
145 .when_some(self.meta.clone(), |this, meta| {
146 this.child(Label::new(meta).size(LabelSize::Small).color(Color::Muted))
147 })
148 })
149 }
150}
151
152pub fn tooltip_container<V>(
153 window: &mut Window,
154 cx: &mut Context<V>,
155 f: impl FnOnce(Div, &mut Window, &mut Context<V>) -> Div,
156) -> impl IntoElement {
157 let ui_font = ThemeSettings::get_global(cx).ui_font.clone();
158
159 // padding to avoid tooltip appearing right below the mouse cursor
160 div().pl_2().pt_2p5().child(
161 v_flex()
162 .elevation_2(cx)
163 .font(ui_font)
164 .text_ui(cx)
165 .text_color(cx.theme().colors().text)
166 .py_1()
167 .px_2()
168 .map(|el| f(el, window, cx)),
169 )
170}
171
172pub struct LinkPreview {
173 link: SharedString,
174}
175
176impl LinkPreview {
177 pub fn new(url: &str, cx: &mut App) -> AnyView {
178 let mut wrapped_url = String::new();
179 for (i, ch) in url.chars().enumerate() {
180 if i == 500 {
181 wrapped_url.push('…');
182 break;
183 }
184 if i % 100 == 0 && i != 0 {
185 wrapped_url.push('\n');
186 }
187 wrapped_url.push(ch);
188 }
189 cx.new(|_| LinkPreview {
190 link: wrapped_url.into(),
191 })
192 .into()
193 }
194}
195
196impl Render for LinkPreview {
197 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
198 tooltip_container(window, cx, |el, _, _| {
199 el.child(
200 Label::new(self.link.clone())
201 .size(LabelSize::XSmall)
202 .color(Color::Muted),
203 )
204 })
205 }
206}