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