From 5d627f76696e4424f0375328af2868affcc013a5 Mon Sep 17 00:00:00 2001 From: same mf who else Date: Sat, 21 Mar 2026 15:30:25 +0200 Subject: [PATCH] all possible insanities simultaneously. but at least deleting more than i'm writing (hopefully!) --- src/.scratch.rs | 21 -- src/color.rs | 10 +- src/draw.rs | 971 ++++++++++++++++++++++++------------------------ src/term.rs | 618 +++++++++++++++--------------- src/text.rs | 63 +++- 5 files changed, 868 insertions(+), 815 deletions(-) diff --git a/src/.scratch.rs b/src/.scratch.rs index 1b68884..c07d40b 100644 --- a/src/.scratch.rs +++ b/src/.scratch.rs @@ -1439,25 +1439,4 @@ impl> MenuItem { ///// TUI helper defs. //impl Tui { - ////pub const fn null () -> Color { Color::Reset } - ////pub const fn red () -> Color { Color::Rgb(255,0, 0) } - ////pub const fn orange () -> Color { Color::Rgb(255,128,0) } - ////pub const fn yellow () -> Color { Color::Rgb(255,255,0) } - ////pub const fn brown () -> Color { Color::Rgb(128,255,0) } - ////pub const fn green () -> Color { Color::Rgb(0,255,0) } - ////pub const fn electric () -> Color { Color::Rgb(0,255,128) } - ////pub const fn g (g: u8) -> Color { Color::Rgb(g, g, g) } - ////fn bg0 () -> Color { Color::Rgb(20, 20, 20) } - ////fn bg () -> Color { Color::Rgb(28, 35, 25) } - ////fn border_bg () -> Color { Color::Rgb(40, 50, 30) } - ////fn border_fg (f: bool) -> Color { if f { Self::bo1() } else { Self::bo2() } } - ////fn title_fg (f: bool) -> Color { if f { Self::ti1() } else { Self::ti2() } } - ////fn separator_fg (_: bool) -> Color { Color::Rgb(0, 0, 0) } - ////fn mode_bg () -> Color { Color::Rgb(150, 160, 90) } - ////fn mode_fg () -> Color { Color::Rgb(255, 255, 255) } - ////fn status_bar_bg () -> Color { Color::Rgb(28, 35, 25) } - ////fn bo1 () -> Color { Color::Rgb(100, 110, 40) } - ////fn bo2 () -> Color { Color::Rgb(70, 80, 50) } - ////fn ti1 () -> Color { Color::Rgb(150, 160, 90) } - ////fn ti2 () -> Color { Color::Rgb(120, 130, 100) } //} diff --git a/src/color.rs b/src/color.rs index cab02c2..d1e7889 100644 --- a/src/color.rs +++ b/src/color.rs @@ -55,7 +55,15 @@ impl ItemColor { } } -pub struct ItemTheme {} +pub struct ItemTheme { + pub base: ItemColor, + pub light: ItemColor, + pub lighter: ItemColor, + pub lightest: ItemColor, + pub dark: ItemColor, + pub darker: ItemColor, + pub darkest: ItemColor, +} impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base)); impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base)); impl ItemTheme { diff --git a/src/draw.rs b/src/draw.rs index 323093e..8033666 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -5,9 +5,10 @@ use crate::{*, lang::*, color::*}; /// ``` /// use tengri::draw::*; /// struct TestScreen(bool); +/// impl Screen for TestScreen { type Unit = u16; } /// struct TestWidget(bool); /// impl Draw for TestWidget { -/// fn draw (&self, screen: &mut T) { +/// fn draw (&self, screen: &mut T) -> Usually> { /// screen.0 |= self.0; /// } /// } @@ -16,26 +17,47 @@ use crate::{*, lang::*, color::*}; /// TestWidget(true).draw(&mut screen); /// TestWidget(false).draw(&mut screen); /// ``` -pub trait Draw { - fn draw (&self, to: &mut T); +pub trait Draw { + fn draw (&self, to: &mut T) -> Usually>; } -impl Draw for () { - fn draw (&self, __: &mut T) {} +impl Draw for () { + fn draw (&self, __: &mut T) -> Usually> { + Ok(Default::default()) + } } -impl Draw for F { - fn draw (&self, to: &mut T) { self(to) } +impl> Draw for &D { + fn draw (&self, to: &mut T) -> Usually> { + (*self).draw(to) + } } -//impl> Draw for &D { - //fn draw (&self, to: &mut T) { (*self).draw(to) } -//} -impl> Draw for Arc { - fn draw (&self, to: &mut T) { (**self).draw(to) } +impl> Draw for Arc { + fn draw (&self, to: &mut T) -> Usually> { + (**self).draw(to) + } } -impl> Draw for RwLock { - fn draw (&self, to: &mut T) { self.read().unwrap().draw(to) } +impl> Draw for RwLock { + fn draw (&self, to: &mut T) -> Usually> { + self.read().unwrap().draw(to) + } } -impl> Draw for Option { - fn draw (&self, to: &mut T) { if let Some(draw) = self { draw.draw(to) } } +impl> Draw for Option { + fn draw (&self, to: &mut T) -> Usually> { + self.map(|draw|draw.draw(to)).transpose()?.flatten() + } +} + +/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. +pub struct ThunkUsually>>( + pub F, + std::marker::PhantomData +); +pub const fn thunk Usually>> (draw: F) -> Thunk { + Thunk(draw, std::marker::PhantomData) +} +implUsually>> Draw for Thunk { + fn draw (&self, to: &mut T) -> Usually> { + (self.0)(to) + } } /// Only render when condition is true. @@ -45,8 +67,8 @@ impl> Draw for Option { /// tengri::when(true, "Yes") /// # } /// ``` -pub fn when (condition: bool, draw: impl Draw) -> impl Draw { - move|to: &mut T|if condition { draw.draw(to); } +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. @@ -56,8 +78,449 @@ pub fn when (condition: bool, draw: impl Draw) -> impl Draw { /// tengri::either(true, "Yes", "No") /// # } /// ``` -pub fn either (condition: bool, a: impl Draw, b: impl Draw) -> impl Draw { - move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) } +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) }) +} + +/// Something that has `[0, 0]` at a particular point. +pub trait HasOrigin { + fn origin (&self) -> Origin; +} +impl> HasOrigin for T { + fn origin (&self) -> Origin { self.as_ref() } +} + +/// Where is [0, 0] located? +/// +/// ``` +/// use tengri::draw::Origin; +/// let _ = Origin::NW.align(()) +/// ``` +#[cfg_attr(test, derive(Arbitrary))] +#[derive(Debug, Copy, Clone, Default)] pub enum Origin { + #[default] C, X, Y, NW, N, NE, E, SE, S, SW, W +} +impl Origin { + pub fn align (&self, a: impl Draw) -> impl Draw { + align(*self, a) + } +} + +/// ``` +/// use tengri::draw::{align, Origin::*}; +/// let _ = align(NW, "test"); +/// let _ = align(SE, "test"); +/// ``` +pub fn align (origin: Origin, a: impl Draw) -> impl Draw { + thunk(move|to: &mut T| { todo!() }) +} + +/// A numeric type that can be used as coordinate. +/// +/// FIXME: Replace with `num` crate? +/// FIXME: Use AsRef/AsMut? +/// +/// ``` +/// use tengri::draw::Coord; +/// 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()) } +} + +/// A cardinal direction. +#[cfg_attr(test, derive(Arbitrary))] +#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split { + North, South, East, West, Above, #[default] Below +} + +pub const fn east (a: impl Draw, b: impl Draw) -> impl Draw { + Split::East.half(a, b) +} +pub const fn north (a: impl Draw, b: impl Draw) -> impl Draw { + Split::North.half(a, b) +} +pub const fn west (a: impl Draw, b: impl Draw) -> impl Draw { + Split::West.half(a, b) +} +pub const fn south (a: impl Draw, b: impl Draw) -> impl Draw { + Split::South.half(a, b) +} +pub const fn above (a: impl Draw, b: impl Draw) -> impl Draw { + Split::Above.half(a, b) +} +pub const fn below (a: impl Draw, b: impl Draw) -> impl Draw { + Split::Below.half(a, b) +} +impl Split { + /// ``` + /// use tengri::draw::Split::*; + /// let _ = Above.bsp((), ()); + /// let _ = Below.bsp((), ()); + /// let _ = North.bsp((), ()); + /// let _ = South.bsp((), ()); + /// let _ = East.bsp((), ()); + /// let _ = West.bsp((), ()); + /// ``` + pub const fn half (&self, a: impl Draw, b: impl Draw) -> impl Draw { + thunk(move|to: &mut T|{ + let (area_a, area_b) = to.xywh().split_half(self); + let (origin_a, origin_b) = self.origins(); + match self { + Self::Below => { + to.place(area_b, &origin_b.align(b)); + to.place(area_a, &origin_a.align(a)); + }, + _ => { + to.place(area_a, &origin_a.align(a)); + to.place(area_b, &origin_b.align(b)); + } + } + Ok(to.wh()) // FIXME: compute and return actually used area + }) + } + /// 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) -> (Origin, Origin) { + use Origin::*; + 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), + } + } + /// Iterate over a collection of renderables: + /// + /// ``` + /// use tengri::draw::{Origin::*, Split::*}; + /// let _ = Below.iter([ + /// NW.align(W(15).max(W(10).min("Leftbar"))), + /// NE.align(W(12).max(W(10).min("Rightbar"))), + /// Center.align(W(40).max(H(20.max("Center")))) + /// ].iter(), |x|x); + /// ``` + pub fn iter , F: Fn(U)->dyn Draw> ( + _items: impl Iterator, _cb: F + ) -> impl Draw { + thunk(move|_to: &mut T|{ todo!() }) + } +} + +/// Coordinate along horizontal axis. +#[derive(Copy, Clone, Debug, Default)] pub struct X(pub N); +impl X { + pub const fn push > (x: N, a: impl Draw) -> impl Draw { + a + } + pub const fn pull > (x: N, a: impl Draw) -> impl Draw { + a + } +} +#[derive(Copy, Clone, Debug, Default)] pub struct W(pub N); +impl W { + pub const fn max > (w: Option, a: impl Draw) -> impl Draw { + WH::max(w, None, a) + } + pub const fn min > (w: Option, a: impl Draw) -> impl Draw { + WH::min(w, None, a) + } + pub const fn exact > (w: T::Unit, c: impl Draw) -> impl Draw { + WH::exact(Some(w), None, c) + } + /// Shrink drawing area symmetrically. + /// + /// ``` + /// let padded = tengri::W(3).pad("Hello"); + /// ``` + pub const fn pad > (x: N, draw: impl Draw) + -> impl Draw + { + thunk(move|to: &mut T|draw.draw(todo!())) + } +} +#[derive(Copy, Clone, Debug, Default)] pub struct Y(pub N); +impl Y { + pub const fn push > (x: N, a: impl Draw) -> impl Draw { + a + } + pub const fn pull > (x: N, a: impl Draw) -> impl Draw { + a + } +} +#[derive(Copy, Clone, Debug, Default)] pub struct H(pub N); +impl H { + pub const fn max > (h: Option, a: impl Draw) -> impl Draw { + WH::max(None, h, a) + } + pub const fn min > (h: Option, a: impl Draw) -> impl Draw { + WH::min(None, h, a) + } + pub const fn exact > (h: T::Unit, c: impl Draw) -> impl Draw { + WH::exact(None, Some(h), c) + } + /// Shrink drawing area symmetrically. + /// + /// ``` + /// let padded = tengri::W::pad(3, "Hello"); + /// ``` + pub const fn pad > (x: N, draw: impl Draw) -> impl Draw { + thunk(move|to: &mut T|draw.draw(todo!())) + } +} +/// An origin point (X, Y). +/// +/// ``` +/// let xy = tengri::XY(0u16, 0); +/// ``` +#[cfg_attr(test, derive(Arbitrary))] #[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct XY(pub C, pub C); +impl HasX for XY { fn x (&self) -> X { X(self.0) } } +impl HasY for XY { fn y (&self) -> Y { Y(self.1) } } +impl XY { + pub const fn push > (&self, a: impl Draw) -> impl Draw { + a + } + pub const fn pull > (&self, a: impl Draw) -> impl Draw { + a + } +} +/// A size (Width, Height). +/// +/// ``` +/// let wh = tengri::WH(0u16, 0); +/// ``` +#[cfg_attr(test, derive(Arbitrary))] #[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct WH(pub C, pub C); +impl HasW for WH { fn w (&self) -> W { W(self.0) } } +impl HasH for WH { fn h (&self) -> H { H(self.1) } } +impl WH { + /// Shrink drawing area symmetrically. + /// + /// ``` + /// let padded = tengri::WH(3, 5).pad("Hello"); + /// ``` + pub const fn pad > (&self, draw: impl Draw) + -> impl Draw + { + thunk(move|to: &mut T|draw.draw(todo!())) + } + + /// Only draw content if area is above a certain size. + /// + /// ``` + /// let min = tengri::WH::min(3, 5, "Hello"); // 5x5 + /// ``` + pub const fn min > (w: Option, h: Option, draw: impl Draw) + -> impl Draw + { + thunk(move|to: &mut T|draw.draw(todo!())) + } + + /// Set the maximum width and/or height of the content. + /// + /// ``` + /// let max = tengri::WH::max(Some(3), Some(5), "Hello"); + /// ``` + pub const fn max > (w: Option, h: Option, draw: impl Draw) + -> impl Draw + { + thunk(move|to: &mut T|draw.draw(todo!())) + } + + /// Set the maximum width and/or height of the content. + /// + /// ``` + /// let exact = tengri::WH::exact(Some(3), Some(5), "Hello"); + /// ``` + pub const fn exact > (w: Option, h: Option, draw: impl Draw) + -> impl Draw + { + thunk(move|to: &mut T|draw.draw(todo!())) + } + + /// Limit size of drawing area + /// ``` + /// let clipped = tengri::WH::clip(Some(3), Some(5), "Hello"); + /// ``` + pub const fn clip > (w: Option, h: Option, draw: impl Draw) -> impl Draw { + thunk(move|to: &mut T|draw.draw(todo!())) + } +} +/// Point with size. +/// +/// ``` +/// let xywh = tengri::XYWH(0u16, 0, 0, 0); +/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(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 C, pub C, pub C, pub C); +impl HasX for XYWH { fn x (&self) -> X { X(self.0) } } +impl HasY for XYWH { fn y (&self) -> Y { Y(self.1) } } +impl HasW for XYWH { fn w (&self) -> W { W(self.2) } } +impl HasH for XYWH { fn h (&self) -> H { H(self.3) } } +impl XYWH { + pub fn zero () -> Self { + Self(0.into(), 0.into(), 0.into(), 0.into()) + } + pub fn center (&self) -> XY { + let Self(x, y, w, h) = *self; + XY(x.plus(w/2.into()), y.plus(h/2.into())) + } + pub fn centered (&self) -> XY { + let Self(x, y, w, h) = *self; + XY(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) { + use Split::*; + let XYWH(x, y, w, h) = self.xywh(); + match self { + South => (XYWH(x, y, w, h - h / 2), XYWH(x, y + h / 2, w, h / 2)), + East => (XYWH(x, y, w - w / 2, h), XYWH(x + w / 2, y, w / 2, h)), + North => (XYWH(x, y + h / 2, w, h - h / 2), XYWH(x, y, w, h / 2)), + West => (XYWH(x + w / 2, y, w - w / 2, h), XYWH(x, y, w / 2, h)), + Above | Below => (XYWH(x, y, w, h), XYWH(x, y, w, h)) + } + } +} + +pub trait HasXYWH: HasXY + HasWH { + fn xywh (&self) -> XYWH; +} +pub trait HasXY: HasX + HasY { + fn xy (&self) -> XY; +} +pub trait HasWH: HasX + HasY { + fn wh (&self) -> WH; +} +pub trait HasX { + fn x (&self) -> X; + fn iter_x (&self) -> impl Iterator where Self: HasW + HasOrigin { + self.x_west()..self.x_east() + } + fn x_west (&self) -> N where Self: HasW + HasOrigin { + use Origin::*; + 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: HasW + HasOrigin { + use Origin::*; + 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: HasW + HasOrigin { + todo!() + } +} +pub trait HasY { + fn y (&self) -> Y; + fn iter_y (&self) -> impl Iterator where Self: HasH + HasOrigin { + self.y_north()..self.y_south() + } + fn y_north (&self) -> N where Self: HasH + HasOrigin { + let a = self.origin(); + let h = self.h(); + use Origin::*; + 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: HasH + HasOrigin { + let a = self.origin(); + let h = self.h(); + use Origin::*; + 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: HasH + HasOrigin { + todo!() + } +} +pub trait HasW { + fn w (&self) -> W; + fn w_min (&self) -> W { self.w() } + fn w_max (&self) -> W { self.w() } +} +pub trait HasH { + fn h (&self) -> H; + fn h_min (&self) -> H { self.h() } + fn h_max (&self) -> H { self.h() } +} +impl>> HasX for T { + fn x (&self) -> X { *self.as_ref() } +} +impl>> HasY for T { + fn y (&self) -> Y { *self.as_ref() } +} +impl + HasY> HasXY for T { + fn xy (&self) -> XY { XY(self.x(), self.y()) } +} +impl + HasY> HasWH for T { + fn wh (&self) -> WH { WH(self.x(), self.h()) } +} +impl + HasWH> HasXYWH for T { + fn xywh (&self) -> XYWH { XYWH(self.x(), self.y(), self.w(), self.h()) } } /// Output target. @@ -69,198 +532,38 @@ pub fn either (condition: bool, a: impl Draw, b: impl Draw) -> impl Dr /// /// impl Screen for TestOut { /// type Unit = u16; -/// fn area (&self) -> impl TwoD { self.0 } -/// fn area_mut (&mut self) -> &mut impl TwoD { &mut self.0 } -/// fn show + ?Sized> (&mut self, area: impl TwoD, _: &T) { -/// println!("show: {area:?}"); +/// fn place + ?Sized> (&mut self, area: impl TwoD, _: &T) { +/// println!("place: {area:?}"); /// () /// } /// } /// /// impl Draw for String { -/// fn draw (&self, to: &mut TestOut) { +/// fn draw (&self, to: &mut TestOut) -> Usually> { /// to.area_mut().set_w(self.len() as u16); /// } /// } /// ``` -pub trait Screen: Area + Aligned + Send + Sync + Sized { +pub trait Screen: HasXYWH + HasOrigin + Send + Sync + Sized { type Unit: Coord; /// Render drawable in area specified by `area` - fn show <'t, T: Draw + ?Sized> (&mut self, area: impl Area, content: &'t T); - - fn clipped_mut (&mut self, w: Option, h: Option) - -> &mut Self; - fn padded_mut (&mut self, w: Self::Unit, h: Self::Unit) - -> &mut Self; -} -impl X for O { fn x (&self) -> O::Unit { self.x() } } -impl Y for O { fn y (&self) -> O::Unit { self.y() } } -impl W for O { fn w (&self) -> O::Unit { self.w() } } -impl H for O { fn h (&self) -> O::Unit { self.h() } } - -/// Something that has area in 2D space. -pub trait Area: W + H { - fn wh (&self) -> WH { - WH(self.w(), self.h()) + fn place <'t, T: Draw + ?Sized> (&mut self, area: impl HasWH, content: &'t T) { + unimplemented!() } - fn clip_w (&self, w: N) -> [N;2] { - [self.w().min(w), self.h()] - } - fn clip_h (&self, h: N) -> [N;2] { - [self.w(), self.h().min(h)] - } - fn at_least (&self, w: N, h: N) -> Usually<&Self> { - if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) } - Ok(self) - } - fn clip_wh (&self, w: Option, h: Option) -> [N;2] { - let w = w.map(|w|self.w().min(w)).unwrap_or(self.w()); - let h = h.map(|h|self.h().min(h)).unwrap_or(self.h()); - [w, h] - } -} - -/// Something that has length along horizontal axis. -pub trait W { - fn w (&self) -> N { N::zero() } - fn w_min (&self) -> N { self.w() } - fn w_max (&self) -> N { self.w() } -} -fn w_exact > (_w: N, _c: impl Draw) -> impl Draw { - move|_to: &mut T|{ todo!() } -} -fn w_fill > (_c: impl Draw) -> impl Draw { - move|_to: &mut T|{ todo!() } -} -fn w_min > (w: N, x: impl Draw) -> impl Draw { - min(Some(w), None, x) -} -fn w_max (w: N, x: impl Draw) -> impl Draw { - max(Some(w), None, x) -} - -/// Something that has length along vertical axis. -pub trait H { - fn h (&self) -> N { N::zero() } - fn h_min (&self) -> N { self.h() } - fn h_max (&self) -> N { self.h() } -} -fn h_exact > (_h: N, _c: impl Draw) -> impl Draw { - move|_to: &mut T|{ todo!() } -} -fn h_fill > (_c: impl Draw) -> impl Draw { - move|_to: &mut T|{ todo!() } -} -fn h_min > (h: N, x: impl Draw) -> impl Draw { - min(None, Some(h), x) -} -fn h_max (h: N, x: impl Draw) -> impl Draw { - max(None, Some(h), x) } /// Something that has a [Measure] of its rendered size. pub trait Measured {} -/// Point along (X, Y). -pub trait Point: X + Y { - fn xy (&self) -> XY { XY(self.x(), self.y()) } -} - -/// Something that has `[0, 0]` at a particular point. -pub trait Anchored { fn anchor (&self) -> Align; } - /// Something that has a bounding box /// /// ``` /// use tengri::{Bounded, XYWH}; /// let bounded: Bounded = Bounded(0, 0 ,0 ,0 ,""); /// ``` -pub trait Bounding : Point + Area + Anchored { - /// Iterate over every covered X coordinate. - fn iter_x (&self) -> impl Iterator where N: std::iter::Step { - self.x_left()..self.x_right() - } - /// Iterate over every covered Y coordinate. - fn iter_y (&self) -> impl Iterator where N: std::iter::Step { - self.y_top()..self.y_bottom() - } - fn x_left (&self) -> N { - use Align::*; - let w = self.w(); - let a = self.anchor(); - 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_right (&self) -> N { - use Align::*; - let w = self.w(); - let a = self.anchor(); - 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 y_top (&self) -> N { - use Align::*; - let a = self.anchor(); - 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_bottom (&self) -> N { - use Align::*; - let a = self.anchor(); - 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) - } +pub trait Bounding : HasXY + HasWH + HasOrigin { } -/// A cardinal direction. -/// -/// ``` -/// let direction = tengri::Direction::Above; -/// ``` -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction { - North, South, East, West, Above, Below -} - -/// A numeric type that can be used as coordinate. -/// -/// FIXME: Replace with `num` crate? -/// FIXME: Use AsRef/AsMut? -pub trait Coord: Send + Sync + Copy - + Add - + Sub - + Mul - + Div - + Ord + PartialEq + Eq - + Debug + Display + Default - + From + Into - + Into - + Into -{ - fn zero () -> Self { 0.into() } - fn plus (self, other: Self) -> Self; - /// Saturating subtraction - fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } } - fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) } -} - -/// Point along horizontal axis. -pub trait X { - fn x (&self) -> N { N::zero() } - fn push (a: impl Draw) -> impl Draw { a } - fn pull (a: impl Draw) -> impl Draw { a } -} - -/// Point along vertical axis. -pub trait Y { - fn y (&self) -> N { N::zero() } - fn push (a: impl Draw) -> impl Draw { a } - fn pull (a: impl Draw) -> impl Draw { a } -} - - //impl> Draw for Bounded { //fn draw (&self, to: &mut O) { //let area = to.area(); @@ -270,296 +573,4 @@ pub trait Y { //} //} -/// A point (X, Y). -/// -/// ``` -/// let xy = tengri::XY(0u16, 0); -/// ``` -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct XY(pub C, pub C); -impl X for XY { fn x (&self) -> N { self.0 } } -impl Y for XY { fn y (&self) -> N { self.1 } } - -/// A size (Width, Height). -/// -/// ``` -/// let wh = tengri::WH(0u16, 0); -/// ``` -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct WH(pub C, pub C); -impl W for WH { fn w (&self) -> N { self.0 } } -impl H for WH { fn h (&self) -> N { self.1 } } -impl WH { - fn min > (&self, x: impl Draw) -> impl Draw { - min(Some(self.w()), Some(self.h()), x) - } - fn exact > (&self, c: impl Draw) -> impl Draw { - move|_to: &mut T|{ todo!() } - } - fn fill > (c: impl Draw) -> impl Draw { - move|_to: &mut T|{ todo!() } - } -} - -/// Point with size. -/// -/// ``` -/// let xywh = tengri::XYWH(0u16, 0, 0, 0); -/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(20, 20)); -/// ``` -/// -/// * [ ] TODO: anchor field (determines at which corner/side is X0 Y0) -/// -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH( - pub C, pub C, pub C, pub C -); -impl X for XYWH { fn x (&self) -> N { self.0 } } -impl Y for XYWH { fn y (&self) -> N { self.1 } } -impl W for XYWH { fn w (&self) -> N { self.0 } } -impl H for XYWH { fn h (&self) -> N { self.1 } } - -impl XYWH { - pub fn zero () -> Self { - Self(0.into(), 0.into(), 0.into(), 0.into()) - } - pub fn clipped_w (&self, w: N) -> XYWH { - Self(self.x(), self.y(), self.w().min(w), self.h()) - } - pub fn clipped_h (&self, h: N) -> XYWH { - Self(self.x(), self.y(), self.w(), self.h().min(h)) - } - pub fn clipped (&self, wh: WH) -> XYWH { - Self(self.x(), self.y(), wh.w(), wh.h()) - } - pub fn center (&self) -> XY { - XY(self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())) - } - pub fn centered (&self) -> XY { - let Self(x, y, w, h) = *self; - XY(x.minus(w/2.into()), y.minus(h/2.into())) - } - pub fn centered_x (&self, n: N) -> XYWH { - let Self(x, y, w, h) = *self; - XYWH((x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()) - } - pub fn centered_y (&self, n: N) -> XYWH { - let Self(x, y, w, h) = *self; - XYWH(x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n) - } - pub fn centered_xy (&self, [n, m]: [N;2]) -> XYWH { - let Self(x, y, w, h) = *self; - XYWH((x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m) - } -} - - -/// Set maximum width and/or height of drawing area. -pub fn clip , N: Coord> (w: Option, h: Option, draw: impl Draw) -> impl Draw { - move|to: &mut T|draw.draw(to.clipped_mut(w, h)) -} - -/// Shrink drawing area symmetrically. -pub fn pad , N: Coord> (w: N, h: N, draw: impl Draw) -> impl Draw { - move|to: &mut T|draw.draw(to.padded_mut(w, h)) -} -pub fn pad_x , N: Coord> (w: N, draw: impl Draw) -> impl Draw { - pad(w, N::zero(), draw) -} -pub fn pad_y , N: Coord> (h: N, draw: impl Draw) -> impl Draw { - pad(N::zero(), h, draw) -} -pub fn pad_xy , N: Coord> (p: N, draw: impl Draw) -> impl Draw { - pad(p, p, draw) -} - -/// 9th of area to place. -/// -/// ``` -/// let alignment = tengri::Align::C; -/// use ::tengri::*; -/// let area = XYWH(10u16, 10, 20, 20); -/// fn test (area: XYWH, item: &impl Draw, expected: [u16;4]) { -/// //assert_eq!(Lay::layout(item, area), expected); -/// //assert_eq!(Draw::layout(item, area), expected); -/// }; -/// -/// let four = exact_xy(4, 4, "foo"); -/// test(area, &Align::nw(four()), [10, 10, 4, 4]); -/// test(area, &Align::n(four()), [18, 10, 4, 4]); -/// test(area, &Align::ne(four()), [26, 10, 4, 4]); -/// test(area, &Align::e(four()), [26, 18, 4, 4]); -/// test(area, &Align::se(four()), [26, 26, 4, 4]); -/// test(area, &Align::s(four()), [18, 26, 4, 4]); -/// test(area, &Align::sw(four()), [10, 26, 4, 4]); -/// test(area, &Align::w(four()), [10, 18, 4, 4]); -/// -/// let two_by_four = exact_xy(4, 2, "foo"); -/// test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]); -/// test(area, &Align::n(two_by_four()), [18, 10, 4, 2]); -/// test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]); -/// test(area, &Align::e(two_by_four()), [26, 19, 4, 2]); -/// test(area, &Align::se(two_by_four()), [26, 28, 4, 2]); -/// test(area, &Align::s(two_by_four()), [18, 28, 4, 2]); -/// test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]); -/// test(area, &Align::w(two_by_four()), [10, 19, 4, 2]); -/// pub struct Align(pub Align, pub T); -/// ``` -#[cfg_attr(test, derive(Arbitrary))] -#[derive(Debug, Copy, Clone, Default)] pub enum Align { - #[default] C, X, Y, NW, N, NE, E, SE, S, SW, W -} -pub trait Aligned { - fn aligned (&mut self, align: Align) -> &mut Self; -} -/// Set 0, 0 of drawing subarea. -pub fn align (alignment: Align, draw: impl Draw) -> impl Draw { - move|to: &mut T|draw.draw(to.aligned(alignment)) -} -impl Align { - pub fn n (x: impl Draw) -> impl Draw { align(Align::N, x) } - pub fn s (x: impl Draw) -> impl Draw { align(Align::S, x) } - pub fn e (x: impl Draw) -> impl Draw { align(Align::E, x) } - pub fn w (x: impl Draw) -> impl Draw { align(Align::W, x) } - pub fn nw (x: impl Draw) -> impl Draw { align(Align::NW, x) } - pub fn ne (x: impl Draw) -> impl Draw { align(Align::NE, x) } - pub fn sw (x: impl Draw) -> impl Draw { align(Align::SW, x) } - pub fn se (x: impl Draw) -> impl Draw { align(Align::SE, x) } - pub fn c (x: impl Draw) -> impl Draw { align(Align::C, x) } -} - -/// Only draw content if area is above a certain size. -/// -/// ``` -/// let minimum = tengri::min_wh(3, 5, "Hello"); // 5x5 -/// ``` -pub fn min (w: Option, h: Option, draw: impl Draw) -> impl Draw { - move|to: &mut T| { todo!() }//draw.draw(to.min(w, h)) -} - -/// Set the maximum width and/or height of the content. -/// -/// ``` -/// let maximum = tengri::max_wh(3, 5, "Hello"); // 3x1 -/// ``` -pub fn max (w: Option, h: Option, draw: impl Draw) -> impl Draw { - move|to: &mut T| { todo!() }// { todo!() }//draw.draw(to.max(w, h)) -} - -// pub fn displace ... - -pub fn iter , F: Fn(U)->dyn Draw> ( - _items: impl Iterator, _cb: F -) -> impl Draw { - move|_to: &mut T|{ todo!() } -} - -/// Split screen between two items, or layer them atop each other. -/// -/// ``` -/// use tengri::Direction::*; -/// let _ = tengri::draw::bsp(Above, (), ()); -/// let _ = tengri::draw::bsp(Below, (), ()); -/// let _ = tengri::draw::bsp(North, (), ()); -/// let _ = tengri::draw::bsp(South, (), ()); -/// let _ = tengri::draw::bsp(East, (), ()); -/// let _ = tengri::draw::bsp(West, (), ()); -/// ``` -pub fn bsp (_dir: Direction, a: impl Draw, b: impl Draw) -> impl Draw { - move|_to: &mut T|{ todo!() } -} -pub fn bsp_n (a: impl Draw, b: impl Draw) -> impl Draw { - bsp(Direction::North, a, b) -} -pub fn bsp_s (a: impl Draw, b: impl Draw) -> impl Draw { - bsp(Direction::South, a, b) -} -pub fn bsp_e (a: impl Draw, b: impl Draw) -> impl Draw { - bsp(Direction::East, a, b) -} -pub fn bsp_w (a: impl Draw, b: impl Draw) -> impl Draw { - bsp(Direction::West, a, b) -} -pub fn bsp_a (a: impl Draw, b: impl Draw) -> impl Draw { - bsp(Direction::Above, a, b) -} -pub fn bsp_b (a: impl Draw, b: impl Draw) -> impl Draw { - bsp(Direction::Below, a, b) -} - -/// Clear a pre-allocated buffer, then write into it. -#[macro_export] macro_rules! rewrite { - ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } -} - -/// FIXME: This macro should be some variant of `eval`, too. -/// But taking into account the different signatures (resolving them into 1?) -#[cfg(feature = "lang")] #[macro_export] macro_rules! draw { - ($State:ident: $Output:ident: $layers:expr) => { - impl Draw<$Output> for $State { - fn draw (&self, to: &mut $Output) { - for layer in $layers { layer(self, to) } - } - } - } -} - -/// FIXME: This is generic: should be called `eval` and be part of [dizzle]. -#[cfg(feature = "lang")] #[macro_export] macro_rules! view { - ($State:ident: $Output:ident: $namespaces:expr) => { - impl Understand<$Output, ()> for $State { - fn understand_expr <'a> (&'a self, to: &mut $Output, expr: &'a impl Expression) -> Usually<()> { - for namespace in $namespaces { if namespace(self, to, expr)? { return Ok(()) } } - Err(format!("{}::<{}, ()>::understand_expr: unexpected: {expr:?}", - stringify! { $State }, - stringify! { $Output }).into()) - } - } - } -} - -/// Stack things on top of each other, -#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = bsp_b(bsp, $expr);)* bsp -}}); - -/// Stack southward. -#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = bsp_s(bsp, $expr);)* bsp -}}); - -/// Stack northward. -#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = bsp_n(bsp, $expr);)* bsp -}}); - -/// Stack eastward. -#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = bsp_e(bsp, $expr);)* bsp -}}); - -/// Memoize a rendering. -/// -/// ``` -/// let _ = tengri::Memo::new((), ()); -/// ``` -#[derive(Debug, Default)] pub struct Memo { - pub value: T, - pub view: Arc> -} - -impl Memo { - pub fn new (value: T, view: U) -> Self { - Self { value, view: Arc::new(view.into()) } - } - pub fn update (&mut self, newval: T, draw: impl Fn(&mut U, &T, &T)->R) -> Option { - if newval != self.value { - let result = draw(&mut*self.view.write().unwrap(), &newval, &self.value); - self.value = newval; - return Some(result); - } - None - } -} +// pub fn displace ... diff --git a/src/term.rs b/src/term.rs index d88cc8c..809ec2c 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,4 +1,4 @@ -use crate::{*, lang::*, play::*, draw::*, color::*, text::*}; +use crate::{*, lang::*, play::*, draw::{*, Split::*}, color::*, text::*}; use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; use rand::distributions::uniform::UniformSampler; use ::{ @@ -8,149 +8,140 @@ use ::{ }, better_panic::{Settings, Verbosity}, ratatui::{ - prelude::{Color, Style, Buffer, Position, Backend}, + prelude::{Style, Buffer as ScreenBuffer, Position, Backend, Color}, style::{Modifier, Color::*}, backend::{CrosstermBackend, ClearType}, layout::{Size, Rect}, - buffer::Cell + buffer::{Buffer, Cell}, }, crossterm::{ + ExecutableCommand, terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, } }; -/// Marker trait for structs that may be root of TUI app. -pub trait Tui: Draw + Do> {} - -/// `Tui` is automatically implemented. -impl + Do>> Tui for T {} - -/// Spawn the TUI input thread which reads keys from the terminal. -pub fn tui_input + Send + Sync + 'static> ( - exited: &Arc, state: &Arc>, poll: Duration -) -> Result { - let exited = exited.clone(); - let state = state.clone(); - Thread::new_poll(exited.clone(), poll, move |_| { - let event = read().unwrap(); - match event { - - // Hardcoded exit. - Event::Key(KeyEvent { - modifiers: KeyModifiers::CONTROL, - code: KeyCode::Char('c'), - kind: KeyEventKind::Press, - state: KeyEventState::NONE - }) => { exited.store(true, Relaxed); }, - - // Handle all other events by the state: - _ => { - let event = TuiEvent::from_crossterm(event); - if let Err(e) = state.write().unwrap().handle(&event) { - panic!("{e}") - } - } +pub struct Tui(pub Buffer, pub XYWH); +impl Screen for Tui { type Unit = u16; } +impl HasX for Tui { fn x (&self) -> X { self.1.x() } } +impl HasY for Tui { fn y (&self) -> Y { self.1.y() } } +impl HasW for Tui { fn w (&self) -> W { self.1.w() } } +impl HasH for Tui { fn h (&self) -> H { self.1.h() } } +impl HasOrigin for Tui { fn origin (&self) -> Origin { Origin::NW } } +impl AsRef for Tui { fn as_ref (&self) -> &Buffer { &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 { &self.0 } } +impl AsMut> for Tui { fn as_mut (&mut self) -> &mut XYWH { &mut self.0 } } +impl Tui { + fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> WH { + tui_update(self.0, self.1, callback); + self.1.wh() + } + fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) { + for cell in self.buffer().content.iter_mut() { + cell.fg = fg; + cell.bg = bg; + cell.modifier = modifier; + } + } + fn blit (&mut self, text: &impl AsRef, x: u16, y: u16, style: Option