tooltip.rs

  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}