1use crate::{h_flex, prelude::*, Icon, IconName, 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_flex()
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_flex()
22 .flex_none()
23 .gap_0p5()
24 .p_0p5()
25 .rounded_sm()
26 .text_color(cx.theme().colors().text_muted)
27 .when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
28 .when(keystroke.modifiers.control, |el| {
29 el.child(KeyIcon::new(IconName::Control))
30 })
31 .when(keystroke.modifiers.alt, |el| {
32 el.child(KeyIcon::new(IconName::Option))
33 })
34 .when(keystroke.modifiers.command, |el| {
35 el.child(KeyIcon::new(IconName::Command))
36 })
37 .when(keystroke.modifiers.shift, |el| {
38 el.child(KeyIcon::new(IconName::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<IconName> {
66 match keystroke.key.as_str() {
67 "left" => Some(IconName::ArrowLeft),
68 "right" => Some(IconName::ArrowRight),
69 "up" => Some(IconName::ArrowUp),
70 "down" => Some(IconName::ArrowDown),
71 "backspace" => Some(IconName::Backspace),
72 "delete" => Some(IconName::Delete),
73 "return" => Some(IconName::Return),
74 "enter" => Some(IconName::Return),
75 "tab" => Some(IconName::Tab),
76 "space" => Some(IconName::Space),
77 "escape" => Some(IconName::Escape),
78 "pagedown" => Some(IconName::PageDown),
79 "pageup" => Some(IconName::PageUp),
80 _ => None,
81 }
82 }
83
84 pub fn new(key_binding: gpui::KeyBinding) -> Self {
85 Self { key_binding }
86 }
87}
88
89#[derive(IntoElement)]
90pub struct Key {
91 key: SharedString,
92}
93
94impl RenderOnce for Key {
95 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
96 let single_char = self.key.len() == 1;
97
98 div()
99 .py_0()
100 .map(|this| {
101 if single_char {
102 this.w(rems(14. / 16.)).flex().flex_none().justify_center()
103 } else {
104 this.px_0p5()
105 }
106 })
107 .h(rems(14. / 16.))
108 .text_ui()
109 .line_height(relative(1.))
110 .text_color(cx.theme().colors().text_muted)
111 .child(self.key.clone())
112 }
113}
114
115impl Key {
116 pub fn new(key: impl Into<SharedString>) -> Self {
117 Self { key: key.into() }
118 }
119}
120
121#[derive(IntoElement)]
122pub struct KeyIcon {
123 icon: IconName,
124}
125
126impl RenderOnce for KeyIcon {
127 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
128 div().w(rems(14. / 16.)).child(
129 Icon::new(self.icon)
130 .size(IconSize::Small)
131 .color(Color::Muted),
132 )
133 }
134}
135
136impl KeyIcon {
137 pub fn new(icon: IconName) -> Self {
138 Self { icon }
139 }
140}