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