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