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