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