1use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
2use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke};
3
4#[derive(IntoElement, Clone)]
5pub struct KeyBinding {
6 /// A keybinding consists of a key and a set of modifier keys.
7 /// More then one keybinding produces a chord.
8 ///
9 /// This should always contain at least one element.
10 key_binding: gpui::KeyBinding,
11}
12
13impl RenderOnce for KeyBinding {
14 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
15 h_stack()
16 .flex_none()
17 .gap_2()
18 .children(self.key_binding.keystrokes().iter().map(|keystroke| {
19 let key_icon = Self::icon_for_key(&keystroke);
20
21 h_stack()
22 .flex_none()
23 .gap_0p5()
24 .bg(cx.theme().colors().element_background)
25 .p_0p5()
26 .rounded_sm()
27 .when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
28 .when(keystroke.modifiers.control, |el| {
29 el.child(KeyIcon::new(Icon::Control))
30 })
31 .when(keystroke.modifiers.alt, |el| {
32 el.child(KeyIcon::new(Icon::Option))
33 })
34 .when(keystroke.modifiers.command, |el| {
35 el.child(KeyIcon::new(Icon::Command))
36 })
37 .when(keystroke.modifiers.shift, |el| {
38 el.child(KeyIcon::new(Icon::Shift))
39 })
40 .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon)))
41 .when(key_icon.is_none(), |el| {
42 el.child(Key::new(keystroke.key.to_uppercase().clone()))
43 })
44 }))
45 }
46}
47
48impl KeyBinding {
49 pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
50 let key_binding = cx.bindings_for_action(action).last().cloned()?;
51 Some(Self::new(key_binding))
52 }
53
54 // like for_action(), but lets you specify the context from which keybindings
55 // are matched.
56 pub fn for_action_in(
57 action: &dyn Action,
58 focus: &FocusHandle,
59 cx: &mut WindowContext,
60 ) -> Option<Self> {
61 let key_binding = cx.bindings_for_action_in(action, focus).last().cloned()?;
62 Some(Self::new(key_binding))
63 }
64
65 fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {
66 match keystroke.key.as_str() {
67 "left" => Some(Icon::ArrowLeft),
68 "right" => Some(Icon::ArrowRight),
69 "up" => Some(Icon::ArrowUp),
70 "down" => Some(Icon::ArrowDown),
71 "backspace" => Some(Icon::Backspace),
72 "delete" => Some(Icon::Delete),
73 _ => None,
74 }
75 }
76
77 pub fn new(key_binding: gpui::KeyBinding) -> Self {
78 Self { key_binding }
79 }
80}
81
82#[derive(IntoElement)]
83pub struct Key {
84 key: SharedString,
85}
86
87impl RenderOnce for Key {
88 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
89 let single_char = self.key.len() == 1;
90
91 div()
92 .py_0()
93 .map(|this| {
94 if single_char {
95 this.w(rems(14. / 16.)).flex().flex_none().justify_center()
96 } else {
97 this.px_0p5()
98 }
99 })
100 .h(rems(14. / 16.))
101 .text_ui()
102 .line_height(relative(1.))
103 .text_color(cx.theme().colors().text)
104 .child(self.key.clone())
105 }
106}
107
108impl Key {
109 pub fn new(key: impl Into<SharedString>) -> Self {
110 Self { key: key.into() }
111 }
112}
113
114#[derive(IntoElement)]
115pub struct KeyIcon {
116 icon: Icon,
117}
118
119impl RenderOnce for KeyIcon {
120 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
121 div()
122 .w(rems(14. / 16.))
123 .child(IconElement::new(self.icon).size(IconSize::Small))
124 }
125}
126
127impl KeyIcon {
128 pub fn new(icon: Icon) -> Self {
129 Self { icon }
130 }
131}