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