From 53c9438f7d9990d60adc1a25cccfdd2971a07500 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Mon, 29 Jun 2026 19:56:32 +0300 Subject: [PATCH] try to sort TuiKey/TuiEvent mess --- src/term.rs | 139 ++++++++++++++++++++++++++-------------------- src/term/event.rs | 25 +++++++++ src/term/keys.rs | 57 ++----------------- 3 files changed, 108 insertions(+), 113 deletions(-) create mode 100644 src/term/event.rs diff --git a/src/term.rs b/src/term.rs index 02f858b..c2e836d 100644 --- a/src/term.rs +++ b/src/term.rs @@ -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 (state: T) -> Usually<()> where + T: View + Apply> + 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> 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 > + 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}") + } + }, + + } + }) } /// 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::*; diff --git a/src/term/event.rs b/src/term/event.rs new file mode 100644 index 0000000..76af779 --- /dev/null +++ b/src/term/event.rs @@ -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 + } +} diff --git a/src/term/keys.rs b/src/term/keys.rs index 248e97d..a0ccca8 100644 --- a/src/term/keys.rs +++ b/src/term/keys.rs @@ -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> 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 > + 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. +/// TUI key spec. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] -pub struct TuiEvent(pub Event); +pub struct TuiKey(pub Option, 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, pub KeyModifiers); - impl TuiKey { const SPLIT: char = '/'; pub fn from_crossterm (event: KeyEvent) -> Self {