From 6fd87ce4ede4fe994d96ed8b3f5eb9c441fe4cf8 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 14 Jan 2025 20:04:59 +0100 Subject: [PATCH] move tui run methods to in/out and relax Sized constraint --- tui/src/lib.rs | 7 +- tui/src/tui_edn_keymap.rs | 40 ------- tui/src/tui_input.rs | 220 +++++++++++++++++++++----------------- tui/src/tui_output.rs | 40 ++++++- tui/src/tui_run.rs | 73 ++----------- 5 files changed, 169 insertions(+), 211 deletions(-) diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 917d32d0..372acb13 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -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; diff --git a/tui/src/tui_edn_keymap.rs b/tui/src/tui_edn_keymap.rs index 5b5be2d9..e73ca37b 100644 --- a/tui/src/tui_edn_keymap.rs +++ b/tui/src/tui_edn_keymap.rs @@ -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 { - //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::>(); - //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:?}") - //} - //} - //})) -//} diff --git a/tui/src/tui_input.rs b/tui/src/tui_input.rs index f7df95eb..38275ec8 100644 --- a/tui/src/tui_input.rs +++ b/tui/src/tui_input.rs @@ -1,107 +1,135 @@ use crate::*; -pub use crossterm::event::Event; -use Event as CrosstermEvent; - -#[derive(Debug, Clone)] -pub struct TuiIn(pub Arc, pub CrosstermEvent); - +use std::time::Duration; +use std::thread::{spawn, JoinHandle}; +use crossterm::event::{poll, read}; +#[derive(Debug, Clone)] pub struct TuiIn(pub Arc, 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 + 'static> ( + engine: &Arc>, + state: &Arc>, + 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 + //}) + //}; +//} diff --git a/tui/src/tui_output.rs b/tui/src/tui_output.rs index 9a119463..83e65bc7 100644 --- a/tui/src/tui_output.rs +++ b/tui/src/tui_output.rs @@ -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 + 'static> ( + engine: &Arc>, + state: &Arc>, + 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); } diff --git a/tui/src/tui_run.rs b/tui/src/tui_run.rs index bda30725..a5d35f37 100644 --- a/tui/src/tui_run.rs +++ b/tui/src/tui_run.rs @@ -1,85 +1,24 @@ use crate::*; -use ratatui::prelude::Size; use std::time::Duration; -use std::thread::{spawn, JoinHandle}; - -pub trait TuiRun + Handle + Sized + 'static> { +pub trait TuiRun + Handle + 'static> { /// Run an app in the main loop. fn run (&self, state: &Arc>) -> Usually<()>; - /// Spawn the input thread. - fn run_input (&self, state: &Arc>, poll: Duration) -> JoinHandle<()>; - /// Spawn the output thread. - fn run_output (&self, state: &Arc>, sleep: Duration) -> JoinHandle<()>; } - -impl + Handle + Sized + 'static> TuiRun for Arc> { +impl + Handle + 'static> TuiRun for Arc> { fn run (&self, state: &Arc>) -> 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>, 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>, 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); - }) - } }