keybinding.rs

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