use std::cell::{Cell, RefCell};
use std::rc::Rc;
use crate::input::{KeyEvent as InKeyEvent, KeyEventKind};
use crate::reactive::{create_root, Scope, Signal};
use crate::term::{Capabilities, KittyFlags};
use crate::ui::{Key, KeyChord, Mods};
use super::events::{convert_key, convert_mods};
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum KeyFidelity {
Full,
#[default]
Degraded,
}
#[derive(Default)]
struct Frame {
down: Vec<Key>,
pressed: Vec<(Key, Mods)>,
released: Vec<Key>,
focus_cleared: bool,
suspend_cleared: bool,
}
struct Store {
enabled: Cell<bool>,
gen: Signal<u64>,
fidelity: Signal<KeyFidelity>,
frame: RefCell<Frame>,
}
thread_local! {
static STORE: RefCell<Option<Rc<Store>>> = const { RefCell::new(None) };
}
fn store() -> Rc<Store> {
STORE.with(|slot| {
if let Some(s) = slot.borrow().as_ref() {
return s.clone();
}
let (root, (gen, fidelity)) =
create_root(|cx| (cx.signal(0u64), cx.signal(KeyFidelity::default())));
std::mem::forget(root);
let s = Rc::new(Store {
enabled: Cell::new(false),
gen,
fidelity,
frame: RefCell::new(Frame::default()),
});
*slot.borrow_mut() = Some(s.clone());
s
})
}
#[derive(Copy, Clone)]
pub struct KeyState {
gen: Signal<u64>,
fidelity: Signal<KeyFidelity>,
}
impl KeyState {
pub fn fidelity(self) -> KeyFidelity {
self.fidelity.get()
}
pub fn fidelity_untracked(self) -> KeyFidelity {
self.fidelity.get_untracked()
}
pub fn is_down(self, key: Key) -> bool {
let _ = self.gen.get();
with_frame(|f| f.down.contains(&key))
}
pub fn keys_down(self) -> Vec<Key> {
let _ = self.gen.get();
with_frame(|f| f.down.clone())
}
pub fn any_down(self) -> bool {
let _ = self.gen.get();
with_frame(|f| !f.down.is_empty())
}
pub fn pressed(self, key: Key) -> bool {
let _ = self.gen.get();
with_frame(|f| f.pressed.iter().any(|(k, _)| *k == key))
}
pub fn pressed_chord(self, chord: KeyChord) -> bool {
let _ = self.gen.get();
let chord = chord.normalized();
with_frame(|f| {
f.pressed
.iter()
.any(|(k, m)| KeyChord { key: *k, mods: *m }.normalized() == chord)
})
}
pub fn released(self, key: Key) -> bool {
let _ = self.gen.get();
with_frame(|f| f.released.contains(&key))
}
pub fn focus_cleared(self) -> bool {
let _ = self.gen.get();
with_frame(|f| f.focus_cleared)
}
pub fn suspend_cleared(self) -> bool {
let _ = self.gen.get();
with_frame(|f| f.suspend_cleared)
}
}
fn with_frame<R>(f: impl FnOnce(&Frame) -> R) -> R {
let s = store();
let frame = s.frame.borrow();
f(&frame)
}
pub fn use_key_state(_cx: Scope) -> KeyState {
key_state()
}
pub fn key_state() -> KeyState {
let s = store();
s.enabled.set(true);
KeyState {
gen: s.gen,
fidelity: s.fidelity,
}
}
pub fn hold_gesture_label(fidelity: KeyFidelity, chord: KeyChord) -> String {
match fidelity {
KeyFidelity::Full => format!("hold {}", chord.display()),
KeyFidelity::Degraded => format!("press {} to start/stop", chord.display()),
}
}
pub(crate) fn release_events_live(caps: &Capabilities, flags: KittyFlags) -> bool {
caps.kitty_keyboard && (flags.0 & KittyFlags::REPORT_EVENT_TYPES) != 0
}
pub(crate) fn publish_fidelity(full: bool) {
let s = store();
let fid = if full {
KeyFidelity::Full
} else {
KeyFidelity::Degraded
};
if s.fidelity.get_untracked() != fid {
if fid == KeyFidelity::Degraded {
let drained = {
let mut f = s.frame.borrow_mut();
let had = !f.down.is_empty();
while let Some(key) = f.down.pop() {
f.released.push(key);
}
had
};
if drained {
bump(&s);
}
}
s.fidelity.set(fid);
}
}
pub(crate) fn begin_turn() {
let s = store();
if !s.enabled.get() {
return;
}
let had_edges = {
let mut f = s.frame.borrow_mut();
let had =
!f.pressed.is_empty() || !f.released.is_empty() || f.focus_cleared || f.suspend_cleared;
f.pressed.clear();
f.released.clear();
f.focus_cleared = false;
f.suspend_cleared = false;
had
};
if had_edges {
bump(&s);
}
}
pub(crate) fn on_key_event(ev: &InKeyEvent) {
let s = store();
if !s.enabled.get() {
return;
}
let Some(key) = convert_key(ev.code) else {
return;
};
let full = s.fidelity.get_untracked() == KeyFidelity::Full;
let changed = {
let mut f = s.frame.borrow_mut();
match ev.kind {
KeyEventKind::Press => {
f.pressed.push((key, convert_mods(ev.mods.without_locks())));
if full && !f.down.contains(&key) {
f.down.push(key);
}
true
}
KeyEventKind::Repeat => {
if full && !f.down.contains(&key) {
f.down.push(key);
true
} else {
false
}
}
KeyEventKind::Release => {
if full {
f.released.push(key);
if let Some(at) = f.down.iter().position(|k| *k == key) {
f.down.swap_remove(at);
}
true
} else {
false
}
}
}
};
if changed {
bump(&s);
}
}
pub(crate) fn on_focus_lost() {
let s = store();
if !s.enabled.get() {
return;
}
{
let mut f = s.frame.borrow_mut();
f.focus_cleared = true;
while let Some(key) = f.down.pop() {
f.released.push(key);
}
}
bump(&s);
}
pub(crate) fn on_suspend() {
let s = store();
if !s.enabled.get() {
return;
}
{
let mut f = s.frame.borrow_mut();
f.suspend_cleared = true;
while let Some(key) = f.down.pop() {
f.released.push(key);
}
}
bump(&s);
}
fn bump(s: &Store) {
s.gen.update(|g| *g = g.wrapping_add(1));
}
#[cfg(test)]
#[path = "keys_tests.rs"]
mod tests;