tooltip.rs

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