move tui run methods to in/out and relax Sized constraint

This commit is contained in:
🪞👃🪞 2025-01-14 20:04:59 +01:00
parent 44201ebf76
commit 6fd87ce4ed
5 changed files with 169 additions and 211 deletions

View file

@ -23,12 +23,9 @@ mod tui_border; pub use self::tui_border::*;
mod tui_field; pub use self::tui_field::*;
mod tui_buffer; pub use self::tui_buffer::*;
mod tui_file; pub use self::tui_file::*;
mod tui_edn_keymap; pub use self::tui_edn_keymap::*;
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::*}};
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
pub(crate) use std::io::{stdout, Stdout};
pub(crate) use std::error::Error;
pub(crate) use std::path::PathBuf;
pub(crate) use std::ffi::OsString;
@ -49,7 +46,7 @@ pub use ::crossterm;
pub(crate) use crossterm::{
ExecutableCommand,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState},
event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
};
pub use ::ratatui;

View file

@ -1,5 +1,4 @@
use crate::*;
impl EdnInput for TuiIn {
fn matches_edn (&self, token: &str) -> bool {
if let Some(event) = KeyMatcher::new(token).build() {
@ -80,7 +79,6 @@ impl KeyMatcher {
}
}
}
#[cfg(test)] #[test] fn test_parse_key () {
use KeyModifiers as Mods;
let test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y)));
@ -95,41 +93,3 @@ impl KeyMatcher {
test(":ctrl-alt-shift-x",
KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT ));
}
//fn parse_key (spec: std::str::Chars<'_>, mut mods: KeyModifiers) -> Option<Event> {
//Some(Event::Key(match spec.chars() {
//":enter" | ":return" => KeyEvent::new(KeyCode::Enter, mods),
//":delete" | ":del" => KeyEvent::new(KeyCode::Enter, mods),
//":comma" => KeyEvent::new(KeyCode::Char(','), mods),
//":period" => KeyEvent::new(KeyCode::Char('.'), mods),
//":plus" => KeyEvent::new(KeyCode::Char('+'), mods),
//":underscore" => KeyEvent::new(KeyCode::Char('_'), mods),
//":up" => KeyEvent::new(KeyCode::Up, mods),
//":down" => KeyEvent::new(KeyCode::Down, mods),
//":left" => KeyEvent::new(KeyCode::Left, mods),
//":right" => KeyEvent::new(KeyCode::Right, mods),
//_ => {
//let chars = spec.chars().collect::<Vec<_>>();
//match chars.as_slice() {
//[':', c] => KeyEvent::new(KeyCode::Char(*c), mods),
//[':', c @ ..] => {
//let mut splits = c.split(|c|*c=='-');
//while let Some(split) = splits.next() {
//match split {
//['c','t','r','l'] => mods |= KeyModifiers::CONTROL,
//['a','l','t'] => mods |= KeyModifiers::ALT,
//['s','h','i','f','t'] => mods |= KeyModifiers::SHIFT,
//_ => return parse_key(split.iter().collect(), mods)
//}
//}
//panic!("invalid key event {spec:?}")
//}
//_ => panic!("invalid key event {spec:?}")
//}
//}
//}))
//}

View file

@ -1,107 +1,135 @@
use crate::*;
pub use crossterm::event::Event;
use Event as CrosstermEvent;
#[derive(Debug, Clone)]
pub struct TuiIn(pub Arc<AtomicBool>, pub CrosstermEvent);
use std::time::Duration;
use std::thread::{spawn, JoinHandle};
use crossterm::event::{poll, read};
#[derive(Debug, Clone)] pub struct TuiIn(pub Arc<AtomicBool>, pub Event);
impl Input for TuiIn {
type Event = Event;
type Handled = bool;
fn event (&self) -> &CrosstermEvent { &self.1 }
fn event (&self) -> &Event { &self.1 }
fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) }
fn done (&self) { self.0.store(true, Relaxed); }
}
impl TuiIn {
/// Spawn the input thread.
pub fn run_input <T: Handle<TuiIn> + 'static> (
engine: &Arc<RwLock<Tui>>,
state: &Arc<RwLock<T>>,
timer: Duration
) -> JoinHandle<()> {
let exited = engine.read().unwrap().exited.clone();
let state = state.clone();
spawn(move || loop {
if exited.fetch_and(true, Relaxed) {
break
}
if poll(timer).is_ok() {
let event = read().unwrap();
match event {
/// Define a key
pub const fn key (code: KeyCode) -> Event {
let modifiers = KeyModifiers::NONE;
let kind = KeyEventKind::Press;
let state = KeyEventState::NONE;
Event::Key(KeyEvent { code, modifiers, kind, state })
}
/// Add Ctrl modifier to key
pub const fn ctrl (event: Event) -> Event {
match event {
Event::Key(mut event) => {
event.modifiers = event.modifiers.union(KeyModifiers::CONTROL)
},
_ => {}
crossterm::event::Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
}) => {
exited.store(true, Relaxed);
},
_ => {
let exited = exited.clone();
if let Err(e) = state.write().unwrap().handle(&TuiIn(exited, event)) {
panic!("{e}")
}
}
}
}
})
}
event
}
/// Add Alt modifier to key
pub const fn alt (event: Event) -> Event {
match event {
Event::Key(mut event) => {
event.modifiers = event.modifiers.union(KeyModifiers::ALT)
},
_ => {}
}
event
}
/// Add Shift modifier to key
pub const fn shift (event: Event) -> Event {
match event {
Event::Key(mut event) => {
event.modifiers = event.modifiers.union(KeyModifiers::SHIFT)
},
_ => {}
}
event
}
#[macro_export] macro_rules! kpat {
(Ctrl-Alt-$code:pat) => { kpat!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) };
(Ctrl-$code:pat) => { kpat!($code, KeyModifiers::CONTROL) };
(Alt-$code:pat) => { kpat!($code, KeyModifiers::ALT) };
(Shift-$code:pat) => { kpat!($code, KeyModifiers::SHIFT) };
($code:pat) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
($code:pat, $modifiers: pat) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: $modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
}
#[macro_export] macro_rules! kexp {
(Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::from_bits(0b0000_0110).unwrap()) };
(Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) };
(Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) };
(Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) };
($code:ident) => { key_event_expr!($code) };
($code:expr) => { key_event_expr!($code) };
}
#[macro_export] macro_rules! key_event_expr {
($code:expr, $modifiers: expr) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: $modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
($code:expr) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
}
//.
///// Define a key
//pub const fn key (code: KeyCode) -> Event {
//let modifiers = KeyModifiers::NONE;
//let kind = KeyEventKind::Press;
//let state = KeyEventState::NONE;
//Event::Key(KeyEvent { code, modifiers, kind, state })
//}
///// Add Ctrl modifier to key
//pub const fn ctrl (event: Event) -> Event {
//match event {
//Event::Key(mut event) => {
//event.modifiers = event.modifiers.union(KeyModifiers::CONTROL)
//},
//_ => {}
//}
//event
//}
///// Add Alt modifier to key
//pub const fn alt (event: Event) -> Event {
//match event {
//Event::Key(mut event) => {
//event.modifiers = event.modifiers.union(KeyModifiers::ALT)
//},
//_ => {}
//}
//event
//}
///// Add Shift modifier to key
//pub const fn shift (event: Event) -> Event {
//match event {
//Event::Key(mut event) => {
//event.modifiers = event.modifiers.union(KeyModifiers::SHIFT)
//},
//_ => {}
//}
//event
//}
//#[macro_export] macro_rules! kpat {
//(Ctrl-Alt-$code:pat) => { kpat!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) };
//(Ctrl-$code:pat) => { kpat!($code, KeyModifiers::CONTROL) };
//(Alt-$code:pat) => { kpat!($code, KeyModifiers::ALT) };
//(Shift-$code:pat) => { kpat!($code, KeyModifiers::SHIFT) };
//($code:pat) => {
//crossterm::event::Event::Key(KeyEvent {
//code: $code,
//modifiers: KeyModifiers::NONE,
//kind: KeyEventKind::Press,
//state: KeyEventState::NONE
//})
//};
//($code:pat, $modifiers: pat) => {
//crossterm::event::Event::Key(KeyEvent {
//code: $code,
//modifiers: $modifiers,
//kind: KeyEventKind::Press,
//state: KeyEventState::NONE
//})
//};
//}
//#[macro_export] macro_rules! kexp {
//(Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::from_bits(0b0000_0110).unwrap()) };
//(Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) };
//(Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) };
//(Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) };
//($code:ident) => { key_event_expr!($code) };
//($code:expr) => { key_event_expr!($code) };
//}
//#[macro_export] macro_rules! key_event_expr {
//($code:expr, $modifiers: expr) => {
//crossterm::event::Event::Key(KeyEvent {
//code: $code,
//modifiers: $modifiers,
//kind: KeyEventKind::Press,
//state: KeyEventState::NONE
//})
//};
//($code:expr) => {
//crossterm::event::Event::Key(KeyEvent {
//code: $code,
//modifiers: KeyModifiers::NONE,
//kind: KeyEventKind::Press,
//state: KeyEventState::NONE
//})
//};
//}

View file

@ -1,11 +1,11 @@
use crate::*;
use std::time::Duration;
use std::thread::{spawn, JoinHandle};
#[derive(Default)]
pub struct TuiOut {
pub buffer: Buffer,
pub area: [u16;4]
}
impl Output for TuiOut {
type Unit = u16;
type Size = [Self::Unit;2];
@ -19,8 +19,42 @@ impl Output for TuiOut {
*self.area_mut() = last;
}
}
impl TuiOut {
/// Spawn the output thread.
pub fn run_output <T: Render<TuiOut> + 'static> (
engine: &Arc<RwLock<Tui>>,
state: &Arc<RwLock<T>>,
timer: Duration
) -> JoinHandle<()> {
let exited = engine.read().unwrap().exited.clone();
let engine = engine.clone();
let state = state.clone();
let Size { width, height } = engine.read().unwrap().backend.size().expect("get size failed");
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height });
spawn(move || loop {
if exited.fetch_and(true, Relaxed) {
break
}
let t0 = engine.read().unwrap().perf.get_t0();
let Size { width, height } = engine.read().unwrap().backend.size()
.expect("get size failed");
if let Ok(state) = state.try_read() {
let size = Rect { x: 0, y: 0, width, height };
if buffer.area != size {
engine.write().unwrap().backend.clear_region(ClearType::All)
.expect("clear failed");
buffer.resize(size);
buffer.reset();
}
let mut output = TuiOut { buffer, area: [0, 0, width, height] };
state.render(&mut output);
buffer = engine.write().unwrap().flip(output.buffer, size);
}
let t1 = engine.read().unwrap().perf.get_t1(t0).unwrap();
buffer.set_string(0, 0, &format!("{:>3}.{:>3}ms", t1.as_millis(), t1.as_micros() % 1000), Style::default());
std::thread::sleep(timer);
})
}
pub fn buffer_update (&mut self, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) {
buffer_update(&mut self.buffer, area, callback);
}

View file

@ -1,85 +1,24 @@
use crate::*;
use ratatui::prelude::Size;
use std::time::Duration;
use std::thread::{spawn, JoinHandle};
pub trait TuiRun<R: Render<TuiOut> + Handle<TuiIn> + Sized + 'static> {
pub trait TuiRun<R: Render<TuiOut> + Handle<TuiIn> + 'static> {
/// Run an app in the main loop.
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
/// Spawn the input thread.
fn run_input (&self, state: &Arc<RwLock<R>>, poll: Duration) -> JoinHandle<()>;
/// Spawn the output thread.
fn run_output (&self, state: &Arc<RwLock<R>>, sleep: Duration) -> JoinHandle<()>;
}
impl<T: Render<TuiOut> + Handle<TuiIn> + Sized + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
impl<T: Render<TuiOut> + Handle<TuiIn> + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
let _input_thread = self.run_input(state, Duration::from_millis(100));
let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100));
self.write().unwrap().setup()?;
let render_thread = self.run_output(state, Duration::from_millis(10));
let render_thread = TuiOut::run_output(self, state, Duration::from_millis(10));
match render_thread.join() {
Ok(result) => {
self.write().unwrap().teardown()?;
println!("\n\rRan successfully: {result:?}\n\r");
},
Err(e) => {
Err(error) => {
self.write().unwrap().teardown()?;
panic!("\n\rRender thread failed.\n\r")
panic!("\n\rRender thread failed: {error:?}.\n\r")
},
}
Ok(())
}
fn run_input (&self, state: &Arc<RwLock<T>>, poll: Duration) -> JoinHandle<()> {
let exited = self.read().unwrap().exited.clone();
let state = state.clone();
spawn(move || loop {
if exited.fetch_and(true, Relaxed) {
break
}
if ::crossterm::event::poll(poll).is_ok() {
let event = ::crossterm::event::read().unwrap();
match event {
kpat!(Ctrl-KeyCode::Char('c')) => {
exited.store(true, Relaxed);
},
_ => {
let exited = exited.clone();
if let Err(e) = state.write().unwrap().handle(&TuiIn(exited, event)) {
panic!("{e}")
}
}
}
}
})
}
fn run_output (&self, state: &Arc<RwLock<T>>, sleep: Duration) -> JoinHandle<()> {
let exited = self.read().unwrap().exited.clone();
let engine = self.clone();
let state = state.clone();
let Size { width, height } = engine.read().unwrap().backend.size().expect("get size failed");
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height });
spawn(move || loop {
if exited.fetch_and(true, Relaxed) {
break
}
let t0 = engine.read().unwrap().perf.get_t0();
let Size { width, height } = engine.read().unwrap().backend.size()
.expect("get size failed");
if let Ok(state) = state.try_read() {
let size = Rect { x: 0, y: 0, width, height };
if buffer.area != size {
engine.write().unwrap().backend.clear_region(ClearType::All)
.expect("clear failed");
buffer.resize(size);
buffer.reset();
}
let mut output = TuiOut { buffer, area: [0, 0, width, height] };
state.render(&mut output);
buffer = engine.write().unwrap().flip(output.buffer, size);
}
let t1 = engine.read().unwrap().perf.get_t1(t0).unwrap();
buffer.set_string(0, 0, &format!("{:>03}.{:>03}ms", t1.as_millis(), t1.as_micros() % 1000), Style::default());
std::thread::sleep(sleep);
})
}
}