mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
move tui run methods to in/out and relax Sized constraint
This commit is contained in:
parent
44201ebf76
commit
6fd87ce4ed
5 changed files with 169 additions and 211 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:?}")
|
||||
//}
|
||||
//}
|
||||
//}))
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
//})
|
||||
//};
|
||||
//}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue