Hi, I'm currently in process of building a TUI. I need to know how would you realize something like time related key stroke tracker. I wish I could add some cool features, such as hitting 'c' twice and it would pop a new layout. I found the std::time that lets you use Duration::time() to set time on something. Do I need to take time snaps or am I overthinking it? Thanks!
You are not overthinking it, but you only need a tiny state machine.
For a TUI, track keystrokes with std::time::Instant, not Duration::time().
Use this idea:
use std::time::{Duration, Instant};
struct KeyTracker {
last_key: Option<char>,
last_time: Option<Instant>,
}
impl KeyTracker {
fn new() -> Self {
Self {
last_key: None,
last_time: None,
}
}
fn press(&mut self, key: char) -> bool {
let now = Instant::now();
let double_tap_window = Duration::from_millis(400);
let is_double_tap =
self.last_key == Some(key)
&& self
.last_time
.map(|t| now.duration_since(t) <= double_tap_window)
.unwrap_or(false);
self.last_key = Some(key);
self.last_time = Some(now);
is_double_tap
}
}
Then in your event loop:
let mut keys = KeyTracker::new();
loop {
if let Event::Key(event) = crossterm::event::read()? {
match event.code {
KeyCode::Char('c') => {
if keys.press('c') {
// c pressed twice quickly
app.show_new_layout();
}
}
KeyCode::Char('q') => break,
_ => {}
}
}
}
The important bit:
Instant::now()
You take a timestamp when a key is pressed, then compare the next press against it.
Use Instant for measuring elapsed time. Use SystemTime only for calendar/wall-clock time. For keyboard timing, Instant is the right spell.
For more complex combos later, store a small buffer:
Vec<(char, Instant)>
Then you can support:
- c c -> open layout
- g g -> top
- d d -> delete line
- ctrl+x c -> command
Your mental model is right: snap time on each input event, compare with previous snap. That’s all.
you have two options:
-
store the history of key events, at least the recent ones, and tag each event with a timestamp. how many recent events to store depending on the input patterns of your use case.
this may need more work, but it could support more sophisticated input patterns, like multi-key chords (e.g
jkorkjpressed together as a single command). -
in the event loop, set a timeout when reading inputs, and translate the timeout as a special kind of input event (a pseduo key, if you will). then you can use this special timeout event to define key bindings.
for example,
ctrl - x, <Timeout>can be easily differentiated fromctrl - x, ctrl - x; or similarly, thec, <Timeout>is a single click, whilec, cis a double click. (this can also be extended to support tripple click if you need to).
thanks much!!!