use crate::task::Task; use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; use ::std::time::Duration; use ::dizzle::{Usually, Apply, impl_from}; use ::crossterm::event::{ read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState }; /// Spawn the TUI input thread which reads keys from the terminal. pub fn tui_input > + Send + Sync + 'static> ( exited: &Arc, state: &Arc>, poll: Duration ) -> Result { let exited = exited.clone(); let state = state.clone(); Task::new_poll(exited.clone(), poll, move |_| { let event = read().unwrap(); match event { // Hardcoded exit. Event::Key(KeyEvent { modifiers: KeyModifiers::CONTROL, code: KeyCode::Char('c'), kind: KeyEventKind::Press, state: KeyEventState::NONE }) => { exited.store(true, Relaxed); }, // Handle all other events by the state: event => { if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) { panic!("{e}") } }, } }) } /// TUI input loop event. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(pub Event); impl_from!(TuiEvent: |e: Event| TuiEvent(e)); impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)))); impl Ord for TuiEvent { fn cmp (&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other) .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf } } /// TUI key spec. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey(pub Option, pub KeyModifiers); impl TuiKey { const SPLIT: char = '/'; pub fn from_crossterm (event: KeyEvent) -> Self { Self(Some(event.code), event.modifiers) } pub fn to_crossterm (&self) -> Option { self.0.map(|code|Event::Key(KeyEvent { code, modifiers: self.1, kind: KeyEventKind::Press, state: KeyEventState::NONE, })) } pub fn named (token: &str) -> Option { use KeyCode::*; Some(match token { "up" => Up, "down" => Down, "left" => Left, "right" => Right, "esc" | "escape" => Esc, "enter" | "return" => Enter, "delete" | "del" => Delete, "backspace" => Backspace, "tab" => Tab, "space" => Char(' '), "comma" => Char(','), "period" => Char('.'), "plus" => Char('+'), "minus" | "dash" => Char('-'), "equal" | "equals" => Char('='), "underscore" => Char('_'), "backtick" => Char('`'), "lt" => Char('<'), "gt" => Char('>'), "cbopen" | "openbrace" => Char('{'), "cbclose" | "closebrace" => Char('}'), "bropen" | "openbracket" => Char('['), "brclose" | "closebracket" => Char(']'), "pgup" | "pageup" => PageUp, "pgdn" | "pagedown" => PageDown, "f1" => F(1), "f2" => F(2), "f3" => F(3), "f4" => F(4), "f5" => F(5), "f6" => F(6), "f7" => F(7), "f8" => F(8), "f9" => F(9), "f10" => F(10), "f11" => F(11), "f12" => F(12), _ => return None, }) } }