diff --git a/engine/src/input.rs b/engine/src/input.rs index b1a3ed34..6a77db37 100644 --- a/engine/src/input.rs +++ b/engine/src/input.rs @@ -1,6 +1,11 @@ use crate::*; use std::sync::{Arc, Mutex, RwLock}; +/// Handle input +pub trait Handle: Send + Sync { + fn handle (&mut self, context: &E::Input) -> Perhaps; +} + /// Current input state pub trait Input { /// Type of input event @@ -23,11 +28,6 @@ pub trait Input { } } -/// Handle input -pub trait Handle: Send + Sync { - fn handle (&mut self, context: &E::Input) -> Perhaps; -} - impl> Handle for &mut H { fn handle (&mut self, context: &E::Input) -> Perhaps { (*self).handle(context) diff --git a/engine/src/lib.rs b/engine/src/lib.rs index c4078b54..e0a81b8e 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,8 +1,8 @@ -mod component; pub use self::component::*; -mod engine; pub use self::engine::*; -mod input; pub use self::input::*; -mod output; pub use self::output::*; -mod tui; pub use self::tui::*; +//mod component; pub use self::component::*; +mod engine; pub use self::engine::*; +mod input; pub use self::input::*; +mod output; pub use self::output::*; +mod tui; pub use self::tui::*; pub use std::error::Error; @@ -58,5 +58,22 @@ pub type Perhaps = Result, Box>; } #[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> { + use std::sync::{Arc, RwLock}; + struct TestComponent(String); + impl Content for TestComponent { + fn content (&self) -> Option> { + Some(&self.0) + } + } + impl Handle for TestComponent { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + Ok(None) + } + } + let engine = Tui::new()?; + engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed); + let state = TestComponent("hello world".into()); + let state = std::sync::Arc::new(std::sync::RwLock::new(state)); + engine.run(&state)?; Ok(()) } diff --git a/engine/src/output.rs b/engine/src/output.rs index c50769bb..c41272fc 100644 --- a/engine/src/output.rs +++ b/engine/src/output.rs @@ -1,16 +1,6 @@ use crate::*; use std::sync::{Arc, Mutex, RwLock}; -/// Rendering target -pub trait Output { - /// Current output area - fn area (&self) -> E::Area; - /// Mutable pointer to area - fn area_mut (&mut self) -> &mut E::Area; - /// Render widget in area - fn render_in (&mut self, area: E::Area, widget: &dyn Render) -> Usually<()>; -} - /// Write content to output buffer. pub trait Render: Send + Sync { /// Minimum size to use @@ -23,32 +13,55 @@ pub trait Render: Send + Sync { } } -impl Render for &dyn Render {} -impl Render for &mut dyn Render {} -impl Render for Box> {} -impl> Render for &R {} -impl> Render for &mut R {} -impl> Render for Option {} -impl> Render for Arc {} -impl> Render for Mutex {} -impl> Render for RwLock {} - /// Something that can be represented by a renderable component. -pub trait Content: Render + Send + Sync { - fn content (&self) -> Option> where (): Render { - None::<()> +pub trait Content: Send + Sync { + fn content (&self) -> Option>; +} + +impl> Render for C { + /// Minimum size to use + fn min_size (&self, to: E::Size) -> Perhaps { + self.content().map(|content|content.min_size(to)) + .unwrap_or(Ok(None)) + } + /// Draw to output render target + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.content().map(|content|content.render(to)) + .unwrap_or(Ok(())) } } -impl Content for &dyn Render {} -impl Content for &mut dyn Render {} -impl Content for Box> {} -impl> Content for &C {} -impl> Content for &mut C {} -impl> Content for Option {} -impl> Content for Arc {} -impl> Content for Mutex {} -impl> Content for RwLock {} +/// Rendering target +pub trait Output { + /// Current output area + fn area (&self) -> E::Area; + /// Mutable pointer to area + fn area_mut (&mut self) -> &mut E::Area; + /// Render widget in area + fn render_in (&mut self, area: E::Area, widget: &dyn Render) -> Usually<()>; +} + +//impl Render for &dyn Render {} +//impl Render for &mut dyn Render {} +//impl Render for Box> {} +//impl> Render for &R {} +//impl> Render for &mut R {} +//impl> Render for Option {} +//impl> Render for Arc {} +//impl> Render for Mutex {} +//impl> Render for RwLock {} + +//impl Content for &dyn Render {} +//impl Content for &mut dyn Render {} +//impl Content for Box> {} +//impl> Content for &C {} +//impl> Content for &mut C {} +//impl> Content for Option {} +//impl> Content for Arc {} +//impl> Content for Mutex {} +//impl> Content for RwLock {} + +//impl> Render for C {} /**** diff --git a/engine/src/tui.rs b/engine/src/tui.rs index 7d6aa090..11670ce9 100644 --- a/engine/src/tui.rs +++ b/engine/src/tui.rs @@ -74,30 +74,53 @@ impl Engine for Tui { } impl Tui { - /// Run the main loop. - pub fn run + Sized + 'static> ( - state: Arc> - ) -> Usually>> { + /// Construct a new TUI engine and wrap it for shared ownership. + pub fn new () -> Usually>> { let backend = CrosstermBackend::new(stdout()); let area = backend.size()?; - let engine = Self { + Ok(Arc::new(RwLock::new(Self { exited: Arc::new(AtomicBool::new(false)), buffer: Buffer::empty(area), area: [area.x, area.y, area.width, area.height], backend, - }; - let engine = Arc::new(RwLock::new(engine)); - let _input_thread = Self::spawn_input_thread(&engine, &state, Duration::from_millis(100)); - engine.write().unwrap().setup()?; - let render_thread = Self::spawn_render_thread(&engine, &state, Duration::from_millis(10)); - render_thread.join().expect("main thread failed"); - engine.write().unwrap().teardown()?; - Ok(state) + }))) } - fn spawn_input_thread + Sized + 'static> ( - engine: &Arc>, state: &Arc>, poll: Duration - ) -> JoinHandle<()> { - let exited = engine.read().unwrap().exited.clone(); + /// Update the display buffer. + fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { + if self.buffer.area != size { + self.backend.clear_region(ClearType::All).unwrap(); + self.buffer.resize(size); + self.buffer.reset(); + } + let updates = self.buffer.diff(&buffer); + self.backend.draw(updates.into_iter()).expect("failed to render"); + self.backend.flush().expect("failed to flush output buffer"); + std::mem::swap(&mut self.buffer, &mut buffer); + buffer.reset(); + buffer + } +} + +pub trait TuiRun + Handle + Sized + '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> { + fn run (&self, state: &Arc>) -> Usually<()> { + let _input_thread = self.run_input(state, Duration::from_millis(100)); + self.write().unwrap().setup()?; + let render_thread = self.run_output(state, Duration::from_millis(10)); + render_thread.join().expect("main thread failed"); + self.write().unwrap().teardown()?; + 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) { @@ -119,11 +142,9 @@ impl Tui { } }) } - fn spawn_render_thread + Sized + 'static> ( - engine: &Arc>, state: &Arc>, sleep: Duration - ) -> JoinHandle<()> { - let exited = engine.read().unwrap().exited.clone(); - let engine = engine.clone(); + 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 = engine.read().unwrap().backend.size().expect("get size failed"); let mut buffer = Buffer::empty(size); @@ -150,19 +171,6 @@ impl Tui { std::thread::sleep(sleep); }) } - fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { - if self.buffer.area != size { - self.backend.clear_region(ClearType::All).unwrap(); - self.buffer.resize(size); - self.buffer.reset(); - } - let updates = self.buffer.diff(&buffer); - self.backend.draw(updates.into_iter()).expect("failed to render"); - self.backend.flush().expect("failed to flush output buffer"); - std::mem::swap(&mut self.buffer, &mut buffer); - buffer.reset(); - buffer - } } pub struct TuiInput { @@ -257,68 +265,6 @@ impl Input for TuiInput { }; } -/* -/// Define a key -pub const fn key (code: KeyCode) -> KeyEvent { - let modifiers = KeyModifiers::NONE; - let kind = KeyEventKind::Press; - let state = KeyEventState::NONE; - KeyEvent { code, modifiers, kind, state } -} - -/// Add Ctrl modifier to key -pub const fn ctrl (key: KeyEvent) -> KeyEvent { - KeyEvent { modifiers: key.modifiers.union(KeyModifiers::CONTROL), ..key } -} - -/// Add Alt modifier to key -pub const fn alt (key: KeyEvent) -> KeyEvent { - KeyEvent { modifiers: key.modifiers.union(KeyModifiers::ALT), ..key } -} - -/// Add Shift modifier to key -pub const fn shift (key: KeyEvent) -> KeyEvent { - KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key } -} - -/// Define a keymap -#[macro_export] macro_rules! keymap { - ($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => { - &[ - $((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),* - ] as &'static [KeyBinding<$T>] - } -} - -*/ - -/* - -impl TuiInput { - // TODO remove - pub fn handle_keymap (&self, state: &mut T, keymap: &KeyMap) -> Usually { - match self.event() { - TuiEvent::Input(Key(event)) => { - for (code, modifiers, _, _, command) in keymap.iter() { - if *code == event.code && modifiers.bits() == event.modifiers.bits() { - return command(state) - } - } - }, - _ => {} - }; - Ok(false) - } -} - -pub type KeyHandler = &'static dyn Fn(&mut T)->Usually; - -pub type KeyBinding = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler); - -pub type KeyMap = [KeyBinding]; - -*/ - pub struct TuiOutput { pub buffer: Buffer, pub area: [u16;4] @@ -415,6 +361,18 @@ impl Render for &str { } } +impl Render for &String { + fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { + // TODO: line breaks + Ok(Some([self.chars().count() as u16, 1])) + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + let [x, y, ..] = to.area(); + //let [w, h] = self.min_size(to.area().wh())?.unwrap(); + Ok(to.blit(&self, x, y, None)) + } +} + impl Render for String { fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { // TODO: line breaks @@ -438,3 +396,65 @@ pub fn buffer_update (buf: &mut Buffer, area: [u16;4], callback: &impl Fn(&mut C } } } + +/* +/// Define a key +pub const fn key (code: KeyCode) -> KeyEvent { + let modifiers = KeyModifiers::NONE; + let kind = KeyEventKind::Press; + let state = KeyEventState::NONE; + KeyEvent { code, modifiers, kind, state } +} + +/// Add Ctrl modifier to key +pub const fn ctrl (key: KeyEvent) -> KeyEvent { + KeyEvent { modifiers: key.modifiers.union(KeyModifiers::CONTROL), ..key } +} + +/// Add Alt modifier to key +pub const fn alt (key: KeyEvent) -> KeyEvent { + KeyEvent { modifiers: key.modifiers.union(KeyModifiers::ALT), ..key } +} + +/// Add Shift modifier to key +pub const fn shift (key: KeyEvent) -> KeyEvent { + KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key } +} + +/// Define a keymap +#[macro_export] macro_rules! keymap { + ($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => { + &[ + $((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),* + ] as &'static [KeyBinding<$T>] + } +} + +*/ + +/* + +impl TuiInput { + // TODO remove + pub fn handle_keymap (&self, state: &mut T, keymap: &KeyMap) -> Usually { + match self.event() { + TuiEvent::Input(Key(event)) => { + for (code, modifiers, _, _, command) in keymap.iter() { + if *code == event.code && modifiers.bits() == event.modifiers.bits() { + return command(state) + } + } + }, + _ => {} + }; + Ok(false) + } +} + +pub type KeyHandler = &'static dyn Fn(&mut T)->Usually; + +pub type KeyBinding = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler); + +pub type KeyMap = [KeyBinding]; + +*/