diff --git a/src/draw.rs b/src/draw.rs index 9b8ebdb..b33a155 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,12 +1,6 @@ use crate::{*, lang::*, color::*, space::*}; /// Drawable that supports dynamic dispatch. -/// -/// Drawables are composable, e.g. the [when] and [either] conditionals -/// or the layout constraints. -/// -/// Drawables are consumable, i.e. the [Draw::draw] method receives an -/// owned `self` and does not return it, consuming the drawable. /// /// ``` /// use tengri::draw::*; @@ -26,29 +20,25 @@ use crate::{*, lang::*, color::*, space::*}; pub trait Draw { fn draw (self, to: &mut T) -> Usually>; } - impl Draw for () { fn draw (self, __: &mut T) -> Usually> { Ok(Default::default()) } } - impl> Draw for Option { fn draw (self, to: &mut T) -> Usually> { Ok(self.map(|draw|draw.draw(to)).transpose()?.unwrap_or_default()) } } -pub const fn thunk Usually>> (draw: F) -> Thunk { - Thunk(draw, std::marker::PhantomData) -} - /// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. pub struct ThunkUsually>>( pub F, std::marker::PhantomData ); - +pub const fn thunk Usually>> (draw: F) -> Thunk { + Thunk(draw, std::marker::PhantomData) +} implUsually>> Draw for Thunk { fn draw (self, to: &mut T) -> Usually> { (self.0)(to) @@ -86,8 +76,8 @@ pub const fn either (condition: bool, a: impl Draw, b: impl Draw< /// /// impl Screen for TestOut { /// type Unit = u16; -/// fn place_at + ?Sized> (&mut self, area: impl TwoD, _: &T) { -/// println!("placed: {area:?}"); +/// fn place + ?Sized> (&mut self, area: impl TwoD, _: &T) { +/// println!("place: {area:?}"); /// () /// } /// } @@ -111,14 +101,3 @@ pub trait Screen: Space + Send + Sync + Sized { unimplemented!() } } - -/// Emit a [Draw]able. -pub trait View { - fn view (&self) -> impl Draw; -} - -impl> Draw for &V { - fn draw (self, to: &mut T) -> Usually> { - self.view().draw(to) - } -} diff --git a/src/eval.rs b/src/eval.rs index 0089ee3..d47aae6 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,5 +1,4 @@ -use crate::{*, term::*, draw::*, ratatui::prelude::*}; -use dizzle::*; +use crate::*; /// Interpret layout operation. /// @@ -166,7 +165,7 @@ use dizzle::*; /// impl Understand for State {} /// # fn main () -> tengri::Usually<()> { /// let state = State; -/// let mut out = Tui::default(); +/// let mut out = TuiOut::default(); /// tengri::eval_view_tui(&state, &mut out, "")?; /// tengri::eval_view_tui(&state, &mut out, "text Hello world!")?; /// tengri::eval_view_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?; @@ -175,9 +174,9 @@ use dizzle::*; /// # Ok(()) } /// ``` pub fn eval_view_tui <'a, S> ( - state: &S, output: &mut Tui, expr: impl Expression + 'a + state: &S, output: &mut Buffer, expr: impl Expression + 'a ) -> Usually where - S: Understand + S: Understand + for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, u16> + for<'b>Namespace<'b, Color> @@ -200,21 +199,18 @@ pub fn eval_view_tui <'a, S> ( Some("fg") => { let arg0 = arg0?.expect("fg: expected arg 0 (color)"); let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")); - output.place(&fg(color, thunk(move|output: &mut Tui|{ - state.understand(output, &arg1)?; - // FIXME?: don't max out the used area? - Ok(output.area().into()) - }))) + let thunk = Thunk::new(move|output: &mut Buffer|state.understand(output, &arg1).unwrap()); + output.place(&TuiOut::fg(color, thunk)) }, Some("bg") => { + //panic!("expr: {expr:?}\nhead: {head:?}\nfrags: {frags:?}\nargs: {args:?}\narg0: {arg0:?}\ntail0: {tail0:?}\narg1: {arg1:?}\ntail1: {tail1:?}\narg2: {arg2:?}"); + //panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");; + //panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}"); let arg0 = arg0?.expect("bg: expected arg 0 (color)"); let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")); - output.place(&bg(color, thunk(move|output: &mut Tui|{ - state.understand(output, &arg1)?; - // FIXME?: don't max out the used area? - Ok(output.area().into()) - }))) + let thunk = Thunk::new(move|output: &mut Buffer|state.understand(output, &arg1).unwrap()); + output.place(&TuiOut::bg(color, thunk)) }, _ => return Ok(false) diff --git a/src/exit.rs b/src/exit.rs deleted file mode 100644 index 2d07816..0000000 --- a/src/exit.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::sync::{Arc, atomic::AtomicBool}; -use crate::Usually; - -#[derive(Clone)] pub struct Exit(Arc); - -impl Exit { - pub fn run (run: impl Fn(Self)->Usually) -> Usually { - run(Self(Arc::new(AtomicBool::new(false)))) - } -} diff --git a/src/keys.rs b/src/keys.rs index 2d2037c..54217c4 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,4 +1,4 @@ -use crate::task::Task; +use crate::play::Thread; use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; use ::std::time::Duration; @@ -10,10 +10,10 @@ use ::crossterm::event::{ /// Spawn the TUI input thread which reads keys from the terminal. pub fn tui_input > + Send + Sync + 'static> ( exited: &Arc, state: &Arc>, poll: Duration -) -> Result { +) -> Result { let exited = exited.clone(); let state = state.clone(); - Task::new_poll(exited.clone(), poll, move |_| { + Thread::new_poll(exited.clone(), poll, move |_| { let event = read().unwrap(); match event { diff --git a/src/lib.rs b/src/lib.rs index 4a1cbd3..7897606 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,14 +25,9 @@ pub(crate) use ::{ #[cfg(feature = "lang")] pub extern crate dizzle as lang; #[cfg(feature = "lang")] pub use ::dizzle::{self, Usually, Perhaps, impl_default}; -#[cfg(feature = "lang")] pub mod eval; #[cfg(feature = "time")] pub mod time; - #[cfg(feature = "play")] pub mod play; -#[cfg(feature = "play")] pub mod exit; -#[cfg(feature = "play")] pub mod task; - #[cfg(feature = "sing")] pub extern crate jack; #[cfg(feature = "sing")] pub mod sing; #[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; diff --git a/src/play.rs b/src/play.rs index 96c83c4..f07a4f7 100644 --- a/src/play.rs +++ b/src/play.rs @@ -1,5 +1,65 @@ use crate::{*, time::*, lang::*}; use ::std::{thread::JoinHandle, time::Duration}; +#[cfg(feature = "term")] use ::crossterm::event::poll; + +#[derive(Clone)] pub struct Exit(Arc); + +impl Exit { + pub fn run (run: impl Fn(Self)->Usually) -> Usually { + run(Self(Arc::new(AtomicBool::new(false)))) + } +} + +#[derive(Debug)] pub struct Thread { + /// Exit flag. + pub exit: Arc, + /// Performance counter. + pub perf: Arc, + /// Use this to wait for the thread to finish. + pub join: JoinHandle<()>, +} + +impl Thread { + /// Spawn a TUI thread that runs `callt least one, then repeats until `exit`. + pub fn new (exit: Arc, mut call: F) -> Result + where F: FnMut(&PerfModel)->() + Send + Sync + 'static + { + let perf = Arc::new(PerfModel::default()); + Ok(Self { + exit: exit.clone(), + perf: perf.clone(), + join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || { + while !exit.fetch_and(true, Relaxed) { + let _ = perf.cycle(&mut call); + } + })?.into() + }) + } + + /// Spawn a thread that runs `call` least one, then repeats + /// until `exit`, sleeping for `time` msec after every iteration. + pub fn new_sleep ( + exit: Arc, time: Duration, mut call: F + ) -> Result + where F: FnMut(&PerfModel)->() + Send + Sync + 'static + { + Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); }) + } + + /// Spawn a thread that uses [crossterm::event::poll] + /// to run `call` every `time` msec. + #[cfg(feature = "term")] pub fn new_poll ( + exit: Arc, time: Duration, mut call: F + ) -> Result + where F: FnMut(&PerfModel)->() + Send + Sync + 'static + { + Self::new(exit, move |perf| { if poll(time).is_ok() { let _ = call(perf); } }) + } + + pub fn join (self) -> Result<(), Box> { + self.join.join() + } +} /// Define an enum containing commands, and implement [Command] trait for over given `State`. #[macro_export] macro_rules! def_command ( @@ -22,7 +82,7 @@ use ::std::{thread::JoinHandle, time::Duration}; }); /// Implement [Handle] for given `State` and `handler`. -#[macro_export] macro_rules! impl_handle { +#[macro_export] macro_rules! handle { (|$self:ident:$State:ty,$input:ident|$handler:expr) => { impl ::tengri::Handle for $State { fn handle (&mut $self, $input: &E) -> Perhaps { diff --git a/src/space.rs b/src/space.rs index 9039d42..78b90fa 100644 --- a/src/space.rs +++ b/src/space.rs @@ -11,11 +11,6 @@ use crate::{*, draw::*}; /// #[cfg_attr(test, derive(Arbitrary))] #[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH(pub N, pub N, pub N, pub N); -impl From<&ratatui::prelude::Rect> for XYWH { - fn from (rect: &ratatui::prelude::Rect) -> Self { - Self(rect.x, rect.y, rect.width, rect.height) - } -} impl X for XYWH { fn x (&self) -> N { self.0 } fn w (&self) -> N { self.2 } } impl Y for XYWH { fn y (&self) -> N { self.0 } fn h (&self) -> N { self.2 } } impl XYWH { diff --git a/src/task.rs b/src/task.rs deleted file mode 100644 index dc37898..0000000 --- a/src/task.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::{ - time::Duration, - sync::{Arc, atomic::{AtomicBool, Ordering::*}}, - thread::{Builder, JoinHandle, sleep}, -}; -#[cfg(feature = "term")] use ::crossterm::event::poll; -use crate::time::PerfModel; - -#[derive(Debug)] pub struct Task { - /// Exit flag. - pub exit: Arc, - /// Performance counter. - pub perf: Arc, - /// Use this to wait for the thread to finish. - pub join: JoinHandle<()>, -} - -impl Task { - /// Spawn a TUI thread that runs `callt least one, then repeats until `exit`. - pub fn new (exit: Arc, mut call: F) -> Result - where F: FnMut(&PerfModel)->() + Send + Sync + 'static - { - let perf = Arc::new(PerfModel::default()); - Ok(Self { - exit: exit.clone(), - perf: perf.clone(), - join: Builder::new().name("tengri tui output".into()).spawn(move || { - while !exit.fetch_and(true, Relaxed) { - let _ = perf.cycle(&mut call); - } - })?.into() - }) - } - - /// Spawn a thread that runs `call` least one, then repeats - /// until `exit`, sleeping for `time` msec after every iteration. - pub fn new_sleep ( - exit: Arc, time: Duration, mut call: F - ) -> Result - where F: FnMut(&PerfModel)->() + Send + Sync + 'static - { - Self::new(exit, move |perf| { - let _ = call(perf); - sleep(time); - }) - } - - /// Spawn a thread that uses [crossterm::event::poll] - /// to run `call` every `time` msec. - #[cfg(feature = "term")] pub fn new_poll ( - exit: Arc, time: Duration, mut call: F - ) -> Result - where F: FnMut(&PerfModel)->() + Send + Sync + 'static - { - Self::new(exit, move |perf| { - if poll(time).is_ok() { - let _ = call(perf); - } - }) - } - - pub fn join (self) -> Result<(), Box> { - self.join.join() - } -} diff --git a/src/term.rs b/src/term.rs index c038785..b59edbd 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,4 +1,4 @@ -use crate::{*, lang::*, play::*, draw::*, space::{*, Split::*}, color::*, text::*, task::*}; +use crate::{*, lang::*, play::*, draw::*, space::{*, Split::*}, color::*, text::*}; use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; use rand::distributions::uniform::UniformSampler; use ::{ @@ -509,14 +509,14 @@ pub fn tui_output + Send + Sync exited: &Arc, state: &Arc>, sleep: Duration -) -> Usually { +) -> Usually { let state = state.clone(); tui_setup()?; let mut backend = CrosstermBackend::new(output); let Size { width, height } = backend.size().expect("get size failed"); let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height }); let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height }); - Ok(Task::new_sleep(exited.clone(), sleep, move |perf| { + Ok(Thread::new_sleep(exited.clone(), sleep, move |perf| { let Size { width, height } = backend.size().expect("get size failed"); if let Ok(state) = state.try_read() { tui_resize(&mut backend, &mut buffer_a, Rect { x: 0, y: 0, width, height }); diff --git a/src/text.rs b/src/text.rs index 9fe1dd4..a853845 100644 --- a/src/text.rs +++ b/src/text.rs @@ -17,12 +17,12 @@ pub(crate) use ::unicode_width::*; self.as_str().draw(to) } } - impl Draw for std::sync::Arc { + impl Draw for &std::sync::Arc { fn draw (self, to: &mut Tui) -> Usually> { self.as_ref().draw(to) } } - impl Draw for &std::sync::Arc { + impl Draw for std::sync::Arc { fn draw (self, to: &mut Tui) -> Usually> { self.as_ref().draw(to) }