diff --git a/dizzle b/dizzle index 192a1d8..44b2be5 160000 --- a/dizzle +++ b/dizzle @@ -1 +1 @@ -Subproject commit 192a1d8257a9f2ad43ebceacb7b5ca348c601471 +Subproject commit 44b2be57ca8d1f69a95f2eb02f4b5474ec77ac0a diff --git a/src/.scratch.rs b/src/.scratch.rs index c07d40b..1b68884 100644 --- a/src/.scratch.rs +++ b/src/.scratch.rs @@ -1439,4 +1439,25 @@ 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 50f1eed..cab02c2 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,10 +1,6 @@ use ::ratatui::style::Color; use crate::lang::impl_from; -pub(crate) use ::palette::{ - Okhsl, Srgb, OklabHue, Mix, okhsl::UniformOkhsl, - convert::{FromColor, FromColorUnclamped} -}; -use rand::distributions::uniform::UniformSampler; +pub(crate) use ::palette::{Okhsl, Srgb, OklabHue, okhsl::UniformOkhsl}; pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor { todo!(); } @@ -30,17 +26,13 @@ pub trait HasColor { fn color (&self) -> ItemColor; } } } -#[derive(Copy, Clone, Debug, Default)] -pub struct ItemColor { - term: Color, - okhsl: Okhsl -} -impl_from!(ItemColor: |term: Color| Self { term, okhsl: rgb_to_okhsl(term) }); -impl_from!(ItemColor: |okhsl: Okhsl| Self { okhsl, term: okhsl_to_rgb(okhsl) }); +pub struct ItemColor {} +impl_from!(ItemColor: |rgb: Color| Self { rgb, okhsl: rgb_to_okhsl(rgb) }); +impl_from!(ItemColor: |okhsl: Okhsl| Self { okhsl, rgb: okhsl_to_rgb(okhsl) }); // A single color within item theme parameters, in OKHSL and RGB representations. impl ItemColor { - #[cfg(feature = "term")] pub const fn from_tui (term: Color) -> Self { - Self { term, okhsl: Okhsl::new_const(OklabHue::new(0.0), 0.0, 0.0) } + #[cfg(feature = "tui")] pub const fn from_tui (rgb: Color) -> Self { + Self { rgb, okhsl: Okhsl::new_const(OklabHue::new(0.0), 0.0, 0.0) } } pub fn random () -> Self { let mut rng = ::rand::thread_rng(); @@ -63,20 +55,12 @@ impl ItemColor { } } -pub struct ItemTheme { - pub base: ItemColor, - pub light: ItemColor, - pub lighter: ItemColor, - pub lightest: ItemColor, - pub dark: ItemColor, - pub darker: ItemColor, - pub darkest: ItemColor, -} +pub struct ItemTheme {} impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base)); impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base)); impl ItemTheme { - #[cfg(feature = "term")] pub const G: [Self;256] = { - let mut builder = dizzle::konst::array::ArrayBuilder::new(); + #[cfg(feature = "tui")] pub const G: [Self;256] = { + let mut builder = konst::array::ArrayBuilder::new(); while !builder.is_full() { let index = builder.len() as u8; let light = (index as f64 * 1.15) as u8; @@ -104,7 +88,7 @@ impl ItemTheme { pub const G00: Self = { let color: ItemColor = ItemColor { okhsl: Okhsl { hue: OklabHue::new(0.0), lightness: 0.0, saturation: 0.0 }, - term: Color::Rgb(0, 0, 0) + rgb: Color::Rgb(0, 0, 0) }; Self { base: color, @@ -116,7 +100,7 @@ impl ItemTheme { darkest: color, } }; - #[cfg(feature = "term")] pub fn from_tui_color (base: Color) -> Self { + #[cfg(feature = "tui")] pub fn from_tui_color (base: Color) -> Self { Self::from_item_color(ItemColor::from_tui(base)) } pub fn from_item_color (base: ItemColor) -> Self { diff --git a/src/draw.rs b/src/draw.rs index dd252db..323093e 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,14 +1,13 @@ -use crate::{*, lang::*, color::*, space::*}; +use crate::{*, lang::*, color::*}; /// Drawable that supports dynamic dispatch. -/// +/// /// ``` /// 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) -> Usually> { +/// fn draw (&self, screen: &mut T) { /// screen.0 |= self.0; /// } /// } @@ -17,32 +16,26 @@ use crate::{*, lang::*, color::*, space::*}; /// TestWidget(true).draw(&mut screen); /// TestWidget(false).draw(&mut screen); /// ``` -pub trait Draw { - fn draw (self, to: &mut T) -> Usually>; +pub trait Draw { + fn draw (&self, to: &mut T); } -impl Draw for () { - fn draw (self, __: &mut T) -> Usually> { - Ok(Default::default()) - } +impl Draw for () { + fn draw (&self, __: &mut T) {} } -impl> Draw for Option { - fn draw (self, to: &mut T) -> Usually> { - Ok(self.map(|draw|draw.draw(to)).transpose()?.unwrap_or_default()) - } +impl Draw for F { + fn draw (&self, to: &mut T) { self(to) } } - -/// 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) +//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) } } -implUsually>> Draw for Thunk { - fn draw (self, to: &mut T) -> Usually> { - (self.0)(to) - } +impl> Draw for RwLock { + fn draw (&self, to: &mut T) { self.read().unwrap().draw(to) } +} +impl> Draw for Option { + fn draw (&self, to: &mut T) { if let Some(draw) = self { draw.draw(to) } } } /// Only render when condition is true. @@ -52,8 +45,8 @@ implUsually>> Draw for Thunk (condition: bool, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut T|if condition { draw.draw(to) } else { Ok(Default::default()) }) +pub fn when (condition: bool, draw: impl Draw) -> impl Draw { + move|to: &mut T|if condition { draw.draw(to); } } /// Render one thing if a condition is true and another false. @@ -63,8 +56,8 @@ pub const fn when (condition: bool, draw: impl Draw) -> impl Draw /// tengri::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) }) +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) } } /// Output target. @@ -76,25 +69,497 @@ pub const fn either (condition: bool, a: impl Draw, b: impl Draw< /// /// impl Screen for TestOut { /// type Unit = u16; -/// fn place + ?Sized> (&mut self, area: impl TwoD, _: &T) { -/// println!("place: {area:?}"); +/// 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:?}"); /// () /// } /// } /// /// impl Draw for String { -/// fn draw (&self, to: &mut TestOut) -> Usually> { +/// fn draw (&self, to: &mut TestOut) { /// to.area_mut().set_w(self.len() as u16); /// } /// } /// ``` -pub trait Screen: Space + Send + Sync + Sized { +pub trait Screen: Area + Aligned + Send + Sync + Sized { type Unit: Coord; /// Render drawable in area specified by `area` - fn place <'t, T: Draw + ?Sized> ( - &mut self, content: &'t T, area: Option> - ) { - let area = area.unwrap_or_else(||self.xywh()); - unimplemented!() + 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 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) + } +} + +/// 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(); + //*to.area_mut() = self.0; + //self.1.draw(to); + //*to.area_mut() = area; + //} +//} + +/// 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 } } diff --git a/src/keys.rs b/src/keys.rs deleted file mode 100644 index 54217c4..0000000 --- a/src/keys.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::play::Thread; - -use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; -use ::std::time::Duration; -use ::dizzle::{Usually, Do, impl_from}; -use ::crossterm::event::{ - read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState -}; - -/// 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: - event => { - if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) { - panic!("{e}") - } - }, - - } - }) -} - -/// TUI input loop event. -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] -pub struct TuiEvent(pub Event); -impl_from!(TuiEvent: |e: Event| TuiEvent(e)); -impl_from!(TuiEvent: |c: char| TuiEvent(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)))); -impl Ord for TuiEvent { - fn cmp (&self, other: &Self) -> std::cmp::Ordering { - self.partial_cmp(other) - .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf - } -} - -/// TUI key spec. -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] -pub struct TuiKey(pub Option, pub KeyModifiers); -impl TuiKey { - const SPLIT: char = '/'; - pub fn from_crossterm (event: KeyEvent) -> Self { - Self(Some(event.code), event.modifiers) - } - pub fn to_crossterm (&self) -> Option { - self.0.map(|code|Event::Key(KeyEvent { - code, - modifiers: self.1, - kind: KeyEventKind::Press, - state: KeyEventState::NONE, - })) - } - pub fn named (token: &str) -> Option { - use KeyCode::*; - Some(match token { - "up" => Up, - "down" => Down, - "left" => Left, - "right" => Right, - "esc" | "escape" => Esc, - "enter" | "return" => Enter, - "delete" | "del" => Delete, - "backspace" => Backspace, - "tab" => Tab, - "space" => Char(' '), - "comma" => Char(','), - "period" => Char('.'), - "plus" => Char('+'), - "minus" | "dash" => Char('-'), - "equal" | "equals" => Char('='), - "underscore" => Char('_'), - "backtick" => Char('`'), - "lt" => Char('<'), - "gt" => Char('>'), - "cbopen" | "openbrace" => Char('{'), - "cbclose" | "closebrace" => Char('}'), - "bropen" | "openbracket" => Char('['), - "brclose" | "closebracket" => Char(']'), - "pgup" | "pageup" => PageUp, - "pgdn" | "pagedown" => PageDown, - "f1" => F(1), - "f2" => F(2), - "f3" => F(3), - "f4" => F(4), - "f5" => F(5), - "f6" => F(6), - "f7" => F(7), - "f8" => F(8), - "f9" => F(9), - "f10" => F(10), - "f11" => F(11), - "f12" => F(12), - _ => return None, - }) - } -} diff --git a/src/lang.rs b/src/lang.rs index 8266bae..54aa0c2 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -1,5 +1,5 @@ -#[cfg(feature = "term")] -impl crate::term::TuiKey { +#[cfg(feature = "tui")] impl TuiKey { + #[cfg(feature = "lang")] pub fn from_dsl (dsl: impl Language) -> Usually { if let Some(word) = dsl.word()? { @@ -37,4 +37,5 @@ impl crate::term::TuiKey { return Err(format!("TuiKey: unspecified").into()) } } + } diff --git a/src/lib.rs b/src/lib.rs index 7897606..2fad25c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,11 +33,9 @@ pub(crate) use ::{ #[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; #[cfg(feature = "draw")] pub mod draw; -#[cfg(feature = "draw")] pub mod space; #[cfg(feature = "draw")] pub mod color; #[cfg(feature = "text")] pub mod text; #[cfg(feature = "term")] pub mod term; -#[cfg(feature = "term")] pub mod keys; #[cfg(feature = "term")] pub extern crate ratatui; #[cfg(feature = "term")] pub extern crate crossterm; diff --git a/src/play.rs b/src/play.rs index 28c4795..b608106 100644 --- a/src/play.rs +++ b/src/play.rs @@ -1,6 +1,6 @@ use crate::{*, time::*, lang::*}; use ::std::{thread::JoinHandle, time::Duration}; -#[cfg(feature = "term")] use ::crossterm::event::poll; +#[cfg(feature = "tui")] use ::crossterm::event::poll; #[derive(Clone)] pub struct Exit(Arc); @@ -21,8 +21,8 @@ impl Exit { impl Thread { /// Spawn a TUI thread that runs `callt least one, then repeats until `exit`. - pub fn new (exit: Arc, mut call: F) -> Result - where F: FnMut(&PerfModel)->() + Send + Sync + 'static + pub fn new (exit: Arc, call: F) -> Result + where F: Fn(&PerfModel)->() + Send + Sync + 'static { let perf = Arc::new(PerfModel::default()); Ok(Self { @@ -30,7 +30,7 @@ impl Thread { perf: perf.clone(), join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || { while !exit.fetch_and(true, Relaxed) { - let _ = perf.cycle(&mut call); + let _ = perf.cycle(&call); } })?.into() }) @@ -39,19 +39,19 @@ impl Thread { /// 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 + exit: Arc, time: Duration, call: F ) -> Result - where F: FnMut(&PerfModel)->() + Send + Sync + 'static + where F: Fn(&PerfModel)->() + Send + Sync + 'static { Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); }) } /// Spawn a thread that uses [crossterm::event::poll] /// to run `call` every `time` msec. - #[cfg(feature = "term")] pub fn new_poll ( - exit: Arc, time: Duration, mut call: F + #[cfg(feature = "tui")]pub fn new_poll ( + exit: Arc, time: Duration, call: F ) -> Result - where F: FnMut(&PerfModel)->() + Send + Sync + 'static + where F: Fn(&PerfModel)->() + Send + Sync + 'static { Self::new(exit, move |perf| { if poll(time).is_ok() { let _ = call(perf); } }) } diff --git a/src/space.rs b/src/space.rs deleted file mode 100644 index b7c7e96..0000000 --- a/src/space.rs +++ /dev/null @@ -1,482 +0,0 @@ -use crate::{*, draw::*}; - -/// 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 N, pub N, pub N, pub N); -impl X for XYWH { fn x (&self) -> N { self.0 } fn w (&self) -> N { self.2 } } -impl Y for XYWH { fn y (&self) -> N { self.0 } fn h (&self) -> N { self.2 } } -impl XYWH { - 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) { - use Split::*; - 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)) - } - } -} - -/// 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() } } -pub struct Anchor(Origin, T); -impl AsRef for Anchor { - fn as_ref (&self) -> &Origin { - &self.0 - } -} -impl> Draw for Anchor { - fn draw (self, to: &mut T) -> Usually> { - todo!() - } -} - -pub const fn origin_nw (a: impl Draw) -> impl Draw { - Anchor(Origin::NW, a) -} -pub const fn origin_n (a: impl Draw) -> impl Draw { - Anchor(Origin::N, a) -} -pub const fn origin_ne (a: impl Draw) -> impl Draw { - Anchor(Origin::NE, a) -} -pub const fn origin_w (a: impl Draw) -> impl Draw { - Anchor(Origin::W, a) -} -pub const fn origin_c (a: impl Draw) -> impl Draw { - Anchor(Origin::C, a) -} -pub const fn origin_e (a: impl Draw) -> impl Draw { - Anchor(Origin::E, a) -} -pub const fn origin_sw (a: impl Draw) -> impl Draw { - Anchor(Origin::SW, a) -} -pub const fn origin_s (a: impl Draw) -> impl Draw { - Anchor(Origin::S, a) -} -pub const fn origin_se (a: impl Draw) -> impl Draw { - Anchor(Origin::SE, a) -} - -/// 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(); - let a = origin_a.align(a); - let b = origin_b.align(b); - match self { - Self::Below => { - to.place(&b, Some(area_b)); - to.place(&a, Some(area_b)); - }, - _ => { - to.place(&a, Some(area_a)); - to.place(&b, Some(area_a)); - } - } - Ok(to.xywh()) // 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), - } - } -} - -/// Horizontal axis. -pub trait X { - fn x (&self) -> N; - fn w (&self) -> N { N::zero() } - fn w_min (&self) -> N { self.w() } - fn w_max (&self) -> N { self.w() } - fn iter_x (&self) -> impl Iterator where Self: HasOrigin { - self.x_west()..self.x_east() - } - fn x_west (&self) -> N where Self: 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: 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: HasOrigin { - todo!() - } -} -pub const fn x_push (x: T::Unit, a: impl Draw) -> impl Draw { - a -} -pub const fn x_pull (x: T::Unit, a: impl Draw) -> impl Draw { - a -} -pub const fn w_max (w: Option, a: impl Draw) -> impl Draw { - wh_max(w, None, a) -} -pub const fn w_min (w: Option, a: impl Draw) -> impl Draw { - wh_min(w, None, a) -} -pub const fn w_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 w_pad (x: T::Unit, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut T|draw.draw(todo!())) -} - -pub trait Y { - fn y (&self) -> N; - fn h (&self) -> N { N::zero() } - fn h_min (&self) -> N { self.h() } - fn h_max (&self) -> N { self.h() } - fn iter_y (&self) -> impl Iterator 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(); - 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: 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: HasOrigin { - todo!() - } -} -pub const fn y_push (x: T::Unit, a: impl Draw) -> impl Draw { - a -} -pub const fn y_pull (x: T::Unit, a: impl Draw) -> impl Draw { - a -} -pub const fn h_max (h: Option, a: impl Draw) -> impl Draw { - wh_max(None, h, a) -} -pub const fn h_min (h: Option, a: impl Draw) -> impl Draw { - wh_min(None, h, a) -} -pub const fn h_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 h_pad (x: T::Unit, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut T|draw.draw(todo!())) -} - -pub trait Space: X + Y { - fn xywh (&self) -> XYWH { XYWH(self.x(), self.y(), self.w(), self.h()) } - // FIXME: factor origin - fn lrtb (&self) -> [N;4] { [self.x(), self.y(), self.x()+self.w(), self.y()+self.h()] } -} -impl + Y> Space for T {} -pub const fn xy_push (x: T::Unit, y: T::Unit, a: impl Draw) -> impl Draw { - a -} -pub const fn xy_pull (x: T::Unit, y: T::Unit, a: impl Draw) -> impl Draw { - a -} -/// Shrink drawing area symmetrically. -/// -/// ``` -/// let padded = tengri::WH(3, 5).pad("Hello"); -/// ``` -pub const fn wh_pad (w: T::Unit, h: T::Unit, 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 wh_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 wh_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 wh_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 wh_clip ( - w: Option, h: Option, draw: impl Draw -) -> impl Draw { - thunk(move|to: &mut T|draw.draw(todo!())) -} - -pub const fn wh_full (a: impl Draw) -> impl Draw { a } -pub const fn w_full (a: impl Draw) -> impl Draw { a } -pub const fn h_full (a: impl Draw) -> impl Draw { a } - -#[macro_export] macro_rules! north { - ($($tt:tt)*) => { unimplemented!() }; -} -#[macro_export] macro_rules! south { - ($($tt:tt)*) => { unimplemented!() }; -} -#[macro_export] macro_rules! east { - ($($tt:tt)*) => { unimplemented!() }; -} -#[macro_export] macro_rules! west { - ($($tt:tt)*) => { unimplemented!() }; -} -#[macro_export] macro_rules! above { - ($($tt:tt)*) => { unimplemented!() }; -} -#[macro_export] macro_rules! below { - ($($tt:tt)*) => { unimplemented!() }; -} - -/// 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 < - T: Screen, - V: Fn()->I, - I: Iterator>, - F: Fn(&dyn Draw)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut T|{ todo!() }) -} -pub fn iter_north < - T: Screen, - V: Fn()->I, - I: Iterator>, - F: Fn(&dyn Draw)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut T|{ todo!() }) -} -pub fn iter_east < - T: Screen, - V: Fn()->I, - I: Iterator>, - F: Fn(&dyn Draw)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut T|{ todo!() }) -} -pub fn iter_south < - T: Screen, - V: Fn()->I, - I: Iterator>, - F: Fn(&dyn Draw)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut T|{ todo!() }) -} -pub fn iter_west < - T: Screen, - V: Fn()->I, - I: Iterator>, - F: Fn(&dyn Draw)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut T|{ todo!() }) -} diff --git a/src/term.rs b/src/term.rs index b06c0b3..d88cc8c 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,140 +1,156 @@ -use crate::{*, lang::*, play::*, draw::*, space::{*, Split::*}, color::*, text::*}; +use crate::{*, lang::*, play::*, draw::*, color::*, text::*}; use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; use rand::distributions::uniform::UniformSampler; use ::{ std::{ io::{stdout, Write}, - time::Duration, - ops::{Deref, DerefMut}, + time::Duration }, better_panic::{Settings, Verbosity}, ratatui::{ - prelude::{Style, Buffer as ScreenBuffer, Position, Backend, Color}, + prelude::{Color, Style, Buffer, Position, Backend}, style::{Modifier, Color::*}, backend::{CrosstermBackend, ClearType}, layout::{Size, Rect}, - buffer::{Buffer, Cell}, + buffer::Cell }, crossterm::{ - ExecutableCommand, terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, } }; -pub struct Tui(pub Buffer, pub XYWH); -impl Screen for Tui { type Unit = u16; } -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 HasOrigin for Tui { fn origin (&self) -> Origin { Origin::NW } } -impl X for Tui { - fn x (&self) -> u16 { self.1.0 } - fn w (&self) -> u16 { self.1.2 } -} -impl Y for Tui { - fn y (&self) -> u16 { self.1.1 } - fn h (&self) -> u16 { self.1.3 } -} -impl Tui { - fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH { - for row in 0..self.h() { - let y = self.y() + row; - for col in 0..self.w() { - let x = self.x() + col; - if x < self.0.area.width && y < self.0.area.height { - if let Some(cell) = self.0.cell_mut(Position { x, y }) { - callback(cell, col, row); - } + +/// 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}") } } } - self.xywh() - } - fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) { - for cell in self.0.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