keybinding.rs

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