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