keybinding_hint.rs

  1use crate::{h_flex, prelude::*};
  2use crate::{ElevationIndex, KeyBinding};
  3use gpui::{point, AnyElement, 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, IntoComponent)]
 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(rems_from_px(kb_size.0))),
203            )
204            .children(self.suffix)
205    }
206}
207
208impl ComponentPreview for KeybindingHint {
209    fn preview(window: &mut Window, _cx: &App) -> AnyElement {
210        let enter_fallback = gpui::KeyBinding::new("enter", menu::Confirm, None);
211        let enter = KeyBinding::for_action(&menu::Confirm, window)
212            .unwrap_or(KeyBinding::new(enter_fallback));
213
214        v_flex()
215            .gap_6()
216            .children(vec![
217                example_group_with_title(
218                    "Basic",
219                    vec![
220                        single_example(
221                            "With Prefix",
222                            KeybindingHint::with_prefix("Go to Start:", enter.clone())
223                                .into_any_element(),
224                        ),
225                        single_example(
226                            "With Suffix",
227                            KeybindingHint::with_suffix(enter.clone(), "Go to End")
228                                .into_any_element(),
229                        ),
230                        single_example(
231                            "With Prefix and Suffix",
232                            KeybindingHint::new(enter.clone())
233                                .prefix("Confirm:")
234                                .suffix("Execute selected action")
235                                .into_any_element(),
236                        ),
237                    ],
238                ),
239                example_group_with_title(
240                    "Sizes",
241                    vec![
242                        single_example(
243                            "Small",
244                            KeybindingHint::new(enter.clone())
245                                .size(Pixels::from(12.0))
246                                .prefix("Small:")
247                                .into_any_element(),
248                        ),
249                        single_example(
250                            "Medium",
251                            KeybindingHint::new(enter.clone())
252                                .size(Pixels::from(16.0))
253                                .suffix("Medium")
254                                .into_any_element(),
255                        ),
256                        single_example(
257                            "Large",
258                            KeybindingHint::new(enter.clone())
259                                .size(Pixels::from(20.0))
260                                .prefix("Large:")
261                                .suffix("Size")
262                                .into_any_element(),
263                        ),
264                    ],
265                ),
266                example_group_with_title(
267                    "Elevations",
268                    vec![
269                        single_example(
270                            "Surface",
271                            KeybindingHint::new(enter.clone())
272                                .elevation(ElevationIndex::Surface)
273                                .prefix("Surface:")
274                                .into_any_element(),
275                        ),
276                        single_example(
277                            "Elevated Surface",
278                            KeybindingHint::new(enter.clone())
279                                .elevation(ElevationIndex::ElevatedSurface)
280                                .suffix("Elevated")
281                                .into_any_element(),
282                        ),
283                        single_example(
284                            "Editor Surface",
285                            KeybindingHint::new(enter.clone())
286                                .elevation(ElevationIndex::EditorSurface)
287                                .prefix("Editor:")
288                                .suffix("Surface")
289                                .into_any_element(),
290                        ),
291                        single_example(
292                            "Modal Surface",
293                            KeybindingHint::new(enter.clone())
294                                .elevation(ElevationIndex::ModalSurface)
295                                .prefix("Modal:")
296                                .suffix("Enter")
297                                .into_any_element(),
298                        ),
299                    ],
300                ),
301            ])
302            .into_any_element()
303    }
304}