try to sort TuiKey/TuiEvent mess

This commit is contained in:
facile pop culture reference 2026-06-29 19:56:32 +03:00
parent dd7091bda1
commit 53c9438f7d
3 changed files with 108 additions and 113 deletions

View file

@ -1,7 +1,7 @@
mod border;
pub use self::border::*;
use crate::{*, lang::*, draw::*, task::*};
use crate::{*, lang::*, draw::*, task::*, exit::*};
//use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
//use rand::distributions::uniform::UniformSampler;
use ::{
@ -22,69 +22,84 @@ use ::{
//event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
}
},
crossterm::event::{
read, Event, KeyEvent, KeyModifiers, KeyCode, KeyEventKind, KeyEventState
},
};
/// Implement standard [main] entrypoint for TUI apps.
#[macro_export] macro_rules! tui_main {
($state:expr) => {
pub fn main () -> Usually<()> {
use ::{
std::sync::{
Arc,
RwLock
},
std::panic::{
set_hook,
PanicHookInfo,
},
std::time::{
Duration
},
better_panic::{
Settings,
Verbosity
},
ratatui::{
backend::{
Backend,
CrosstermBackend
},
crossterm::{
ExecutableCommand,
terminal::{
disable_raw_mode,
LeaveAlternateScreen
}
}
},
tengri::{
exit::Exit,
keys::tui_input,
term::tui_output,
}
};
let panic = Settings::auto()
.verbosity(Verbosity::Full)
.create_panic_handler();
set_hook(Box::new(move |info: &PanicHookInfo|{
stdout().execute(LeaveAlternateScreen).unwrap();
CrosstermBackend::new(stdout()).show_cursor().unwrap();
disable_raw_mode().unwrap();
panic(info);
}));
Exit::run(|exit|{
let state = Arc::new(RwLock::new($state));
let scan = Duration::from_millis(100);
let input = tui_input(exit.as_ref(), &state, scan)?;
let frame = Duration::from_millis(10);
let output = tui_output(stdout(), exit.as_ref(), &state, frame)?;
output.join();
stdout().execute(LeaveAlternateScreen)?;
CrosstermBackend::new(stdout()).show_cursor()?;
disable_raw_mode()?;
Ok(())
})
($state:expr) => { pub fn main () -> Usually<()> { tui_run_main($state) } }
}
pub fn tui_run_main <T> (state: T) -> Usually<()> where
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static
{
tui_setup_panic();
Exit::run(|exit|{
let state = Arc::new(RwLock::new(state));
let scan = Duration::from_millis(100);
let input = tui_input(exit.as_ref(), &state, scan)?;
let frame = Duration::from_millis(10);
let output = tui_output(exit.as_ref(), &state, frame, std::io::stdout())?;
output.join();
stdout().execute(LeaveAlternateScreen)?;
CrosstermBackend::new(stdout()).show_cursor()?;
disable_raw_mode()?;
Ok(())
})
}
pub fn tui_setup_panic () {
use ::std::panic::{set_hook, PanicHookInfo};
use ::better_panic::{Settings, Verbosity};
let panic = Settings::auto()
.verbosity(Verbosity::Full)
.create_panic_handler();
set_hook(Box::new(move |info: &PanicHookInfo|{
stdout().execute(LeaveAlternateScreen).unwrap();
CrosstermBackend::new(stdout()).show_cursor().unwrap();
disable_raw_mode().unwrap();
panic(info);
}));
}
/// Enable TUI keyboard input for main state struct.
#[macro_export] macro_rules! tui_keys {
(|$self:ident:$State:ty,$input:ident|$body:block) => {
impl Apply<TuiEvent, Usually<()>> for $State {
fn apply (&mut $self, $input: &TuiEvent) -> Usually<()> $body
}
}
};
}
/// Spawn the TUI input thread which reads keys from the terminal.
pub fn tui_input <T: Apply<TuiEvent, Usually<()>> + 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}")
}
},
}
})
}
/// Enable TUI output for state struct.
@ -549,6 +564,8 @@ use self::colors::*; mod colors {
pub const fn tui_yellow () -> Color { Color::Rgb(255,255,0) }
}
/// Terminal keyboard input.
#[cfg(feature = "term")] pub mod event;
#[cfg(feature = "term")] pub use self::event::*;
#[cfg(feature = "term")] pub mod keys;
#[cfg(feature = "term")] pub use self::keys::*;

25
src/term/event.rs Normal file
View file

@ -0,0 +1,25 @@
use ::dizzle::impl_from;
use ::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers};
/// TUI input loop event.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiEvent(
/// Wrapped [crossterm::event::Event].
pub Event
);
impl_from!(TuiEvent: |e: Event| TuiEvent(
e));
impl_from!(TuiEvent: |e: KeyEvent| TuiEvent(
Event::Key(e)));
impl_from!(TuiEvent: |c: KeyCode| TuiEvent(
Event::Key(KeyEvent::new(c, KeyModifiers::NONE))));
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
}
}

View file

@ -1,69 +1,22 @@
use crate::task::Task;
use crate::{task::Task, term::TuiEvent};
use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
use ::std::time::Duration;
use ::dizzle::{Language, Symbol, Usually, Apply, impl_from};
use ::dizzle::{Language, Symbol, Usually, Apply};
use ::crossterm::event::{
read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState
};
/// Enable TUI keyboard input for main state struct.
#[macro_export] macro_rules! tui_keys {
(|$self:ident:$State:ty,$input:ident|$body:block) => {
impl Apply<TuiEvent, Usually<()>> for $State {
fn apply (&mut $self, $input: &TuiEvent) -> Usually<()> $body
}
};
}
/// Spawn the TUI input thread which reads keys from the terminal.
pub fn tui_input <T: Apply<TuiEvent, Usually<()>> + 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.
/// TUI key spec.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiEvent(pub Event);
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
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 {
impl Ord for TuiKey {
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 {