mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 03:36:41 +01:00
wip: big flat pt.3, testing standalone tui
This commit is contained in:
parent
a5628fb663
commit
cb680ab096
4 changed files with 189 additions and 139 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
||||
/****
|
||||
|
||||
|
|
|
|||
|
|
@ -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>];
|
||||
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue