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 .debug_selector(|| {
70 format!(
71 "KEY_BINDING-{}",
72 self.key_binding
73 .keystrokes()
74 .iter()
75 .map(|k| k.key.to_string())
76 .collect::<Vec<_>>()
77 .join(" ")
78 )
79 })
80 .flex_none()
81 .gap_2()
82 .children(self.key_binding.keystrokes().iter().map(|keystroke| {
83 let key_icon = Self::icon_for_key(keystroke);
84
85 h_flex()
86 .flex_none()
87 .map(|el| match self.platform_style {
88 PlatformStyle::Mac => el.gap_0p5(),
89 PlatformStyle::Linux | PlatformStyle::Windows => el,
90 })
91 .p_0p5()
92 .rounded_sm()
93 .text_color(cx.theme().colors().text_muted)
94 .when(keystroke.modifiers.function, |el| {
95 match self.platform_style {
96 PlatformStyle::Mac => el.child(Key::new("fn")),
97 PlatformStyle::Linux | PlatformStyle::Windows => {
98 el.child(Key::new("Fn")).child(Key::new("+"))
99 }
100 }
101 })
102 .when(keystroke.modifiers.control, |el| {
103 match self.platform_style {
104 PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Control)),
105 PlatformStyle::Linux | PlatformStyle::Windows => {
106 el.child(Key::new("Ctrl")).child(Key::new("+"))
107 }
108 }
109 })
110 .when(keystroke.modifiers.alt, |el| match self.platform_style {
111 PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Option)),
112 PlatformStyle::Linux | PlatformStyle::Windows => {
113 el.child(Key::new("Alt")).child(Key::new("+"))
114 }
115 })
116 .when(keystroke.modifiers.platform, |el| {
117 match self.platform_style {
118 PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Command)),
119 PlatformStyle::Linux => {
120 el.child(Key::new("Super")).child(Key::new("+"))
121 }
122 PlatformStyle::Windows => {
123 el.child(Key::new("Win")).child(Key::new("+"))
124 }
125 }
126 })
127 .when(keystroke.modifiers.shift, |el| match self.platform_style {
128 PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Shift)),
129 PlatformStyle::Linux | PlatformStyle::Windows => {
130 el.child(Key::new("Shift")).child(Key::new("+"))
131 }
132 })
133 .map(|el| match key_icon {
134 Some(icon) => el.child(KeyIcon::new(icon)),
135 None => el.child(Key::new(keystroke.key.to_uppercase())),
136 })
137 }))
138 }
139}
140
141#[derive(IntoElement)]
142pub struct Key {
143 key: SharedString,
144}
145
146impl RenderOnce for Key {
147 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
148 let single_char = self.key.len() == 1;
149
150 div()
151 .py_0()
152 .map(|this| {
153 if single_char {
154 this.w(rems_from_px(14.))
155 .flex()
156 .flex_none()
157 .justify_center()
158 } else {
159 this.px_0p5()
160 }
161 })
162 .h(rems_from_px(14.))
163 .text_ui()
164 .line_height(relative(1.))
165 .text_color(cx.theme().colors().text_muted)
166 .child(self.key.clone())
167 }
168}
169
170impl Key {
171 pub fn new(key: impl Into<SharedString>) -> Self {
172 Self { key: key.into() }
173 }
174}
175
176#[derive(IntoElement)]
177pub struct KeyIcon {
178 icon: IconName,
179}
180
181impl RenderOnce for KeyIcon {
182 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
183 div().w(rems_from_px(14.)).child(
184 Icon::new(self.icon)
185 .size(IconSize::Small)
186 .color(Color::Muted),
187 )
188 }
189}
190
191impl KeyIcon {
192 pub fn new(icon: IconName) -> Self {
193 Self { icon }
194 }
195}