1use crate::{h_flex, prelude::*, Icon, IconName, IconSize};
2use gpui::{relative, 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 /// The [`PlatformStyle`] to use when displaying this keybinding.
13 platform_style: PlatformStyle,
14}
15
16impl KeyBinding {
17 pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
18 let key_binding = cx.bindings_for_action(action).last().cloned()?;
19 Some(Self::new(key_binding))
20 }
21
22 // like for_action(), but lets you specify the context from which keybindings
23 // are matched.
24 pub fn for_action_in(
25 action: &dyn Action,
26 focus: &FocusHandle,
27 cx: &mut WindowContext,
28 ) -> Option<Self> {
29 let key_binding = cx.bindings_for_action_in(action, focus).last().cloned()?;
30 Some(Self::new(key_binding))
31 }
32
33 fn icon_for_key(keystroke: &Keystroke) -> Option<IconName> {
34 match keystroke.key.as_str() {
35 "left" => Some(IconName::ArrowLeft),
36 "right" => Some(IconName::ArrowRight),
37 "up" => Some(IconName::ArrowUp),
38 "down" => Some(IconName::ArrowDown),
39 "backspace" => Some(IconName::Backspace),
40 "delete" => Some(IconName::Delete),
41 "return" => Some(IconName::Return),
42 "enter" => Some(IconName::Return),
43 "tab" => Some(IconName::Tab),
44 "space" => Some(IconName::Space),
45 "escape" => Some(IconName::Escape),
46 "pagedown" => Some(IconName::PageDown),
47 "pageup" => Some(IconName::PageUp),
48 _ => None,
49 }
50 }
51
52 pub fn new(key_binding: gpui::KeyBinding) -> Self {
53 Self {
54 key_binding,
55 platform_style: PlatformStyle::platform(),
56 }
57 }
58
59 /// Sets the [`PlatformStyle`] for this [`KeyBinding`].
60 pub fn platform_style(mut self, platform_style: PlatformStyle) -> Self {
61 self.platform_style = platform_style;
62 self
63 }
64}
65
66impl RenderOnce for KeyBinding {
67 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
68 h_flex()
69 .flex_none()
70 .gap_2()
71 .children(self.key_binding.keystrokes().iter().map(|keystroke| {
72 let key_icon = Self::icon_for_key(keystroke);
73
74 h_flex()
75 .flex_none()
76 .map(|el| match self.platform_style {
77 PlatformStyle::Mac => el.gap_0p5(),
78 PlatformStyle::Linux | PlatformStyle::Windows => el,
79 })
80 .p_0p5()
81 .rounded_sm()
82 .text_color(cx.theme().colors().text_muted)
83 .when(keystroke.modifiers.function, |el| {
84 match self.platform_style {
85 PlatformStyle::Mac => el.child(Key::new("fn")),
86 PlatformStyle::Linux | PlatformStyle::Windows => {
87 el.child(Key::new("Fn")).child(Key::new("+"))
88 }
89 }
90 })
91 .when(keystroke.modifiers.control, |el| {
92 match self.platform_style {
93 PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Control)),
94 PlatformStyle::Linux | PlatformStyle::Windows => {
95 el.child(Key::new("Ctrl")).child(Key::new("+"))
96 }
97 }
98 })
99 .when(keystroke.modifiers.alt, |el| match self.platform_style {
100 PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Option)),
101 PlatformStyle::Linux | PlatformStyle::Windows => {
102 el.child(Key::new("Alt")).child(Key::new("+"))
103 }
104 })
105 .when(keystroke.modifiers.command, |el| {
106 match self.platform_style {
107 PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Command)),
108 PlatformStyle::Linux => {
109 el.child(Key::new("Super")).child(Key::new("+"))
110 }
111 PlatformStyle::Windows => {
112 el.child(Key::new("Win")).child(Key::new("+"))
113 }
114 }
115 })
116 .when(keystroke.modifiers.shift, |el| match self.platform_style {
117 PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Shift)),
118 PlatformStyle::Linux | PlatformStyle::Windows => {
119 el.child(Key::new("Shift")).child(Key::new("+"))
120 }
121 })
122 .map(|el| match key_icon {
123 Some(icon) => el.child(KeyIcon::new(icon)),
124 None => el.child(Key::new(keystroke.key.to_uppercase())),
125 })
126 }))
127 }
128}
129
130#[derive(IntoElement)]
131pub struct Key {
132 key: SharedString,
133}
134
135impl RenderOnce for Key {
136 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
137 let single_char = self.key.len() == 1;
138
139 div()
140 .py_0()
141 .map(|this| {
142 if single_char {
143 this.w(rems_from_px(14.))
144 .flex()
145 .flex_none()
146 .justify_center()
147 } else {
148 this.px_0p5()
149 }
150 })
151 .h(rems_from_px(14.))
152 .text_ui()
153 .line_height(relative(1.))
154 .text_color(cx.theme().colors().text_muted)
155 .child(self.key.clone())
156 }
157}
158
159impl Key {
160 pub fn new(key: impl Into<SharedString>) -> Self {
161 Self { key: key.into() }
162 }
163}
164
165#[derive(IntoElement)]
166pub struct KeyIcon {
167 icon: IconName,
168}
169
170impl RenderOnce for KeyIcon {
171 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
172 div().w(rems_from_px(14.)).child(
173 Icon::new(self.icon)
174 .size(IconSize::Small)
175 .color(Color::Muted),
176 )
177 }
178}
179
180impl KeyIcon {
181 pub fn new(icon: IconName) -> Self {
182 Self { icon }
183 }
184}