use crate::{Event, KeyCode};
use std::time::Duration;
const MAX_KEYS: usize = 32;
#[derive(Clone, Copy, Debug)]
struct Tracked {
code: KeyCode,
down: bool,
pressed: bool,
released: bool,
expires: Option<Duration>,
}
#[derive(Clone, Debug)]
pub struct Keys {
real_release: bool,
first_hold: Duration,
repeat_hold: Duration,
tracked: Vec<Tracked>,
}
impl Keys {
pub fn new() -> Keys {
Keys {
real_release: false,
first_hold: Duration::from_millis(300),
repeat_hold: Duration::from_millis(100),
tracked: Vec::with_capacity(MAX_KEYS),
}
}
pub fn real_release(mut self, on: bool) -> Keys {
self.real_release = on;
self
}
pub fn timeouts(mut self, first: Duration, repeat: Duration) -> Keys {
self.first_hold = first;
self.repeat_hold = repeat;
self
}
pub fn event(&mut self, event: &Event, now: Duration) {
match event {
Event::Key(key) => self.press(key.code, now),
Event::KeyUp(key) => self.release(key.code),
_ => {}
}
}
pub fn tick(&mut self, now: Duration) {
for key in self.tracked.iter_mut().filter(|key| key.down) {
if matches!(key.expires, Some(at) if at <= now) {
key.down = false;
key.released = true;
key.expires = None;
}
}
}
pub fn pressed(&self, code: KeyCode) -> bool {
self.find(code).is_some_and(|key| key.pressed)
}
pub fn held(&self, code: KeyCode) -> bool {
self.find(code).is_some_and(|key| key.down)
}
pub fn released(&self, code: KeyCode) -> bool {
self.find(code).is_some_and(|key| key.released)
}
pub fn axis(&self, negative: KeyCode, positive: KeyCode) -> i32 {
i32::from(self.held(positive)) - i32::from(self.held(negative))
}
pub fn end_tick(&mut self) {
for key in &mut self.tracked {
key.pressed = false;
key.released = false;
}
self.tracked.retain(|key| key.down);
}
fn find(&self, code: KeyCode) -> Option<&Tracked> {
let code = normalise(code);
self.tracked.iter().find(|key| key.code == code)
}
fn press(&mut self, code: KeyCode, now: Duration) {
let code = normalise(code);
let first = self.hold_until(now, self.first_hold);
let repeat = self.hold_until(now, self.repeat_hold);
if let Some(index) = self.tracked.iter().position(|key| key.code == code) {
let key = &mut self.tracked[index];
if key.down {
key.expires = repeat;
} else {
key.down = true;
key.pressed = true;
key.expires = first;
}
} else if self.tracked.len() < MAX_KEYS {
self.tracked.push(Tracked {
code,
down: true,
pressed: true,
released: false,
expires: first,
});
}
}
fn release(&mut self, code: KeyCode) {
let code = normalise(code);
if let Some(key) = self
.tracked
.iter_mut()
.find(|key| key.code == code && key.down)
{
key.down = false;
key.released = true;
key.expires = None;
}
}
fn hold_until(&self, now: Duration, span: Duration) -> Option<Duration> {
if self.real_release {
None
} else {
Some(now.saturating_add(span))
}
}
}
impl Default for Keys {
fn default() -> Keys {
Keys::new()
}
}
fn normalise(code: KeyCode) -> KeyCode {
match code {
KeyCode::Char(c) => KeyCode::Char(c.to_lowercase().next().unwrap_or(c)),
other => other,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Key, KeyCode, Modifiers};
fn ms(n: u64) -> Duration {
Duration::from_millis(n)
}
fn press(code: KeyCode) -> Event {
Event::Key(Key::new(code))
}
fn release(code: KeyCode) -> Event {
Event::KeyUp(Key::new(code))
}
#[test]
fn a_fresh_press_is_pressed_and_held_then_only_held_after_end_tick() {
let mut keys = Keys::new();
keys.event(&press(KeyCode::Left), ms(0));
assert!(keys.pressed(KeyCode::Left));
assert!(keys.held(KeyCode::Left));
keys.end_tick();
assert!(!keys.pressed(KeyCode::Left));
assert!(keys.held(KeyCode::Left));
}
#[test]
fn an_emulated_press_ends_on_the_first_tick_after_first_hold() {
let mut keys = Keys::new();
keys.event(&press(KeyCode::Left), ms(0));
keys.tick(ms(299));
assert!(keys.held(KeyCode::Left));
keys.tick(ms(300));
assert!(!keys.held(KeyCode::Left));
assert!(keys.released(KeyCode::Left));
}
#[test]
fn an_emulated_repeat_extends_the_hold_without_pressing_again() {
let mut keys = Keys::new();
keys.event(&press(KeyCode::Left), ms(0));
keys.end_tick();
keys.event(&press(KeyCode::Left), ms(250));
assert!(!keys.pressed(KeyCode::Left), "a repeat is not a new press");
keys.tick(ms(300));
assert!(
keys.held(KeyCode::Left),
"the repeat pushed the end to 350 ms"
);
keys.tick(ms(350));
assert!(!keys.held(KeyCode::Left));
}
#[test]
fn os_repeats_every_30_ms_keep_a_key_held_and_letting_go_ends_it_soon() {
let mut keys = Keys::new();
keys.event(&press(KeyCode::Right), ms(0));
let mut t = 0;
while t < 1000 {
t += 30;
keys.event(&press(KeyCode::Right), ms(t));
keys.tick(ms(t));
assert!(keys.held(KeyCode::Right), "held at {t} ms");
keys.end_tick();
}
keys.tick(ms(t + 100));
assert!(!keys.held(KeyCode::Right));
assert!(keys.released(KeyCode::Right));
}
#[test]
fn real_release_holds_with_no_timeout_until_key_up() {
let mut keys = Keys::new().real_release(true);
keys.event(&press(KeyCode::Left), ms(0));
keys.tick(ms(60_000));
assert!(keys.held(KeyCode::Left), "ticks never expire a real hold");
keys.end_tick();
keys.event(&release(KeyCode::Left), ms(60_001));
assert!(!keys.held(KeyCode::Left));
assert!(
keys.released(KeyCode::Left),
"released on the tick it went up"
);
}
#[test]
fn real_release_repeats_do_not_press_again_and_stray_key_ups_are_ignored() {
let mut keys = Keys::new().real_release(true);
keys.event(&press(KeyCode::Left), ms(0));
keys.end_tick();
keys.event(&press(KeyCode::Left), ms(30));
assert!(!keys.pressed(KeyCode::Left));
assert!(keys.held(KeyCode::Left));
keys.event(&release(KeyCode::Up), ms(40));
assert!(!keys.released(KeyCode::Up));
assert!(!keys.held(KeyCode::Up));
assert!(keys.held(KeyCode::Left));
}
#[test]
fn a_key_up_also_releases_an_emulated_hold() {
let mut keys = Keys::new();
keys.event(&press(KeyCode::Left), ms(0));
keys.event(&release(KeyCode::Left), ms(10));
assert!(!keys.held(KeyCode::Left));
assert!(keys.released(KeyCode::Left));
}
#[test]
fn modifiers_are_ignored_and_chars_match_case_insensitively() {
let mut keys = Keys::new();
let shift_a = Event::Key(Key {
code: KeyCode::Char('A'),
modifiers: Modifiers {
shift: true,
alt: false,
ctrl: false,
},
});
keys.event(&shift_a, ms(0));
assert!(keys.held(KeyCode::Char('a')));
assert!(keys.pressed(KeyCode::Char('A')));
let ctrl_a = Event::Key(Key {
code: KeyCode::Char('a'),
modifiers: Modifiers {
shift: false,
alt: false,
ctrl: true,
},
});
keys.event(&ctrl_a, ms(5));
assert!(keys.held(KeyCode::Char('a')));
}
#[test]
fn axis_is_minus_one_zero_or_one_and_zero_when_both_are_held() {
let mut keys = Keys::new();
assert_eq!(keys.axis(KeyCode::Left, KeyCode::Right), 0);
keys.event(&press(KeyCode::Left), ms(0));
assert_eq!(keys.axis(KeyCode::Left, KeyCode::Right), -1);
keys.event(&press(KeyCode::Right), ms(0));
assert_eq!(keys.axis(KeyCode::Left, KeyCode::Right), 0);
keys.event(&release(KeyCode::Left), ms(1));
assert_eq!(keys.axis(KeyCode::Left, KeyCode::Right), 1);
}
#[test]
fn two_keys_are_held_independently() {
let mut keys = Keys::new();
keys.event(&press(KeyCode::Left), ms(0));
keys.tick(ms(200));
keys.event(&press(KeyCode::Up), ms(200));
keys.tick(ms(350));
assert!(!keys.held(KeyCode::Left));
assert!(keys.held(KeyCode::Up));
}
#[test]
fn at_most_32_keys_are_tracked_and_storage_never_grows() {
let mut keys = Keys::new();
let before = keys.tracked.capacity();
for i in 0..1000u32 {
let c = char::from_u32(0x4e00 + i).unwrap();
keys.event(&press(KeyCode::Char(c)), ms(0));
}
assert_eq!(keys.tracked.capacity(), before);
assert!(keys.tracked.len() <= 32);
assert!(keys.held(char::from_u32(0x4e00).map(KeyCode::Char).unwrap()));
assert!(!keys.held(char::from_u32(0x4e00 + 999).map(KeyCode::Char).unwrap()));
}
#[test]
fn ctrl_c_is_just_a_key_the_tracker_does_not_quit() {
let mut keys = Keys::new();
let ctrl_c = Event::Key(Key {
code: KeyCode::Char('c'),
modifiers: Modifiers {
shift: false,
alt: false,
ctrl: true,
},
});
keys.event(&ctrl_c, ms(0));
assert!(keys.pressed(KeyCode::Char('c')));
}
#[test]
fn absurd_times_do_not_panic() {
let mut keys = Keys::new();
keys.event(&press(KeyCode::Left), Duration::MAX);
keys.tick(Duration::MAX);
keys.event(&press(KeyCode::Left), Duration::MAX);
keys.tick(Duration::MAX);
keys.end_tick();
}
}