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                    .gap_0p5()
101                    .p_0p5()
102                    .rounded_sm()
103                    .text_color(cx.theme().colors().text_muted)
104                    .when(keystroke.modifiers.function, |el| match self.display {
105                        KeyBindingDisplay::Mac => el.child(Key::new("fn")),
106                        KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => {
107                            el.child(Key::new("Fn"))
108                        }
109                    })
110                    .when(keystroke.modifiers.control, |el| match self.display {
111                        KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Control)),
112                        KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => {
113                            el.child(Key::new("Ctrl"))
114                        }
115                    })
116                    .when(keystroke.modifiers.alt, |el| match self.display {
117                        KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Option)),
118                        KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => {
119                            el.child(Key::new("Alt"))
120                        }
121                    })
122                    .when(keystroke.modifiers.command, |el| match self.display {
123                        KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Command)),
124                        KeyBindingDisplay::Linux => el.child(Key::new("Super")),
125                        KeyBindingDisplay::Windows => el.child(Key::new("Win")),
126                    })
127                    .when(keystroke.modifiers.shift, |el| match self.display {
128                        KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Option)),
129                        KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => {
130                            el.child(Key::new("Shift"))
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(14. / 16.)).flex().flex_none().justify_center()
155                } else {
156                    this.px_0p5()
157                }
158            })
159            .h(rems(14. / 16.))
160            .text_ui()
161            .line_height(relative(1.))
162            .text_color(cx.theme().colors().text_muted)
163            .child(self.key.clone())
164    }
165}
166
167impl Key {
168    pub fn new(key: impl Into<SharedString>) -> Self {
169        Self { key: key.into() }
170    }
171}
172
173#[derive(IntoElement)]
174pub struct KeyIcon {
175    icon: IconName,
176}
177
178impl RenderOnce for KeyIcon {
179    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
180        div().w(rems(14. / 16.)).child(
181            Icon::new(self.icon)
182                .size(IconSize::Small)
183                .color(Color::Muted),
184        )
185    }
186}
187
188impl KeyIcon {
189    pub fn new(icon: IconName) -> Self {
190        Self { icon }
191    }
192}