mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-04-25 13:40:43 +02:00
110 lines
3.9 KiB
Rust
110 lines
3.9 KiB
Rust
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 <T: Apply<TuiEvent, Usually<T>> + Send + Sync + 'static> (
|
|
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
|
) -> Result<Task, std::io::Error> {
|
|
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<KeyCode>, 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<Event> {
|
|
self.0.map(|code|Event::Key(KeyEvent {
|
|
code,
|
|
modifiers: self.1,
|
|
kind: KeyEventKind::Press,
|
|
state: KeyEventState::NONE,
|
|
}))
|
|
}
|
|
pub fn named (token: &str) -> Option<KeyCode> {
|
|
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,
|
|
})
|
|
}
|
|
}
|