tooltip.rs

  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}