keybinding_hint.rs

  1use crate::{h_flex, prelude::*};
  2use crate::{ElevationIndex, KeyBinding};
  3use gpui::{point, App, BoxShadow, IntoElement, Window};
  4use smallvec::smallvec;
  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/// ```
 14/// use ui::prelude::*;
 15///
 16/// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+S"))
 17///     .prefix("Save:")
 18///     .size(Pixels::from(14.0));
 19/// ```
 20#[derive(Debug, IntoElement, Clone)]
 21pub struct KeybindingHint {
 22    prefix: Option<SharedString>,
 23    suffix: Option<SharedString>,
 24    keybinding: KeyBinding,
 25    size: Option<Pixels>,
 26    elevation: Option<ElevationIndex>,
 27}
 28
 29impl KeybindingHint {
 30    /// Creates a new `KeybindingHint` with the specified keybinding.
 31    ///
 32    /// This method initializes a new `KeybindingHint` instance with the given keybinding,
 33    /// setting all other fields to their default values.
 34    ///
 35    /// # Examples
 36    ///
 37    /// ```
 38    /// use ui::prelude::*;
 39    ///
 40    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+C"));
 41    /// ```
 42    pub fn new(keybinding: KeyBinding) -> Self {
 43        Self {
 44            prefix: None,
 45            suffix: None,
 46            keybinding,
 47            size: None,
 48            elevation: None,
 49        }
 50    }
 51
 52    /// Creates a new `KeybindingHint` with a prefix and keybinding.
 53    ///
 54    /// This method initializes a new `KeybindingHint` instance with the given prefix and keybinding,
 55    /// setting all other fields to their default values.
 56    ///
 57    /// # Examples
 58    ///
 59    /// ```
 60    /// use ui::prelude::*;
 61    ///
 62    /// let hint = KeybindingHint::with_prefix("Copy:", KeyBinding::from_str("Ctrl+C"));
 63    /// ```
 64    pub fn with_prefix(prefix: impl Into<SharedString>, keybinding: KeyBinding) -> Self {
 65        Self {
 66            prefix: Some(prefix.into()),
 67            suffix: None,
 68            keybinding,
 69            size: None,
 70            elevation: None,
 71        }
 72    }
 73
 74    /// Creates a new `KeybindingHint` with a keybinding and suffix.
 75    ///
 76    /// This method initializes a new `KeybindingHint` instance with the given keybinding and suffix,
 77    /// setting all other fields to their default values.
 78    ///
 79    /// # Examples
 80    ///
 81    /// ```
 82    /// use ui::prelude::*;
 83    ///
 84    /// let hint = KeybindingHint::with_suffix(KeyBinding::from_str("Ctrl+V"), "Paste");
 85    /// ```
 86    pub fn with_suffix(keybinding: KeyBinding, suffix: impl Into<SharedString>) -> Self {
 87        Self {
 88            prefix: None,
 89            suffix: Some(suffix.into()),
 90            keybinding,
 91            size: None,
 92            elevation: None,
 93        }
 94    }
 95
 96    /// Sets the prefix for the keybinding hint.
 97    ///
 98    /// This method allows adding or changing the prefix text that appears before the keybinding.
 99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use ui::prelude::*;
104    ///
105    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+X"))
106    ///     .prefix("Cut:");
107    /// ```
108    pub fn prefix(mut self, prefix: impl Into<SharedString>) -> Self {
109        self.prefix = Some(prefix.into());
110        self
111    }
112
113    /// Sets the suffix for the keybinding hint.
114    ///
115    /// This method allows adding or changing the suffix text that appears after the keybinding.
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use ui::prelude::*;
121    ///
122    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+F"))
123    ///     .suffix("Find");
124    /// ```
125    pub fn suffix(mut self, suffix: impl Into<SharedString>) -> Self {
126        self.suffix = Some(suffix.into());
127        self
128    }
129
130    /// Sets the size of the keybinding hint.
131    ///
132    /// This method allows specifying the size of the keybinding hint in pixels.
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use ui::prelude::*;
138    ///
139    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+Z"))
140    ///     .size(Pixels::from(16.0));
141    /// ```
142    pub fn size(mut self, size: impl Into<Option<Pixels>>) -> Self {
143        self.size = size.into();
144        self
145    }
146
147    /// Sets the elevation of the keybinding hint.
148    ///
149    /// This method allows specifying the elevation index for the keybinding hint,
150    /// which affects its visual appearance in terms of depth or layering.
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use ui::prelude::*;
156    ///
157    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+A"))
158    ///     .elevation(ElevationIndex::new(1));
159    /// ```
160    pub fn elevation(mut self, elevation: impl Into<Option<ElevationIndex>>) -> Self {
161        self.elevation = elevation.into();
162        self
163    }
164}
165
166impl RenderOnce for KeybindingHint {
167    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
168        let colors = cx.theme().colors().clone();
169
170        let size = self
171            .size
172            .unwrap_or(TextSize::Small.rems(cx).to_pixels(window.rem_size()));
173        let kb_size = size - px(2.0);
174        let kb_bg = if let Some(elevation) = self.elevation {
175            elevation.on_elevation_bg(cx)
176        } else {
177            theme::color_alpha(colors.element_background, 0.6)
178        };
179
180        h_flex()
181            .items_center()
182            .gap_0p5()
183            .font_buffer(cx)
184            .text_size(size)
185            .text_color(colors.text_muted)
186            .children(self.prefix)
187            .child(
188                h_flex()
189                    .items_center()
190                    .rounded_md()
191                    .px_0p5()
192                    .mr_0p5()
193                    .border_1()
194                    .border_color(kb_bg)
195                    .bg(kb_bg.opacity(0.8))
196                    .shadow(smallvec![BoxShadow {
197                        color: cx.theme().colors().editor_background.opacity(0.8),
198                        offset: point(px(0.), px(1.)),
199                        blur_radius: px(0.),
200                        spread_radius: px(0.),
201                    }])
202                    .child(self.keybinding.size(kb_size)),
203            )
204            .children(self.suffix)
205    }
206}
207
208impl ComponentPreview for KeybindingHint {
209    fn description() -> impl Into<Option<&'static str>> {
210        "Used to display hint text for keyboard shortcuts. Can have a prefix and suffix."
211    }
212
213    fn examples(window: &mut Window, _cx: &mut App) -> Vec<ComponentExampleGroup<Self>> {
214        let home_fallback = gpui::KeyBinding::new("home", menu::SelectFirst, None);
215        let home = KeyBinding::for_action(&menu::SelectFirst, window)
216            .unwrap_or(KeyBinding::new(home_fallback));
217
218        let end_fallback = gpui::KeyBinding::new("end", menu::SelectLast, None);
219        let end = KeyBinding::for_action(&menu::SelectLast, window)
220            .unwrap_or(KeyBinding::new(end_fallback));
221
222        let enter_fallback = gpui::KeyBinding::new("enter", menu::Confirm, None);
223        let enter = KeyBinding::for_action(&menu::Confirm, window)
224            .unwrap_or(KeyBinding::new(enter_fallback));
225
226        let escape_fallback = gpui::KeyBinding::new("escape", menu::Cancel, None);
227        let escape = KeyBinding::for_action(&menu::Cancel, window)
228            .unwrap_or(KeyBinding::new(escape_fallback));
229
230        vec![
231            example_group_with_title(
232                "Basic",
233                vec![
234                    single_example(
235                        "With Prefix",
236                        KeybindingHint::with_prefix("Go to Start:", home.clone()),
237                    ),
238                    single_example(
239                        "With Suffix",
240                        KeybindingHint::with_suffix(end.clone(), "Go to End"),
241                    ),
242                    single_example(
243                        "With Prefix and Suffix",
244                        KeybindingHint::new(enter.clone())
245                            .prefix("Confirm:")
246                            .suffix("Execute selected action"),
247                    ),
248                ],
249            ),
250            example_group_with_title(
251                "Sizes",
252                vec![
253                    single_example(
254                        "Small",
255                        KeybindingHint::new(home.clone())
256                            .size(Pixels::from(12.0))
257                            .prefix("Small:"),
258                    ),
259                    single_example(
260                        "Medium",
261                        KeybindingHint::new(end.clone())
262                            .size(Pixels::from(16.0))
263                            .suffix("Medium"),
264                    ),
265                    single_example(
266                        "Large",
267                        KeybindingHint::new(enter.clone())
268                            .size(Pixels::from(20.0))
269                            .prefix("Large:")
270                            .suffix("Size"),
271                    ),
272                ],
273            ),
274            example_group_with_title(
275                "Elevations",
276                vec![
277                    single_example(
278                        "Surface",
279                        KeybindingHint::new(home.clone())
280                            .elevation(ElevationIndex::Surface)
281                            .prefix("Surface:"),
282                    ),
283                    single_example(
284                        "Elevated Surface",
285                        KeybindingHint::new(end.clone())
286                            .elevation(ElevationIndex::ElevatedSurface)
287                            .suffix("Elevated"),
288                    ),
289                    single_example(
290                        "Editor Surface",
291                        KeybindingHint::new(enter.clone())
292                            .elevation(ElevationIndex::EditorSurface)
293                            .prefix("Editor:")
294                            .suffix("Surface"),
295                    ),
296                    single_example(
297                        "Modal Surface",
298                        KeybindingHint::new(escape.clone())
299                            .elevation(ElevationIndex::ModalSurface)
300                            .prefix("Modal:")
301                            .suffix("Escape"),
302                    ),
303                ],
304            ),
305        ]
306    }
307}