use crate::compat::BTreeSet;
#[derive(Debug, Default)]
pub struct KeyRepeatState {
held: BTreeSet<u32>,
}
impl KeyRepeatState {
pub fn new() -> Self {
Self { held: BTreeSet::new() }
}
pub fn note_press(&mut self, key: u32) -> bool {
!self.held.insert(key)
}
pub fn note_release(&mut self, key: u32) {
self.held.remove(&key);
}
pub fn reset(&mut self) {
self.held.clear();
}
}
thread_local! {
static DISPATCH_REPEAT: core::cell::Cell<bool> = const { core::cell::Cell::new(false) };
}
struct RepeatFlagGuard {
previous: bool,
}
impl Drop for RepeatFlagGuard {
fn drop(&mut self) {
let _ = DISPATCH_REPEAT.try_with(|cell| cell.set(self.previous));
}
}
pub fn with_dispatch_repeat<T>(repeat: bool, deliver: impl FnOnce() -> T) -> T {
let previous = DISPATCH_REPEAT.try_with(|cell| cell.replace(repeat)).unwrap_or(false);
let _guard = RepeatFlagGuard { previous };
deliver()
}
pub fn is_dispatch_repeat() -> bool {
DISPATCH_REPEAT.try_with(|cell| cell.get()).unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_first_press_is_not_a_repeat_and_the_second_is() {
let mut state = KeyRepeatState::new();
assert!(!state.note_press(65));
assert!(state.note_press(65), "a second press with no release is the platform repeat");
state.note_release(65);
assert!(!state.note_press(65), "after the release the key starts a new run");
}
#[test]
fn an_unmatched_release_is_ignored() {
let mut state = KeyRepeatState::new();
state.note_release(65);
assert!(!state.note_press(65));
}
#[test]
fn independent_keys_do_not_affect_each_other() {
let mut state = KeyRepeatState::new();
assert!(!state.note_press(65));
assert!(!state.note_press(66));
assert!(state.note_press(65));
assert!(state.note_press(66));
}
#[test]
fn the_dispatch_flag_is_scoped_and_restores_the_outer_value() {
assert!(!is_dispatch_repeat());
let observed = with_dispatch_repeat(true, || {
let inner = is_dispatch_repeat();
let nested = with_dispatch_repeat(false, is_dispatch_repeat);
(inner, nested, is_dispatch_repeat())
});
assert_eq!(observed, (true, false, true));
assert!(!is_dispatch_repeat(), "the flag must not leak out of the dispatch");
}
#[test]
fn a_panicking_handler_does_not_leak_the_flag() {
let outcome = std::panic::catch_unwind(|| {
with_dispatch_repeat(true, || panic!("handler failure"));
});
assert!(outcome.is_err());
assert!(!is_dispatch_repeat(), "the guard restores the flag during an unwind");
}
}