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