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