|
| 1 | +keybinds-rs |
| 2 | +=========== |
| 3 | + |
| 4 | +> [!CAUTION] |
| 5 | +> This crate is work in progress yet. |
| 6 | +
|
| 7 | +[keybinds-rs][crates-io] is a small Rust crate to define/parse/match key bindings. |
| 8 | + |
| 9 | +- Provide a syntax to easily define key bindings in a configuration file like `Ctrl+A` |
| 10 | +- Support key sequences like `Ctrl+X CtrolS` |
| 11 | +- Parse/Generate the key bindings configuration using [serde][] |
| 12 | +- Platform-agnostic core API with minimal dependencies |
| 13 | +- **TODO:** Support several platforms as optional features |
| 14 | + |
| 15 | +## Usage |
| 16 | + |
| 17 | +This crate is platform-agnostic. Define key bindings by `KeyBinds` and build `KeyBindMatcher` instance with it. |
| 18 | +Pass each key input to the `trigger` method and it returns a triggered action. Key sequence and key combination |
| 19 | +can be parsed using `FromStr` trait. |
| 20 | + |
| 21 | +```rust |
| 22 | +use keybinds::{KeyBind, KeyBinds, KeyBindMatcher, KeyInput, Key, Mods}; |
| 23 | + |
| 24 | +// Actions triggered by key bindings |
| 25 | +#[derive(PartialEq, Eq, Debug)] |
| 26 | +enum Action { |
| 27 | + SayHello, |
| 28 | + OpenFile, |
| 29 | + ExitApp, |
| 30 | +} |
| 31 | + |
| 32 | +// Key bindings to trigger the actions |
| 33 | +let keybinds = KeyBinds::new(vec![ |
| 34 | + // Key sequence "hello" |
| 35 | + KeyBind::multiple("h e l l o".parse().unwrap(), Action::SayHello), |
| 36 | + // Key combination "Ctrl + Shift + Enter" |
| 37 | + KeyBind::single("Ctrl+Shift+Enter".parse().unwrap(), Action::OpenFile), |
| 38 | + // Sequence of key combinations |
| 39 | + KeyBind::multiple("Ctrl+x Ctrl+c".parse().unwrap(), Action::ExitApp), |
| 40 | +]); |
| 41 | + |
| 42 | +let mut matcher = KeyBindMatcher::new(keybinds); |
| 43 | + |
| 44 | +// Trigger `SayHello` action |
| 45 | +assert_eq!(matcher.trigger(KeyInput::from('h')), None); |
| 46 | +assert_eq!(matcher.trigger(KeyInput::from('e')), None); |
| 47 | +assert_eq!(matcher.trigger(KeyInput::from('l')), None); |
| 48 | +assert_eq!(matcher.trigger(KeyInput::from('l')), None); |
| 49 | +assert_eq!(matcher.trigger(KeyInput::from('o')), Some(&Action::SayHello)); |
| 50 | + |
| 51 | +// Trigger `OpenFile` action |
| 52 | +let action = matcher.trigger(KeyInput::new(Key::Enter, Mods::CTRL | Mods::SHIFT)); |
| 53 | +assert_eq!(action, Some(&Action::OpenFile)); |
| 54 | + |
| 55 | +// Trigger `ExitApp` action |
| 56 | +assert_eq!(matcher.trigger(KeyInput::new('x', Mods::CTRL)), None); |
| 57 | +assert_eq!(matcher.trigger(KeyInput::new('c', Mods::CTRL)), Some(&Action::ExitApp)); |
| 58 | +``` |
| 59 | + |
| 60 | +## Syntax for key sequence and combination |
| 61 | + |
| 62 | +Keys are joint with `+` as a key combination like `Ctrl+a`. The last key must be a normal key and others must be modifier |
| 63 | +keys. |
| 64 | + |
| 65 | +Normal keys are a single character (e.g. `a`, `X`, `あ`) or a special key name (e.g. `Up`, `Enter`, `Tab`). Note that |
| 66 | +upper case characters like `A` are equivalent to the lower case ones like `a`. For representing `A` key, explicitly |
| 67 | +specify `Shift` modifier key. |
| 68 | + |
| 69 | +The following modifier keys are available: |
| 70 | + |
| 71 | +- `Ctrl`: Ctrl key |
| 72 | +- `Cmd`: Command key |
| 73 | +- `Mod`: Command key on macOS, Ctrl key on other platforms |
| 74 | +- `Shift`: Shift key |
| 75 | +- `Alt`: Alt key |
| 76 | +- `Option`: An alias to Alt key |
| 77 | + |
| 78 | +here are some examples of key combinations: |
| 79 | + |
| 80 | +```ignore |
| 81 | +a |
| 82 | +Enter |
| 83 | +Mod+x |
| 84 | +Ctrl+Shift+Left |
| 85 | +``` |
| 86 | + |
| 87 | +Key combinations are joint with whitespaces as a key sequence. When key combinations are input in the order, they |
| 88 | +trigger the action. |
| 89 | + |
| 90 | +Here are some examples of key sequences: |
| 91 | + |
| 92 | +```ignore |
| 93 | +h e l l o |
| 94 | +Ctrl+x Ctrl+c |
| 95 | +``` |
| 96 | + |
| 97 | +## [serde][] support |
| 98 | + |
| 99 | +### Parsing key bindings configurations |
| 100 | + |
| 101 | +`KeyBinds` implements serde's `Deserialize` trait. This is an example to parse key bindings as TOML. |
| 102 | + |
| 103 | +```rust |
| 104 | +use serde::Deserialize; |
| 105 | +use keybinds::{KeyBinds, KeyBindMatcher, Key, Mods, KeyInput}; |
| 106 | + |
| 107 | +// Actions triggered by key bindings |
| 108 | +#[derive(Deserialize, PartialEq, Eq, Debug)] |
| 109 | +enum Action { |
| 110 | + OpenFile, |
| 111 | + ExitApp, |
| 112 | +} |
| 113 | + |
| 114 | +// Configuration file format of your application |
| 115 | +#[derive(Deserialize)] |
| 116 | +pub struct Config { |
| 117 | + pub bindings: KeyBinds<Action>, |
| 118 | +} |
| 119 | + |
| 120 | +let configuration = r#" |
| 121 | +[bindings] |
| 122 | +"Ctrl+Shift+Enter" = "OpenFile" |
| 123 | +"Ctrl+x Ctrl+c" = "ExitApp" |
| 124 | +"#; |
| 125 | + |
| 126 | +// Parse the TOML input |
| 127 | +let config: Config = toml::from_str(configuration).unwrap(); |
| 128 | + |
| 129 | +// Use the key bindings parsed from the TOML input |
| 130 | +let mut matcher = KeyBindMatcher::new(config.bindings); |
| 131 | +let action = matcher.trigger(KeyInput::new(Key::Enter, Mods::CTRL | Mods::SHIFT)); |
| 132 | +assert_eq!(action, Some(&Action::OpenFile)); |
| 133 | +``` |
| 134 | + |
| 135 | +## License |
| 136 | + |
| 137 | +This crate is licensed under [the MIT license](./LICENSE.txt). |
| 138 | + |
| 139 | +[crates-io]: https://crates.io/crates/keybinds |
| 140 | +[serde]: https://serde.rs/ |
0 commit comments