diff --git a/Cargo.toml b/Cargo.toml
index cefc039..3355dd8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ documentation.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
+exclude = ["examples/*"]
[lib]
doctest = false
diff --git a/README.md b/README.md
index 235bbaa..cd4bfe9 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,23 @@ See `keymap-rs` in action with the [WASM example](https://rezigned.com/keymap-rs
+
+
+
+
+ | Runtime Reload (Rebind keys at runtime) |
+
+
+
+
+
+
+ |
+
+
+
+
+
---
## 📦 Installation
@@ -120,8 +137,11 @@ let config = Action::keymap_config();
match config.get(&key) {
Some(action) => match action {
Action::Quit => break,
- Action::Jump => println!("Jump! Symbol: {:?}, Help: {:?}",
- action.keymap_item().symbol, action.keymap_item().help),
+ Action::Jump => println!(
+ "Jump! Symbol: {:?}, Help: {:?}",
+ action.keymap_item().symbol,
+ action.keymap_item().help)
+ ),
_ => println!("Action: {action:?} - {}", action.keymap_item().description),
}
_ => {}
diff --git a/examples/README.md b/examples/README.md
index d93428b..50d46ea 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -32,6 +32,10 @@ Shows how to load key mappings exclusively from external configuration files, ig
Explores combining derive macro defaults with external configuration overrides, covering configuration precedence and key group patterns like `@digit`.
+### [`capturing.rs`](./capturing.rs)
+**Key group capturing using `.get_bound()`**
+
+Demonstrates dynamic key capture with key group patterns like `@any`, `@digit`, `@alpha`, etc. The `Shoot(char)` variant captures the actual pressed character at runtime via `.get_bound()`.
### [`modes.rs`](./modes.rs)
**Multi-mode application with different key mappings**
@@ -43,6 +47,11 @@ Illustrates building applications with multiple modes (like `vim`), where differ
Explains how to handle multi-key sequences (like `j j` for double-tap actions), including sequence detection, timing-based handling, and sequence timeout management.
+### [`reload.rs`](./reload.rs)
+**Runtime keymap reload**
+
+Demonstrates swapping key bindings at runtime by re-deserializing a `DerivedConfig` from inline TOML configs. Press `r` to rotate between predefined configuration sets.
+
---
## WebAssembly Example
diff --git a/examples/action.rs b/examples/action.rs
index 4be3481..1dbd152 100644
--- a/examples/action.rs
+++ b/examples/action.rs
@@ -3,32 +3,31 @@
#[derive(Debug, keymap::KeyMap, Hash, PartialEq, Eq, Clone)]
pub(crate) enum Action {
/// Jump over obstacles
- #[key("space", "@digit")]
+ #[key("space", "@digit", symbol = "␣", help = "jump")]
Jump,
/// Climb or move up
- #[key("up")]
+ #[key("up", symbol = "↑", help = "move up")]
Up,
/// Drop or crouch down
- #[key("down")]
+ #[key("down", symbol = "↓", help = "move down")]
Down,
/// Move leftward
- #[key("left")]
+ #[key("left", symbol = "←", help = "move left")]
Left,
/// Move rightward
- #[key("right")]
+ #[key("right", symbol = "→", help = "move right")]
Right,
/// Exit or pause game
- #[key("q", "esc")]
+ #[key("q", "esc", symbol = "esc", help = "quit")]
Quit,
/// Key Group Capturing action (e.g. tracking which character was pressed).
- /// `char` will be captured from any matched key group macro (like `@any` or `@digit`) at runtime.
- #[key("@any")]
+ #[key("@any", help = "shoot")]
Shoot(char),
}
diff --git a/examples/backend/crossterm.rs b/examples/backend/crossterm.rs
index 5d196f7..b386f6d 100644
--- a/examples/backend/crossterm.rs
+++ b/examples/backend/crossterm.rs
@@ -1,9 +1,12 @@
use std::io;
use crossterm::{
+ cursor,
event::{read, Event, KeyEvent},
- terminal::{disable_raw_mode, enable_raw_mode},
+ execute,
+ terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},
};
+use std::io::{stdout, Write};
#[allow(dead_code)]
pub(crate) fn run(mut f: F) -> io::Result<()>
@@ -11,10 +14,21 @@ where
F: FnMut(KeyEvent) -> bool,
{
enable_raw_mode()?;
+ stdout().flush()?;
+
+ let (_, row) = cursor::position()?;
loop {
if let Event::Key(key) = read()? {
+ execute!(
+ stdout(),
+ cursor::MoveTo(0, row),
+ Clear(ClearType::FromCursorDown),
+ )?;
+
let quit = f(key);
+ stdout().flush()?;
+
if quit {
break;
}
diff --git a/examples/backend/mod.rs b/examples/backend/mod.rs
index 932f061..a69d350 100644
--- a/examples/backend/mod.rs
+++ b/examples/backend/mod.rs
@@ -1,3 +1,5 @@
+use std::io::Write;
+
use keymap::Item;
#[cfg(feature = "crossterm")]
@@ -32,28 +34,41 @@ pub(crate) use mock::{run, Key};
#[allow(dead_code)]
pub(crate) fn print(s: &str) -> bool {
- println!("{s}\r");
+ print!("\r{s}");
+ std::io::stdout().flush().ok();
false
}
#[allow(dead_code)]
pub(crate) fn quit(s: &str) -> bool {
println!("{s}\r");
+ std::io::stdout().flush().ok();
true
}
+// ANSI colors
+const RESET: &str = "\x1b[0m";
+const COLOR_DIM: &str = "\x1b[38;2;68;72;85m";
+const COLOR_KEY: &str = "\x1b[38;2;148;226;213m";
+const COLOR_TEXT: &str = "\x1b[38;2;166;173;200m";
+
#[allow(dead_code)]
pub(crate) fn print_config(items: &[(T, Item)]) {
- println!("--- keymap ---");
+ let keys = items
+ .iter()
+ .map(|(_, v)| {
+ format!(
+ "{COLOR_KEY}{}{RESET} {COLOR_TEXT}{}{RESET}",
+ v.symbol.clone().unwrap_or_default(),
+ v.help.clone().unwrap_or(v.description.clone())
+ )
+ })
+ .collect::>()
+ .join(&format!(" {COLOR_DIM}|{RESET} "));
- items.iter().for_each(|(action, v)| {
- println!(
- "{action:?} = keys: {:?}, description: {}",
- v.keys, v.description
- )
- });
-
- println!("--------------");
+ println!("\r{keys}");
+ std::io::stdout().flush().ok();
}
+
#[allow(unused)]
fn main() {}
diff --git a/examples/backend/termion.rs b/examples/backend/termion.rs
index b041e87..ccc5337 100644
--- a/examples/backend/termion.rs
+++ b/examples/backend/termion.rs
@@ -12,13 +12,23 @@ where
let stdin = stdin();
let mut stdout = stdout().into_raw_mode()?;
+ write!(stdout, "{}", termion::cursor::Save)?;
+ stdout.flush()?;
+
for key in stdin.keys() {
+ write!(
+ stdout,
+ "{}{}",
+ termion::cursor::Restore,
+ termion::clear::AfterCursor,
+ )?;
+
let quit = f(key.unwrap());
+ stdout.flush()?;
+
if quit {
break;
}
-
- stdout.flush().unwrap();
}
write!(stdout, "{}", termion::cursor::Show)
diff --git a/examples/capturing.rs b/examples/capturing.rs
index a82dc09..db9e437 100644
--- a/examples/capturing.rs
+++ b/examples/capturing.rs
@@ -4,7 +4,7 @@ mod backend;
#[path = "./action.rs"]
mod action;
-use crate::backend::{print, quit, run};
+use crate::backend::{print, print_config, quit, run};
use action::Action;
use keymap::{DerivedConfig, KeyMapConfig};
@@ -16,11 +16,9 @@ Jump = { keys = ["j"], description = "Jump!" }
fn main() -> std::io::Result<()> {
println!("# Example: Key Group Capturing using .get_bound()");
- println!("- Press any key to see it captured by Action::Shoot(char)");
- println!("- Press 'j' to see Action::Jump (unit variant)");
- println!("- Press 'q' or 'esc' to quit");
let config: DerivedConfig = toml::from_str(CONFIG).unwrap();
+ print_config(&config.items);
run(|key| match config.get_bound(&key) {
Some(action) => match action {
diff --git a/examples/config.rs b/examples/config.rs
index c9014b1..066f2d6 100644
--- a/examples/config.rs
+++ b/examples/config.rs
@@ -4,7 +4,7 @@ mod backend;
#[path = "./action.rs"]
mod action;
-use crate::backend::{print, quit, run};
+use crate::backend::{print, print_config, quit, run};
use action::Action;
use keymap::{Config, KeyMapConfig};
@@ -18,6 +18,7 @@ fn main() -> std::io::Result<()> {
println!("# Example: External configuration with Config");
let config: Config = toml::from_str(CONFIG).unwrap();
+ print_config(&config.items);
// Use .get() for high-performance reference lookup of the "default" variant.
// To capture the actual key pressed (e.g. the 'a' in @any), use .get_bound()
diff --git a/examples/derive.rs b/examples/derive.rs
index 1ee26f2..1dd0aad 100644
--- a/examples/derive.rs
+++ b/examples/derive.rs
@@ -4,13 +4,14 @@ mod backend;
#[path = "./action.rs"]
mod action;
-use crate::backend::{print, quit, run};
+use crate::backend::{print, print_config, quit, run};
use action::Action;
use keymap::KeyMapConfig;
fn main() -> std::io::Result<()> {
println!("# Example: Using the KeyMap derive macro");
let config = Action::keymap_config();
+ print_config(&config.items);
// Use .get() for high-performance reference lookup of the "default" variant.
// To capture the actual key pressed (e.g. the 'a' in @any), use .get_bound()
diff --git a/examples/derived_config.rs b/examples/derived_config.rs
index 38e9f3b..77e87d4 100644
--- a/examples/derived_config.rs
+++ b/examples/derived_config.rs
@@ -4,7 +4,7 @@ mod backend;
#[path = "./action.rs"]
mod action;
-use crate::backend::{print, quit, run};
+use crate::backend::{print, print_config, quit, run};
use action::Action;
use keymap::{DerivedConfig, KeyMapConfig};
@@ -19,6 +19,7 @@ fn main() -> std::io::Result<()> {
println!("# Example: Merging derive macros with external config using DerivedConfig");
let config: DerivedConfig = toml::from_str(CONFIG).unwrap();
+ print_config(&config.items);
// Use .get() for high-performance reference lookup of the "default" variant.
// To capture the actual key pressed (e.g. the 'a' in @any), use .get_bound()
diff --git a/examples/modes.rs b/examples/modes.rs
index 8aa5c0d..9005c95 100644
--- a/examples/modes.rs
+++ b/examples/modes.rs
@@ -3,7 +3,7 @@ use std::collections::HashMap;
#[path = "./backend/mod.rs"]
mod backend;
-use crate::backend::{print, quit, run};
+use crate::backend::{print, print_config, quit, run};
use keymap::DerivedConfig;
use serde::Deserialize;
@@ -45,7 +45,11 @@ fn main() -> std::io::Result<()> {
let mut mode = "home";
println!("# Example: Multi-mode application with different key mappings");
- println!("mode: {mode}\r");
+
+ if let Some(Actions::Home(config)) = modes.get("home") {
+ print_config(&config.items);
+ }
+ println!("\rmode: {mode}");
run(move |key| match modes.get(mode).unwrap() {
Actions::Home(config) => match config.get(&key) {
diff --git a/examples/reload.gif b/examples/reload.gif
new file mode 100644
index 0000000..4b0afc9
Binary files /dev/null and b/examples/reload.gif differ
diff --git a/examples/reload.rs b/examples/reload.rs
new file mode 100644
index 0000000..94af50f
--- /dev/null
+++ b/examples/reload.rs
@@ -0,0 +1,69 @@
+#[path = "./backend/mod.rs"]
+mod backend;
+
+#[path = "./action.rs"]
+mod action;
+
+use std::cell::RefCell;
+use std::io::Write;
+
+use crate::backend::{print, print_config, quit, run};
+use action::Action;
+use keymap::{DerivedConfig, KeyMap, KeyMapConfig, ToKeyMap};
+
+// Multiple inline configs to switch between at runtime.
+// Press 'r' to reload the keymap — the active config rotates on each reload.
+const CONFIG_A: &str = r#"
+Jump = { keys = ["j"] }
+Up = { keys = ["k"] }
+"#;
+
+const CONFIG_B: &str = r#"
+Jump = { keys = ["w"] }
+Down = { keys = ["s"] }
+Left = { keys = ["a"] }
+Right = { keys = ["d"] }
+"#;
+
+fn main() -> std::io::Result<()> {
+ println!("# Example: Runtime keymap reload");
+ println!("\rPress 'r' to rotate between configs at runtime");
+
+ let configs = [CONFIG_A, CONFIG_B];
+ let config = RefCell::new(toml::from_str::>(configs[0]).unwrap());
+ let active = RefCell::new(0usize);
+
+ print_config(&config.borrow().items);
+
+ let reload_key = KeyMap::from(keymap::node::Key::Char('r'));
+
+ run(move |key| {
+ // Press 'r' to reload keymap from the next inline config
+ if let Ok(k) = key.to_keymap() {
+ if k == reload_key {
+ let next = (*active.borrow() + 1) % configs.len();
+ *config.borrow_mut() = toml::from_str(configs[next]).unwrap();
+ *active.borrow_mut() = next;
+
+ // Replace the stale header shortcuts in-place
+ print!("\r\x1b[1A\x1b[J");
+ std::io::stdout().flush().ok();
+ print_config(&config.borrow().items);
+ print(&format!("** switched to config {next} **"));
+ return false;
+ }
+ }
+
+ let config = config.borrow();
+ match config.get(&key) {
+ Some(action) => match action {
+ Action::Quit => quit("quit!"),
+ Action::Shoot(_) => print("Shoot!"),
+ Action::Up | Action::Down | Action::Left | Action::Right | Action::Jump => print(
+ &format!("{action:?} = {}", action.keymap_item().description),
+ ),
+ },
+ None => print(&format!("{key:?}")),
+ }
+ })
+}
diff --git a/examples/reload.tape b/examples/reload.tape
new file mode 100644
index 0000000..4fcc0f0
--- /dev/null
+++ b/examples/reload.tape
@@ -0,0 +1,29 @@
+Output examples/reload.gif
+
+Require
+ cargo
+
+Type "cargo run --example reload --features crossterm"
+Enter
+Sleep 1s
+
+Type "j"
+Sleep 1s
+
+Type "k"
+Sleep 1s
+
+# Press 'r' — rotate to CONFIG_B
+Type "r"
+Sleep 2s
+
+# From CONFIG_B
+Type "w"
+Sleep 1s
+
+Type "a"
+Sleep 1s
+
+# Press 'q' — quit
+Type "q"
+Sleep 1s
diff --git a/examples/sequences.rs b/examples/sequences.rs
index 311d5e7..1a2ac56 100644
--- a/examples/sequences.rs
+++ b/examples/sequences.rs
@@ -6,22 +6,26 @@ mod action;
use std::time::{Duration, Instant};
-use crate::backend::{print, quit, run, Key};
+use crate::backend::{print, print_config, quit, run, Key};
use action::Action;
use keymap::{DerivedConfig, KeyMapConfig};
// Override default key mapping defined via #[derive(KeyMap)] in Action.
pub(crate) const CONFIG: &str = r#"
Jump = { keys = ["j j"], description = "Jump Jump!" }
+Shoot = { keys = ["enter"] }
"#;
fn main() -> std::io::Result<()> {
println!("# Example: Key Sequences (j j)");
+
let config: DerivedConfig = toml::from_str(CONFIG).unwrap();
let mut last_key: Option = None;
let mut last_time = Instant::now();
+ print_config(&config.items);
+
run(move |key| {
let ret = match config.get(&key) {
Some(action) => match action {
diff --git a/examples/simple.rs b/examples/simple.rs
index e4c8446..ea2580c 100644
--- a/examples/simple.rs
+++ b/examples/simple.rs
@@ -35,6 +35,7 @@ esc = "Quit"
fn main() -> std::io::Result<()> {
let config: Config = toml::from_str(CONFIG).unwrap();
println!("# Example: Basic key mapping without derive macros");
+ println!("\r↑ up | ↓ down | ← left | → right | ␣/^G jump | q/esc quit");
run(|key| {
let Some((_, action)) = config.0.get_key_value(&key.to_keymap().unwrap()) else {
diff --git a/examples/wasm/src/main.rs b/examples/wasm/src/main.rs
index 2aa7b19..dcfec26 100644
--- a/examples/wasm/src/main.rs
+++ b/examples/wasm/src/main.rs
@@ -20,15 +20,15 @@ extern "C" {
#[derive(Debug, Clone, keymap::KeyMap, Hash, PartialEq, Eq)]
pub enum Action {
/// Jump over obstacles
- #[key("space", symbol = "↑", help = "jump")] // symbol gets overridden by toml config
+ #[key("space", symbol = "␣", help = "jump")] // symbol gets overridden by toml config
Jump,
/// Move leftward
- #[key("left", help = "move left")]
+ #[key("left", symbol = "←", help = "move left")]
Left,
/// Move rightward
- #[key("right", help = "move right")]
+ #[key("right", symbol = "→", help = "move right")]
Right,
/// Pause