tooltip.rs

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