From a27dacff93438b633aaa8dc9b1bc41fa51c41974 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Sat, 1 Aug 2026 17:52:37 +0300 Subject: [PATCH] moar flat --- Cargo.toml | 3 +- examples/mode_00.rs | 6 +- examples/mode_01.rs | 5 +- examples/mode_02.rs | 16 +- src/color.rs | 0 src/coord.rs | 40 --- src/draw/color.rs | 0 src/draw/coord.rs | 1 - src/draw/lrtb.rs | 47 --- src/draw/sizer.rs | 31 -- src/draw/thunk.rs | 49 ---- src/draw/xywh.rs | 115 -------- src/exit.rs | 25 -- src/layout/align.rs | 6 +- src/layout/area.rs | 2 +- src/layout/exact.rs | 50 +++- src/layout/full.rs | 2 +- src/{draw => layout}/iter.rs | 19 +- src/layout/max.rs | 4 +- src/layout/min.rs | 2 +- src/{draw => layout}/split.rs | 193 +++++++----- src/lib.rs | 532 ++++++++++++++++++++++++++++------ src/origin.rs | 0 src/task.rs | 65 ----- src/term/border.rs | 8 +- src/term/colors.rs | 0 src/term/repeat.rs | 6 +- src/term/scroll.rs | 4 +- src/text.rs | 0 29 files changed, 649 insertions(+), 582 deletions(-) delete mode 100644 src/color.rs delete mode 100644 src/coord.rs delete mode 100644 src/draw/color.rs delete mode 100644 src/draw/coord.rs delete mode 100644 src/draw/lrtb.rs delete mode 100644 src/draw/sizer.rs delete mode 100644 src/draw/thunk.rs delete mode 100644 src/draw/xywh.rs delete mode 100644 src/exit.rs rename src/{draw => layout}/iter.rs (86%) rename src/{draw => layout}/split.rs (73%) delete mode 100644 src/origin.rs delete mode 100644 src/task.rs delete mode 100644 src/term/colors.rs delete mode 100644 src/text.rs diff --git a/Cargo.toml b/Cargo.toml index 2b7dc2c..4811fb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,12 @@ version = "0.15.0" description = "UI metaframework." [features] -default = ["lang", "sing", "midi", "draw", "play", "term", "text", "time", "rand", "okhsl", "eval"] +default = ["lang", "sing", "midi", "draw", "play", "term", "text", "time", "rand", "okhsl", "eval", "exit"] bumpalo = ["dep:bumpalo"] draw = [] gui = ["draw", "dep:winit"] eval = [] +exit = [] lang = ["dep:dizzle"] midi = ["dep:midly"] okhsl = ["dep:palette"] diff --git a/examples/mode_00.rs b/examples/mode_00.rs index 443028e..f53db74 100644 --- a/examples/mode_00.rs +++ b/examples/mode_00.rs @@ -2,7 +2,7 @@ use ::tengri::{ *, - lang::*, + dizzle::*, crossterm::event::{KeyEvent, Event::*, KeyCode::*}, ratatui::style::Color, }; @@ -13,8 +13,8 @@ tui_app!(State { }); tui_view!(self: State { - thunk(|to: &mut Tui|{ - let _ = "DEMO [MODE 00]".align_sw().draw(to); + draw(|to: &mut Tui|{ + let _ = "Demo [Mode 00]".align_sw().draw(to); let _ = ShowSize.align_se().draw(to); let _ = format!("Counter:\n{}", self.counter).align_c().draw(to); Ok(Some(to.area())) diff --git a/examples/mode_01.rs b/examples/mode_01.rs index 2324245..7392e15 100644 --- a/examples/mode_01.rs +++ b/examples/mode_01.rs @@ -1,9 +1,8 @@ //! Mode 01: Direct view, actions with history -use itertools::Itertools; use ::tengri::{ *, - lang::*, + dizzle::{*, itertools::Itertools}, crossterm::event::{Event::*, KeyEvent, KeyCode::*}, ratatui::style::Color, }; @@ -28,7 +27,7 @@ tui_keys!(self: State, input { }); tui_view!(self: State { - let title = "Demo Mode 00"; + let title = "Demo [Mode 01]"; let items = self.history.iter().take(10).map(|x|format!("{x:?}")).join("\n"); let history = format!("History: {}\n{items}", self.history.len()); let cursor = format!("Counter: {}", self.cursor); diff --git a/examples/mode_02.rs b/examples/mode_02.rs index 74c729c..e019313 100644 --- a/examples/mode_02.rs +++ b/examples/mode_02.rs @@ -2,7 +2,7 @@ use ::tengri::{ *, - lang::*, + dizzle::*, crossterm::event::{Event::*, KeyEvent, KeyCode::*}, ratatui::style::Color, }; @@ -20,11 +20,15 @@ tui_view!(self: State { let index = self.cursor + 1; let wh = (self.size.w(), self.size.h()); let src = VIEWS.get(self.cursor).unwrap_or(&""); - let heading = format!("State {}/{} in {:?}", index, VIEWS.len(), &wh); - let title = bg(Color::Rgb(60, 10, 10), heading.align_n().push_xy(2, 1)).exact_h(3); - let code = bg(Color::Rgb(10, 60, 10), format!("Source:\n{}", src).align_n().push_xy(2, 1)).min_h(4); - let widget = thunk(move|to: &mut Tui|self.interpret(to, &src)); - self.size.of(east(south(title, code).max_w(40), widget)) + let heading = format!("Demo [Mode 02]\nExample {}/{}\nSize {:?}", index, VIEWS.len(), &wh); + let title = bg(Color::Rgb(60, 10, 10), heading); + let code = bg(Color::Rgb(10, 60, 10), format!("Source:\n{}", src).max_h(10)); + let widget = draw(move|to: &mut Tui|self.interpret(to, &src)); // FIXME this forcefills + let sidebar = bg(Color::Rgb(20, 20, 20), north(code, title)).max_w(40); + let content = bg(Color::Rgb(64, 64, 64), widget); + east(sidebar, content) + //self.size.of(bg(Color::Rgb(10, 10, 10), south(east(sidebar, content), code)).full_w()) + //self.size.of(bg(Color::Rgb(10, 10, 10), east(sidebar, content)).full_w()) }); impl Interpret>> for State { diff --git a/src/color.rs b/src/color.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/coord.rs b/src/coord.rs deleted file mode 100644 index 6f0b611..0000000 --- a/src/coord.rs +++ /dev/null @@ -1,40 +0,0 @@ -use super::*; - -/// 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) } -} diff --git a/src/draw/color.rs b/src/draw/color.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/draw/coord.rs b/src/draw/coord.rs deleted file mode 100644 index 4563e55..0000000 --- a/src/draw/coord.rs +++ /dev/null @@ -1 +0,0 @@ -use super::*; diff --git a/src/draw/lrtb.rs b/src/draw/lrtb.rs deleted file mode 100644 index 7900a08..0000000 --- a/src/draw/lrtb.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::{*, layout::*, draw::*}; -use Azimuth::*; - -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!() - } -} diff --git a/src/draw/sizer.rs b/src/draw/sizer.rs deleted file mode 100644 index 7ff5ed5..0000000 --- a/src/draw/sizer.rs +++ /dev/null @@ -1,31 +0,0 @@ -use super::*; -use std::sync::atomic::Ordering; - -/// 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(Ordering::Relaxed) as u16 } - fn y (&self) -> u16 { self.1.load(Ordering::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 { - thunk(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) - }) - } -} diff --git a/src/draw/thunk.rs b/src/draw/thunk.rs deleted file mode 100644 index 3754fe8..0000000 --- a/src/draw/thunk.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::*; -use super::*; - -/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. -pub struct Thunk(pub F, std::marker::PhantomData); - -implPerhaps>> Draw for Thunk { - fn draw (self, to: &mut T) -> Perhaps> { - (self.0)(to) - } -} - -/// Basic [Draw]able closure. -/// -/// ``` -/// # use tengri::*; -/// # fn test () -> impl Draw { -/// thunk(|to: &mut Tui|Ok(Some(to.1))) -/// # } -/// ``` -pub const fn thunk Perhaps>> ( - draw: F -) -> Thunk { - Thunk(draw, std::marker::PhantomData) -} - -/// Only render when condition is true. -/// -/// ``` -/// # use tengri::*; -/// # fn test () -> impl Draw { -/// when(true, "Yes") -/// # } -/// ``` -pub const fn when (condition: bool, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut T|if condition { draw.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 { - thunk(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) }) -} diff --git a/src/draw/xywh.rs b/src/draw/xywh.rs deleted file mode 100644 index 5b308d1..0000000 --- a/src/draw/xywh.rs +++ /dev/null @@ -1,115 +0,0 @@ -use super::*; -use Split::*; - -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 {} diff --git a/src/exit.rs b/src/exit.rs deleted file mode 100644 index 76078fa..0000000 --- a/src/exit.rs +++ /dev/null @@ -1,25 +0,0 @@ -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 - } -} diff --git a/src/layout/align.rs b/src/layout/align.rs index faf6247..444d89a 100644 --- a/src/layout/align.rs +++ b/src/layout/align.rs @@ -8,11 +8,13 @@ pub struct Align( impl> Draw for Align { fn layout (&self, area: XYWH) -> Perhaps> { - let XYWH(x0, y0, w0, h0) = area; Ok(align::(area, self.1.layout(area)?, self.0)) } fn draw (self, to: &mut S) -> Perhaps> { - Ok(self.layout(to.area())?.map(|area|to.clip(area, |to|self.1.draw(to))).transpose()?.flatten()) + Ok(self.layout(to.area())? + .map(|area|to.clip(area, |to|self.1.draw(to))) + .transpose()? + .flatten()) } } diff --git a/src/layout/area.rs b/src/layout/area.rs index 16ee05d..b6d68da 100644 --- a/src/layout/area.rs +++ b/src/layout/area.rs @@ -1,4 +1,4 @@ -use crate::{*, draw::*}; +use crate::*; /// ``` /// use tengri::*; diff --git a/src/layout/exact.rs b/src/layout/exact.rs index c1d1299..e94f51d 100644 --- a/src/layout/exact.rs +++ b/src/layout/exact.rs @@ -1,4 +1,4 @@ -use crate::{*, draw::*}; +use crate::*; /// Set size of of drawing area. /// @@ -8,22 +8,42 @@ use crate::{*, draw::*}; /// let _ = "".exact_h(1); /// let _ = "".exact_wh(1, 1); /// ``` -pub enum Exact, X: Into>> { - __(PhantomData), +pub enum Exact, X: Into>> { + __(PhantomData), W(I, X), H(I, X), WH(I, X, X), } -impl_draw!(, X: Into>,>|self: Exact, to: T|{ - let area: XYWH = to.area(); - let (item, area) = match self { - Self::W(item, w1) => - (item, XYWH(area.0, area.1, w1.into().unwrap_or(area.2), area.3)), - Self::H(item, h1) => - (item, XYWH(area.0, area.1, area.2, h1.into().unwrap_or(area.3))), - Self::WH(item, w1, h1) => - (item, XYWH(area.0, area.1, w1.into().unwrap_or(area.2), h1.into().unwrap_or(area.3))), - _ => return Ok(None) + +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, |to|item.draw(to)) + } +} + +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!() }; - to.clip(area, |to|item.draw(to)) -}); + XYWH(area.0, area.1, w, h) +} diff --git a/src/layout/full.rs b/src/layout/full.rs index 28ce0c5..5dbd40f 100644 --- a/src/layout/full.rs +++ b/src/layout/full.rs @@ -1,4 +1,4 @@ -use crate::{*, draw::*}; +use crate::*; /// Use whole drawing area along one or both axes. /// diff --git a/src/draw/iter.rs b/src/layout/iter.rs similarity index 86% rename from src/draw/iter.rs rename to src/layout/iter.rs index c6c0d13..e1cb3d9 100644 --- a/src/draw/iter.rs +++ b/src/layout/iter.rs @@ -1,18 +1,17 @@ use super::*; -use crate::layout::*; /// Iterate over a collection of the same kind of [Draw]able: pub fn iter <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) + draw(move|_to: &mut S|{ todo!() }) } /// Iterate over a collection of the same kind of [Draw]able: pub fn iter_once <'a, S: Screen, D: 'a, U: Draw> ( _iter: impl Iterator, _draw: impl Fn(D, usize)->U, ) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) + draw(move|_to: &mut S|{ todo!() }) } /// Iterate over a collection of various [Draw]ables: @@ -23,43 +22,43 @@ pub fn iter_dyn < I: Iterator, // Type of the iterator F: Fn(&D, usize)->dyn Draw, // Function that returns [Draw]able from iterator item > (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) + draw(move|_to: &mut S|{ todo!() }) } pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) + draw(move|_to: &mut S|{ todo!() }) } pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) + draw(move|_to: &mut S|{ todo!() }) } pub fn iter_east_fixed <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _height: S::Unit, _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) + draw(move|_to: &mut S|{ todo!() }) } pub fn iter_south <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) + draw(move|_to: &mut S|{ todo!() }) } pub fn iter_south_fixed <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _height: S::Unit, _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) + draw(move|_to: &mut S|{ todo!() }) } pub fn iter_west <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) + draw(move|_to: &mut S|{ todo!() }) } pub fn row_south (south: S::Unit, height: S::Unit, content: impl Draw) diff --git a/src/layout/max.rs b/src/layout/max.rs index 035eabf..9fc1c40 100644 --- a/src/layout/max.rs +++ b/src/layout/max.rs @@ -1,4 +1,4 @@ -use crate::{*, draw::*}; +use crate::*; /// Set maximum size of of drawing area. /// @@ -50,6 +50,6 @@ impl, X: Into> + Copy> Draw for Max return Ok(None) }; - to.clip(area, draw(item)) + to.clip(area, |to|item.draw(to)) } } diff --git a/src/layout/min.rs b/src/layout/min.rs index 9f96684..d747253 100644 --- a/src/layout/min.rs +++ b/src/layout/min.rs @@ -1,4 +1,4 @@ -use crate::{*, draw::*}; +use crate::*; /// Only draw content if area is above a certain size. /// diff --git a/src/draw/split.rs b/src/layout/split.rs similarity index 73% rename from src/draw/split.rs rename to src/layout/split.rs index 62ca578..555c75a 100644 --- a/src/draw/split.rs +++ b/src/layout/split.rs @@ -1,58 +1,4 @@ -use super::{*, layout::*}; - -#[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 { - Split::East.half(a, b) -} - -pub const fn north , B: Draw> (a: A, b: B) -> impl Draw { - Split::North.half(a, b) -} - -pub const fn west , B: Draw> (a: A, b: B) -> impl Draw { - Split::West.half(a, b) -} - -pub const fn south , B: Draw> (a: A, b: B) -> impl Draw { - Split::South.half(a, b) -} - -pub const fn above , B: Draw> (a: A, b: B) -> impl Draw { - Split::Above.half(a, b) -} - -pub const fn below , B: Draw> (a: A, b: B) -> impl Draw { - Split::Below.half(a, b) -} +use super::*; /// Split along an axis. Direction determines order. #[cfg_attr(test, derive(Arbitrary))] @@ -65,6 +11,28 @@ pub const fn below , B: Draw> (a: A, b: B) -> impl Draw #[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 { /// ``` @@ -77,11 +45,7 @@ impl Split { /// let _ = Split::West.stack("", ""); /// ``` pub const fn stack , B: Draw> (&self, a: A, b: B) -> impl Draw { - thunk(move|to: &mut S|{ - let (area_a, area_b) = stack_areas(self, to.area(), &a, &b)?; - let (drawn_a, drawn_b) = draw_stacks(self, to, a, area_a, None, b, area_b, None)?; - Ok(stack_drawn(self, drawn_a, drawn_b)) - }) + Pair(*self, a, b, PhantomData) } /// ``` @@ -94,7 +58,7 @@ impl Split { /// let _ = Split::West.half("", ""); /// ``` pub const fn half , B: Draw> (&self, a: A, b: B) -> impl Draw { - thunk(move|to: &mut S|{ + 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)?; @@ -157,17 +121,22 @@ fn draw_stacks ( area_b: impl Into>>, origin_b: impl Into>, ) -> Usually<(Option>, Option>)> { - Ok(match split { - Split::Below => { - let drawn_b = to.clip(area_b.into(), |to|b.align(origin_b.into()).draw(to))?; - let drawn_a = to.clip(area_a.into(), |to|a.align(origin_a.into()).draw(to))?; - (drawn_a, drawn_b) - }, - _ => { - let drawn_a = to.clip(area_a.into(), |to|a.align(origin_a.into()).draw(to))?; - let drawn_b = to.clip(area_b.into(), |to|b.align(origin_b.into()).draw(to))?; - (drawn_a, drawn_b) - } + let draw_a = |to: &mut S|Ok::<_, Box>(if let Some(origin_a) = origin_a.into() { + to.clip(area_a.into(), |to|a.align(origin_a).draw(to))? + } else { + to.clip(area_a.into(), |to|a.draw(to))? + }); + let draw_b = |to: &mut S|Ok::<_, Box>(if let Some(origin_b) = origin_b.into() { + to.clip(area_b.into(), |to|b.align(origin_b).draw(to))? + } else { + to.clip(area_b.into(), |to|b.draw(to))? + }); + 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)?) }) } @@ -197,7 +166,7 @@ pub fn stack_areas ( ), Split::North => ( if let Some(used) = area_a { - Some(XYWH(used.x(), area.y() + used.h(), used.w(), used.h())) + Some(XYWH(used.x(), (area.y() + area.h()).minus(used.h()), used.w(), used.h())) } else { None }, @@ -211,7 +180,7 @@ pub fn stack_areas ( ), Split::West => ( if let Some(used) = area_a { - Some(XYWH(area.x() + used.w(), used.y(), used.w(), used.h())) + Some(XYWH((area.x() + area.w()).minus(used.w()), used.y(), used.w(), used.h())) } else { None }, @@ -253,18 +222,94 @@ fn stack_drawn ( } } +#[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 fadccdc..48415ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,6 @@ macro_rules! features { features! { "time": [ time ], - "play": [ exit, task ], "sing": [ sing ] } @@ -192,7 +191,7 @@ macro_rules! eval_xy ( ) => {{ // frags.next(): 2nd slash-delimited fragment: /x, /y, /xy let variant = $variant; - let thunk = thunk(move|screen|$state.interpret(screen, &$arg)); + let thunk = draw(move|screen|$state.interpret(screen, &$arg)); match variant { // X variant Some("x") => thunk.$x().draw($output), @@ -215,7 +214,7 @@ macro_rules! eval_xy ( ) => {{ // frags.next(): 2nd slash-delimited fragment: /x, /y, /xy let variant = $variant; - let thunk = thunk(move|screen|$state.interpret(screen, &match 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) @@ -236,9 +235,109 @@ macro_rules! eval_xy ( }}; ); +#[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. /// @@ -338,9 +437,82 @@ macro_rules! eval_xy ( } } - /// The opposite of [thunk]? - pub fn draw > (item: T) -> impl FnOnce(&mut S) -> Perhaps> { - move|to: &mut S|item.draw(to) + /// 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>; @@ -367,34 +539,168 @@ macro_rules! eval_xy ( //} //} - /// 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 { - () - } - } - impl> Draw for &V { fn draw (self, to: &mut T) -> Perhaps> { self.view().draw(to) } } - features! { - "draw": [ - iter, - lrtb, - sizer, - split, - thunk, - xywh - ] + 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!() + } } } @@ -444,7 +750,7 @@ macro_rules! eval_xy ( #[cfg(feature = "draw")] pub use self::layout::*; #[cfg(feature = "draw")] mod layout { - use crate::{*, draw::*}; + use crate::*; impl> Layout for T {} @@ -613,21 +919,66 @@ macro_rules! eval_xy ( #[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::{*, draw::*}; + use crate::*; use dizzle::LanguageError::*; use ::ratatui::style::Color; use ::rand::distributions::uniform::UniformSampler; @@ -814,8 +1165,10 @@ macro_rules! eval_xy ( } } -#[cfg(feature = "text")] -mod text { +#[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. @@ -878,10 +1231,7 @@ mod text { #[cfg(feature = "term")] pub use self::term::*; #[cfg(feature = "term")] mod term { use crate::*; - use crate::draw::*; - use crate::layout::*; use Color::*; - #[cfg(feature = "text")] use crate::text::*; #[macro_export] macro_rules! tui_app { ($Struct:ident { $($fields:tt)* }) => { @@ -982,6 +1332,7 @@ mod text { 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 @@ -989,6 +1340,7 @@ mod text { /// Current draw area pub XYWH ); + impl Tui { pub fn setup_panic () { use ::std::panic::{set_hook, PanicHookInfo}; @@ -1151,7 +1503,7 @@ mod text { /// } /// ``` pub fn catcher > (result: Usually) -> impl Draw { - thunk(move|to: &mut Tui|match result { + draw(move|to: &mut Tui|match result { Ok(content) => content.draw(to), Err(e) => { let err_fg = Color::Rgb(255,224,244); @@ -1166,7 +1518,7 @@ mod text { /// Interpret TUI-specific layout operation. /// /// ``` - /// use tengri::{*, lang::*, ratatui::prelude::Color}; + /// use tengri::{*, dizzle::*, ratatui::prelude::Color}; /// /// #[namespace(bool)] /// #[namespace(u8)] @@ -1233,10 +1585,7 @@ mod text { Some("fg") => { let arg0 = arg0?.expect("fg: expected arg 0 (color)"); if let Some(color) = Namespace::namespace(state, arg0)? { - fg(color, thunk(move|to: &mut Tui|{ - state.interpret(to, &arg1)?; - Ok(Some(to.area().into())) // FIXME?: don't max out the used area? - })).draw(to) + fg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(to) } else { return Err(format!("fg: {arg0:?}: not a color").into()) } @@ -1245,10 +1594,7 @@ mod text { Some("bg") => { let arg0 = arg0?.expect("bg: expected arg 0 (color)"); if let Some(color) = Namespace::namespace(state, arg0)? { - bg(color, thunk(move|to: &mut Tui|{ - state.interpret(to, &arg1)?; - Ok(Some(to.area().into())) // FIXME?: don't max out the used area? - })).draw(to) + bg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(to) } else { return Err(format!("bg: {arg0:?}: not a color").into()) } @@ -1310,6 +1656,7 @@ mod text { Ok(Some(XYWH(x0, y, string_width, 1))) } } + impl Screen for Tui { type Unit = u16; /// Render drawable in subarea specified by `area` @@ -1349,21 +1696,21 @@ mod text { } pub const fn fill_char (c: char) -> impl Draw { - thunk(move|to: &mut Tui|Ok(Some(to.update(&|cell,_,_|{ + draw(move|to: &mut Tui|Ok(Some(to.update(&|cell,_,_|{ cell.set_char(c); })))) } /// Draw contents with modifier applied. - pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut Tui|{ + pub const fn modify (on: bool, modifier: Modifier, item: impl Draw) -> impl Draw { + draw(move|to: &mut Tui|{ fill_mod(on, modifier).draw(to)?; - draw.draw(to) + item.draw(to) }) } pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw { - thunk(move|to: &mut Tui|Ok(Some({ + draw(move|to: &mut Tui|Ok(Some({ if on { to.update(&|cell,_,_|cell.modifier.insert(modifier)) } else { @@ -1373,8 +1720,8 @@ mod text { } /// Draw contents with bold modifier applied. - pub const fn bold (on: bool, draw: impl Draw) -> impl Draw { - modify(on, Modifier::BOLD, draw) + pub const fn bold (on: bool, item: impl Draw) -> impl Draw { + modify(on, Modifier::BOLD, item) } #[cfg(feature = "text")] @@ -1421,7 +1768,7 @@ mod text { layout_text_u16(self.as_ref(), area) } fn draw (self, to: &mut Tui) -> Drawn { - let XYWH(x, y, w, ..) = to.1; + let XYWH(x, y, w, ..) = to.area(); let mut width: u16 = 1; let mut chars = self.1.as_ref().chars(); while let Some(c) = chars.next() { @@ -1443,14 +1790,14 @@ mod text { #[cfg(feature = "text")] fn layout_text_u16 (text: &str, area: XYWH) -> Perhaps> { - let XYWH(x, y, ..) = area; + let XYWH(x, y, w, h) = area; let mut max_w = 0u16; let mut max_h = 0u16; for line in text.split("\n") { max_h += 1; max_w = max_w.max(line.len() as u16); } - Ok(Some(XYWH(x, y, max_w, max_h))) + Ok(Some(XYWH(x, y, w.min(max_w), h.min(max_h)))) } pub struct ShowSize; @@ -1467,31 +1814,56 @@ mod text { } } + pub struct ShowSizeOf(pub T); + + impl> Draw for ShowSizeOf { + fn layout (&self, area: XYWH) -> Perhaps> { + self.0.layout(area) + } + fn draw (self, to: &mut Tui) -> Drawn { + Ok(self.0.draw(to)?.map(|used|{ + let _ = to.text(&format!("{used:?}"), used.0, used.1, u16::MAX); + used + })) + } + } + /// Apply foreground color. - pub const fn fg (fg: Color, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut Tui|{ + pub const fn fg (fg: Color, item: impl Draw) -> impl Draw { + draw(move|to: &mut Tui|{ to.update(&|cell,_,_|{ cell.set_fg(fg); }); - draw.draw(to) + item.draw(to) }) } /// Apply background color. - pub const fn bg (bg: Color, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut Tui|{ - to.update(&|cell,_,_|{ cell.set_bg(bg); }); - draw.draw(to) - }) + pub const fn bg (bg: Color, item: impl Draw) -> impl Draw { + Background(bg, item) } - pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut Tui|{ + pub struct Background(Color, T); + + impl> Draw for Background { + fn layout (&self, area: XYWH) -> Drawn { + self.1.layout(area) + } + fn draw (self, to: &mut Tui) -> Drawn { + self.layout(to.area()).map(|area|to.clip(area, |to|{ + to.update(&|cell,_,_|{ cell.set_bg(self.0); }); + self.1.draw(to) + }))? + } + } + + pub const fn fg_bg (fg: Color, bg: Color, item: impl Draw) -> impl Draw { + draw(move|to: &mut Tui|{ to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); }); - draw.draw(to) + item.draw(to) }) } pub const fn fill_ul (color: Option) -> impl Draw { - thunk(move|to: &mut Tui|Ok(Some(if let Some(color) = color { + draw(move|to: &mut Tui|Ok(Some(if let Some(color) = color { to.update(&|cell,_,_|{ cell.modifier.insert(Modifier::UNDERLINED); cell.underline_color = color; @@ -1532,7 +1904,6 @@ mod text { mod buffer; pub use self::buffer::*; mod repeat; pub use self::repeat::*; mod scroll; pub use self::scroll::*; - mod colors; pub use self::colors::*; mod phat; pub use self::phat::*; mod button; pub use self::button::*; @@ -1553,7 +1924,7 @@ mod text { /// Interpret layout operation. /// /// ``` - /// # use tengri::{*, lang::*}; + /// # use tengri::{*, dizzle::*}; /// /// struct State {/*app-specific*/} /// impl<'b> Namespace<'b, u16> for State {} @@ -1598,29 +1969,28 @@ mod text { Some("when") => when( state.namespace(arg0?)?.unwrap(), - thunk(move|output: &mut O|{state.interpret(output, &arg1)}) + draw(move|output: &mut O|{state.interpret(output, &arg1)}) ).draw(output), Some("either") => either( state.namespace(arg0?)?.unwrap(), - thunk(move|output: &mut O|{state.interpret(output, &arg1)}), - thunk(move|output: &mut O|{state.interpret(output, &arg2)}), + draw(move|output: &mut O|{state.interpret(output, &arg1)}), + draw(move|output: &mut O|{state.interpret(output, &arg2)}), ).draw(output), Some("bsp") => eval_enum!("bsp", output, state, frags.next(), arg0, Split { "n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below }).stack( - thunk(move|output: &mut O|{state.interpret(output, &arg0)}), - thunk(move|output: &mut O|{state.interpret(output, &arg1)}), + draw(move|output: &mut O|{state.interpret(output, &arg0)}), + draw(move|output: &mut O|{state.interpret(output, &arg1)}), ).draw(output), - Some("align") => thunk(move|output: &mut O|{ - state.interpret(output, &arg0) - }).align(eval_enum!("align", output, state, frags.next(), arg0, 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), + Some("align") => draw(move|output: &mut O|{state.interpret(output, &arg0)}) + .align(eval_enum!("align", output, state, frags.next(), arg0, 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), Some("exact") => eval_xy!( "exact" => expr, head, output, state, frags.next(), exact_wh, exact_w, exact_h, arg0, arg1, arg2, diff --git a/src/origin.rs b/src/origin.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/task.rs b/src/task.rs deleted file mode 100644 index dc37898..0000000 --- a/src/task.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::{ - time::Duration, - sync::{Arc, atomic::{AtomicBool, Ordering::*}}, - thread::{Builder, JoinHandle, sleep}, -}; -#[cfg(feature = "term")] use ::crossterm::event::poll; -use crate::time::PerfModel; - -#[derive(Debug)] pub struct Task { - /// Exit flag. - pub exit: Arc, - /// Performance counter. - pub perf: Arc, - /// Use this to wait for the thread to finish. - pub join: JoinHandle<()>, -} - -impl Task { - /// Spawn a TUI thread that runs `callt least one, then repeats until `exit`. - pub fn new (exit: Arc, mut call: F) -> Result - where F: FnMut(&PerfModel)->() + Send + Sync + 'static - { - let perf = Arc::new(PerfModel::default()); - Ok(Self { - exit: exit.clone(), - perf: perf.clone(), - join: Builder::new().name("tengri tui output".into()).spawn(move || { - while !exit.fetch_and(true, Relaxed) { - let _ = perf.cycle(&mut call); - } - })?.into() - }) - } - - /// Spawn a thread that runs `call` least one, then repeats - /// until `exit`, sleeping for `time` msec after every iteration. - pub fn new_sleep ( - exit: Arc, time: Duration, mut call: F - ) -> Result - where F: FnMut(&PerfModel)->() + Send + Sync + 'static - { - Self::new(exit, move |perf| { - let _ = call(perf); - sleep(time); - }) - } - - /// Spawn a thread that uses [crossterm::event::poll] - /// to run `call` every `time` msec. - #[cfg(feature = "term")] pub fn new_poll ( - exit: Arc, time: Duration, mut call: F - ) -> Result - where F: FnMut(&PerfModel)->() + Send + Sync + 'static - { - Self::new(exit, move |perf| { - if poll(time).is_ok() { - let _ = call(perf); - } - }) - } - - pub fn join (self) -> Result<(), Box> { - self.join.join() - } -} diff --git a/src/term/border.rs b/src/term/border.rs index 6a07494..b38a5b7 100644 --- a/src/term/border.rs +++ b/src/term/border.rs @@ -5,9 +5,9 @@ use super::{*, draw::*}; /// ``` /// /// TODO /// ``` -pub fn border (on: bool, style: impl BorderStyle, draw: impl Draw) -> impl Draw { - let content = draw.pad_wh(1, 1); - let outline = when(on, thunk(move|to: &mut Tui|{ +pub fn border (on: bool, style: impl BorderStyle, item: impl Draw) -> impl Draw { + let content = item.pad_wh(1, 1); + let outline = when(on, draw(move|to: &mut Tui|{ let XYWH(x, y, w, h) = to.1; if w > 0 && h > 0 { to.blit(&style.border_nw(), x, y, style.style()); @@ -49,7 +49,7 @@ macro_rules! border { //impl Layout for $T {} impl Draw for $T { fn draw (self, to: &mut Tui) -> Perhaps> { - when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to).map(Some))).draw(to) + when(self.enabled(), draw(|to: &mut Tui|BorderStyle::draw(self, to).map(Some))).draw(to) } } )+} diff --git a/src/term/colors.rs b/src/term/colors.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/term/repeat.rs b/src/term/repeat.rs index bcfe42c..0e5b812 100644 --- a/src/term/repeat.rs +++ b/src/term/repeat.rs @@ -2,7 +2,7 @@ use super::{*, draw::*}; use ratatui::{prelude::{Position}}; pub const fn x_repeat (c: &str) -> impl Draw { - thunk(move|to: &mut Tui|{ + draw(move|to: &mut Tui|{ let XYWH(x, y, w, _h) = to.xywh(); for x in x..x+w { if let Some(cell) = to.0.cell_mut(Position::from((x, y))) { @@ -14,7 +14,7 @@ pub const fn x_repeat (c: &str) -> impl Draw { } pub const fn y_repeat (c: &str) -> impl Draw { - thunk(move|to: &mut Tui|{ + draw(move|to: &mut Tui|{ let XYWH(x, y, _w, h) = to.xywh(); for y in y..y+h { if let Some(cell) = to.0.cell_mut(Position::from((x, y))) { @@ -26,7 +26,7 @@ pub const fn y_repeat (c: &str) -> impl Draw { } pub const fn xy_repeat (c: &str) -> impl Draw { - thunk(move|to: &mut Tui|{ + draw(move|to: &mut Tui|{ let XYWH(x, y, w, h) = to.xywh(); let a = c.len(); for (_v, y) in (y..y+h).enumerate() { diff --git a/src/term/scroll.rs b/src/term/scroll.rs index 1e53655..83f761f 100644 --- a/src/term/scroll.rs +++ b/src/term/scroll.rs @@ -7,7 +7,7 @@ pub const ICON_DEC_H: &[char] = &[' ', '🞀', ' ']; pub const ICON_INC_H: &[char] = &[' ', '🞂', ' ']; pub fn x_scroll () -> impl Draw { - thunk(|Tui(buf, XYWH(x1, y1, w, _h)): &mut Tui|{ + draw(|Tui(buf, XYWH(x1, y1, w, _h)): &mut Tui|{ let x2 = *x1 + *w; for (i, x) in (*x1..=x2).enumerate() { if let Some(cell) = buf.cell_mut(Position::from((x, *y1))) { @@ -35,7 +35,7 @@ pub fn x_scroll () -> impl Draw { } pub fn y_scroll () -> impl Draw { - thunk(|Tui(buf, XYWH(x1, y1, _w, h)): &mut Tui|{ + draw(|Tui(buf, XYWH(x1, y1, _w, h)): &mut Tui|{ let y2 = *y1 + *h; for (i, y) in (*y1..=y2).enumerate() { if let Some(cell) = buf.cell_mut(Position::from((*x1, y))) { diff --git a/src/text.rs b/src/text.rs deleted file mode 100644 index e69de29..0000000