From eb899906f93b0ab19a2ace3fa7f46afba24b27bf Mon Sep 17 00:00:00 2001 From: same mf who else Date: Sat, 21 Mar 2026 17:35:31 +0200 Subject: [PATCH] 13e woo --- src/color.rs | 28 ++- src/draw.rs | 501 ++------------------------------------------------- src/lang.rs | 5 +- src/lib.rs | 1 + src/play.rs | 4 +- src/space.rs | 385 +++++++++++++++++++++++++++++++++++++++ src/term.rs | 287 +++++++++++++---------------- src/text.rs | 144 +++++++-------- 8 files changed, 627 insertions(+), 728 deletions(-) create mode 100644 src/space.rs diff --git a/src/color.rs b/src/color.rs index d1e7889..50f1eed 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,6 +1,10 @@ use ::ratatui::style::Color; use crate::lang::impl_from; -pub(crate) use ::palette::{Okhsl, Srgb, OklabHue, okhsl::UniformOkhsl}; +pub(crate) use ::palette::{ + Okhsl, Srgb, OklabHue, Mix, okhsl::UniformOkhsl, + convert::{FromColor, FromColorUnclamped} +}; +use rand::distributions::uniform::UniformSampler; pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor { todo!(); } @@ -26,13 +30,17 @@ pub trait HasColor { fn color (&self) -> ItemColor; } } } -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) }); +#[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) }); // A single color within item theme parameters, in OKHSL and RGB representations. impl ItemColor { - #[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) } + #[cfg(feature = "term")] pub const fn from_tui (term: Color) -> Self { + Self { term, okhsl: Okhsl::new_const(OklabHue::new(0.0), 0.0, 0.0) } } pub fn random () -> Self { let mut rng = ::rand::thread_rng(); @@ -67,8 +75,8 @@ 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 = "tui")] pub const G: [Self;256] = { - let mut builder = konst::array::ArrayBuilder::new(); + #[cfg(feature = "term")] pub const G: [Self;256] = { + let mut builder = dizzle::konst::array::ArrayBuilder::new(); while !builder.is_full() { let index = builder.len() as u8; let light = (index as f64 * 1.15) as u8; @@ -96,7 +104,7 @@ impl ItemTheme { pub const G00: Self = { let color: ItemColor = ItemColor { okhsl: Okhsl { hue: OklabHue::new(0.0), lightness: 0.0, saturation: 0.0 }, - rgb: Color::Rgb(0, 0, 0) + term: Color::Rgb(0, 0, 0) }; Self { base: color, @@ -108,7 +116,7 @@ impl ItemTheme { darkest: color, } }; - #[cfg(feature = "tui")] pub fn from_tui_color (base: Color) -> Self { + #[cfg(feature = "term")] pub fn from_tui_color (base: Color) -> Self { Self::from_item_color(ItemColor::from_tui(base)) } pub fn from_item_color (base: ItemColor) -> Self { diff --git a/src/draw.rs b/src/draw.rs index 8033666..3eadbf1 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,4 +1,4 @@ -use crate::{*, lang::*, color::*}; +use crate::{*, lang::*, color::*, space::*}; /// Drawable that supports dynamic dispatch. /// @@ -8,7 +8,7 @@ use crate::{*, lang::*, color::*}; /// 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) -> Usually> { /// screen.0 |= self.0; /// } /// } @@ -18,45 +18,45 @@ use crate::{*, lang::*, color::*}; /// TestWidget(false).draw(&mut screen); /// ``` pub trait Draw { - fn draw (&self, to: &mut T) -> Usually>; + fn draw (&self, to: &mut T) -> Usually>; } impl Draw for () { - fn draw (&self, __: &mut T) -> Usually> { + fn draw (&self, __: &mut T) -> Usually> { Ok(Default::default()) } } impl> Draw for &D { - fn draw (&self, to: &mut T) -> Usually> { + fn draw (&self, to: &mut T) -> Usually> { (*self).draw(to) } } impl> Draw for Arc { - fn draw (&self, to: &mut T) -> Usually> { + fn draw (&self, to: &mut T) -> Usually> { (**self).draw(to) } } impl> Draw for RwLock { - fn draw (&self, to: &mut T) -> Usually> { + fn draw (&self, to: &mut T) -> Usually> { self.read().unwrap().draw(to) } } impl> Draw for Option { - fn draw (&self, to: &mut T) -> Usually> { - self.map(|draw|draw.draw(to)).transpose()?.flatten() + fn draw (&self, to: &mut T) -> Usually> { + Ok(self.as_ref().map(|draw|draw.draw(to)).transpose()?.unwrap_or_default()) } } /// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. -pub struct ThunkUsually>>( +pub struct ThunkUsually>>( pub F, std::marker::PhantomData ); -pub const fn thunk Usually>> (draw: F) -> Thunk { +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) +implUsually>> Draw for Thunk { + fn draw (&self, to: &mut T) -> Usually> { + (&self.0)(to) } } @@ -82,447 +82,6 @@ pub const fn either (condition: bool, a: impl Draw, b: 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. /// /// ``` @@ -539,38 +98,18 @@ impl + HasWH> HasXYWH for T { /// } /// /// impl Draw for String { -/// fn draw (&self, to: &mut TestOut) -> Usually> { +/// fn draw (&self, to: &mut TestOut) -> Usually> { /// to.area_mut().set_w(self.len() as u16); /// } /// } /// ``` -pub trait Screen: HasXYWH + HasOrigin + Send + Sync + Sized { +pub trait Screen: Space + Send + Sync + Sized { type Unit: Coord; /// Render drawable in area specified by `area` - fn place <'t, T: Draw + ?Sized> (&mut self, area: impl HasWH, content: &'t T) { + fn place <'t, T: Draw + ?Sized> ( + &mut self, content: &'t T, area: Option> + ) { + let area = area.unwrap_or_else(||self.xywh()); unimplemented!() } } - -/// Something that has a [Measure] of its rendered size. -pub trait Measured {} - -/// Something that has a bounding box -/// -/// ``` -/// use tengri::{Bounded, XYWH}; -/// let bounded: Bounded = Bounded(0, 0 ,0 ,0 ,""); -/// ``` -pub trait Bounding : HasXY + HasWH + HasOrigin { -} - -//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; - //} -//} - -// pub fn displace ... diff --git a/src/lang.rs b/src/lang.rs index 54aa0c2..8266bae 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -1,5 +1,5 @@ -#[cfg(feature = "tui")] impl TuiKey { - +#[cfg(feature = "term")] +impl crate::term::TuiKey { #[cfg(feature = "lang")] pub fn from_dsl (dsl: impl Language) -> Usually { if let Some(word) = dsl.word()? { @@ -37,5 +37,4 @@ return Err(format!("TuiKey: unspecified").into()) } } - } diff --git a/src/lib.rs b/src/lib.rs index 2fad25c..76e268b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ 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; diff --git a/src/play.rs b/src/play.rs index b608106..80115d6 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 = "tui")] use ::crossterm::event::poll; +#[cfg(feature = "term")] use ::crossterm::event::poll; #[derive(Clone)] pub struct Exit(Arc); @@ -48,7 +48,7 @@ impl Thread { /// Spawn a thread that uses [crossterm::event::poll] /// to run `call` every `time` msec. - #[cfg(feature = "tui")]pub fn new_poll ( + #[cfg(feature = "term")]pub fn new_poll ( exit: Arc, time: Duration, call: F ) -> Result where F: Fn(&PerfModel)->() + Send + Sync + 'static diff --git a/src/space.rs b/src/space.rs new file mode 100644 index 0000000..e3a2058 --- /dev/null +++ b/src/space.rs @@ -0,0 +1,385 @@ +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() } +} + +/// 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(&origin_b.align(b), Some(area_b)); + to.place(&origin_a.align(a), Some(area_b)); + }, + _ => { + to.place(&origin_a.align(a), Some(area_a)); + to.place(&origin_b.align(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), + } + } + /// 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!() }) + } +} + +/// 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!())) +} diff --git a/src/term.rs b/src/term.rs index 809ec2c..d0a92e6 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,10 +1,11 @@ -use crate::{*, lang::*, play::*, draw::{*, Split::*}, color::*, text::*}; +use crate::{*, lang::*, play::*, draw::*, space::{*, Split::*}, color::*, text::*}; use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; use rand::distributions::uniform::UniformSampler; use ::{ std::{ io::{stdout, Write}, - time::Duration + time::Duration, + ops::{Deref, DerefMut}, }, better_panic::{Settings, Verbosity}, ratatui::{ @@ -20,25 +21,36 @@ use ::{ event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, } }; - 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 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 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 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)) -> WH { - tui_update(self.0, self.1, callback); - self.1.wh() + fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH { + for row in 0..self.h() { + let y = self.y() + row; + for col in 0..self.w() { + let x = self.x() + col; + if x < self.0.area.width && y < self.0.area.height { + if let Some(cell) = self.0.cell_mut(Position { x, y }) { + callback(cell, col, row); + } + } + } + } + self.xywh() } fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) { - for cell in self.buffer().content.iter_mut() { + for cell in self.0.content.iter_mut() { cell.fg = fg; cell.bg = bg; cell.modifier = modifier; @@ -47,9 +59,8 @@ impl Tui { fn blit (&mut self, text: &impl AsRef, x: u16, y: u16, style: Option