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