1use gpui::{Action, AnyView, IntoElement, Render, VisualContext};
2use settings::Settings;
3use theme::ThemeSettings;
4
5use crate::prelude::*;
6use crate::{h_flex, v_flex, Color, KeyBinding, Label, LabelSize, StyledExt};
7
8pub struct Tooltip {
9 title: SharedString,
10 meta: Option<SharedString>,
11 key_binding: Option<KeyBinding>,
12}
13
14impl Tooltip {
15 pub fn text(title: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView {
16 cx.new_view(|_cx| Self {
17 title: title.into(),
18 meta: None,
19 key_binding: None,
20 })
21 .into()
22 }
23
24 pub fn for_action(
25 title: impl Into<SharedString>,
26 action: &dyn Action,
27 cx: &mut WindowContext,
28 ) -> AnyView {
29 cx.new_view(|cx| Self {
30 title: title.into(),
31 meta: None,
32 key_binding: KeyBinding::for_action(action, cx),
33 })
34 .into()
35 }
36
37 pub fn with_meta(
38 title: impl Into<SharedString>,
39 action: Option<&dyn Action>,
40 meta: impl Into<SharedString>,
41 cx: &mut WindowContext,
42 ) -> AnyView {
43 cx.new_view(|cx| Self {
44 title: title.into(),
45 meta: Some(meta.into()),
46 key_binding: action.and_then(|action| KeyBinding::for_action(action, cx)),
47 })
48 .into()
49 }
50
51 pub fn new(title: impl Into<SharedString>) -> Self {
52 Self {
53 title: title.into(),
54 meta: None,
55 key_binding: None,
56 }
57 }
58
59 pub fn meta(mut self, meta: impl Into<SharedString>) -> Self {
60 self.meta = Some(meta.into());
61 self
62 }
63
64 pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
65 self.key_binding = key_binding.into();
66 self
67 }
68}
69
70impl Render for Tooltip {
71 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
72 tooltip_container(cx, |el, _| {
73 el.child(
74 h_flex()
75 .gap_4()
76 .child(self.title.clone())
77 .when_some(self.key_binding.clone(), |this, key_binding| {
78 this.justify_between().child(key_binding)
79 }),
80 )
81 .when_some(self.meta.clone(), |this, meta| {
82 this.child(Label::new(meta).size(LabelSize::Small).color(Color::Muted))
83 })
84 })
85 }
86}
87
88pub fn tooltip_container<V>(
89 cx: &mut ViewContext<V>,
90 f: impl FnOnce(Div, &mut ViewContext<V>) -> Div,
91) -> impl IntoElement {
92 let ui_font = ThemeSettings::get_global(cx).ui_font.clone();
93
94 // padding to avoid tooltip appearing right below the mouse cursor
95 div().pl_2().pt_2p5().child(
96 v_flex()
97 .elevation_2(cx)
98 .font(ui_font)
99 .text_ui(cx)
100 .text_color(cx.theme().colors().text)
101 .py_1()
102 .px_2()
103 .map(|el| f(el, cx)),
104 )
105}
106
107pub struct LinkPreview {
108 link: SharedString,
109}
110
111impl LinkPreview {
112 pub fn new(url: &str, cx: &mut WindowContext) -> AnyView {
113 let mut wrapped_url = String::new();
114 for (i, ch) in url.chars().enumerate() {
115 if i == 500 {
116 wrapped_url.push('…');
117 break;
118 }
119 if i % 100 == 0 && i != 0 {
120 wrapped_url.push('\n');
121 }
122 wrapped_url.push(ch);
123 }
124 cx.new_view(|_cx| LinkPreview {
125 link: wrapped_url.into(),
126 })
127 .into()
128 }
129}
130
131impl Render for LinkPreview {
132 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
133 tooltip_container(cx, |el, _| {
134 el.child(
135 Label::new(self.link.clone())
136 .size(LabelSize::XSmall)
137 .color(Color::Muted),
138 )
139 })
140 }
141}