keybinding_hint.rs

  1use crate::KeyBinding;
  2use crate::{h_flex, prelude::*};
  3use gpui::{AnyElement, App, BoxShadow, FontStyle, Hsla, IntoElement, Window, point};
  4use smallvec::smallvec;
  5use theme::Appearance;
  6
  7/// Represents a hint for a keybinding, optionally with a prefix and suffix.
  8///
  9/// This struct allows for the creation and customization of a keybinding hint,
 10/// which can be used to display keyboard shortcuts or commands in a user interface.
 11///
 12/// # Examples
 13///
 14/// ```
 15/// use ui::prelude::*;
 16///
 17/// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+S"))
 18///     .prefix("Save:")
 19///     .size(Pixels::from(14.0));
 20/// ```
 21#[derive(Debug, IntoElement, IntoComponent)]
 22pub struct KeybindingHint {
 23    prefix: Option<SharedString>,
 24    suffix: Option<SharedString>,
 25    keybinding: KeyBinding,
 26    size: Option<Pixels>,
 27    background_color: Hsla,
 28}
 29
 30impl KeybindingHint {
 31    /// Creates a new `KeybindingHint` with the specified keybinding.
 32    ///
 33    /// This method initializes a new `KeybindingHint` instance with the given keybinding,
 34    /// setting all other fields to their default values.
 35    ///
 36    /// # Examples
 37    ///
 38    /// ```
 39    /// use ui::prelude::*;
 40    ///
 41    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+C"), Hsla::new(0.0, 0.0, 0.0, 1.0));
 42    /// ```
 43    pub fn new(keybinding: KeyBinding, background_color: Hsla) -> Self {
 44        Self {
 45            prefix: None,
 46            suffix: None,
 47            keybinding,
 48            size: None,
 49            background_color,
 50        }
 51    }
 52
 53    /// Creates a new `KeybindingHint` with a prefix and keybinding.
 54    ///
 55    /// This method initializes a new `KeybindingHint` instance with the given prefix and keybinding,
 56    /// setting all other fields to their default values.
 57    ///
 58    /// # Examples
 59    ///
 60    /// ```
 61    /// use ui::prelude::*;
 62    ///
 63    /// let hint = KeybindingHint::with_prefix("Copy:", KeyBinding::from_str("Ctrl+C"), Hsla::new(0.0, 0.0, 0.0, 1.0));
 64    /// ```
 65    pub fn with_prefix(
 66        prefix: impl Into<SharedString>,
 67        keybinding: KeyBinding,
 68        background_color: Hsla,
 69    ) -> Self {
 70        Self {
 71            prefix: Some(prefix.into()),
 72            suffix: None,
 73            keybinding,
 74            size: None,
 75            background_color,
 76        }
 77    }
 78
 79    /// Creates a new `KeybindingHint` with a keybinding and suffix.
 80    ///
 81    /// This method initializes a new `KeybindingHint` instance with the given keybinding and suffix,
 82    /// setting all other fields to their default values.
 83    ///
 84    /// # Examples
 85    ///
 86    /// ```
 87    /// use ui::prelude::*;
 88    ///
 89    /// let hint = KeybindingHint::with_suffix(KeyBinding::from_str("Ctrl+V"), "Paste", Hsla::new(0.0, 0.0, 0.0, 1.0));
 90    /// ```
 91    pub fn with_suffix(
 92        keybinding: KeyBinding,
 93        suffix: impl Into<SharedString>,
 94        background_color: Hsla,
 95    ) -> Self {
 96        Self {
 97            prefix: None,
 98            suffix: Some(suffix.into()),
 99            keybinding,
100            size: None,
101            background_color,
102        }
103    }
104
105    /// Sets the prefix for the keybinding hint.
106    ///
107    /// This method allows adding or changing the prefix text that appears before the keybinding.
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// use ui::prelude::*;
113    ///
114    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+X"))
115    ///     .prefix("Cut:");
116    /// ```
117    pub fn prefix(mut self, prefix: impl Into<SharedString>) -> Self {
118        self.prefix = Some(prefix.into());
119        self
120    }
121
122    /// Sets the suffix for the keybinding hint.
123    ///
124    /// This method allows adding or changing the suffix text that appears after the keybinding.
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use ui::prelude::*;
130    ///
131    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+F"))
132    ///     .suffix("Find");
133    /// ```
134    pub fn suffix(mut self, suffix: impl Into<SharedString>) -> Self {
135        self.suffix = Some(suffix.into());
136        self
137    }
138
139    /// Sets the size of the keybinding hint.
140    ///
141    /// This method allows specifying the size of the keybinding hint in pixels.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use ui::prelude::*;
147    ///
148    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+Z"))
149    ///     .size(Pixels::from(16.0));
150    /// ```
151    pub fn size(mut self, size: impl Into<Option<Pixels>>) -> Self {
152        self.size = size.into();
153        self
154    }
155}
156
157impl RenderOnce for KeybindingHint {
158    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
159        let colors = cx.theme().colors().clone();
160        let is_light = cx.theme().appearance() == Appearance::Light;
161
162        let border_color =
163            self.background_color
164                .blend(colors.text.alpha(if is_light { 0.08 } else { 0.16 }));
165        let bg_color =
166            self.background_color
167                .blend(colors.text.alpha(if is_light { 0.06 } else { 0.12 }));
168        let shadow_color = colors.text.alpha(if is_light { 0.04 } else { 0.08 });
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
175        let mut base = h_flex();
176
177        base.text_style()
178            .get_or_insert_with(Default::default)
179            .font_style = Some(FontStyle::Italic);
180
181        base.items_center()
182            .gap_0p5()
183            .font_buffer(cx)
184            .text_size(size)
185            .text_color(colors.text_disabled)
186            .children(self.prefix)
187            .child(
188                h_flex()
189                    .items_center()
190                    .rounded_sm()
191                    .px_0p5()
192                    .mr_0p5()
193                    .border_1()
194                    .border_color(border_color)
195                    .bg(bg_color)
196                    .shadow(smallvec![BoxShadow {
197                        color: shadow_color,
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: &mut 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        let bg_color = cx.theme().colors().surface_background;
216
217        v_flex()
218            .gap_6()
219            .children(vec![
220                example_group_with_title(
221                    "Basic",
222                    vec![
223                        single_example(
224                            "With Prefix",
225                            KeybindingHint::with_prefix("Go to Start:", enter.clone(), bg_color)
226                                .into_any_element(),
227                        ),
228                        single_example(
229                            "With Suffix",
230                            KeybindingHint::with_suffix(enter.clone(), "Go to End", bg_color)
231                                .into_any_element(),
232                        ),
233                        single_example(
234                            "With Prefix and Suffix",
235                            KeybindingHint::new(enter.clone(), bg_color)
236                                .prefix("Confirm:")
237                                .suffix("Execute selected action")
238                                .into_any_element(),
239                        ),
240                    ],
241                ),
242                example_group_with_title(
243                    "Sizes",
244                    vec![
245                        single_example(
246                            "Small",
247                            KeybindingHint::new(enter.clone(), bg_color)
248                                .size(Pixels::from(12.0))
249                                .prefix("Small:")
250                                .into_any_element(),
251                        ),
252                        single_example(
253                            "Medium",
254                            KeybindingHint::new(enter.clone(), bg_color)
255                                .size(Pixels::from(16.0))
256                                .suffix("Medium")
257                                .into_any_element(),
258                        ),
259                        single_example(
260                            "Large",
261                            KeybindingHint::new(enter.clone(), bg_color)
262                                .size(Pixels::from(20.0))
263                                .prefix("Large:")
264                                .suffix("Size")
265                                .into_any_element(),
266                        ),
267                    ],
268                ),
269            ])
270            .into_any_element()
271    }
272}