keybinding.rs

  1use gpui::Action;
  2use strum::EnumIter;
  3
  4use crate::prelude::*;
  5
  6#[derive(Component)]
  7pub struct KeyBinding {
  8    /// A keybinding consists of a key and a set of modifier keys.
  9    /// More then one keybinding produces a chord.
 10    ///
 11    /// This should always contain at least one element.
 12    key_binding: gpui::KeyBinding,
 13}
 14
 15impl KeyBinding {
 16    pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
 17        // todo! this last is arbitrary, we want to prefer users key bindings over defaults,
 18        // and vim over normal (in vim mode), etc.
 19        let key_binding = cx.bindings_for_action(action).last().cloned()?;
 20        Some(Self::new(key_binding))
 21    }
 22
 23    pub fn new(key_binding: gpui::KeyBinding) -> Self {
 24        Self { key_binding }
 25    }
 26
 27    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
 28        div()
 29            .flex()
 30            .gap_2()
 31            .children(self.key_binding.keystrokes().iter().map(|keystroke| {
 32                div()
 33                    .flex()
 34                    .gap_1()
 35                    .when(keystroke.modifiers.control, |el| el.child(Key::new("^")))
 36                    .when(keystroke.modifiers.alt, |el| el.child(Key::new("")))
 37                    .when(keystroke.modifiers.command, |el| el.child(Key::new("")))
 38                    .when(keystroke.modifiers.shift, |el| el.child(Key::new("")))
 39                    .child(Key::new(keystroke.key.clone()))
 40            }))
 41    }
 42}
 43
 44#[derive(Component)]
 45pub struct Key {
 46    key: SharedString,
 47}
 48
 49impl Key {
 50    pub fn new(key: impl Into<SharedString>) -> Self {
 51        Self { key: key.into() }
 52    }
 53
 54    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
 55        div()
 56            .px_2()
 57            .py_0()
 58            .rounded_md()
 59            .text_ui_sm()
 60            .text_color(cx.theme().colors().text)
 61            .bg(cx.theme().colors().element_background)
 62            .child(self.key.clone())
 63    }
 64}
 65
 66// NOTE: The order the modifier keys appear in this enum impacts the order in
 67// which they are rendered in the UI.
 68#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 69pub enum ModifierKey {
 70    Control,
 71    Alt,
 72    Command,
 73    Shift,
 74}
 75
 76#[cfg(feature = "stories")]
 77pub use stories::*;
 78
 79#[cfg(feature = "stories")]
 80mod stories {
 81    use super::*;
 82    use crate::Story;
 83    use gpui::{action, Div, Render};
 84    use itertools::Itertools;
 85
 86    pub struct KeybindingStory;
 87
 88    #[action]
 89    struct NoAction {}
 90
 91    pub fn binding(key: &str) -> gpui::KeyBinding {
 92        gpui::KeyBinding::new(key, NoAction {}, None)
 93    }
 94
 95    impl Render for KeybindingStory {
 96        type Element = Div<Self>;
 97
 98        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
 99            let all_modifier_permutations =
100                ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
101
102            Story::container(cx)
103                .child(Story::title_for::<_, KeyBinding>(cx))
104                .child(Story::label(cx, "Single Key"))
105                .child(KeyBinding::new(binding("Z")))
106                .child(Story::label(cx, "Single Key with Modifier"))
107                .child(
108                    div()
109                        .flex()
110                        .gap_3()
111                        .child(KeyBinding::new(binding("ctrl-c")))
112                        .child(KeyBinding::new(binding("alt-c")))
113                        .child(KeyBinding::new(binding("cmd-c")))
114                        .child(KeyBinding::new(binding("shift-c"))),
115                )
116                .child(Story::label(cx, "Single Key with Modifier (Permuted)"))
117                .child(
118                    div().flex().flex_col().children(
119                        all_modifier_permutations
120                            .chunks(4)
121                            .into_iter()
122                            .map(|chunk| {
123                                div()
124                                    .flex()
125                                    .gap_4()
126                                    .py_3()
127                                    .children(chunk.map(|permutation| {
128                                        KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
129                                    }))
130                            }),
131                    ),
132                )
133                .child(Story::label(cx, "Single Key with All Modifiers"))
134                .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
135                .child(Story::label(cx, "Chord"))
136                .child(KeyBinding::new(binding("a z")))
137                .child(Story::label(cx, "Chord with Modifier"))
138                .child(KeyBinding::new(binding("ctrl-a shift-z")))
139        }
140    }
141}