diff --git a/examples/mode_02.rs b/examples/mode_02.rs index a2106b3..1ec2069 100644 --- a/examples/mode_02.rs +++ b/examples/mode_02.rs @@ -37,7 +37,6 @@ impl_keywords!(Tui, XYWH, State [ kw_split, kw_align, kw_exact, - kw_fixed, kw_min, kw_max, kw_push, diff --git a/src/layout.rs b/src/layout.rs new file mode 100644 index 0000000..9ef91cc --- /dev/null +++ b/src/layout.rs @@ -0,0 +1,103 @@ +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) + } + + 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) + } +} + +/// 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 area; pub use self::area::*; +mod axis; pub use self::axis::*; +mod azimuth; pub use self::azimuth::*; +mod cond; pub use self::cond::*; +mod iter; pub use self::iter::*; diff --git a/src/layout/align.rs b/src/layout/align.rs deleted file mode 100644 index f7779fd..0000000 --- a/src/layout/align.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::*; -use Azimuth::*; - -pub struct Align( - pub(crate) Option, - pub(crate) T, -); - -impl> Draw for Align { - fn layout (&self, area: XYWH) -> Perhaps> { - Ok(align::(area, self.1.layout(area)?, self.0)) - } - fn draw (self, to: &mut S) -> Perhaps> { - Ok(if let Some(area) = self.layout(to.area())? { - to.clip(area, self.1)? - } else { - None - }) - } -} - -fn align ( - area0: XYWH, area: Option>, azimuth: Option -) -> Option> { - area.map(|XYWH(x, y, w, h)|{ - let XYWH(x0, y0, w0, h0) = area0; - match azimuth { - Some(NW) => XYWH(x0, y0, w, h), - Some(N) => XYWH(x0 + w0.minus(w) / 2.into(), y0, w, h), - Some(NE) => XYWH((x0 + w0).minus(w), y0, w, h), - Some(W) => XYWH(x0, y0 + h0.minus(h) / 2.into(), w, h), - Some(C) => XYWH(x0 + w0.minus(w) / 2.into(), y0 + h0.minus(h) / 2.into(), w, h), - Some(E) => XYWH((x0 + w0).minus(w), y0 + h0.minus(h) / 2.into(), w, h), - Some(SW) => XYWH(x0, (y0 + h0).minus(h), w, h), - Some(S) => XYWH(x0 + w0.minus(w) / 2.into(), (y0 + h0).minus(h), w, h), - Some(SE) => XYWH((x0 + w0).minus(w), (y0 + h0).minus(h), w, h), - Some(X) => XYWH(x0 + w0.minus(w) / 2.into(), y, w, h), - Some(Y) => XYWH(x, y0 + h0.minus(h) / 2.into(), w, h), - None => XYWH(x, y, w, h) - } - }) -} - -#[cfg(test)] #[test] fn test_align () { -} diff --git a/src/layout/area.rs b/src/layout/area.rs index 6533d35..a806c92 100644 --- a/src/layout/area.rs +++ b/src/layout/area.rs @@ -18,5 +18,5 @@ pub struct Area>( ); impl_draw!(,>|self: Area, to: S|{ - to.clip(self.0, self.1) + to.draw(self.0, &self.1) }); diff --git a/src/layout/axis.rs b/src/layout/axis.rs new file mode 100644 index 0000000..0cb59e9 --- /dev/null +++ b/src/layout/axis.rs @@ -0,0 +1,245 @@ +use crate::*; + +macro_rules! def_layout_modifier { + ( + $Trait:ident ($fn_x:ident $fn_y:ident $fn_xy:ident), + $Struct:ident { $X:ident $Y:ident $XY:ident } $kw_name:ident $name:literal + |$self:ident, $to:ident| $body:block + ) => { + impl> $Trait for T {} + + pub trait $Trait: Draw + Sized { + fn $fn_x > + Copy> (self, x: N) + -> $Struct { $Struct::$X(self, x) } + fn $fn_y > + Copy> (self, y: N) + -> $Struct { $Struct::$Y(self, y) } + fn $fn_xy > + Copy> (self, x: N, y: N) + -> $Struct { $Struct::$XY(self, x, y) } + } + + pub enum $Struct, X: Into>> { + __(PhantomData), $X(I, X), $Y(I, X), $XY(I, X, X), + } + + impl , X: Into> + Copy> Draw for $Struct { + fn draw (&$self, $to: &mut S) -> Perhaps> { + $body + } + } + + fn_kw_layout!($kw_name |state, output, expr| { + let head = expr.head()?; + let mut frags = head.src()?.unwrap_or_default().split("/"); + Ok(matches!(expr.head()?, Some("exact")).then(||{ + let args = expr.tail(); + let arg0 = args.head(); + let tail0 = args.tail(); + let arg1 = tail0.head(); + let tail1 = tail0.tail(); + let arg2 = tail1.head(); + eval_xy!( + $name => expr, head, output, state, frags.next(), + $fn_xy, $fn_x, $fn_y, + arg0, arg1, arg2, + ) + }).transpose()?.flatten()) + }); + } +} + +def_layout_modifier!( + CanExact (exact_w exact_h exact_wh), + Exact { W H WH } kw_exact "exact" |self, to| { + let XYWH(x, y, w0, h0) = to.area(); + let (item, w, h) = match self { + Self::W(item, w) => (item, (*w).into().unwrap_or(w0), h0), + Self::H(item, h) => (item, w0, (*h).into().unwrap_or(h0)), + Self::WH(item, w, h) => (item, (*w).into().unwrap_or(h0), (*h).into().unwrap_or(h0)), + _ => unreachable!() + }; + to.draw(XYWH(x, y, w0, h0), item) + } +); + +def_layout_modifier!( + CanMin (min_w min_h min_wh), + Min { W H WH } kw_min "min" |self, to| { + match self { + Self::__(_) => unreachable!(), + Self::W(item, w1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => { + let w: S::Unit = (*w1).into().map(|w1|w.max(w1)).unwrap_or(w); + to.draw(XYWH(x, y, w, h), item) + }, + Self::H(item, h1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => { + let h: S::Unit = (*h1).into().map(|h1|h.max(h1)).unwrap_or(h); + to.draw(XYWH(x, y, w, h), item) + }, + Self::WH(item, w1, h1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => { + let w: S::Unit = (*w1).into().map(|w1|w.max(w1)).unwrap_or(w); + let h: S::Unit = (*h1).into().map(|h1|h.max(h1)).unwrap_or(h); + to.draw(XYWH(x, y, w, h), item) + }, + _ => Ok(None) + } + } +); + +def_layout_modifier!( + CanMax (max_w max_h max_wh), + Max { W H WH } kw_max "max" |self, to| { + let area: XYWH = to.area(); + let (item, area) = match self { + Self::W(item, max_w) => (item, XYWH( + area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2), + area.3 + )), + Self::H(item, max_h) => (item, XYWH( + area.0, area.1, area.2, + (*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3) + )), + Self::WH(item, max_w, max_h) => (item, XYWH( + area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2), + (*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3), + )), + _ => return Ok(None) + }; + to.draw(area, item) + } +); + +def_layout_modifier!( + CanPad (pad_w pad_h pad_wh), + Pad { X Y XY } kw_pad "pad" |self, to| { + let area = to.area(); + let (item, area) = match self { + Self::X(item, w1) => { + let w1: S::Unit = (*w1).into().unwrap_or_default(); + (item, XYWH(area.0 + w1, area.1, area.2.minus(w1 + w1), area.3)) + }, + Self::Y(item, h1) => { + let h1: S::Unit = (*h1).into().unwrap_or_default(); + (item, XYWH(area.0, area.1 + h1, area.2, area.3.minus(h1 + h1))) + }, + Self::XY(item, w1, h1) => { + let w1: S::Unit = (*w1).into().unwrap_or_default(); + let h1: S::Unit = (*h1).into().unwrap_or_default(); + (item, XYWH(area.0 + w1, area.1 + h1, area.2.minus(w1 + w1), area.3.minus(h1 + h1))) + }, + _ => return Ok(None) + }; + item.draw(to) + } +); + +def_layout_modifier!( + CanPush (push_x push_y push_xy), + Push { X Y XY } kw_push "push" |self, to| { + match self { + Self::__(_) => unreachable!(), + Self::X(item, x1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => { + to.draw(XYWH(x + (*x1).into().unwrap_or_default(), y, w, h), item) + }, + Self::Y(item, y1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => { + to.draw(XYWH(x, y + (*y1).into().unwrap_or_default(), w, h), item) + }, + Self::XY(item, x1, y1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => { + to.draw(XYWH(x + (*x1).into().unwrap_or_default(), y + (*y1).into().unwrap_or_default(), w, h), item) + }, + _ => Ok(None) + } + } +); + +def_layout_modifier!( + CanPull (pull_x pull_y pull_xy), + Pull { X Y XY } kw_pull "pull" |self, to| { + todo!() + } +); + +/// Use whole drawing area along one or both axes. +/// +/// ``` +/// # fn doctest_layout_full () -> Result<(), Box> { +/// use tengri::{Layout, Draw, XYWH}; +/// let area = XYWH(0u16, 0, 80, 25); +/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1))); +/// assert_eq!("1".full_w().layout(area)?, Some(XYWH(0u16, 0, 80, 1))); +/// assert_eq!("1".full_h().layout(area)?, Some(XYWH(0u16, 0, 1, 25))); +/// assert_eq!("1".full_wh().layout(area)?, Some(XYWH(0u16, 0, 80, 25))); +/// # Ok(()) } +/// ``` +pub enum Full> { + __(PhantomData), + W(I), + H(I), + WH(I), +} + +impl_draw!(,>|self: Full, to: T|{ + let XYWH(x0, y0, w0, h0) = to.area(); + match self { + Self::W(item) => if let Some(XYWH(_, y, _, h)) = to.size(None, item)? { + to.draw(XYWH(x0, y, w0, h), item) + } else { + Ok(None) + }, + Self::H(item) => if let Some(XYWH(x, _, w, _)) = to.size(None, item)? { + to.draw(XYWH(x, y0, w, h0), item) + } else { + Ok(None) + }, + Self::WH(item) => if let Some(XYWH(..)) = to.size(None, item)? { + to.draw(XYWH(x0, y0, w0, h0), item) + } else { + Ok(None) + }, + _ => unreachable!(), + } +}); + +/// Only draw content if area is above a certain size. +/// +/// ``` +/// # fn doctest_layout_min () -> Result<(), Box> { +/// use tengri::{Layout, Draw, XYWH}; +/// let area = XYWH(1u16, 1, 80, 25); +/// assert_eq!("1".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 5, 1))); +/// assert_eq!("1".min_h(5).layout(area)?, Some(XYWH(1u16, 1, 1, 5))); +/// assert_eq!("1".min_wh(5, 5).layout(area)?, Some(XYWH(1u16, 1, 5, 5))); +/// assert_eq!("123456".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 6, 1))); +/// # Ok(()) } +/// ``` +/// Move content in the negative direction of one or both axes. +/// +/// ``` +/// # fn doctest_layout_pull () -> Result<(), Box> { +/// use tengri::{Layout, Draw, XYWH}; +/// let area = XYWH(1u16, 1, 80, 25); +/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1))); +/// assert_eq!("1".pull_x(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1))); +/// assert_eq!("1".pull_y(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1))); +/// assert_eq!("1".pull_xy(1, 1).layout(area)?, Some(XYWH(0u16, 0, 1, 1))); +/// # Ok(()) } +/// ``` +/// Move content in the positive direction of one or both axes. +/// +/// ``` +/// # fn doctest_layout_push () -> Result<(), Box> { +/// use tengri::{Layout, Draw, XYWH}; +/// let area = XYWH(0u16, 0, 80, 25); +/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1))); +/// assert_eq!("1".push_x(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1))); +/// assert_eq!("1".push_y(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1))); +/// assert_eq!("1".push_xy(1, 1).layout(area)?, Some(XYWH(1u16, 1, 1, 1))); +/// # Ok(()) } +/// ``` +#[cfg(test)] #[test] fn test_exact () -> Usually<()> { + let mut screen = Tui::Layout(XYWH(0, 0, 80, 25)); + assert_eq!("FOOBAR\nKILROY".draw(&mut screen)?, Some(XYWH(0, 0, 6, 2))); + assert_eq!("FOOBAR\nKILROY".exact_w(3).draw(&mut screen)?, Some(XYWH(0, 0, 3, 2))); + assert_eq!("FOOBAR\nKILROY".exact_h(1).draw(&mut screen)?, Some(XYWH(0, 0, 6, 1))); + assert_eq!("FOOBAR\nKILROY".exact_wh(2, 1).draw(&mut screen)?, Some(XYWH(0, 0, 2, 1))); + Ok(()) +} + diff --git a/src/layout/azimuth.rs b/src/layout/azimuth.rs new file mode 100644 index 0000000..ce72b95 --- /dev/null +++ b/src/layout/azimuth.rs @@ -0,0 +1,455 @@ +use crate::*; +use Azimuth::*; + +/// 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 +} + +pub struct Origin( + pub(crate) Option, + pub(crate) T +); + +impl_draw!(,>|self: Origin, _to: S|{ + todo!() +}); + +/// Something that has `[0, 0]` at a particular point. +pub trait HasOrigin { + fn origin (&self) -> Azimuth; +} + +impl> HasOrigin for T { + fn origin (&self) -> Azimuth { + *self.as_ref() + } +} + +fn_kw_layout!(kw_align |state, output, expr| { + Ok(matches!(expr.head()?, Some("align")).then(||{ + draw(move|output: &mut O|{state.interpret(output, &expr.tail().head())}).align( + eval_enum!("align", output, state, + expr.head().src()?.unwrap_or_default().split("/").skip(1).next(), + expr.tail().head(), + Azimuth { + "c" => C, "x" => X, "y" => Y, + "n" => N, "s" => S, "e" => E, "w" => W, + "nw" => NW, "sw" => SW, "ne" => NE, "se" => SE, + }) + ).draw(output) + }).transpose()?.flatten()) +}); + +impl> CanAlign for T {} + +pub trait CanAlign: Draw + Sized { + 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) -> Align { + Align(Some(Azimuth::N), self) + } + fn align_s (self) -> Align { + Align(Some(Azimuth::S), self) + } + fn align_e (self) -> Align { + Align(Some(Azimuth::E), self) + } + fn align_w (self) -> Align { + Align(Some(Azimuth::W), self) + } + fn align_ne (self) -> Align { + Align(Some(Azimuth::NE), self) + } + fn align_se (self) -> Align { + Align(Some(Azimuth::SE), self) + } + fn align_nw (self) -> Align { + Align(Some(Azimuth::NW), self) + } + fn align_sw (self) -> Align { + Align(Some(Azimuth::SW), self) + } +} + +pub struct Align( + pub(crate) Option, + pub(crate) T, +); + +impl> Draw for Align { + fn draw (&self, to: &mut S) -> Perhaps> { + let area = to.area(); + Ok(if let Some(area) = align::(area, to.size(area, &self.1)?, self.0) { + to.draw(area, &self.1)? + } else { + None + }) + } +} + +fn align ( + area0: XYWH, area: Option>, azimuth: Option +) -> Option> { + area.map(|XYWH(x, y, w, h)|{ + let XYWH(x0, y0, w0, h0) = area0; + match azimuth { + Some(NW) => XYWH(x0, y0, w, h), + Some(N) => XYWH(x0 + w0.minus(w) / 2.into(), y0, w, h), + Some(NE) => XYWH((x0 + w0).minus(w), y0, w, h), + Some(W) => XYWH(x0, y0 + h0.minus(h) / 2.into(), w, h), + Some(C) => XYWH(x0 + w0.minus(w) / 2.into(), y0 + h0.minus(h) / 2.into(), w, h), + Some(E) => XYWH((x0 + w0).minus(w), y0 + h0.minus(h) / 2.into(), w, h), + Some(SW) => XYWH(x0, (y0 + h0).minus(h), w, h), + Some(S) => XYWH(x0 + w0.minus(w) / 2.into(), (y0 + h0).minus(h), w, h), + Some(SE) => XYWH((x0 + w0).minus(w), (y0 + h0).minus(h), w, h), + Some(X) => XYWH(x0 + w0.minus(w) / 2.into(), y, w, h), + Some(Y) => XYWH(x, y0 + h0.minus(h) / 2.into(), w, h), + None => XYWH(x, y, w, h) + } + }) +} + +fn_kw_layout!(kw_split |state, output, expr| { + let head = expr.head(); + let mut frags = head.src()?.unwrap_or_default().split("/"); + Ok(matches!(frags.next(), Some("bsp")).then(||{ + eval_enum!("bsp", output, state, frags.next(), expr.tail().head()?, Split { + "n" => North, + "s" => South, + "e" => East, + "w" => West, + "a" => Above, + "b" => Below + }).stack( + draw(move|output: &mut O|{ + state.interpret(output, &expr.tail().tail().head()?) + }), + draw(move|output: &mut O|{ + state.interpret(output, &expr.tail().tail().tail().head()?) + }), + ).draw(output) + }).transpose()?.flatten()) +}); + +/// Split along an axis. Direction determines order. +#[cfg_attr(test, derive(Arbitrary))] +#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split { + North, + South, + East, + West, + Above, + #[default] Below +} + +pub struct Pair, B: Draw>(Split, A, B, PhantomData); + +pub fn split , B: Draw> ( + split: Split, a: A, b: B +) -> Pair { + Pair(split, a, b, PhantomData) +} + +impl, B: Draw> Draw for Pair { + fn draw (&self, to: &mut S) -> Drawn { + let Self(split, a, b, ..) = self; + let (area_a, area_b) = stack_areas(split, to, a, b)?; + let (drawn_a, drawn_b) = draw_stacks(split, to, a, area_a, None, b, area_b, None)?; + Ok(stack_drawn(split, drawn_a, drawn_b)) + } +} + +impl Split { + + /// ``` + /// use tengri::*; + /// let _ = Split::Above.stack("", ""); + /// let _ = Split::Below.stack("", ""); + /// let _ = Split::North.stack("", ""); + /// let _ = Split::South.stack("", ""); + /// let _ = Split::East.stack("", ""); + /// let _ = Split::West.stack("", ""); + /// ``` + pub const fn stack , B: Draw> (&self, a: A, b: B) -> impl Draw { + Pair(*self, a, b, PhantomData) + } + + /// ``` + /// use tengri::*; + /// let _ = Split::Above.half("", ""); + /// let _ = Split::Below.half("", ""); + /// let _ = Split::North.half("", ""); + /// let _ = Split::South.half("", ""); + /// let _ = Split::East.half("", ""); + /// let _ = Split::West.half("", ""); + /// ``` + pub const fn half , B: Draw> (&self, a: &A, b: &B) -> impl Draw { + draw(move|to: &mut S|{ + let (area_a, area_b) = to.xywh().split_half(self); + let (origin_a, origin_b) = self.origins(); + let (drawn_a, drawn_b) = draw_stacks(self, to, a, area_a, origin_a, b, area_b, origin_b)?; + Ok(stack_drawn(self, drawn_a, drawn_b)) + }) + } + + /// Newly split areas begin at the center of the split + /// to maintain centeredness in the user's field of view. + /// + /// Use [align] to override that and always start + /// at the top, bottom, etc. + /// + /// ``` + /// /* + /// + /// Split east: Split south: + /// | | | | A | + /// | <-A|B-> | |---------| + /// | | | | B | + /// + /// */ + /// ``` + const fn origins (&self) -> (Azimuth, Azimuth) { + use Azimuth::*; + match self { + Self::South => (S, N), + Self::East => (E, W), + Self::North => (N, S), + Self::West => (W, E), + Self::Above => (C, C), + Self::Below => (C, C), + } + } + + ///// ``` + ///// use tengri::*; + ///// let _ = Split::Below.iter([ + ///// "Leftbar" + ///// .min_w(10).max_w(15).align(Azimuth::NW), + ///// "Rightbar" + ///// .min_w(10).max_w(12).align(Azimuth::NE), + ///// "Center" + ///// .min_w(20).max_w(40).align(Azimuth::C), + ///// ].iter()); + ///// ``` + //pub fn iter > (&self, _: impl Iterator) { + //todo!() + //} + +} + +fn draw_stacks ( + split: &Split, + to: &mut S, + a: impl Draw, + area_a: impl Into>>, + origin_a: impl Into>, + b: impl Draw, + area_b: impl Into>>, + origin_b: impl Into>, +) -> Usually<(Option>, Option>)> { + let draw_a = |to: &mut S|Ok::<_, Box>(if let Some(origin_a) = origin_a.into() { + to.draw(area_a.into(), a.align(origin_a))? + } else { + to.draw(area_a.into(), a)? + }); + let draw_b = |to: &mut S|Ok::<_, Box>(if let Some(origin_b) = origin_b.into() { + to.draw(area_b.into(), b.align(origin_b))? + } else { + to.draw(area_b.into(), b)? + }); + Ok(if matches!(split, Split::Below) { + let drawn_b = draw_b(to)?; + let drawn_a = draw_a(to)?; + (drawn_a, drawn_b) + } else { + (draw_a(to)?, draw_b(to)?) + }) +} + +pub fn stack_areas ( + split: &Split, + to: &mut S, + a: impl Draw, + b: impl Draw, +) -> Usually<(Option>, Option>)> { + let area_a = to.draw(None, a)?; + Ok(match split { + Split::South => ( + area_a, + area_a + .map(|used|to.size(XYWH(to.x(), to.y() + used.h(), to.w(), to.h().minus(used.h())), b)) + .transpose()? + .flatten() + ), + Split::East => ( + area_a, + area_a.map(|used|to.size(XYWH(to.x() + used.w(), to.y(), to.w().minus(used.w()), to.h()), b)) + .transpose()? + .flatten() + ), + Split::North => ( + area_a.map(|used|XYWH(used.x(), (to.y() + to.h()).minus(used.h()), used.w(), used.h())), + if let Some(used) = area_a { + to.size(XYWH(to.x(), to.y(), to.w(), to.h().minus(used.h())), b)? + } else { + to.size(None, b)? + .map(|area_b|XYWH(area_b.x(), area_b.y() + area_b.h(), area_b.w(), area_b.h())) + } + ), + Split::West => ( + area_a.map(|used|XYWH((to.x() + to.w()).minus(used.w()), used.y(), used.w(), used.h())), + if let Some(used) = area_a { + to.size(XYWH(to.x(), to.y(), to.w().minus(used.w()), to.h()), b)? + } else { + to.size(None, b)? + .map(|area_b|XYWH(area_b.x() + area_b.w(), area_b.y(), area_b.w(), area_b.h())) + } + ), + Split::Above | Split::Below => ( + area_a, + to.size(None, b)?, + ), + }) +} + +fn stack_drawn ( + split: &Split, + drawn_a: Option>, + drawn_b: Option>, +) -> Option> { + if let (Some(XYWH(xa, ya, wa, ha)), Some(XYWH(xb, yb, wb, hb))) = (drawn_a, drawn_b) { + match split { + Split::South => Some(XYWH(xa.min(xb), ya, wa.max(wb), ha + hb)), + Split::East => Some(XYWH(xa, ya.min(yb), wa + wb, ha.max(hb))), + Split::North => Some(XYWH(xa.min(xb), yb, wa.max(wb), ha + hb)), + Split::West => Some(XYWH(xb, ya.min(yb), wa + wb, ha.max(hb))), + Split::Above | Split::Below => + Some(XYWH(xa.min(xb), ya.min(yb), wa.max(wb), ha.max(hb))), + } + } else if let Some(a) = drawn_a { + Some(a) + } else if let Some(b) = drawn_b { + Some(b) + } else { + None + } +} + +#[macro_export] macro_rules! north { + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) }; +} + +#[macro_export] macro_rules! south { + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) }; +} + +#[macro_export] macro_rules! east { + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) }; +} + +#[macro_export] macro_rules! west { + ($head:expr $(,)?) => { $head }; + ($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) }; +} + +#[macro_export] macro_rules! above { + ($head:expr $(,)?) => { $head }; + ($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) }; +} + +#[macro_export] macro_rules! below { + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) }; +} + +pub const fn east , B: Draw> (a: A, b: B) -> impl Draw { + Pair(Split::East, a, b, PhantomData) +} + +pub const fn north , B: Draw> (a: A, b: B) -> impl Draw { + Pair(Split::North, a, b, PhantomData) +} + +pub const fn west , B: Draw> (a: A, b: B) -> impl Draw { + Pair(Split::West, a, b, PhantomData) +} + +pub const fn south , B: Draw> (a: A, b: B) -> impl Draw { + Pair(Split::South, a, b, PhantomData) +} + +pub const fn above , B: Draw> (a: A, b: B) -> impl Draw { + Pair(Split::Above, a, b, PhantomData) +} + +pub const fn below , B: Draw> (a: A, b: B) -> impl Draw { + Pair(Split::Below, a, b, PhantomData) +} + +#[cfg(test)] #[test] fn test_stack_areas () -> Usually<()> { + let area = XYWH(0u16, 0, 80, 25); + + assert_eq!(stack_areas(&Split::East, &mut Tui::Layout(area), &"foo", &"bar")?, ( + Some(XYWH(0u16, 0, 3, 1)), + Some(XYWH(3u16, 0, 3, 1)), + )); + + assert_eq!(stack_areas(&Split::South, &mut Tui::Layout(area), &"foo", &"bar")?, ( + Some(XYWH(0u16, 0, 3, 1)), + Some(XYWH(0u16, 1, 3, 1)), + )); + + Ok(()) +} + +#[cfg(test)] #[test] fn test_split_stack () -> Usually<()> { + use Split::*; + fn size_of , B: Draw> (stack: &Pair) -> Perhaps> { + Tui::Layout(XYWH(0, 0, 80, 25)).size(None, stack) + } + assert_eq!(size_of(&split(East, "foo", "bar"))?, + Some(XYWH(0, 0, 6, 1))); + assert_eq!(size_of(&split(South, "foo", "bar"))?, + Some(XYWH(0, 0, 3, 2))); + assert_eq!(size_of(&split(South, split(East, "foo", "bar"), "baz"))?, + Some(XYWH(0, 0, 6, 2))); + assert_eq!(size_of(&split(East, split(South, "foo", "bar"), "baz"))?, + Some(XYWH(0, 0, 6, 2))); + return Ok(()); +} + +#[cfg(test)] #[test] fn test_align () -> Usually<()> { + let mut screen = Tui::Layout(XYWH(0, 0, 80, 25)); + assert_eq!("FOOBAR\nKILROY".draw(&mut screen)?, Some(XYWH(0, 0, 6, 2))); + assert_eq!("FOOBAR\nKILROY".align_nw().draw(&mut screen)?, Some(XYWH(0, 0, 6, 2))); + assert_eq!("FOOBAR\nKILROY".align_n().draw(&mut screen)?, Some(XYWH(37, 0, 6, 2))); + assert_eq!("FOOBAR\nKILROY".align_ne().draw(&mut screen)?, Some(XYWH(74, 0, 6, 2))); + assert_eq!("FOOBAR\nKILROY".align_w().draw(&mut screen)?, Some(XYWH(0, 11, 6, 2))); + assert_eq!("FOOBAR\nKILROY".align_c().draw(&mut screen)?, Some(XYWH(37, 11, 6, 2))); + assert_eq!("FOOBAR\nKILROY".align_e().draw(&mut screen)?, Some(XYWH(74, 11, 6, 2))); + assert_eq!("FOOBAR\nKILROY".align_sw().draw(&mut screen)?, Some(XYWH(0, 23, 6, 2))); + assert_eq!("FOOBAR\nKILROY".align_s().draw(&mut screen)?, Some(XYWH(37, 23, 6, 2))); + assert_eq!("FOOBAR\nKILROY".align_se().draw(&mut screen)?, Some(XYWH(74, 23, 6, 2))); + Ok(()) +} diff --git a/src/layout/cond.rs b/src/layout/cond.rs new file mode 100644 index 0000000..a95d8f1 --- /dev/null +++ b/src/layout/cond.rs @@ -0,0 +1,46 @@ +use crate::*; + +fn_kw_layout!(kw_when |state, output, expr| { + Ok(matches!(expr.head()?, Some("when")).then(||{ + when(state.namespace(expr.tail().head())?.unwrap(), + draw(move|output: &mut O|{state.interpret(output, &expr.tail().tail().head())}) + ).draw(output) + }).transpose()?.flatten()) +}); + +/// 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()) }) +} + +fn_kw_layout!(kw_either |state, output, expr| { + Ok(matches!(expr.head()?, Some("either")).then(||{ + either(state.namespace(expr.tail().head()?)?.unwrap(), + draw(move|output: &mut O|{ + state.interpret(output, &expr.tail().tail().head()?) + }), + draw(move|output: &mut O|{ + state.interpret(output, &expr.tail().tail().tail().head()?) + }), + ).draw(output) + }).transpose()?.flatten()) +}); + +/// 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) }) +} diff --git a/src/layout/exact.rs b/src/layout/exact.rs deleted file mode 100644 index ef51dc2..0000000 --- a/src/layout/exact.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::*; - -/// Set size of of drawing area. -/// -/// ``` -/// use tengri::Layout; -/// let _ = "".exact_w(1); -/// let _ = "".exact_h(1); -/// let _ = "".exact_wh(1, 1); -/// ``` -pub enum Exact, X: Into>> { - __(PhantomData), - W(I, X), - H(I, X), - WH(I, X, X), -} - -impl , X: Into> + Copy> Draw for Exact { - fn layout (&self, area: XYWH) -> Perhaps> { - Ok(Some(layout_exact(self, area))) - } - fn draw (self, to: &mut S) -> Perhaps> { - let area = layout_exact(&self, to.area()); - let item = match self { Self::W(i, ..) => i, Self::H(i, ..) => i, Self::WH(i, ..) => i, _ => unreachable!() }; - to.clip(area, item) - } -} - -fn layout_exact , X: Into> + Copy> ( - exact: &Exact, area: XYWH -) -> XYWH { - let (w, h): (S::Unit, S::Unit) = match exact { - Exact::W(_, w) => { - let w: Option = (*w).into(); - (w.unwrap_or(area.2), area.3) - }, - Exact::H(_, h) => { - let h: Option = (*h).into(); - (area.2, h.unwrap_or(area.3)) - }, - Exact::WH(_, w, h) => { - let w: Option = (*w).into(); - let h: Option = (*h).into(); - (w.unwrap_or(area.2), w.unwrap_or(area.3)) - }, - _ => unreachable!() - }; - XYWH(area.0, area.1, w, h) -} diff --git a/src/layout/full.rs b/src/layout/full.rs deleted file mode 100644 index 5c2a717..0000000 --- a/src/layout/full.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::*; - -/// Use whole drawing area along one or both axes. -/// -/// ``` -/// # fn doctest_layout_full () -> Result<(), Box> { -/// use tengri::{Layout, Draw, XYWH}; -/// let area = XYWH(0u16, 0, 80, 25); -/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1))); -/// assert_eq!("1".full_w().layout(area)?, Some(XYWH(0u16, 0, 80, 1))); -/// assert_eq!("1".full_h().layout(area)?, Some(XYWH(0u16, 0, 1, 25))); -/// assert_eq!("1".full_wh().layout(area)?, Some(XYWH(0u16, 0, 80, 25))); -/// # Ok(()) } -/// ``` -pub enum Full> { - __(PhantomData), - W(I), - H(I), - WH(I), -} - -impl_draw!(,>|self: Full, to: T|{ - let XYWH(x0, y0, w0, h0) = to.area(); - match self { - Self::W(item) => if let Some(XYWH(_, y, _, h)) = item.layout(to.area())? { - to.clip(XYWH(x0, y, w0, h), item) - } else { - Ok(None) - }, - Self::H(item) => if let Some(XYWH(x, _, w, _)) = item.layout(to.area())? { - to.clip(XYWH(x, y0, w, h0), item) - } else { - Ok(None) - }, - Self::WH(item) => if let Some(XYWH(..)) = item.layout(to.area())? { - to.clip(XYWH(x0, y0, w0, h0), item) - } else { - Ok(None) - }, - _ => unreachable!(), - } -}); diff --git a/src/layout/max.rs b/src/layout/max.rs deleted file mode 100644 index 9b3f40a..0000000 --- a/src/layout/max.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::*; - -/// Set maximum size of of drawing area. -/// -/// ``` -/// # fn doctest_layout_max () -> Result<(), Box> { -/// use tengri::{Layout, Draw, XYWH}; -/// let area = XYWH(1u16, 1, 80, 25); -/// assert_eq!("12345".max_w(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1))); -/// assert_eq!("12345".max_h(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1))); -/// assert_eq!("12345".max_wh(1, 1).layout(area)?, Some(XYWH(1u16, 1, 5, 1))); -/// # Ok(()) } -/// ``` -pub enum Max, X: Into> + Copy> { - __(PhantomData), - W(I, X), - H(I, X), - WH(I, X, X), -} - -impl, X: Into> + Copy> Draw for Max { - fn layout (&self, area: XYWH) -> Perhaps> { - Ok(Some(match self { - Self::W(_, max_w) => XYWH( - area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2), - area.3), - Self::H(_, max_h) => XYWH( - area.0, area.1, area.2, - (*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3)), - Self::WH(_, max_w, max_h) => XYWH( - area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2), - (*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3)), - _ => return Ok(None) - })) - } - fn draw (self, to: &mut T) -> Perhaps> { - let area: XYWH = to.area(); - let (item, area) = match self { - Self::W(item, max_w) => (item, XYWH( - area.0, area.1, max_w.into().map(|max|max.min(area.2)).unwrap_or(area.2), - area.3 - )), - Self::H(item, max_h) => (item, XYWH( - area.0, area.1, area.2, - max_h.into().map(|max|max.min(area.3)).unwrap_or(area.3) - )), - Self::WH(item, max_w, max_h) => (item, XYWH( - area.0, area.1, max_w.into().map(|max|max.min(area.2)).unwrap_or(area.2), - max_h.into().map(|max|max.min(area.3)).unwrap_or(area.3), - )), - _ => return Ok(None) - }; - to.clip(area, item) - } -} diff --git a/src/layout/min.rs b/src/layout/min.rs deleted file mode 100644 index 70c6549..0000000 --- a/src/layout/min.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::*; - -/// Only draw content if area is above a certain size. -/// -/// ``` -/// # fn doctest_layout_min () -> Result<(), Box> { -/// use tengri::{Layout, Draw, XYWH}; -/// let area = XYWH(1u16, 1, 80, 25); -/// assert_eq!("1".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 5, 1))); -/// assert_eq!("1".min_h(5).layout(area)?, Some(XYWH(1u16, 1, 1, 5))); -/// assert_eq!("1".min_wh(5, 5).layout(area)?, Some(XYWH(1u16, 1, 5, 5))); -/// assert_eq!("123456".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 6, 1))); -/// # Ok(()) } -/// ``` -pub enum Min, X: Into>> { - __(PhantomData), - W(I, X), - H(I, X), - WH(I, X, X), -} - -impl_draw!(, X: Into>,>|self: Min, to: T|{ - match self { - Self::__(_) => unreachable!(), - Self::W(item, w1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => { - let w = w1.into().map(|w1|w.max(w1)).unwrap_or(w); - to.clip(XYWH(x, y, w, h), item) - }, - Self::H(item, h1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => { - let h = h1.into().map(|h1|h.max(h1)).unwrap_or(h); - to.clip(XYWH(x, y, w, h), item) - }, - Self::WH(item, w1, h1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => { - let w = w1.into().map(|w1|w.max(w1)).unwrap_or(w); - let h = h1.into().map(|h1|h.max(h1)).unwrap_or(h); - to.clip(XYWH(x, y, w, h), item) - }, - _ => Ok(None) - } -}); diff --git a/src/layout/origin.rs b/src/layout/origin.rs deleted file mode 100644 index e5aaff9..0000000 --- a/src/layout/origin.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::*; - -pub struct Origin( - pub(crate) Option, - pub(crate) T -); - -impl_draw!(,>|self: Origin, _to: S|{ - todo!() -}); - -/// Something that has `[0, 0]` at a particular point. -pub trait HasOrigin { - fn origin (&self) -> Azimuth; -} - -impl> HasOrigin for T { - fn origin (&self) -> Azimuth { - *self.as_ref() - } -} diff --git a/src/layout/pad.rs b/src/layout/pad.rs deleted file mode 100644 index 6ac2b4a..0000000 --- a/src/layout/pad.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::*; - -/// Define inner drawing area. -/// -/// ``` -/// use tengri::Layout; -/// let _ = "".pad_w(1); -/// let _ = "".pad_h(1); -/// let _ = "".pad_wh(1, 1); -/// ``` -pub enum Pad, X: Into>> { - __(PhantomData), - W(I, X), - H(I, X), - WH(I, X, X), -} - -impl_draw!(, X: Into>,>|self: Pad, to: T|{ - let area = to.area(); - let (item, area) = match self { - Self::W(item, w1) => { - let w1 = w1.into().unwrap_or(T::Unit::zero()); - (item, XYWH(area.0 + w1, area.1, area.2.minus(w1 + w1), area.3)) - }, - Self::H(item, h1) => { - let h1 = h1.into().unwrap_or(T::Unit::zero()); - (item, XYWH(area.0, area.1 + h1, area.2, area.3.minus(h1 + h1))) - }, - Self::WH(item, w1, h1) => { - let w1 = w1.into().unwrap_or(T::Unit::zero()); - let h1 = h1.into().unwrap_or(T::Unit::zero()); - (item, XYWH(area.0 + w1, area.1 + h1, area.2.minus(w1 + w1), area.3.minus(h1 + h1))) - }, - _ => return Ok(None) - }; - item.draw(to) -}); diff --git a/src/layout/pull.rs b/src/layout/pull.rs deleted file mode 100644 index 287a998..0000000 --- a/src/layout/pull.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::*; - -/// Move content in the negative direction of one or both axes. -/// -/// ``` -/// # fn doctest_layout_pull () -> Result<(), Box> { -/// use tengri::{Layout, Draw, XYWH}; -/// let area = XYWH(1u16, 1, 80, 25); -/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1))); -/// assert_eq!("1".pull_x(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1))); -/// assert_eq!("1".pull_y(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1))); -/// assert_eq!("1".pull_xy(1, 1).layout(area)?, Some(XYWH(0u16, 0, 1, 1))); -/// # Ok(()) } -/// ``` -pub enum Pull, X: Into>> { - __(PhantomData), - X(I, X), - Y(I, X), - XY(I, X, X), -} - -impl_draw!(, X: Into>,>|self: Pull, _to: T|{ - todo!() -}); diff --git a/src/layout/push.rs b/src/layout/push.rs deleted file mode 100644 index 0907ad1..0000000 --- a/src/layout/push.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::*; - -/// Move content in the positive direction of one or both axes. -/// -/// ``` -/// # fn doctest_layout_push () -> Result<(), Box> { -/// use tengri::{Layout, Draw, XYWH}; -/// let area = XYWH(0u16, 0, 80, 25); -/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1))); -/// assert_eq!("1".push_x(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1))); -/// assert_eq!("1".push_y(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1))); -/// assert_eq!("1".push_xy(1, 1).layout(area)?, Some(XYWH(1u16, 1, 1, 1))); -/// # Ok(()) } -/// ``` -pub enum Push, X: Into>> { - __(PhantomData), - X(I, X), - Y(I, X), - XY(I, X, X), -} - -impl_draw!(, X: Into>,>|self: Push, to: T|{ - match self { - Self::__(_) => unreachable!(), - Self::X(item, x1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => { - to.clip(XYWH( - x + x1.into().unwrap_or_default(), y, w, h - ), item) - }, - Self::Y(item, y1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => { - to.clip(XYWH( - x, y + y1.into().unwrap_or_default(), w, h - ), item) - }, - Self::XY(item, x1, y1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => { - to.clip(XYWH( - x + x1.into().unwrap_or_default(), y + y1.into().unwrap_or_default(), w, h - ), item) - }, - _ => Ok(None) - } -}); diff --git a/src/layout/split.rs b/src/layout/split.rs deleted file mode 100644 index 77120c3..0000000 --- a/src/layout/split.rs +++ /dev/null @@ -1,315 +0,0 @@ -use super::*; - -/// Split along an axis. Direction determines order. -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split { - North, - South, - East, - West, - Above, - #[default] Below -} - -pub struct Pair, B: Draw>(Split, A, B, PhantomData); - -pub fn split , B: Draw> ( - split: Split, a: A, b: B -) -> Pair { - Pair(split, a, b, PhantomData) -} - -impl, B: Draw> Draw for Pair { - fn layout (&self, to: XYWH) -> Drawn { - let Self(split, a, b, ..) = self; - let (area_a, area_b) = stack_areas(split, to, a, b)?; - Ok(stack_drawn(split, area_a, area_b)) - } - fn draw (self, to: &mut S) -> Drawn { - let Self(ref split, a, b, ..) = self; - let (area_a, area_b) = stack_areas(split, to.area(), &a, &b)?; - let (drawn_a, drawn_b) = draw_stacks(split, to, a, area_a, None, b, area_b, None)?; - Ok(stack_drawn(split, drawn_a, drawn_b)) - } -} - -impl Split { - - /// ``` - /// use tengri::*; - /// let _ = Split::Above.stack("", ""); - /// let _ = Split::Below.stack("", ""); - /// let _ = Split::North.stack("", ""); - /// let _ = Split::South.stack("", ""); - /// let _ = Split::East.stack("", ""); - /// let _ = Split::West.stack("", ""); - /// ``` - pub const fn stack , B: Draw> (&self, a: A, b: B) -> impl Draw { - Pair(*self, a, b, PhantomData) - } - - /// ``` - /// use tengri::*; - /// let _ = Split::Above.half("", ""); - /// let _ = Split::Below.half("", ""); - /// let _ = Split::North.half("", ""); - /// let _ = Split::South.half("", ""); - /// let _ = Split::East.half("", ""); - /// let _ = Split::West.half("", ""); - /// ``` - pub const fn half , B: Draw> (&self, a: A, b: B) -> impl Draw { - draw(move|to: &mut S|{ - let (area_a, area_b) = to.xywh().split_half(self); - let (origin_a, origin_b) = self.origins(); - let (drawn_a, drawn_b) = draw_stacks(self, to, a, area_a, origin_a, b, area_b, origin_b)?; - Ok(stack_drawn(self, drawn_a, drawn_b)) - }) - } - - /// Newly split areas begin at the center of the split - /// to maintain centeredness in the user's field of view. - /// - /// Use [align] to override that and always start - /// at the top, bottom, etc. - /// - /// ``` - /// /* - /// - /// Split east: Split south: - /// | | | | A | - /// | <-A|B-> | |---------| - /// | | | | B | - /// - /// */ - /// ``` - const fn origins (&self) -> (Azimuth, Azimuth) { - use Azimuth::*; - match self { - Self::South => (S, N), - Self::East => (E, W), - Self::North => (N, S), - Self::West => (W, E), - Self::Above => (C, C), - Self::Below => (C, C), - } - } - - ///// ``` - ///// use tengri::*; - ///// let _ = Split::Below.iter([ - ///// "Leftbar" - ///// .min_w(10).max_w(15).align(Azimuth::NW), - ///// "Rightbar" - ///// .min_w(10).max_w(12).align(Azimuth::NE), - ///// "Center" - ///// .min_w(20).max_w(40).align(Azimuth::C), - ///// ].iter()); - ///// ``` - //pub fn iter > (&self, _: impl Iterator) { - //todo!() - //} - -} - -fn draw_stacks ( - split: &Split, - to: &mut S, - a: impl Draw, - area_a: impl Into>>, - origin_a: impl Into>, - b: impl Draw, - area_b: impl Into>>, - origin_b: impl Into>, -) -> Usually<(Option>, Option>)> { - let draw_a = |to: &mut S|Ok::<_, Box>(if let Some(origin_a) = origin_a.into() { - to.clip(area_a.into(), a.align(origin_a))? - } else { - to.clip(area_a.into(), a)? - }); - let draw_b = |to: &mut S|Ok::<_, Box>(if let Some(origin_b) = origin_b.into() { - to.clip(area_b.into(), b.align(origin_b))? - } else { - to.clip(area_b.into(), b)? - }); - Ok(if matches!(split, Split::Below) { - let drawn_b = draw_b(to)?; - let drawn_a = draw_a(to)?; - (drawn_a, drawn_b) - } else { - (draw_a(to)?, draw_b(to)?) - }) -} - -pub fn stack_areas ( - split: &Split, - area: XYWH, - a: &impl Draw, - b: &impl Draw, -) -> Usually<(Option>, Option>)> { - let area_a = a.layout(area)?; - Ok(match split { - Split::South => ( - area_a, - if let Some(used) = area_a { - b.layout(XYWH(area.x(), area.y() + used.h(), area.w(), area.h().minus(used.h())))? - } else { - None - } - ), - Split::East => ( - area_a, - if let Some(used) = area_a { - b.layout(XYWH(area.x() + used.w(), area.y(), area.w().minus(used.w()), area.h()))? - } else { - None - } - ), - Split::North => ( - if let Some(used) = area_a { - Some(XYWH(used.x(), (area.y() + area.h()).minus(used.h()), used.w(), used.h())) - } else { - None - }, - if let Some(used) = area_a { - b.layout(XYWH(area.x(), area.y(), area.w(), area.h().minus(used.h())))? - } else { - b.layout(area)?.map(|area_b|XYWH( - area_b.x(), area_b.y() + area_b.h(), area_b.w(), area_b.h() - )) - } - ), - Split::West => ( - if let Some(used) = area_a { - Some(XYWH((area.x() + area.w()).minus(used.w()), used.y(), used.w(), used.h())) - } else { - None - }, - if let Some(used) = area_a { - b.layout(XYWH(area.x(), area.y(), area.w().minus(used.w()), area.h()))? - } else { - b.layout(area)?.map(|area_b|XYWH( - area_b.x() + area_b.w(), area_b.y(), area_b.w(), area_b.h() - )) - } - ), - Split::Above | Split::Below => ( - area_a, - b.layout(area)?, - ), - }) -} - -fn stack_drawn ( - split: &Split, - drawn_a: Option>, - drawn_b: Option>, -) -> Option> { - if let (Some(XYWH(xa, ya, wa, ha)), Some(XYWH(xb, yb, wb, hb))) = (drawn_a, drawn_b) { - match split { - Split::South => Some(XYWH(xa.min(xb), ya, wa.max(wb), ha + hb)), - Split::East => Some(XYWH(xa, ya.min(yb), wa + wb, ha.max(hb))), - Split::North => Some(XYWH(xa.min(xb), yb, wa.max(wb), ha + hb)), - Split::West => Some(XYWH(xb, ya.min(yb), wa + wb, ha.max(hb))), - Split::Above | Split::Below => - Some(XYWH(xa.min(xb), ya.min(yb), wa.max(wb), ha.max(hb))), - } - } else if let Some(a) = drawn_a { - Some(a) - } else if let Some(b) = drawn_b { - Some(b) - } else { - None - } -} - -#[macro_export] macro_rules! north { - ($head:expr $(,)?) => { $head }; - ($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) }; -} - -#[macro_export] macro_rules! south { - ($head:expr $(,)?) => { $head }; - ($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) }; -} - -#[macro_export] macro_rules! east { - ($head:expr $(,)?) => { $head }; - ($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) }; -} - -#[macro_export] macro_rules! west { - ($head:expr $(,)?) => { $head }; - ($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) }; -} - -#[macro_export] macro_rules! above { - ($head:expr $(,)?) => { $head }; - ($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) }; -} - -#[macro_export] macro_rules! below { - ($head:expr $(,)?) => { $head }; - ($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) }; -} - -pub const fn east , B: Draw> (a: A, b: B) -> impl Draw { - Pair(Split::East, a, b, PhantomData) -} - -pub const fn north , B: Draw> (a: A, b: B) -> impl Draw { - Pair(Split::North, a, b, PhantomData) -} - -pub const fn west , B: Draw> (a: A, b: B) -> impl Draw { - Pair(Split::West, a, b, PhantomData) -} - -pub const fn south , B: Draw> (a: A, b: B) -> impl Draw { - Pair(Split::South, a, b, PhantomData) -} - -pub const fn above , B: Draw> (a: A, b: B) -> impl Draw { - Pair(Split::Above, a, b, PhantomData) -} - -pub const fn below , B: Draw> (a: A, b: B) -> impl Draw { - Pair(Split::Below, a, b, PhantomData) -} - -#[cfg(test)] mod test { - use crate::*; - - #[test] fn test_stack_areas () -> Usually<()> { - let area = XYWH(0u16, 0, 80, 25); - - assert_eq!(stack_areas(&Split::East, area, &"foo", &"bar")?, ( - Some(XYWH(0u16, 0, 3, 1)), - Some(XYWH(3u16, 0, 3, 1)), - )); - - assert_eq!(stack_areas(&Split::South, area, &"foo", &"bar")?, ( - Some(XYWH(0u16, 0, 3, 1)), - Some(XYWH(0u16, 1, 3, 1)), - )); - - Ok(()) - } - - #[test] fn test_split_stack () -> Usually<()> { - use tengri::{*, Split::*}; - - let stack = split(East, "foo", "bar"); - assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 1))); - - let stack = split(South, "foo", "bar"); - assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 3, 2))); - - let stack = split(South, split(East, "foo", "bar"), "baz"); - assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 2))); - - let stack = split(East, split(South, "foo", "bar"), "baz"); - assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 2))); - - Ok(()) - } -} diff --git a/src/lib.rs b/src/lib.rs index c706bc5..036b573 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,6 @@ pub extern crate unicode_width; #[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}, @@ -30,8 +29,10 @@ pub(crate) use ::{ std::marker::PhantomData }; +#[cfg(all(feature = "draw", feature = "eval"))] macro_rules! fn_kw_layout { ($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => { + #[cfg(all(feature = "draw", feature = "eval"))] pub fn $name ( $state: &S, $output: &mut O, $expr: &str ) -> Perhaps> where @@ -43,8 +44,10 @@ macro_rules! fn_kw_layout { } } +#[cfg(all(feature = "draw", feature = "eval", feature = "term"))] macro_rules! fn_kw_layout_tui { ($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => { + #[cfg(all(feature = "draw", feature = "eval", feature = "term"))] pub fn $name ( $state: &S, $output: &mut Tui, $expr: &str ) -> Perhaps> where @@ -346,7 +349,7 @@ macro_rules! eval_xy ( #[cfg(feature = "sing")] pub use self::sing::*; #[cfg(feature = "sing")] mod sing { use crate::{*, time::PerfModel}; - pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; + pub use ::jack::{*, contrib::{TimebaseInfo, ClosureProcessHandler, Position as JackPosition}}; pub use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*}; use ConnectName::*; use ConnectScope::*; @@ -1362,12 +1365,12 @@ macro_rules! eval_xy ( /// fn area (&self) -> XYWH { /// Default::default() /// } - /// fn clip ( + /// fn draw ( /// &mut self, /// area: impl Into>>, - /// draw: impl Draw + /// draw: Draw /// ) -> Perhaps> { - /// draw.to(self) + /// draw.draw(self) /// } /// } /// @@ -1380,8 +1383,20 @@ macro_rules! eval_xy ( type Unit: Coord; /// Get current clipping area fn area (&self) -> XYWH; - /// Set clipping area - fn clip ( + /// Set current clipping area + fn clip ( + &mut self, + area: impl Into>>, + draw: &impl Fn(&mut Self)->T + ) -> T; + /// Determine used area without drawing + fn size ( + &mut self, + area: impl Into>>, + draw: impl Draw + ) -> Perhaps>; + /// Draw + fn draw ( &mut self, area: impl Into>>, draw: impl Draw @@ -1401,12 +1416,12 @@ macro_rules! eval_xy ( ($(<$($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 + 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 + fn draw (&$self, $to: &mut $To) -> Perhaps::Unit>> $draw } } ); @@ -1426,16 +1441,13 @@ macro_rules! eval_xy ( /// use tengri::*; /// struct MyWidget(bool); /// impl Draw for MyWidget { - /// fn draw (self, to: &mut Tui) -> Perhaps> { + /// 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)) - } + fn draw (&self, to: &mut S) -> Drawn; } /// Emit a [Draw]able. @@ -1474,9 +1486,9 @@ macro_rules! eval_xy ( /// 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) + implPerhaps>> Draw for DrawThunk { + fn draw (&self, to: &mut T) -> Perhaps> { + to.clip(None, &self.0) } } @@ -1486,66 +1498,48 @@ macro_rules! eval_xy ( /// # use tengri::*; /// let _ = draw(|to: &mut Tui|Ok(Some(to.area()))); // draws nothing /// ``` - pub const fn draw Perhaps>> ( + 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 { + 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) + self.as_ref().map(|it|it.draw(to)).transpose().map(Option::unwrap_or_default) }); //impl> Draw for RwLock { - //fn draw (self, __: &mut S) -> Drawn { + //fn draw (&self, __: &mut S) -> Drawn { //todo!() //} //} //impl> Draw for Arc { - //fn draw (self, __: &mut T) -> Perhaps> { + //fn draw (&self, __: &mut T) -> Perhaps> { //todo!() //} //} - impl> Draw for &V { - fn draw (self, to: &mut T) -> Perhaps> { - self.view().draw(to) + impl> Draw for &V { + fn draw (&self, to: &mut T) -> Perhaps> { + (*self).draw(to) } } + //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; @@ -1750,232 +1744,7 @@ macro_rules! eval_xy ( } #[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")] mod layout; #[cfg(feature = "draw")] pub use self::color::*; #[cfg(feature = "draw")] mod color { @@ -2166,7 +1935,7 @@ macro_rules! eval_xy ( } } -#[cfg(feature = "draw")] pub use self::text::*; +#[cfg(feature = "text")] pub use self::text::*; #[cfg(feature = "text")] mod text { #![allow(unused)] @@ -2229,651 +1998,6 @@ macro_rules! eval_xy ( } } -#[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 AsRef> for Tui { - fn as_ref (&self) -> &XYWH { - match self { - Self::Draw(_, area) => area, - Self::Layout(area) => area - } - } - } - - impl Wide for Tui { - fn w (&self) -> u16 { self.as_ref().2 } - } - - impl Tall for Tui { - fn h (&self) -> u16 { self.as_ref().3 } - } - - impl Xy for Tui { - fn x (&self) -> u16 { self.as_ref().0 } - fn y (&self) -> u16 { self.as_ref().1 } - } - - impl HasOrigin for Tui { fn origin (&self) -> Azimuth { Azimuth::NW } } - - /// Terminal output. - pub enum Tui { - Draw ( - /// Ratatui buffer; area is screen size - Buffer, - /// Current draw area - XYWH - ), - Layout ( - 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::Draw(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 let Self::Draw(buffer, ..) = self { - if buffer.area != size { - back.clear_region(ClearType::All).unwrap(); - buffer.resize(size); - buffer.reset(); - } - } - } - pub fn redraw <'b, W: Write> ( - &'b mut self, - back: &mut CrosstermBackend, - mut next: &'b mut Self - ) { - if let Self::Draw(prev, ..) = self && let Self::Draw(next, ..) = next { - let updates = prev.diff(&next); - back.draw(updates.into_iter()).expect("failed to render"); - Backend::flush(back).expect("failed to flush output new"); - std::mem::swap(prev, next); - next.reset(); - } - } - pub fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH { - let XYWH(x0, y0, w, h) = self.area(); - if let Self::Draw(buffer, ..) = self { - for row in 0..h { - let y = y0 + row; - for col in 0..w { - let x = x0 + col; - if x < buffer.area.width && y < buffer.area.height { - if let Some(cell) = buffer.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