From 0b9e9c06962e5cd55b639d9c0a7cef6f11405d81 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Sun, 5 Jul 2026 18:46:22 +0300 Subject: [PATCH] wip: final simplify --- PAPER.qmd | 0 dizzle | 2 +- shell.nix | 29 +++- src/draw.rs | 123 ++++++++++++++--- src/draw/screen.rs | 27 ---- src/draw/thunk.rs | 51 ------- src/draw/view.rs | 8 -- src/eval.rs | 6 +- src/layout.rs | 87 ++++++++++++ src/layout/align.rs | 13 ++ src/layout/area.rs | 15 +++ src/layout/origin.rs | 23 ++++ src/{space => layout}/split.rs | 12 +- src/lib.rs | 46 ++++--- src/space.rs | 198 +-------------------------- src/space/origin.rs | 100 -------------- src/space/{size.rs => sizer.rs} | 25 ++-- src/space/xywh.rs | 231 ++++++++++++++++++++++++++++++-- src/term.rs | 118 ++++++---------- src/term/buffer.rs | 31 +++++ src/text.rs | 6 + 21 files changed, 607 insertions(+), 544 deletions(-) create mode 100644 PAPER.qmd delete mode 100644 src/draw/screen.rs delete mode 100644 src/draw/thunk.rs delete mode 100644 src/draw/view.rs create mode 100644 src/layout.rs create mode 100644 src/layout/align.rs create mode 100644 src/layout/area.rs create mode 100644 src/layout/origin.rs rename src/{space => layout}/split.rs (92%) delete mode 100644 src/space/origin.rs rename src/space/{size.rs => sizer.rs} (53%) create mode 100644 src/term/buffer.rs diff --git a/PAPER.qmd b/PAPER.qmd new file mode 100644 index 0000000..e69de29 diff --git a/dizzle b/dizzle index 0292cea..f8e310b 160000 --- a/dizzle +++ b/dizzle @@ -1 +1 @@ -Subproject commit 0292cea24f5bfe71e0f8c6f4afe3cad105aa8ab8 +Subproject commit f8e310b477afc1aab4701fdc52da474143d78a28 diff --git a/shell.nix b/shell.nix index dba3faf..6f44581 100644 --- a/shell.nix +++ b/shell.nix @@ -1,13 +1,28 @@ #!/usr/bin/env nix-shell {pkgs?import{}}:let - stdenv = pkgs.clang19Stdenv; - name = "tengri"; - nativeBuildInputs = [ pkgs.pkg-config pkgs.clang pkgs.libclang pkgs.mold pkgs.bacon ]; - buildInputs = [ pkgs.libclang pkgs.jack2 ]; - LIBCLANG_PATH = "${pkgs.libclang.lib.outPath}/lib"; - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath []; + name = "tengri"; + stdenv = pkgs.clang19Stdenv; + LIBCLANG_PATH = "${pkgs.libclang.lib.outPath}/lib"; + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath []; in pkgs.mkShell.override { inherit stdenv; } { - inherit name nativeBuildInputs buildInputs LIBCLANG_PATH LD_LIBRARY_PATH; + inherit name LIBCLANG_PATH LD_LIBRARY_PATH; + buildInputs = [ + pkgs.libclang + pkgs.jack2 + ]; + nativeBuildInputs = [ + pkgs.pkg-config + pkgs.clang + pkgs.libclang + pkgs.mold + pkgs.bacon + (pkgs.quarto.overrideAttrs (oldAttrs: { # https://github.com/NixOS/nixpkgs/issues/519484 + postPatch = (oldAttrs.postPatch or "") + '' + substituteInPlace bin/quarto.js \ + --replace-fail "syntax-highlighting" "highlight-style" + ''; + })) + ]; } diff --git a/src/draw.rs b/src/draw.rs index df505d3..23c55b5 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,14 +1,30 @@ use crate::*; pub use crate::space::*; -mod view; -pub use self::view::*; - -mod thunk; -pub use self::thunk::*; - -mod screen; -pub use self::screen::*; +/// Output target. +/// +/// ``` +/// use tengri::{*, draw::*}; +/// +/// struct TestOut>(T); +/// +/// impl> Screen for TestOut { +/// type Unit = u16; +/// fn show + ?Sized> (&mut self, _: D) { +/// println!("placed"); +/// () +/// } +/// } +/// +/// impl_draw!(|self: String, to: TestOut|{ +/// to.area_mut().set_w(self.len() as u16); +/// }); +/// ``` +pub trait Screen: Xy + Wh + Send + Sync + Sized { + type Unit: Coord; + /// Render drawable in subarea specified by `area` + fn show <'t, T: Draw> (&mut self, content: T) -> Perhaps>; +} /// Implement the [Draw] trait for a particular drawable and [Screen]. /// @@ -22,7 +38,7 @@ pub use self::screen::*; #[macro_export] macro_rules! impl_draw ((| $self:ident:$Self:ty, $to:ident:$To:ty |$draw:block)=>{ impl Draw<$To> for $Self { - fn draw ($self, $to: &mut $To) -> Usually> $draw + fn draw ($self, $to: &mut $To) -> Usually::Unit>> $draw } }); /// Drawable that supports dynamic dispatch. @@ -48,29 +64,94 @@ pub use self::screen::*; /// ``` pub trait Draw { fn draw (self, to: &mut S) -> Usually>; + fn layout (&self, area: XYWH) -> Perhaps> { + Ok(Some(area)) + } } + impl Draw for () { fn draw (self, __: &mut S) -> Usually> { Ok(Default::default()) } } -impl> Draw for &D { - fn draw (self, __: &mut S) -> Usually> { - todo!() - } -} + impl> Draw for Option { - fn draw (self, __: &mut S) -> Usually> { - todo!() - } -} -impl> Draw for RwLock { - fn draw (self, __: &mut S) -> Usually> { - todo!() + fn draw (self, to: &mut S) -> Usually> { + self.map(|it|it.draw(to)).transpose().map(Option::unwrap_or_default) } } + +//impl> Draw for RwLock { + //fn draw (self, __: &mut S) -> Usually> { + //todo!() + //} +//} + //impl> Draw for Arc { //fn draw (self, __: &mut T) -> Usually> { //todo!() //} //} + +/// Emit a [Draw]able. +/// +/// Speculative. How to avoid conflicts with [Draw] proper? +pub trait View { + fn view (&self) -> impl Draw; +} + +impl> Draw for &V { + fn draw (self, to: &mut T) -> Usually> { + self.view().draw(to) + } +} + +/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. +pub struct ThunkUsually>>( + pub F, + std::marker::PhantomData +); + +implUsually>> Draw for Thunk { + fn draw (self, to: &mut T) -> Usually> { + (self.0)(to) + } +} + +/// Basic [Draw]able closure. +/// +/// ``` +/// # use tengri::{draw::*, term::*}; +/// # fn test () -> impl tengri::draw::Draw { +/// thunk(|to: &mut Tui|Ok(to.1)) +/// # } +/// ``` +pub const fn thunk Usually>> ( + draw: F +) -> Thunk { + Thunk(draw, std::marker::PhantomData) +} + +/// Only render when condition is true. +/// +/// ``` +/// # use tengri::{draw::*, term::*}; +/// # fn test () -> impl tengri::draw::Draw { +/// when(true, "Yes") +/// # } +/// ``` +pub const fn when (condition: bool, draw: impl Draw) -> impl Draw { + thunk(move|to: &mut T|if condition { draw.draw(to) } else { Ok(Default::default()) }) +} + +/// Render one thing if a condition is true and another false. +/// +/// ``` +/// # use tengri::{draw::*, term::*}; +/// # fn test () -> impl tengri::draw::Draw { +/// either(true, "Yes", "No") +/// # } +/// ``` +pub const fn either (condition: bool, a: impl Draw, b: impl Draw) -> impl Draw { + thunk(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) }) +} diff --git a/src/draw/screen.rs b/src/draw/screen.rs deleted file mode 100644 index 857031d..0000000 --- a/src/draw/screen.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::*; - -/// Output target. -/// -/// ``` -/// use tengri::{*, draw::*}; -/// -/// struct TestOut>(T); -/// -/// impl> Screen for TestOut { -/// type Unit = u16; -/// fn show + ?Sized> (&mut self, area: T, _: D) { -/// println!("placed: {area:?}"); -/// () -/// } -/// } -/// -/// impl_draw!(|self: String, to: TestOut|{ -/// to.area_mut().set_w(self.len() as u16); -/// }); -/// ``` -pub trait Screen: Space + Send + Sync + Sized { - type Unit: Coord; - /// Render drawable in subarea specified by `area` - fn show <'t, T: Draw> (&mut self, area: XYWH, content: T) -> - Usually>; -} diff --git a/src/draw/thunk.rs b/src/draw/thunk.rs deleted file mode 100644 index a56b5f3..0000000 --- a/src/draw/thunk.rs +++ /dev/null @@ -1,51 +0,0 @@ -use super::*; - -/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. -pub struct ThunkUsually>>( - pub F, - std::marker::PhantomData -); - -implUsually>> Draw for Thunk { - fn draw (self, to: &mut T) -> Usually> { - (self.0)(to) - } -} - -/// Basic [Draw]able closure. -/// -/// ``` -/// # use tengri::{draw::*, term::*}; -/// # fn test () -> impl tengri::draw::Draw { -/// thunk(|to: &mut Tui|Ok(to.1)) -/// # } -/// ``` -pub const fn thunk Usually>> ( - draw: F -) -> Thunk { - Thunk(draw, std::marker::PhantomData) -} - -/// Only render when condition is true. -/// -/// ``` -/// # use tengri::{draw::*, term::*}; -/// # fn test () -> impl tengri::draw::Draw { -/// when(true, "Yes") -/// # } -/// ``` -pub const fn when (condition: bool, draw: impl Draw) -> impl Draw { - thunk(move|to: &mut T|if condition { draw.draw(to) } else { Ok(Default::default()) }) -} - -/// Render one thing if a condition is true and another false. -/// -/// ``` -/// # use tengri::{draw::*, term::*}; -/// # fn test () -> impl tengri::draw::Draw { -/// either(true, "Yes", "No") -/// # } -/// ``` -pub const fn either (condition: bool, a: impl Draw, b: impl Draw) -> impl Draw { - thunk(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) }) -} diff --git a/src/draw/view.rs b/src/draw/view.rs deleted file mode 100644 index 82fe908..0000000 --- a/src/draw/view.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::*; - -/// Emit a [Draw]able. -/// -/// Speculative. How to avoid conflicts with [Draw] proper? -pub trait View { - fn view (&self) -> impl Draw; -} diff --git a/src/eval.rs b/src/eval.rs index b64b83e..2da55fd 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -195,7 +195,7 @@ pub fn eval_view_tui <'a, S> ( Some("text") => { if let Some(src) = args?.src()? { - output.show(output.xywh(), &src)? + output.show(src)? } else { return Ok(None) } @@ -204,7 +204,7 @@ pub fn eval_view_tui <'a, S> ( Some("fg") => { let arg0 = arg0?.expect("fg: expected arg 0 (color)"); let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")); - output.show(output.xywh(), &fg(color, thunk(move|output: &mut Tui|{ + output.show(fg(color, thunk(move|output: &mut Tui|{ state.interpret(output, &arg1)?; // FIXME?: don't max out the used area? Ok(output.area().into()) @@ -214,7 +214,7 @@ pub fn eval_view_tui <'a, S> ( Some("bg") => { let arg0 = arg0?.expect("bg: expected arg 0 (color)"); let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")); - output.show(output.xywh(), &bg(color, thunk(move|output: &mut Tui|{ + output.show(bg(color, thunk(move|output: &mut Tui|{ state.interpret(output, &arg1)?; // FIXME?: don't max out the used area? Ok(output.area().into()) diff --git a/src/layout.rs b/src/layout.rs new file mode 100644 index 0000000..5fa4660 --- /dev/null +++ b/src/layout.rs @@ -0,0 +1,87 @@ +use crate::*; + +mod align; pub use self::align::*; +mod area; pub use self::area::*; +mod origin; pub use self::origin::*; +mod split; pub use self::split::*; + +pub enum Layout>, I: Draw> { + __(PhantomData), + MinW(X, I), + MinH(X, I), + MinWH(X, X, I), + MaxW(X, I), + MaxH(X, I), + MaxWH(X, X, I), + ExactW(X, I), + ExactH(X, I), + ExactWH(X, X, I), + ClipW(X, I), + ClipH(X, I), + ClipWH(X, X, I), +} + +impl< + T: Screen, + X: Into>, + I: Draw +> Draw for Layout { + fn draw (self, to: &mut T) -> Usually> { + use Layout::*; + match self { + __(_) => unreachable!(), + MinW(x, it) => { + let x = x.into(); + it.draw(to) + }, + MinH(y, it) => { + let y = y.into(); + it.draw(to) + }, + MinWH(x, y, it) => { + let x = x.into(); + let y = y.into(); + it.draw(to) + }, + MaxW(x, it) => { + let x = x.into(); + it.draw(to) + }, + MaxH(y, it) => { + let y = y.into(); + it.draw(to) + }, + MaxWH(x, y, it) => { + let x = x.into(); + let y = y.into(); + it.draw(to) + }, + ExactW(x, it) => { + let x = x.into(); + it.draw(to) + }, + ExactH(y, it) => { + let y = y.into(); + it.draw(to) + }, + ExactWH(x, y, it) => { + let x = x.into(); + let y = y.into(); + it.draw(to) + }, + ClipW(x, it) => { + let x = x.into(); + it.draw(to) + }, + ClipH(y, it) => { + let y = y.into(); + it.draw(to) + }, + ClipWH(x, y, it) => { + let x = x.into(); + let y = y.into(); + it.draw(to) + }, + } + } +} diff --git a/src/layout/align.rs b/src/layout/align.rs new file mode 100644 index 0000000..eac9e76 --- /dev/null +++ b/src/layout/align.rs @@ -0,0 +1,13 @@ +use crate::*; + +pub struct Align(Option, T); + +/// ``` +/// use tengri::*; +/// let _ = align(None, "unaligned"); +/// let _ = align(Origin::SE, "southeast"); +/// let _ = align(Some(Origin::SE), "southeast"); +/// ``` +pub fn align , U: Into>> (origin: U, it: T) -> Align { + Align(origin.into(), it) +} diff --git a/src/layout/area.rs b/src/layout/area.rs new file mode 100644 index 0000000..eb71215 --- /dev/null +++ b/src/layout/area.rs @@ -0,0 +1,15 @@ +use crate::*; + +pub struct Area>(Option>, T); + +/// ``` +/// use tengri::*; +/// let _ = area(None, "unareaed"); +/// let _ = area([1, 2, 3, 4], "southeast"); +/// let _ = area(Some([1, 2, 3, 4]), "southeast"); +/// ``` +pub fn area , U: Into>>> ( + origin: U, it: T +) -> Area { + Area(origin.into(), it) +} diff --git a/src/layout/origin.rs b/src/layout/origin.rs new file mode 100644 index 0000000..8b8b85e --- /dev/null +++ b/src/layout/origin.rs @@ -0,0 +1,23 @@ +use super::*; + +/// 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 +} + +/// 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() + } +} diff --git a/src/space/split.rs b/src/layout/split.rs similarity index 92% rename from src/space/split.rs rename to src/layout/split.rs index 4fb42cc..2c9bd62 100644 --- a/src/space/split.rs +++ b/src/layout/split.rs @@ -50,16 +50,16 @@ impl Split { 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); + let a = align(origin_a, a); + let b = align(origin_b, b); match self { Self::Below => { - to.show(area_b, &b); - to.show(area_b, &a); + to.show(area(area_b, b))?; + to.show(area(area_b, a))?; }, _ => { - to.show(area_a, &a); - to.show(area_a, &b); + to.show(area(area_a, a))?; + to.show(area(area_b, b))?; } } Ok(to.xywh()) // FIXME: compute and return actually used area diff --git a/src/lib.rs b/src/lib.rs index 4a5a44e..d0ddf0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,30 +25,32 @@ pub(crate) use ::{ std::ops::{Add, Sub, Mul, Div}, std::sync::{Arc, RwLock}, std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::*}, + std::error::Error, + std::marker::PhantomData }; -#[cfg(feature = "lang")] -pub use ::dizzle::{Usually, Perhaps, impl_default}; -/// DSL builtins. -#[cfg(feature = "lang")] pub mod eval; -/// Temporal dimension. -#[cfg(feature = "time")] pub mod time; -/// Circuit breaker for main loop. -#[cfg(feature = "play")] pub mod exit; -/// Thread management. -#[cfg(feature = "play")] pub mod task; -/// Integration with JACK audio backend. -#[cfg(feature = "sing")] pub mod sing; -/// Generic drawing utilities. -#[cfg(feature = "draw")] pub mod draw; -/// Spatial dimension. -#[cfg(feature = "draw")] pub mod space; -/// Color handling. -#[cfg(feature = "draw")] pub mod color; -/// Text handling. -#[cfg(feature = "text")] pub mod text; -/// Terminal output. -#[cfg(feature = "term")] pub mod term; +macro_rules! features { + ($($feature:literal: [ $($module:ident),* ]),*) => { + $( + $( + #[cfg(feature = $feature)] mod $module; + #[cfg(feature = $feature)] pub use $module::*; + )* + )* + } +} + +#[cfg(feature = "lang")] pub use ::dizzle::{Usually, Perhaps, impl_default}; + +features! { + "lang": [ eval ], + "time": [ time ], + "play": [ exit, task ], + "sing": [ sing ], + "draw": [ draw, space, layout, color ], + "text": [ text ], + "term": [ term ] +} /// Define a trait an implement it for various mutation-enabled wrapper types. */ #[macro_export] macro_rules! flex_trait_mut ( diff --git a/src/space.rs b/src/space.rs index fd9ac7d..8be15b4 100644 --- a/src/space.rs +++ b/src/space.rs @@ -1,195 +1,7 @@ -use crate::{*, draw::*}; +use crate::*; #[cfg(test)] use proptest_derive::Arbitrary; -mod coord; -pub use self::coord::*; - -mod xywh; -pub use self::xywh::*; - -mod origin; -pub use self::origin::*; - -mod split; -pub use self::split::*; - -mod iter; -pub use self::iter::*; - -mod size; -pub use self::size::*; - -/// 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::space::w_pad(3, "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::space::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::space::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::space::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::space::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::space::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 } +mod coord; pub use self::coord::*; +mod iter; pub use self::iter::*; +mod sizer; pub use self::sizer::*; +mod xywh; pub use self::xywh::*; diff --git a/src/space/origin.rs b/src/space/origin.rs deleted file mode 100644 index f3f7acc..0000000 --- a/src/space/origin.rs +++ /dev/null @@ -1,100 +0,0 @@ -use super::*; - -pub const fn origin_c (a: impl Draw) -> impl Draw { - Anchor(Origin::C, a) -} - -pub const fn origin_x (a: impl Draw) -> impl Draw { - Anchor(Origin::X, a) -} - -pub const fn origin_y (a: impl Draw) -> impl Draw { - Anchor(Origin::Y, a) -} - -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_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) -} - -/// 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!() - } -} - -/// 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!() }) -} - -pub fn align_n (a: impl Draw) -> impl Draw { - align(Origin::N, a) -} diff --git a/src/space/size.rs b/src/space/sizer.rs similarity index 53% rename from src/space/size.rs rename to src/space/sizer.rs index 4614ff1..90d09bf 100644 --- a/src/space/size.rs +++ b/src/space/sizer.rs @@ -1,32 +1,25 @@ use super::*; +use std::sync::atomic::Ordering; /// Uses [AtomicUsize] to measure size during\ /// rendering (which is normally read-only). #[derive(Default, Debug, Clone)] -pub struct Size( +pub struct Sizer( /// Width pub Arc, /// Height pub Arc, ); -impl PartialEq for Size { - fn eq (&self, _: &Self) -> bool { - todo!() - } +impl Xy for Sizer { + fn x (&self) -> u16 { self.0.load(Ordering::Relaxed) as u16 } + fn y (&self) -> u16 { self.1.load(Ordering::Relaxed) as u16 } } +impl Wide for Sizer { fn w (&self) -> u16 { self.0.load(Relaxed) as u16 } } +impl Tall for Sizer { fn h (&self) -> u16 { self.1.load(Relaxed) as u16 } } +impl PartialEq for Sizer { fn eq (&self, _: &Self) -> bool { todo!() } } -impl X for Size { - fn x (&self) -> u16 { 0 } - fn w (&self) -> u16 { self.0.load(Relaxed) as u16 } -} - -impl Y for Size { - fn y (&self) -> u16 { 0 } - fn h (&self) -> u16 { self.1.load(Relaxed) as u16 } -} - -impl Size { +impl Sizer { pub const fn of (&self, of: impl Draw) -> impl Draw { thunk(move|to: &mut T|{ let area = of.draw(to)?; diff --git a/src/space/xywh.rs b/src/space/xywh.rs index c403f5a..518b314 100644 --- a/src/space/xywh.rs +++ b/src/space/xywh.rs @@ -1,4 +1,76 @@ use super::*; +use Origin::*; +use Split::*; + +pub trait Xy { + fn x (&self) -> N; + fn y (&self) -> N; +} + +pub trait Wh: Wide + Tall { + fn wh (&self) -> [N;2]; +} + +pub trait Xywh: Xy + Wh { + fn xywh (&self) -> XYWH { + XYWH(self.x(), self.y(), self.w(), self.h()) + } +} + +pub trait Lrtb { + // FIXME: factor origin + fn lrtb (&self) -> [N;4] { + [self.x(), self.y(), self.x()+self.w(), self.y()+self.h()] + } +} + +pub trait Wide { + 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 { + let w = self.w(); + let a = self.origin(); + let d = match a { NW|W|SW => 0.into(), N|X|C|Y|S => w/2.into(), NE|E|SE => w }; + self.x().minus(d) + } + fn x_east (&self) -> N where Self: HasOrigin { + let w = self.w(); + let a = self.origin(); + let d = match a { NW|W|SW => w, N|X|C|Y|S => w/2.into(), NE|E|SE => 0.into() }; + self.x().plus(d) + } + fn x_center (&self) -> N where Self: HasOrigin { + todo!() + } +} + +pub trait Tall { + 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(); + let d = match a { NW|N|NE => 0.into(), W|X|C|Y|E => h/2.into(), SW|S|SE => h }; + self.y().minus(d) + } + fn y_south (&self) -> N where Self: HasOrigin { + let a = self.origin(); + let h = self.h(); + let d = match a { NW|N|NE => h, W|X|C|Y|E => h/2.into(), SW|S|SE => 0.into() }; + self.y().plus(d) + } + fn y_center (&self) -> N where Self: HasOrigin { + todo!() + } +} /// Point with size. /// @@ -13,21 +85,14 @@ use super::*; #[cfg_attr(test, derive(Arbitrary))] #[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH(pub N, pub N, pub N, pub N); -impl From<&ratatui::prelude::Rect> for XYWH { - fn from (rect: &ratatui::prelude::Rect) -> Self { - Self(rect.x, rect.y, rect.width, rect.height) - } -} - -impl X for XYWH { +impl Xy for XYWH { fn x (&self) -> N { self.0 } - fn w (&self) -> N { self.2 } + fn y (&self) -> N { self.1 } } -impl Y for XYWH { - fn y (&self) -> N { self.0 } - fn h (&self) -> N { self.2 } -} +impl Wide for XYWH { fn w (&self) -> N { self.2 } } + +impl Tall for XYWH { fn h (&self) -> N { self.3 } } impl XYWH { @@ -67,7 +132,6 @@ impl XYWH { } 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())), @@ -79,3 +143,144 @@ impl XYWH { } } + +impl From<&ratatui::prelude::Rect> for XYWH { + fn from (rect: &ratatui::prelude::Rect) -> Self { + Self(rect.x, rect.y, rect.width, rect.height) + } +} + +impl + Tall> Wh for T { + fn wh (&self) -> [N;2] { + [self.w(), self.h()] + } +} + +impl + Wh> Xywh for T {} + +/// Shrink drawing area symmetrically. +/// +/// ``` +/// let padded = tengri::wh_pad("Hello"); +/// ``` +pub const fn wh_pad (w: T::Unit, h: T::Unit, it: impl Draw) + -> impl Draw +{ + thunk(move|to: &mut T|it.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, it: impl Draw) + -> impl Draw +{ + thunk(move|to: &mut T|it.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, it: impl Draw) + -> impl Draw +{ + thunk(move|_to: &mut T|it.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, it: impl Draw) + -> impl Draw +{ + thunk(move|_to: &mut T|it.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, it: impl Draw +) -> impl Draw { + thunk(move|_to: &mut T|it.draw(todo!())) +} + +pub const fn wh_full (a: impl Draw) -> impl Draw { + todo!(); + a +} + +pub const fn w_full (a: impl Draw) -> impl Draw { + todo!(); + a +} + +pub const fn h_full (a: impl Draw) -> impl Draw { + todo!(); + 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) +} +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::space::w_pad(3, "Hello"); +/// ``` +pub const fn w_pad (x: T::Unit, draw: impl Draw) -> impl Draw { + thunk(move|to: &mut T|draw.draw(todo!())) +} +/// Shrink drawing area symmetrically. +/// +/// ``` +/// let padded = tengri::space::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 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 +} + +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 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 +} + diff --git a/src/term.rs b/src/term.rs index cd31156..bf4df07 100644 --- a/src/term.rs +++ b/src/term.rs @@ -29,7 +29,27 @@ use ::{ /// Implement standard [main] entrypoint for TUI apps. #[macro_export] macro_rules! tui_main { - ($state:expr) => { pub fn main () -> Usually<()> { tui_run_main($state) } } + ($state:expr) => { + pub fn main () -> Usually<()> { tui_run_main($state) } + } +} + +/// Enable TUI keyboard input for main state struct. +#[macro_export] macro_rules! tui_keys { + (|$self:ident:$State:ty,$input:ident|$body:block) => { + impl Apply> for $State { + fn apply (&mut $self, $input: &TuiEvent) -> Usually<()> $body + } + }; +} + +/// Enable TUI output for state struct. +#[macro_export] macro_rules! tui_view { + (|$self:ident: $State:ty|$body:block) => { + impl View for $State { + fn view (&$self) -> impl Draw $body + } + } } pub fn tui_run_main (state: Arc>) -> Usually<()> where @@ -63,15 +83,6 @@ pub fn tui_setup_panic () { })); } -/// Enable TUI keyboard input for main state struct. -#[macro_export] macro_rules! tui_keys { - (|$self:ident:$State:ty,$input:ident|$body:block) => { - impl Apply> for $State { - fn apply (&mut $self, $input: &TuiEvent) -> Usually<()> $body - } - }; -} - /// Spawn the TUI input and output threadsl. pub fn tui_io < T: View + Apply> + Send + Sync + 'static, @@ -117,15 +128,6 @@ pub fn tui_input > + Send + Sync + 'static> ( }) } -/// Enable TUI output for state struct. -#[macro_export] macro_rules! tui_view { - (|$self:ident: $State:ty|$body:block) => { - impl View for $State { - fn view (&$self) -> impl Draw $body - } - } -} - /// Spawn the TUI output thread which writes colored characters to the terminal. /// /// ``` @@ -192,37 +194,31 @@ impl Tui { impl Screen for Tui { type Unit = u16; /// Render drawable in subarea specified by `area` - fn show <'t, T: Draw> ( - &mut self, area: XYWH, content: T - ) -> Usually> { + fn show <'t, T: Draw> (&mut self, content: T) -> Perhaps> { let previous_area = self.1; - self.1 = area; - let result_area = content.draw(self); - self.1 = previous_area; - result_area - } -} - -impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } } - -impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } } - -impl AsMut for Tui { - fn as_mut (&mut self) -> &mut Buffer { - &mut self.0 + if let Some(area) = content.layout(self.1)? { + self.1 = area; + if let Some(result_area) = content.draw(self)? { + self.1 = previous_area; + result_area + } else { + Ok(None) + } + } else { + Ok(None) + } } } +impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } } +impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } } +impl AsMut for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } } +impl Wide for Tui { fn w (&self) -> u16 { self.1.2 } } +impl Tall for Tui { fn h (&self) -> u16 { self.1.3 } } impl HasOrigin for Tui { fn origin (&self) -> Origin { Origin::NW } } - -impl X for Tui { +impl Xy 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 { @@ -520,33 +516,9 @@ pub fn catcher > (result: Usually) -> impl Draw { }) } -/// TUI buffer sized by `usize` instead of `u16`. -#[derive(Default)] pub struct BigBuffer { - pub width: usize, - pub height: usize, - pub content: Vec -} - -impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1)); - -impl_debug!(BigBuffer |self, f| { write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) }); - -impl BigBuffer { - pub fn new (width: usize, height: usize) -> Self { - Self { width, height, content: vec![Cell::default(); width*height] } - } - pub fn get (&self, x: usize, y: usize) -> Option<&Cell> { - let i = self.index_of(x, y); - self.content.get(i) - } - pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> { - let i = self.index_of(x, y); - self.content.get_mut(i) - } - pub fn index_of (&self, x: usize, y: usize) -> usize { - y * self.width + x - } -} +mod event; pub use self::event::*; +mod keys; pub use self::keys::*; +mod buffer; pub use self::buffer::*; use self::colors::*; mod colors { use ratatui::prelude::Color; @@ -572,9 +544,3 @@ use self::colors::*; mod colors { pub const fn tui_title_fg (f: bool) -> Color { if f { tui_ti1() } else { tui_ti2() } } pub const fn tui_yellow () -> Color { Color::Rgb(255,255,0) } } - -#[cfg(feature = "term")] pub mod event; -#[cfg(feature = "term")] pub use self::event::*; - -#[cfg(feature = "term")] pub mod keys; -#[cfg(feature = "term")] pub use self::keys::*; diff --git a/src/term/buffer.rs b/src/term/buffer.rs new file mode 100644 index 0000000..fcd83f1 --- /dev/null +++ b/src/term/buffer.rs @@ -0,0 +1,31 @@ +use crate::*; +use crate::{*, lang::*, draw::*, task::*, exit::*}; +use ::ratatui::buffer::Cell; + +/// TUI buffer sized by `usize` instead of `u16`. +#[derive(Default)] pub struct BigBuffer { + pub width: usize, + pub height: usize, + pub content: Vec +} + +impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1)); + +impl_debug!(BigBuffer |self, f| { write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) }); + +impl BigBuffer { + pub fn new (width: usize, height: usize) -> Self { + Self { width, height, content: vec![Cell::default(); width*height] } + } + pub fn get (&self, x: usize, y: usize) -> Option<&Cell> { + let i = self.index_of(x, y); + self.content.get(i) + } + pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> { + let i = self.index_of(x, y); + self.content.get_mut(i) + } + pub fn index_of (&self, x: usize, y: usize) -> usize { + y * self.width + x + } +} diff --git a/src/text.rs b/src/text.rs index 7eb181e..c9c899b 100644 --- a/src/text.rs +++ b/src/text.rs @@ -25,6 +25,12 @@ pub(crate) use ::unicode_width::*; } } + impl Draw for &std::sync::Arc { + fn draw (self, to: &mut Tui) -> Usually> { + self.as_ref().draw(to) + } + } + impl> Draw for TrimString { fn draw (self, to: &mut Tui) -> Usually> { self.as_ref().draw(to) } }