keybinding_hint.rs

  1use crate::KeyBinding;
  2use crate::prelude::*;
  3use gpui::{AnyElement, App, BoxShadow, FontStyle, Hsla, IntoElement, Window, point};
  4use theme::Appearance;
  5
  6/// Represents a hint for a keybinding, optionally with a prefix and suffix.
  7///
  8/// This struct allows for the creation and customization of a keybinding hint,
  9/// which can be used to display keyboard shortcuts or commands in a user interface.
 10///
 11/// # Examples
 12///
 13/// ```no_run
 14/// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
 15/// use ui::prelude::*;
 16/// use ui::{KeyBinding, KeybindingHint};
 17/// use settings::KeybindSource;
 18///
 19/// # fn example(cx: &App) {
 20/// let hint = KeybindingHint::new(
 21///     KeyBinding::from_keystrokes(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-s").unwrap())].into(), KeybindSource::Base),
 22///     Hsla::black()
 23/// )
 24///     .prefix("Save:")
 25///     .size(Pixels::from(14.0));
 26/// # }
 27/// ```
 28#[derive(Debug, IntoElement, RegisterComponent)]
 29pub struct KeybindingHint {
 30    prefix: Option<SharedString>,
 31    suffix: Option<SharedString>,
 32    keybinding: KeyBinding,
 33    size: Option<Pixels>,
 34    background_color: Hsla,
 35}
 36
 37impl KeybindingHint {
 38    /// Creates a new `KeybindingHint` with the specified keybinding.
 39    ///
 40    /// This method initializes a new `KeybindingHint` instance with the given keybinding,
 41    /// setting all other fields to their default values.
 42    ///
 43    /// # Examples
 44    ///
 45    /// ```no_run
 46    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
 47    /// use ui::prelude::*;
 48    /// use ui::{KeyBinding, KeybindingHint};
 49    /// use settings::KeybindSource;
 50    ///
 51    /// # fn example(cx: &App) {
 52    /// let hint = KeybindingHint::new(
 53    ///     KeyBinding::from_keystrokes(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-c").unwrap())].into(), KeybindSource::Base),
 54    ///     Hsla::black()
 55    /// );
 56    /// # }
 57    /// ```
 58    pub fn new(keybinding: KeyBinding, background_color: Hsla) -> Self {
 59        Self {
 60            prefix: None,
 61            suffix: None,
 62            keybinding,
 63            size: None,
 64            background_color,
 65        }
 66    }
 67
 68    /// Creates a new `KeybindingHint` with a prefix and keybinding.
 69    ///
 70    /// This method initializes a new `KeybindingHint` instance with the given prefix and keybinding,
 71    /// setting all other fields to their default values.
 72    ///
 73    /// # Examples
 74    ///
 75    /// ```no_run
 76    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
 77    /// use ui::prelude::*;
 78    /// use ui::{KeyBinding, KeybindingHint};
 79    /// use settings::KeybindSource;
 80    ///
 81    /// # fn example(cx: &App) {
 82    /// let hint = KeybindingHint::with_prefix(
 83    ///     "Copy:",
 84    ///     KeyBinding::from_keystrokes(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-c").unwrap())].into(), KeybindSource::Base),
 85    ///     Hsla::black()
 86    /// );
 87    /// # }
 88    /// ```
 89    pub fn with_prefix(
 90        prefix: impl Into<SharedString>,
 91        keybinding: KeyBinding,
 92        background_color: Hsla,
 93    ) -> Self {
 94        Self {
 95            prefix: Some(prefix.into()),
 96            suffix: None,
 97            keybinding,
 98            size: None,
 99            background_color,
100        }
101    }
102
103    /// Creates a new `KeybindingHint` with a keybinding and suffix.
104    ///
105    /// This method initializes a new `KeybindingHint` instance with the given keybinding and suffix,
106    /// setting all other fields to their default values.
107    ///
108    /// # Examples
109    ///
110    /// ```no_run
111    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
112    /// use ui::prelude::*;
113    /// use ui::{KeyBinding, KeybindingHint};
114    /// use settings::KeybindSource;
115    ///
116    /// # fn example(cx: &App) {
117    /// let hint = KeybindingHint::with_suffix(
118    ///     KeyBinding::from_keystrokes(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-v").unwrap())].into(), KeybindSource::Base),
119    ///     "Paste",
120    ///     Hsla::black()
121    /// );
122    /// # }
123    /// ```
124    pub fn with_suffix(
125        keybinding: KeyBinding,
126        suffix: impl Into<SharedString>,
127        background_color: Hsla,
128    ) -> Self {
129        Self {
130            prefix: None,
131            suffix: Some(suffix.into()),
132            keybinding,
133            size: None,
134            background_color,
135        }
136    }
137
138    /// Sets the prefix for the keybinding hint.
139    ///
140    /// This method allows adding or changing the prefix text that appears before the keybinding.
141    ///
142    /// # Examples
143    ///
144    /// ```no_run
145    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
146    /// use ui::prelude::*;
147    /// use ui::{KeyBinding, KeybindingHint};
148    /// use settings::KeybindSource;
149    ///
150    /// # fn example(cx: &App) {
151    /// let hint = KeybindingHint::new(
152    ///     KeyBinding::from_keystrokes(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-x").unwrap())].into(), KeybindSource::Base),
153    ///     Hsla::black()
154    /// )
155    ///     .prefix("Cut:");
156    /// # }
157    /// ```
158    pub fn prefix(mut self, prefix: impl Into<SharedString>) -> Self {
159        self.prefix = Some(prefix.into());
160        self
161    }
162
163    /// Sets the suffix for the keybinding hint.
164    ///
165    /// This method allows adding or changing the suffix text that appears after the keybinding.
166    ///
167    /// # Examples
168    ///
169    /// ```no_run
170    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
171    /// use ui::prelude::*;
172    /// use ui::{KeyBinding, KeybindingHint};
173    /// use settings::KeybindSource;
174    ///
175    /// # fn example(cx: &App) {
176    /// let hint = KeybindingHint::new(
177    ///     KeyBinding::from_keystrokes(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-f").unwrap())].into(), KeybindSource::Base),
178    ///     Hsla::black()
179    /// )
180    ///     .suffix("Find");
181    /// # }
182    /// ```
183    pub fn suffix(mut self, suffix: impl Into<SharedString>) -> Self {
184        self.suffix = Some(suffix.into());
185        self
186    }
187
188    /// Sets the size of the keybinding hint.
189    ///
190    /// This method allows specifying the size of the keybinding hint in pixels.
191    ///
192    /// # Examples
193    ///
194    /// ```no_run
195    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
196    /// use ui::prelude::*;
197    /// use ui::{KeyBinding, KeybindingHint};
198    /// use settings::KeybindSource;
199    ///
200    /// # fn example(cx: &App) {
201    /// let hint = KeybindingHint::new(
202    ///     KeyBinding::from_keystrokes(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-z").unwrap())].into(), KeybindSource::Base),
203    ///     Hsla::black()
204    /// )
205    ///     .size(Pixels::from(16.0));
206    /// # }
207    /// ```
208    pub fn size(mut self, size: impl Into<Option<Pixels>>) -> Self {
209        self.size = size.into();
210        self
211    }
212}
213
214impl RenderOnce for KeybindingHint {
215    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
216        let colors = cx.theme().colors();
217        let is_light = cx.theme().appearance() == Appearance::Light;
218
219        let border_color =
220            self.background_color
221                .blend(colors.text.alpha(if is_light { 0.08 } else { 0.16 }));
222
223        let bg_color = self
224            .background_color
225            .blend(colors.text_accent.alpha(if is_light { 0.05 } else { 0.1 }));
226
227        let shadow_color = colors.text.alpha(if is_light { 0.04 } else { 0.08 });
228
229        let size = self
230            .size
231            .unwrap_or(TextSize::Small.rems(cx).to_pixels(window.rem_size()));
232
233        let kb_size = size - px(2.0);
234
235        let mut base = h_flex();
236
237        base.text_style()
238            .get_or_insert_with(Default::default)
239            .font_style = Some(FontStyle::Italic);
240
241        base.gap_1()
242            .font_buffer(cx)
243            .text_size(size)
244            .text_color(colors.text_disabled)
245            .children(self.prefix)
246            .child(
247                h_flex()
248                    .rounded_sm()
249                    .px_0p5()
250                    .mr_0p5()
251                    .border_1()
252                    .border_color(border_color)
253                    .bg(bg_color)
254                    .shadow(vec![BoxShadow {
255                        color: shadow_color,
256                        offset: point(px(0.), px(1.)),
257                        blur_radius: px(0.),
258                        spread_radius: px(0.),
259                    }])
260                    .child(self.keybinding.size(rems_from_px(kb_size))),
261            )
262            .children(self.suffix)
263    }
264}
265
266impl Component for KeybindingHint {
267    fn scope() -> ComponentScope {
268        ComponentScope::DataDisplay
269    }
270
271    fn description() -> Option<&'static str> {
272        Some("Displays a keyboard shortcut hint with optional prefix and suffix text")
273    }
274
275    fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
276        let enter = KeyBinding::for_action(&menu::Confirm, cx);
277
278        let bg_color = cx.theme().colors().surface_background;
279
280        Some(
281            v_flex()
282                .gap_6()
283                .children(vec![
284                    example_group_with_title(
285                        "Basic",
286                        vec![
287                            single_example(
288                                "With Prefix",
289                                KeybindingHint::with_prefix(
290                                    "Go to Start:",
291                                    enter.clone(),
292                                    bg_color,
293                                )
294                                .into_any_element(),
295                            ),
296                            single_example(
297                                "With Suffix",
298                                KeybindingHint::with_suffix(enter.clone(), "Go to End", bg_color)
299                                    .into_any_element(),
300                            ),
301                            single_example(
302                                "With Prefix and Suffix",
303                                KeybindingHint::new(enter.clone(), bg_color)
304                                    .prefix("Confirm:")
305                                    .suffix("Execute selected action")
306                                    .into_any_element(),
307                            ),
308                        ],
309                    ),
310                    example_group_with_title(
311                        "Sizes",
312                        vec![
313                            single_example(
314                                "Small",
315                                KeybindingHint::new(enter.clone(), bg_color)
316                                    .size(Pixels::from(12.0))
317                                    .prefix("Small:")
318                                    .into_any_element(),
319                            ),
320                            single_example(
321                                "Medium",
322                                KeybindingHint::new(enter.clone(), bg_color)
323                                    .size(Pixels::from(16.0))
324                                    .suffix("Medium")
325                                    .into_any_element(),
326                            ),
327                            single_example(
328                                "Large",
329                                KeybindingHint::new(enter, bg_color)
330                                    .size(Pixels::from(20.0))
331                                    .prefix("Large:")
332                                    .suffix("Size")
333                                    .into_any_element(),
334                            ),
335                        ],
336                    ),
337                ])
338                .into_any_element(),
339        )
340    }
341}