//#![feature(anonymous_lifetime_in_impl_trait)] //#![feature(associated_type_defaults)] //#![feature(const_default)] //#![feature(const_option_ops)] //#![feature(const_precise_live_drops)] //#![feature(const_trait_impl)] //#![feature(impl_trait_in_assoc_type)] //#![feature(step_trait)] //#![feature(trait_alias)] //#![feature(type_alias_impl_trait)] //#![feature(type_changing_struct_update)] pub extern crate atomic_float; pub extern crate palette; pub extern crate better_panic; pub extern crate unicode_width; #[cfg(feature = "sing")] pub extern crate jack; #[cfg(feature = "midi")] pub extern crate midly; #[cfg(feature = "term")] pub extern crate ratatui; #[cfg(feature = "term")] pub extern crate crossterm; #[cfg(feature = "lang")] pub extern crate dizzle; #[cfg(test)] #[macro_use] pub extern crate proptest; #[cfg(test)] pub(crate) use proptest_derive::Arbitrary; pub(crate) use ::{ atomic_float::AtomicF64, std::fmt::{Debug, Display}, std::ops::{Add, Sub, Mul, Div}, std::sync::{Arc, RwLock}, std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::*}, std::marker::PhantomData }; macro_rules! features { ($($feature:literal: [ $($module:ident),* ]),*) => { $( $( #[cfg(feature = $feature)] pub mod $module; #[cfg(feature = $feature)] pub use $module::*; )* )* } } #[cfg(feature = "lang")] pub use ::dizzle::{Usually, Perhaps}; #[cfg(feature = "lang")] use ::dizzle::*; features! { "time": [ time ], "sing": [ sing ] } /// Define a trait an implement it for various mutation-enabled wrapper types. */ #[macro_export] macro_rules! flex_trait_mut ( ($Trait:ident $(<$($A:ident:$T:ident),+>)? { $(fn $fn:ident (&mut $self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)* })=>{ pub trait $Trait $(<$($A: $T),+>)? { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret $body)* } impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })* } impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) } })* } impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.get_mut().unwrap().$fn($($arg),*) })* } impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::Mutex<_T_>> { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.lock().unwrap().$fn($($arg),*) })* } impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })* } impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::RwLock<_T_>> { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })* } }; ); /// Implement [Handle] for given `State` and `handler`. #[macro_export] macro_rules! impl_handle { //(|$self:ident:$State:ty,$input:ident|$handler:expr) => { //impl ::tengri::Handle for $State { //fn handle (&mut $self, $input: &E) -> Perhaps { //$handler //} //} //}; ($E:ty: |$self:ident:$State:ty,$input:ident|$handler:expr) => { //impl ::tengri::Handle<$E> for $State { //fn handle (&mut $self, $input: &$E) -> //Perhaps<<$E as ::tengri::Input>::Handled> //{ //$handler //} //} } } /// Implement [Default]. #[macro_export] macro_rules! impl_default { ($T:ty:$e:expr) => { impl Default for $T { fn default () -> Self { $e } } }; } /// Implement [`Debug`] in bulk. #[macro_export] macro_rules! impl_debug (($($S:ty|$self:ident,$w:ident|$body:block)*)=>{ $(impl std::fmt::Debug for $S { fn fmt (&$self, $w: &mut std::fmt::Formatter) -> std::fmt::Result $body })* }); /// Implement [`From`] in bulk. #[macro_export] macro_rules! impl_from ( ($(<$($lt:lifetime),+>)?$Target:ty:|$state:ident:$Source:ty|$cb:expr) => { impl $(<$($lt),+>)? From<$Source> for $Target { fn from ($state:$Source) -> Self { $cb }} }; ($($Struct:ty { $( $(<$($l:lifetime),* $($T:ident$(:$U:ident)?),*>)? ($source:ident: $From:ty) $expr:expr );+ $(;)? })*) => { $( $(impl $(<$($l),* $($T$(:$U)?),*>)? From<$From> for $Struct { fn from ($source: $From) -> Self { $expr } })+ )* }; ); /// Implement [AsRef]. #[macro_export] macro_rules! impl_as_ref (($T:ty: |$self:ident:$S:ty|$x:expr)=>{ impl AsRef<$T> for $S { fn as_ref (&$self) -> &$T { $x } } }); /// Implement [AsMut]. #[macro_export] macro_rules! impl_as_mut (($T:ty: |$self:ident:$S:ty|$x:expr)=>{ impl AsMut<$T> for $S { fn as_mut (&mut $self) -> &mut $T { $x } } }); /// Implement [AsRefOpt]. #[macro_export] macro_rules! impl_as_ref_opt (($T:ty: |$self:ident:$S:ty|$x:expr)=>{ impl AsRefOpt<$T> for $S { fn as_ref_opt (&$self) -> Option<&$T> { $x } } }); /// Implement [AsMutOpt]. #[macro_export] macro_rules! impl_as_mut_opt (($T:ty: |$self:ident:$S:ty|$x:expr)=>{ impl AsMutOpt<$T> for $S { fn as_mut_opt (&mut $self) -> Option<&mut $T> { $x } } }); pub trait AsRefOpt { fn as_ref_opt (&self) -> Option<&T>; } pub trait AsMutOpt { fn as_mut_opt (&mut self) -> Option<&mut T>; } /// Implement [AsRef] and [AsMut]. #[macro_export] macro_rules! impl_has ( ($T:ty: |$self:ident:$S:ty|$x:expr)=>{ impl AsRef<$T> for $S { fn as_ref (&$self) -> &$T { &$x } } impl AsMut<$T> for $S { fn as_mut (&mut $self) -> &mut $T { &mut $x } } }; ($T:ty: |$self:ident:$S:ty|$x:block;$y:block)=>{ impl AsRef<$T> for $S { fn as_ref (&$self) -> &$T $x } impl AsMut<$T> for $S { fn as_mut (&mut $self) -> &mut $T $y } } ); /// Some layout operations exist in multiple variants that take a single argument. /// Their handling in [eval_view] is uniform and goes like this: macro_rules! eval_enum (( $name:literal, $output:ident, $state:ident, $value:expr, $arg0: ident, $Enum:ident { $($v:literal => $V:ident),* $(,)? } ) => {{ match $value { $(Some($v) => $Enum::$V,)* frag => unimplemented!("{}/{frag:?}", $name) } }}); /// Some layout operations exist in XY, X, and Y variants that take 3 or 2 arguments. /// Their handling in [eval_view] is uniform and goes like this: macro_rules! eval_xy ( // Valueless variant: // (fill/x ...) // (fill/y ...) // (fill/xy ...) ( $name:expr => $expr:expr, $head:expr, $output:ident, $state:ident, $variant:expr, $xy:ident, $x:ident, $y:ident, $arg: ident, ) => {{ // frags.next(): 2nd slash-delimited fragment: /x, /y, /xy let variant = $variant; let thunk = draw(move|screen|$state.interpret(screen, &$arg)); match variant { // X variant Some("x") => thunk.$x().draw($output), // Y variant Some("y") => thunk.$y().draw($output), // XY variant (can be omitted) Some("xy") | None => thunk.$xy().draw($output), // Other namespace members are invalid frag => invalid_variant($name, frag, $expr, $head) } }}; // Variadic variant: // (push/x n ...) // (push/y n ...) // (push/xy n m ...) ( $name:expr => $expr:expr, $head:expr, $output:ident, $state:ident, $variant:expr, $xy:ident, $x:ident, $y:ident, $arg0: ident, $arg1: ident, $arg2: ident, ) => {{ // frags.next(): 2nd slash-delimited fragment: /x, /y, /xy let variant = $variant; let thunk = draw(move|screen|$state.interpret(screen, &match variant { Some("x") | Some("y") => $arg1, Some("xy") | None => $arg2, _ => panic!("{}: unsupported axis {variant:?}; try /x, /y, /xy", $name) })); match variant { // X variant Some("x") => thunk.$x($state.namespace($arg0?)?) .draw($output), // Y variant Some("y") => thunk.$y($state.namespace($arg0?)?) .draw($output), // XY variant (can be omitted) Some("xy") | None => thunk.$xy($state.namespace($arg0?)?, $state.namespace($arg1?)?) .draw($output), // Other namespace members are invalid frag => invalid_variant($name, frag, $expr, $head) } }}; ); #[cfg(feature = "exit")] pub use self::exit::*; #[cfg(feature = "exit")] mod exit { use crate::*; use std::sync::{Arc, atomic::AtomicBool}; use crossterm::event::*; #[derive(Clone)] pub struct Exit(Arc); impl Exit { pub fn run (run: impl FnOnce(Self)->Usually) -> Usually { run(Self(Arc::new(AtomicBool::new(false)))) } pub fn is (event: &Event) -> bool { matches!(event, Event::Key(KeyEvent { modifiers: KeyModifiers::CONTROL, code: KeyCode::Char('c'), kind: KeyEventKind::Press, state: KeyEventState::NONE })) } } impl AsRef> for Exit { fn as_ref (&self) -> &Arc { &self.0 } } } #[cfg(feature = "play")] pub use self::task::*; #[cfg(feature = "play")] mod task { 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() } } } #[cfg(feature = "draw")] pub use self::draw::*; #[cfg(feature = "draw")] mod draw { use crate::*; use Azimuth::*; use Split::*; /// Output target. /// /// ``` /// use tengri::*; /// struct TestOut { w: u16, h: u16 }; /// impl Wide for TestOut {} /// impl Tall for TestOut {} /// impl Xy for TestOut { /// fn x (&self) -> u16 { 0 } /// fn y (&self) -> u16 { 0 } /// } /// impl Screen for TestOut { /// type Unit = u16; /// fn show (&mut self, _: impl Draw) -> Perhaps> { /// println!("placed"); /// Ok(None) /// } /// fn area (&self) -> XYWH { /// Default::default() /// } /// fn clip ( /// &mut self, /// area: impl Into>>, /// draw: impl FnOnce(&mut Self)->T /// ) -> T { /// draw(self) /// } /// } /// /// impl_draw!(|self: String, to: TestOut|{ /// to.w = self.len() as u16; /// Ok(None) /// }); /// ``` pub trait Screen: Xy + Wh + Send + Sync + Sized { type Unit: Coord; /// Render drawable in subarea specified by `area` fn show (&mut self, content: impl Draw) -> Perhaps>; /// Get current clipping area fn area (&self) -> XYWH; /// Set clipping area fn clip ( &mut self, area: impl Into>>, draw: impl FnOnce(&mut Self)->T ) -> T; } /// Implement the [Draw] trait for a particular drawable and [Screen]. /// /// ``` /// use tengri::*; /// struct MyDrawable; /// impl_draw!(|self: MyDrawable, to: Tui|{ /// todo!("your draw logic") /// }); /// ``` #[macro_export] macro_rules! impl_draw ( ($(<$($T:ident: $Trait:path,)+>)?| $self:ident:$Self:path, $to:ident:$To:ty |$draw:block)=>{ impl$(<$($T:$Trait),+>)? Draw<$To> for $Self { fn draw ($self, $to: &mut $To) -> Perhaps::Unit>> $draw } }; ($(<$($T:ident: $Trait:path,)+>)?| $self:ident:$Self:ty, $to:ident:$To:ty |$draw:block)=>{ impl$(<$($T:$Trait),+>)? Draw<$To> for $Self { fn draw ($self, $to: &mut $To) -> Perhaps::Unit>> $draw } } ); /// 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. /// /// To draw a thing multiple times, instead of explicitly constructing it /// every time, implement the [View] trait instead, which will construct /// a [Draw]able. /// /// ``` /// use tengri::*; /// struct MyWidget(bool); /// impl Draw for MyWidget { /// fn draw (self, to: &mut Tui) -> Perhaps> { /// todo!("your draw logic") /// } /// } /// ``` pub trait Draw { fn draw (self, to: &mut S) -> Drawn; fn layout (&self, area: XYWH) -> Drawn { Ok(Some(area)) } } /// Emit a [Draw]able. /// /// Speculative. How to avoid conflicts with [Draw] proper? pub trait View { fn view (&self) -> impl Draw; } impl View for () { fn view (&self) -> impl Draw { () } } /// Return a [Draw]able. /// /// ``` /// # use tengri::*; /// let _ = view::(||"drawable"); /// let _ = view::(||Some("drawable")); /// ``` pub const fn view , F: Fn()->T> (view: F) -> impl View { ViewThunk(view, PhantomData) } /// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. pub struct ViewThunk(pub F, std::marker::PhantomData); impl, F: Fn()->T> View for ViewThunk { fn view (&self) -> impl Draw { self.0() } } /// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. pub struct DrawThunk(pub F, std::marker::PhantomData); implPerhaps>> Draw for DrawThunk { fn draw (self, to: &mut T) -> Perhaps> { (self.0)(to) } } /// Basic [Draw]able closure. /// /// ``` /// # use tengri::*; /// let _ = draw(|to: &mut Tui|Ok(Some(to.1))); /// ``` pub const fn draw Perhaps>> ( item: F ) -> DrawThunk { DrawThunk(item, std::marker::PhantomData) } /// Only render when condition is true. /// /// ``` /// # use tengri::*; /// # fn test () -> impl Draw { /// when(true, "Yes") /// # } /// ``` pub const fn when (condition: bool, item: impl Draw) -> impl Draw { draw(move|to: &mut T|if condition { item.draw(to) } else { Ok(Default::default()) }) } /// Render one thing if a condition is true and another false. /// /// ``` /// # use tengri::*; /// # fn test () -> impl Draw { /// either(true, "Yes", "No") /// # } /// ``` pub const fn either (condition: bool, a: impl Draw, b: impl Draw) -> impl Draw { draw(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) }) } pub type Drawn = Perhaps>; impl Draw for () { fn draw (self, _: &mut S) -> Drawn { Ok(None) } } impl_draw!(,>|self: Option, to: S|{ self.map(|it|it.draw(to)).transpose().map(Option::unwrap_or_default) }); //impl> Draw for RwLock { //fn draw (self, __: &mut S) -> Drawn { //todo!() //} //} //impl> Draw for Arc { //fn draw (self, __: &mut T) -> Perhaps> { //todo!() //} //} impl> Draw for &V { fn draw (self, to: &mut T) -> Perhaps> { self.view().draw(to) } } pub trait Xy { fn x (&self) -> N; fn y (&self) -> N; } pub trait Wh: Wide + Tall { fn wh (&self) -> [N;2]; } pub trait Xywh: Xy + Wh { fn xywh (&self) -> XYWH { XYWH(self.x(), self.y(), self.w(), self.h()) } } pub trait Wide: Xy { fn w (&self) -> N { N::zero() } fn w_min (&self) -> N { self.w() } fn w_max (&self) -> N { self.w() } } pub trait Tall { fn h (&self) -> N { N::zero() } fn h_min (&self) -> N { self.h() } fn h_max (&self) -> N { self.h() } } /// Point with size. /// /// ``` /// # use tengri::*; /// let xywh = XYWH(0u16, 0, 0, 0); /// assert_eq!(XYWH(10u16, 10, 20, 20).center(), (20, 20)); /// ``` /// /// * [ ] TODO: origin field (determines at which corner/side is X0 Y0) /// #[cfg_attr(test, derive(Arbitrary))] #[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH(pub N, pub N, pub N, pub N); impl Xy for XYWH { fn x (&self) -> N { self.0 } fn y (&self) -> N { self.1 } } impl Wide for XYWH { fn w (&self) -> N { self.2 } } impl Tall for XYWH { fn h (&self) -> N { self.3 } } impl XYWH { pub fn zero () -> Self { Self(0.into(), 0.into(), 0.into(), 0.into()) } pub fn center (&self) -> (N, N) { let Self(x, y, w, h) = *self; (x.plus(w/2.into()), y.plus(h/2.into())) } pub fn centered (&self) -> (N, N) { let Self(x, y, w, h) = *self; (x.minus(w/2.into()), y.minus(h/2.into())) } pub fn centered_x (&self, n: N) -> Self { let Self(x, y, w, h) = *self; let x_center = (x.plus(w / 2.into())).minus(n / 2.into()); let y_center = y.plus(h / 2.into()); XYWH(x_center, y_center, n, 1.into()) } pub fn centered_y (&self, n: N) -> Self { let Self(x, y, w, h) = *self; let x_center = x.plus(w / 2.into()); let y_corner = (y.plus(h / 2.into())).minus(n / 2.into()); XYWH(x_center, y_corner, 1.into(), n) } pub fn centered_xy (&self, [n, m]: [N;2]) -> Self { let Self(x, y, w, h) = *self; let x_center = (x.plus(w / 2.into())).minus(n / 2.into()); let y_corner = (y.plus(h / 2.into())).minus(m / 2.into()); XYWH(x_center, y_corner, n, m) } pub fn split_half (&self, direction: &Split) -> (Self, Self) { let XYWH(x, y, w, h) = self.xywh(); match direction { South => (XYWH(x, y, w, h - h / 2.into()), XYWH(x, y + h / 2.into(), w, h / 2.into())), East => (XYWH(x, y, w - w / 2.into(), h), XYWH(x + w / 2.into(), y, w / 2.into(), h)), North => (XYWH(x, y + h / 2.into(), w, h - h / 2.into()), XYWH(x, y, w, h / 2.into())), West => (XYWH(x + w / 2.into(), y, w - w / 2.into(), h), XYWH(x, y, w / 2.into(), h)), Above | Below => (XYWH(x, y, w, h), XYWH(x, y, w, h)) } } } impl From<&ratatui::prelude::Rect> for XYWH { fn from (rect: &ratatui::prelude::Rect) -> Self { Self(rect.x, rect.y, rect.width, rect.height) } } impl + Tall> Wh for T { fn wh (&self) -> [N;2] { [self.w(), self.h()] } } impl + Wh> Xywh for T {} impl> Lrtb for T {} pub trait Lrtb: Xywh { fn lrtb (&self) -> [N;4] { // FIXME: factor origin [self.x(), self.y(), self.x()+self.w(), self.y()+self.h()] } fn iter_x (&self) -> std::ops::Range where Self: HasOrigin { self.x_west()..self.x_east() } fn x_west (&self) -> N where Self: HasOrigin { let w = self.w(); let a = self.origin(); let d = match a { NW|W|SW => 0.into(), N|X|C|Y|S => w/2.into(), NE|E|SE => w }; self.x().minus(d) } fn x_east (&self) -> N where Self: HasOrigin { let w = self.w(); let a = self.origin(); let d = match a { NW|W|SW => w, N|X|C|Y|S => w/2.into(), NE|E|SE => 0.into() }; self.x().plus(d) } fn x_center (&self) -> N where Self: HasOrigin { todo!() } fn iter_y (&self) -> std::ops::Range where Self: HasOrigin { self.y_north()..self.y_south() } fn y_north (&self) -> N where Self: HasOrigin { let a = self.origin(); let h = self.h(); let d = match a { NW|N|NE => 0.into(), W|X|C|Y|E => h/2.into(), SW|S|SE => h }; self.y().minus(d) } fn y_south (&self) -> N where Self: HasOrigin { let a = self.origin(); let h = self.h(); let d = match a { NW|N|NE => h, W|X|C|Y|E => h/2.into(), SW|S|SE => 0.into() }; self.y().plus(d) } fn y_center (&self) -> N where Self: HasOrigin { todo!() } } } #[cfg(feature = "draw")] pub use self::coord::*; #[cfg(feature = "draw")] mod coord { use crate::*; /// A numeric type that can be used as coordinate. /// /// FIXME: Replace with `num` crate? /// FIXME: Use AsRef/AsMut? /// /// ``` /// use tengri::*; /// let a: u16 = Coord::zero(); /// let b: u16 = a.plus(1); /// let c: u16 = a.minus(2); /// let d = a.atomic(); /// ``` pub trait Coord: Send + Sync + Copy + Add + Sub + Mul + Div + Ord + PartialEq + Eq + Debug + Display + Default + From + Into + Into + Into //+ std::iter::Step { /// Zero in own type. fn zero () -> Self { 0.into() } /// Addition. fn plus (self, other: Self) -> Self; /// Saturating subtraction. fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } } /// Convert to [AtomicUsize]. fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) } } /// TUI works in u16 coordinates. impl Coord for u16 { fn plus (self, other: Self) -> Self { self.saturating_add(other) } } } #[cfg(feature = "draw")] pub use self::layout::*; #[cfg(feature = "draw")] mod layout { use crate::*; impl> Layout for T {} pub trait Layout: Draw + Sized { fn full_w (self) -> impl Draw { Full::W(self) } fn full_h (self) -> impl Draw { Full::H(self) } fn full_wh (self) -> impl Draw { Full::WH(self) } /// (bsp/e (exact/w 10 "Hello") "World") fn exact_w >> (self, x: N) -> impl Draw { Exact::W(self, x.into()) } /// (bsp/s (exact/h 10 "Hello") "World") fn exact_h >> (self, y: N) -> impl Draw { Exact::H(self, y.into()) } /// (exact/wh 10 2 "Hello World") fn exact_wh >> (self, x: N, y: N) -> impl Draw { Exact::WH(self, x.into(), y.into()) } fn min_w >> (self, x: N) -> impl Draw { Min::W(self, x.into()) } fn min_h >> (self, y: N) -> impl Draw { Min::H(self, y.into()) } fn min_wh >> (self, x: N, y: N) -> impl Draw { Min::WH(self, x.into(), y.into()) } fn max_w >> (self, x: N) -> impl Draw { Max::W(self, x.into()) } fn max_h >> (self, y: N) -> impl Draw { Max::H(self, y.into()) } fn max_wh >> (self, x: N, y: N) -> impl Draw { Max::WH(self, x.into(), y.into()) } fn pad_w >> (self, x: N) -> impl Draw { Pad::W(self, x.into()) } fn pad_h >> (self, y: N) -> impl Draw { Pad::H(self, y.into()) } fn pad_wh >> (self, x: N, y: N) -> impl Draw { Pad::WH(self, x.into(), y.into()) } fn pull_x >> (self, x: N) -> impl Draw { Pull::X(self, x.into()) } fn pull_y >> (self, y: N) -> impl Draw { Pull::Y(self, y.into()) } fn pull_xy >> (self, x: N, y: N) -> impl Draw { Pull::XY(self, x.into(), y.into()) } fn push_x >> (self, x: N) -> impl Draw { Push::X(self, x.into()) } fn push_y >> (self, y: N) -> impl Draw { Push::Y(self, y.into()) } fn push_xy >> (self, x: N, y: N) -> impl Draw { Push::XY(self, x.into(), y.into()) } fn align (self, azimuth: impl Into>) -> Align { Align(azimuth.into(), self) } fn align_c (self) -> Align { Align(Some(Azimuth::C), self) } fn align_x (self) -> Align { Align(Some(Azimuth::X), self) } fn align_y (self) -> Align { Align(Some(Azimuth::Y), self) } fn align_n (self) -> impl Draw { Align(Some(Azimuth::N), self) } fn align_s (self) -> impl Draw { Align(Some(Azimuth::S), self) } fn align_e (self) -> impl Draw { Align(Some(Azimuth::E), self) } fn align_w (self) -> impl Draw { Align(Some(Azimuth::W), self) } fn align_ne (self) -> impl Draw { Align(Some(Azimuth::NE), self) } fn align_se (self) -> impl Draw { Align(Some(Azimuth::SE), self) } fn align_nw (self) -> impl Draw { Align(Some(Azimuth::NW), self) } fn align_sw (self) -> impl Draw { Align(Some(Azimuth::SW), self) } fn origin (self, azimuth: impl Into>) -> impl Draw { Origin(azimuth.into(), self) } fn origin_c (self) -> impl Draw { Origin(Some(Azimuth::C), self) } fn origin_x (self) -> impl Draw { Origin(Some(Azimuth::X), self) } fn origin_y (self) -> impl Draw { Origin(Some(Azimuth::Y), self) } fn origin_n (self) -> impl Draw { Origin(Some(Azimuth::N), self) } fn origin_s (self) -> impl Draw { Origin(Some(Azimuth::S), self) } fn origin_e (self) -> impl Draw { Origin(Some(Azimuth::E), self) } fn origin_w (self) -> impl Draw { Origin(Some(Azimuth::W), self) } fn origin_ne (self) -> impl Draw { Origin(Some(Azimuth::NE), self) } fn origin_se (self) -> impl Draw { Origin(Some(Azimuth::SE), self) } fn origin_nw (self) -> impl Draw { Origin(Some(Azimuth::NW), self) } fn origin_sw (self) -> impl Draw { Origin(Some(Azimuth::SW), self) } } /// Where is [0, 0] located? /// /// ``` /// use tengri::*; /// use Azimuth::*; /// let _ = "".align(NW); /// ``` #[cfg_attr(test, derive(Arbitrary))] #[derive(Debug, Copy, Clone, Default)] pub enum Azimuth { #[default] C, X, Y, NW, N, NE, E, SE, S, SW, W } /// Uses [AtomicUsize] to measure size during\ /// rendering (which is normally read-only). #[derive(Default, Debug, Clone)] pub struct Sizer( /// Width pub Arc, /// Height pub Arc, ); impl Xy for Sizer { fn x (&self) -> u16 { self.0.load(Relaxed) as u16 } fn y (&self) -> u16 { self.1.load(Relaxed) as u16 } } impl Wide for Sizer { fn w (&self) -> u16 { self.0.load(Relaxed) as u16 } } impl Tall for Sizer { fn h (&self) -> u16 { self.1.load(Relaxed) as u16 } } impl PartialEq for Sizer { fn eq (&self, _: &Self) -> bool { todo!() } } impl Sizer { pub const fn of (&self, of: impl Draw) -> impl Draw { draw(move|to: &mut T|{ let area = of.draw(to)?; self.0.store(area.map(|a|a.w()).unwrap_or(T::Unit::zero()).into(), Relaxed); self.1.store(area.map(|a|a.h()).unwrap_or(T::Unit::zero()).into(), Relaxed); Ok(area) }) } } mod align; pub use self::align::*; mod area; pub use self::area::*; mod exact; pub use self::exact::*; mod full; pub use self::full::*; mod iter; pub use self::iter::*; mod max; pub use self::max::*; mod min; pub use self::min::*; mod origin; pub use self::origin::*; mod pad; pub use self::pad::*; mod pull; pub use self::pull::*; mod push; pub use self::push::*; mod split; pub use self::split::*; } #[cfg(feature = "draw")] pub use self::color::*; #[cfg(feature = "draw")] mod color { use crate::*; use dizzle::LanguageError::*; use ::ratatui::style::Color; use ::rand::distributions::uniform::UniformSampler; pub(crate) use ::palette::{ Okhsl, Srgb, OklabHue, Mix, okhsl::UniformOkhsl, convert::{FromColor, FromColorUnclamped} }; pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor { let term = Color::Rgb(r, g, b); ItemColor { okhsl: rgb_to_okhsl(term), term } } pub fn g (g: u8) -> Color { Color::Rgb(g, g, g) } pub fn okhsl_to_rgb (color: Okhsl) -> Color { let Srgb { red, green, blue, .. }: Srgb = Srgb::from_color_unclamped(color); Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,) } pub fn rgb_to_okhsl (color: Color) -> Okhsl { if let Color::Rgb(r, g, b) = color { Okhsl::from_color(Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0)) } else { unreachable!("only Color::Rgb is supported") } } pub trait HasColor { fn color (&self) -> ItemColor; } #[macro_export] macro_rules! has_color { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? { fn color (&$self) -> ItemColor { $cb } } } } #[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct ItemColor { pub term: Color, pub okhsl: Okhsl } impl_from!(ItemColor: |term: Color| Self { term, okhsl: rgb_to_okhsl(term) }); impl_from!(ItemColor: |okhsl: Okhsl| Self { okhsl, term: okhsl_to_rgb(okhsl) }); // A single color within item theme parameters, in OKHSL and RGB representations. impl ItemColor { #[cfg(feature = "term")] pub const fn from_tui (term: Color) -> Self { Self { term, okhsl: Okhsl::new_const(OklabHue::new(0.0), 0.0, 0.0) } } pub fn random () -> Self { let mut rng = ::rand::thread_rng(); let lo = Okhsl::new(-180.0, 0.01, 0.25); let hi = Okhsl::new( 180.0, 0.9, 0.5); UniformOkhsl::new(lo, hi).sample(&mut rng).into() } pub fn random_dark () -> Self { let mut rng = ::rand::thread_rng(); let lo = Okhsl::new(-180.0, 0.025, 0.075); let hi = Okhsl::new( 180.0, 0.5, 0.150); UniformOkhsl::new(lo, hi).sample(&mut rng).into() } pub fn random_near (color: Self, distance: f32) -> Self { color.mix(Self::random(), distance) } pub fn mix (&self, other: Self, distance: f32) -> Self { if distance > 1.0 { panic!("color mixing takes distance between 0.0 and 1.0"); } self.okhsl.mix(other.okhsl, distance).into() } } #[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct ItemTheme { pub base: ItemColor, pub light: ItemColor, pub lighter: ItemColor, pub lightest: ItemColor, pub dark: ItemColor, pub darker: ItemColor, pub darkest: ItemColor, } impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base)); impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base)); impl ItemTheme { #[cfg(feature = "term")] pub const G: [Self;256] = { let mut builder = dizzle::konst::array::ArrayBuilder::new(); while !builder.is_full() { let index = builder.len() as u8; let light = (index as f64 * 1.15) as u8; let lighter = (index as f64 * 1.7) as u8; let lightest = (index as f64 * 1.85) as u8; let dark = (index as f64 * 0.9) as u8; let darker = (index as f64 * 0.6) as u8; let darkest = (index as f64 * 0.3) as u8; builder.push(ItemTheme { base: ItemColor::from_tui(Color::Rgb(index, index, index )), light: ItemColor::from_tui(Color::Rgb(light, light, light, )), lighter: ItemColor::from_tui(Color::Rgb(lighter, lighter, lighter, )), lightest: ItemColor::from_tui(Color::Rgb(lightest, lightest, lightest, )), dark: ItemColor::from_tui(Color::Rgb(dark, dark, dark, )), darker: ItemColor::from_tui(Color::Rgb(darker, darker, darker, )), darkest: ItemColor::from_tui(Color::Rgb(darkest, darkest, darkest, )), }); } builder.build() }; pub fn random () -> Self { ItemColor::random().into() } pub fn random_near (color: Self, distance: f32) -> Self { color.base.mix(ItemColor::random(), distance).into() } pub const G00: Self = { let color: ItemColor = ItemColor { okhsl: Okhsl { hue: OklabHue::new(0.0), lightness: 0.0, saturation: 0.0 }, term: Color::Rgb(0, 0, 0) }; Self { base: color, light: color, lighter: color, lightest: color, dark: color, darker: color, darkest: color, } }; #[cfg(feature = "term")] pub fn from_tui_color (base: Color) -> Self { Self::from_item_color(ItemColor::from_tui(base)) } pub fn from_item_color (base: ItemColor) -> Self { let mut light = base.okhsl; light.lightness = (light.lightness * 1.3).min(1.0); let mut lighter = light; lighter.lightness = (lighter.lightness * 1.3).min(1.0); let mut lightest = base.okhsl; lightest.lightness = 0.95; let mut dark = base.okhsl; dark.lightness = (dark.lightness * 0.75).max(0.0); dark.saturation = (dark.saturation * 0.75).max(0.0); let mut darker = dark; darker.lightness = (darker.lightness * 0.66).max(0.0); darker.saturation = (darker.saturation * 0.66).max(0.0); let mut darkest = darker; darkest.lightness = 0.1; darkest.saturation = (darkest.saturation * 0.50).max(0.0); Self { base, light: light.into(), lighter: lighter.into(), lightest: lightest.into(), dark: dark.into(), darker: darker.into(), darkest: darkest.into(), } } } pub trait ColorDsl: Sized { fn new_g (expr: T, try_to_u8: impl Fn(Perhaps<&str>)->Perhaps) -> Usually; fn new_rgb (expr: T, try_to_u8: impl Fn(Perhaps<&str>)->Perhaps) -> Usually; } impl ColorDsl for Color { fn new_g (expr: T, try_to_u8: impl Fn(Perhaps<&str>)->Perhaps) -> Usually { let n = try_to_u8(expr.tail().map_err(Into::into))?.ok_or(Domain("not gray"))?; Ok(Self::Rgb(n, n, n)) } fn new_rgb (expr: T, try_to_u8: impl Fn(Perhaps<&str>)->Perhaps) -> Usually { let r = try_to_u8(expr.tail().map_err(Into::into))? .ok_or(Domain("not red"))?; let g = try_to_u8(expr.tail().tail().head().map_err(Into::into))? .ok_or(Domain("not green"))?; let b = try_to_u8(expr.tail().tail().tail().head().map_err(Into::into))? .ok_or(Domain("not blue"))?; Ok(Color::Rgb(r, g, b)) } } } #[cfg(feature = "draw")] pub use self::text::*; #[cfg(feature = "text")] mod text { #![allow(unused)] pub(crate) use ::unicode_width::*; /// Displays an owned [str]-like with fixed maximum width. /// /// Width is computed using [unicode_width]. pub struct TrimString>(pub u16, pub T); impl> AsRef for TrimString { fn as_ref (&self) -> &str { self.1.as_ref() } } impl<'a, T: AsRef> TrimString { fn to_ref (&self) -> TrimStr<'_, T> { TrimStr(self.0, &self.1) } } /// Displays a borrowed [str]-like with fixed maximum width /// /// Width is computed using [unicode_width]. pub struct TrimStr<'a, T: AsRef>(pub u16, pub &'a T); impl> AsRef for TrimStr<'_, T> { fn as_ref (&self) -> &str { self.1.as_ref() } } pub(crate) fn width_chars_max (max: u16, text: impl AsRef) -> u16 { let mut width: u16 = 0; let mut chars = text.as_ref().chars(); while let Some(c) = chars.next() { width += c.width().unwrap_or(0) as u16; if width > max { break } } return width } /// Trim string with [unicode_width]. pub fn trim_string (max_width: usize, input: impl AsRef) -> String { let input = input.as_ref(); let mut output = Vec::with_capacity(input.len()); let mut width: usize = 1; let mut chars = input.chars(); while let Some(c) = chars.next() { if width > max_width { break } output.push(c); width += c.width().unwrap_or(0); } return output.into_iter().collect() } } #[cfg(feature = "term")] pub use self::term::*; #[cfg(feature = "term")] mod term { use crate::*; use Color::*; #[macro_export] macro_rules! tui_app { ($Struct:ident { $($fields:tt)* }) => { #[dizzle::namespace(bool)] #[dizzle::namespace(u8)] #[dizzle::namespace(u16)] #[dizzle::namespace(Option)] #[dizzle::namespace(Color Tui::eval_color_expr)] #[derive(Debug, Default)] pub struct $Struct { $($fields)* } tui_main!($Struct { ..Default::default() }); } } /// Implement standard [main] entrypoint for TUI apps. #[macro_export] macro_rules! tui_main { ($state:expr) => { pub fn main () -> Usually<()> { tengri::Tui::setup_panic(); tengri::Tui::run_main( ::std::sync::Arc::new(::std::sync::RwLock::new($state)) ) } } } /// Enable TUI output for state struct. #[macro_export] macro_rules! tui_view { ($self:ident: $State:ty $body:block) => { impl tengri::View for $State { fn view (&$self) -> impl tengri::Draw $body } } } #[macro_export] macro_rules! tui_interpret { ($self:ident: $State:ident, $to:pat, $pat:ident -> $Result:ty { $($body:tt)+ }) => { impl dizzle::Interpret for $State { fn interpret_word <'a> ( &'a $self, $to: &mut Tui, $pat: &'a impl dizzle::Symbol ) -> Usually<$Result> { $($body)+ } fn interpret_expr <'a> ( &'a self, to: &mut Tui, src: &'a impl tengri::Expression ) -> Usually<$Result> { Ok(Some(if let Some(area) = tengri::eval_view(self, to, src)? { area } else if let Some(area) = tengri::Tui::eval_view(self, to, src)? { area } else { return Err(format!("App::interpret_expr: unexpected: {src:?}").into()) })) } } }; } /// Enable TUI keyboard input for main state struct. #[macro_export] macro_rules! tui_keys { ($self:ident:$State:ty,$input:ident $($body:tt)+) => { impl dizzle::Apply> for $State { fn apply (&mut $self, $input: &tengri::TuiEvent) -> Usually<()> $($body)+ } }; } //use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; //use rand::distributions::uniform::UniformSampler; pub(crate) use ::{ std::{ io::{stdout, Write}, time::Duration, ops::{Deref, DerefMut}, }, ratatui::{ prelude::{Style, Position, Backend, Color}, style::{Modifier}, backend::{CrosstermBackend, ClearType}, layout::{Size, Rect}, buffer::{Buffer, Cell}, crossterm::{ ExecutableCommand, terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, //event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, } }, crossterm::event::read, }; impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } } impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } } impl AsMut for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } } impl Wide for Tui { fn w (&self) -> u16 { self.1.2 } } impl Tall for Tui { fn h (&self) -> u16 { self.1.3 } } impl HasOrigin for Tui { fn origin (&self) -> Azimuth { Azimuth::NW } } impl Xy for Tui { fn x (&self) -> u16 { self.1.0 } fn y (&self) -> u16 { self.1.1 } } /// Terminal output. pub struct Tui( /// Ratatui buffer; area is screen size pub Buffer, /// Current draw area pub XYWH ); impl Tui { pub fn setup_panic () { use ::std::panic::{set_hook, PanicHookInfo}; use ::better_panic::{Settings, Verbosity}; let panic = Settings::auto() .verbosity(Verbosity::Full) .create_panic_handler(); set_hook(Box::new(move |info: &PanicHookInfo|{ let _ = Tui::teardown(&mut stdout()); panic(info); })); } pub fn run_main (state: Arc>) -> Usually<()> where T: View + Apply> + Send + Sync + 'static { Exit::run(|exit|{ let scan = Duration::from_millis(100); let frame = Duration::from_millis(10); let (_input, output) = Tui::io(exit.as_ref(), &state, scan, frame, std::io::stdout())?; let _ = output.join(); Tui::teardown(&mut stdout()) }) } /// Spawn the TUI input and output threadsl. pub fn io < T: View + Apply> + Send + Sync + 'static, W: Write + Send + Sync + 'static, > ( exited: &Arc, state: &Arc>, poll: Duration, sleep: Duration, output: W, ) -> Result<(Task, Task), Box> { Ok(( Tui::input(exited, state, poll)?, Tui::output(exited, state, sleep, output)?, )) } /// Spawn the TUI input thread which reads keys from the terminal. pub fn input > + Send + Sync + 'static> ( exited: &Arc, state: &Arc>, poll: Duration ) -> Result { let exited = exited.clone(); let state = state.clone(); Task::new_poll(exited.clone(), poll, move |_| { let event = read().unwrap(); if Exit::is(&event) { exited.store(true, Relaxed); } else if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) { panic!("{e}") } }) } pub fn teardown (backend: &mut W) -> Usually<()> { use ::ratatui::backend::Backend; stdout().execute(LeaveAlternateScreen)?; CrosstermBackend::new(backend).show_cursor()?; disable_raw_mode().map_err(Into::into) } pub fn new (width: u16, height: u16) -> Self { Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height)) } pub fn resize (&mut self, back: &mut CrosstermBackend, width: u16, height: u16) { let size = Rect { x: 0, y: 0, width, height }; if self.0.area != size { back.clear_region(ClearType::All).unwrap(); self.0.resize(size); self.0.reset(); } } pub fn redraw <'b, W: Write> ( &'b mut self, back: &mut CrosstermBackend, mut next: &'b mut Self ) { let updates = self.0.diff(&next.0); back.draw(updates.into_iter()).expect("failed to render"); Backend::flush(back).expect("failed to flush output new"); std::mem::swap(self, &mut next); next.0.reset(); } pub fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH { for row in 0..self.h() { let y = self.y() + row; for col in 0..self.w() { let x = self.x() + col; if x < self.0.area.width && y < self.0.area.height { if let Some(cell) = self.0.cell_mut(Position { x, y }) { callback(cell, col, row); } } } } self.xywh() } pub fn blit (&mut self, text: &impl AsRef, x: u16, y: u16, style: Option