wip: big flat pt.3, testing standalone tui

This commit is contained in:
🪞👃🪞 2024-12-30 18:20:36 +01:00
parent a5628fb663
commit cb680ab096
4 changed files with 189 additions and 139 deletions

View file

@ -1,6 +1,11 @@
use crate::*;
use std::sync::{Arc, Mutex, RwLock};
/// Handle input
pub trait Handle<E: Engine>: Send + Sync {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled>;
}
/// Current input state
pub trait Input<E: Engine> {
/// Type of input event
@ -23,11 +28,6 @@ pub trait Input<E: Engine> {
}
}
/// Handle input
pub trait Handle<E: Engine>: Send + Sync {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled>;
}
impl<E: Engine, H: Handle<E>> Handle<E> for &mut H {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
(*self).handle(context)

View file

@ -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<T> = Result<Option<T>, Box<dyn Error>>;
}
#[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> {
use std::sync::{Arc, RwLock};
struct TestComponent(String);
impl Content<Tui> for TestComponent {
fn content (&self) -> Option<impl Render<Tui>> {
Some(&self.0)
}
}
impl Handle<Tui> for TestComponent {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
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(())
}

View file

@ -1,16 +1,6 @@
use crate::*;
use std::sync::{Arc, Mutex, RwLock};
/// Rendering target
pub trait Output<E: Engine> {
/// 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<E>) -> Usually<()>;
}
/// Write content to output buffer.
pub trait Render<E: Engine>: Send + Sync {
/// Minimum size to use
@ -23,32 +13,55 @@ pub trait Render<E: Engine>: Send + Sync {
}
}
impl<E: Engine> Render<E> for &dyn Render<E> {}
impl<E: Engine> Render<E> for &mut dyn Render<E> {}
impl<E: Engine> Render<E> for Box<dyn Render<E>> {}
impl<E: Engine, R: Render<E>> Render<E> for &R {}
impl<E: Engine, R: Render<E>> Render<E> for &mut R {}
impl<E: Engine, R: Render<E>> Render<E> for Option<R> {}
impl<E: Engine, R: Render<E>> Render<E> for Arc<R> {}
impl<E: Engine, R: Render<E>> Render<E> for Mutex<R> {}
impl<E: Engine, R: Render<E>> Render<E> for RwLock<R> {}
/// Something that can be represented by a renderable component.
pub trait Content<E: Engine>: Render<E> + Send + Sync {
fn content (&self) -> Option<impl Render<E>> where (): Render<E> {
None::<()>
pub trait Content<E: Engine>: Send + Sync {
fn content (&self) -> Option<impl Render<E>>;
}
impl<E: Engine, C: Content<E>> Render<E> for C {
/// Minimum size to use
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
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<E: Engine> Content<E> for &dyn Render<E> {}
impl<E: Engine> Content<E> for &mut dyn Render<E> {}
impl<E: Engine> Content<E> for Box<dyn Render<E>> {}
impl<E: Engine, C: Content<E>> Content<E> for &C {}
impl<E: Engine, C: Content<E>> Content<E> for &mut C {}
impl<E: Engine, C: Content<E>> Content<E> for Option<C> {}
impl<E: Engine, C: Content<E>> Content<E> for Arc<C> {}
impl<E: Engine, C: Content<E>> Content<E> for Mutex<C> {}
impl<E: Engine, C: Content<E>> Content<E> for RwLock<C> {}
/// Rendering target
pub trait Output<E: Engine> {
/// 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<E>) -> Usually<()>;
}
//impl<E: Engine> Render<E> for &dyn Render<E> {}
//impl<E: Engine> Render<E> for &mut dyn Render<E> {}
//impl<E: Engine> Render<E> for Box<dyn Render<E>> {}
//impl<E: Engine, R: Render<E>> Render<E> for &R {}
//impl<E: Engine, R: Render<E>> Render<E> for &mut R {}
//impl<E: Engine, R: Render<E>> Render<E> for Option<R> {}
//impl<E: Engine, R: Render<E>> Render<E> for Arc<R> {}
//impl<E: Engine, R: Render<E>> Render<E> for Mutex<R> {}
//impl<E: Engine, R: Render<E>> Render<E> for RwLock<R> {}
//impl<E: Engine> Content<E> for &dyn Render<E> {}
//impl<E: Engine> Content<E> for &mut dyn Render<E> {}
//impl<E: Engine> Content<E> for Box<dyn Render<E>> {}
//impl<E: Engine, C: Content<E>> Content<E> for &C {}
//impl<E: Engine, C: Content<E>> Content<E> for &mut C {}
//impl<E: Engine, C: Content<E>> Content<E> for Option<C> {}
//impl<E: Engine, C: Content<E>> Content<E> for Arc<C> {}
//impl<E: Engine, C: Content<E>> Content<E> for Mutex<C> {}
//impl<E: Engine, C: Content<E>> Content<E> for RwLock<C> {}
//impl<E: Engine, C: Content<E>> Render<E> for C {}
/****

View file

@ -74,30 +74,53 @@ impl Engine for Tui {
}
impl Tui {
/// Run the main loop.
pub fn run <R: Component<Tui> + Sized + 'static> (
state: Arc<RwLock<R>>
) -> Usually<Arc<RwLock<R>>> {
/// Construct a new TUI engine and wrap it for shared ownership.
pub fn new () -> Usually<Arc<RwLock<Self>>> {
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 <R: Component<Tui> + Sized + 'static> (
engine: &Arc<RwLock<Self>>, state: &Arc<RwLock<R>>, 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<R: Render<Tui> + Handle<Tui> + Sized + '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<Tui> + Handle<Tui> + Sized + '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));
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<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) {
@ -119,11 +142,9 @@ impl Tui {
}
})
}
fn spawn_render_thread <R: Component<Tui> + Sized + 'static> (
engine: &Arc<RwLock<Self>>, state: &Arc<RwLock<R>>, sleep: Duration
) -> JoinHandle<()> {
let exited = engine.read().unwrap().exited.clone();
let engine = engine.clone();
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 = 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<Tui> 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 <T> (&self, state: &mut T, keymap: &KeyMap<T>) -> Usually<bool> {
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<T> = &'static dyn Fn(&mut T)->Usually<bool>;
pub type KeyBinding<T> = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>);
pub type KeyMap<T> = [KeyBinding<T>];
*/
pub struct TuiOutput {
pub buffer: Buffer,
pub area: [u16;4]
@ -415,6 +361,18 @@ impl Render<Tui> for &str {
}
}
impl Render<Tui> 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<Tui> 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 <T> (&self, state: &mut T, keymap: &KeyMap<T>) -> Usually<bool> {
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<T> = &'static dyn Fn(&mut T)->Usually<bool>;
pub type KeyBinding<T> = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>);
pub type KeyMap<T> = [KeyBinding<T>];
*/