From e0747124595d045d12136b264c8d30876c0ed8c5 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Fri, 24 Apr 2026 01:34:43 +0300 Subject: [PATCH 01/25] fixes and refactors - use macros for the evals - move some space stuff into submodules - partial update to doctests --- examples/tui_00.rs | 2 +- src/draw.rs | 30 +++--- src/eval.rs | 211 ++++++++++++++++++------------------- src/lib.rs | 41 +++++--- src/sing.rs | 6 +- src/space.rs | 247 ++------------------------------------------ src/space/origin.rs | 100 ++++++++++++++++++ src/space/split.rs | 127 +++++++++++++++++++++++ src/space/xywh.rs | 80 ++++++++++++++ 9 files changed, 464 insertions(+), 380 deletions(-) create mode 100644 src/space/origin.rs create mode 100644 src/space/split.rs create mode 100644 src/space/xywh.rs diff --git a/examples/tui_00.rs b/examples/tui_00.rs index 730efa4..dc166b8 100644 --- a/examples/tui_00.rs +++ b/examples/tui_00.rs @@ -1,6 +1,6 @@ use ::{ std::{io::stdout, sync::{Arc, RwLock}}, - tengri::{*, term::*, lang::*, keys::*, draw::*, space::*, dizzle::*}, + tengri::{*, term::*, lang::*, keys::*, draw::*, space::*, lang::*}, ratatui::style::Color, }; diff --git a/src/draw.rs b/src/draw.rs index ad47922..98ad3ef 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,4 +1,4 @@ -use crate::{*, lang::*, color::*, space::*}; +use crate::{*, space::*}; /// Drawable that supports dynamic dispatch. /// @@ -13,12 +13,12 @@ use crate::{*, lang::*, color::*, space::*}; /// a [Draw]able. /// /// ``` -/// use tengri::draw::*; +/// use tengri::{*, draw::*, space::*}; /// 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 TestScreen) -> Usually> { /// screen.0 |= self.0; /// } /// } @@ -31,12 +31,12 @@ pub trait Draw { fn draw (self, to: &mut T) -> Usually>; } impl> Draw for &D { - fn draw (self, to: &mut T) -> Usually> { + fn draw (self, __: &mut T) -> Usually> { todo!() } } impl> Draw for Option { - fn draw (self, to: &mut T) -> Usually> { + fn draw (self, __: &mut T) -> Usually> { todo!() } } @@ -66,8 +66,9 @@ implUsually>> Draw for Thunk impl tengri::Draw { -/// tengri::when(true, "Yes") +/// # use tengri::draw::*; +/// # fn test () -> impl tengri::draw::Draw { +/// when(true, "Yes") /// # } /// ``` pub const fn when (condition: bool, draw: impl Draw) -> impl Draw { @@ -77,8 +78,9 @@ pub const fn when (condition: bool, draw: impl Draw) -> impl Draw /// Render one thing if a condition is true and another false. /// /// ``` -/// # fn test () -> impl tengri::Draw { -/// tengri::either(true, "Yes", "No") +/// # use tengri::draw::*; +/// # fn test () -> impl tengri::draw::Draw { +/// either(true, "Yes", "No") /// # } /// ``` pub const fn either (condition: bool, a: impl Draw, b: impl Draw) -> impl Draw { @@ -88,13 +90,13 @@ pub const fn either (condition: bool, a: impl Draw, b: impl Draw< /// Output target. /// /// ``` -/// use tengri::*; +/// use tengri::draw::*; /// -/// struct TestOut(impl TwoD); +/// struct TestOut>(T); /// -/// impl Screen for TestOut { +/// impl> Screen for TestOut { /// type Unit = u16; -/// fn place_at + ?Sized> (&mut self, area: impl TwoD, _: &T) { +/// fn place_at + ?Sized> (&mut self, area: T, _: D) { /// println!("placed: {area:?}"); /// () /// } @@ -114,7 +116,7 @@ pub trait Screen: Space + Send + Sync + Sized { } /// Render drawable in subarea specified by `area` fn place_at <'t, T: Draw + ?Sized> ( - &mut self, area: XYWH, content: &'t T + &mut self, _area: XYWH, _content: &'t T ) { unimplemented!() } diff --git a/src/eval.rs b/src/eval.rs index 9cb0048..9e48051 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,10 +1,65 @@ -use crate::{*, term::*, draw::*, ratatui::prelude::*}; -use dizzle::*; +use crate::{*, term::*, draw::*, space::*, lang::*, ratatui::prelude::*}; + +/// Some layout operations exist in XY, X, and Y variants that take 3 or 2 arguments. +/// Their handling in [eval_view] is uniform and goes like this: +macro_rules! eval_xy (( + $name:expr, $expr:expr, $head:expr, $output:ident, $state:ident, + $variant:expr, $arg0: ident, $arg1: ident, $arg2: ident, + $xy:ident, $x:ident, $y:ident +) => {{ + let variant = $variant; + let cb = thunk(move|output: &mut O|$state.interpret(output, &match variant { + Some("x") | Some("y") => $arg1, + Some("xy") | None => $arg2, + _ => panic!("{}: unsupported axis {variant:?}; try /x, /y, /xy", $name) + })); + match variant { + // XY variant (can be omitted) + Some("xy") | None => xy_push( + $state.namespace($arg0?)?.unwrap(), + $state.namespace($arg1?)?.unwrap(), + cb + ).draw($output), + // X variant + Some("x") => x_push( + $state.namespace($arg0?)?.unwrap(), + cb + ).draw($output), + // Y variant + Some("y") => y_push( + $state.namespace($arg0?)?.unwrap(), + cb + ).draw($output), + // Other namespace members are invalid + frag => { + let name = $name; + let expr = $expr; + let head = $head; + unimplemented!( + "{name}/{frag:?} ({expr:?}) ({head:?}) ({:?})", + head.src()?.unwrap_or_default().split("/").next() + ) + } + } +}}); + +/// Some layout operations exist in multiple variants that take a single argument. +/// Their handling in [eval_view] is uniform and goes like this: +macro_rules! eval_enum (( + $name:literal, $output:ident, $state:ident, $value:expr, $arg0: ident, $Enum:ident { + $($v:literal => $V:ident),* $(,)? + } +) => {{ + match $value { + $(Some($v) => $Enum::$V,)* + frag => unimplemented!("{}/{frag:?}", $name) + } +}}); /// Interpret layout operation. /// /// ``` -/// # use tengri::*; +/// # use tengri::{*, space::*}; /// struct Target { xywh: XYWH /*platform-specific*/} /// impl tengri::Out for Target { /// type Unit = u16; @@ -27,137 +82,87 @@ use dizzle::*; /// // TODO test all /// # Ok(()) } /// ``` -#[cfg(feature = "dsl")] pub fn eval_view <'a, O: Screen + 'a, S> ( +pub fn eval_view <'a, O: Screen + 'a, S> ( state: &S, output: &mut O, expr: &'a impl Expression ) -> Usually where - S: Interpret - + for<'b>Namespace<'b, bool> - + for<'b>Namespace<'b, O::Unit> + S: Interpret> + + for<'b> Namespace<'b, bool> + + for<'b> Namespace<'b, O::Unit> + + for<'b> Namespace<'b, Option> { - // First element of expression is used for dispatch. - // Dispatch is proto-namespaced using separator character + // First element of expression is name of the operation. + // These are quasi-namespaced using the separator character, `/`. let head = expr.head()?; let mut frags = head.src()?.unwrap_or_default().split("/"); + // The rest of the tokens in the expr are arguments. // Their meanings depend on the dispatched operation + // Here we just reference them, so that they are in scope. + // Dereferencing them happens in the dispatch branch. let args = expr.tail(); let arg0 = args.head(); let tail0 = args.tail(); let arg1 = tail0.head(); let tail1 = tail0.tail(); let arg2 = tail1.head(); - // And we also have to do the above binding dance - // so that the Perhapss remain in scope. + + // First `frags.next()` calls returns the namespace. match frags.next() { - Some("when") => output.place(&when( + Some("when") => when( state.namespace(arg0?)?.unwrap(), - move|output: &mut O|state.interpret(output, &arg1) - )), + thunk(move|output: &mut O|state.interpret(output, &arg1)) + ).draw(output), - Some("either") => output.place(&either( + Some("either") => either( state.namespace(arg0?)?.unwrap(), - move|output: &mut O|state.interpret(output, &arg1), - move|output: &mut O|state.interpret(output, &arg2), - )), + thunk(move|output: &mut O|state.interpret(output, &arg1)), + thunk(move|output: &mut O|state.interpret(output, &arg2)), + ).draw(output), - Some("bsp") => output.place(&{ - let a = move|output: &mut O|state.interpret(output, &arg0); - let b = move|output: &mut O|state.interpret(output, &arg1); - bsp(match frags.next() { - Some("n") => Alignment::N, - Some("s") => Alignment::S, - Some("e") => Alignment::E, - Some("w") => Alignment::W, - Some("a") => Alignment::A, - Some("b") => Alignment::B, - frag => unimplemented!("bsp/{frag:?}") - }, a, b) - }), - - Some("align") => output.place(&{ - let a = move|output: &mut O|state.interpret(output, &arg0).unwrap(); - align(match frags.next() { - Some("c") => Alignment::Center, - Some("n") => Alignment::N, - Some("s") => Alignment::S, - Some("e") => Alignment::E, - Some("w") => Alignment::W, - Some("x") => Alignment::X, - Some("y") => Alignment::Y, - frag => unimplemented!("align/{frag:?}") - }, a) - }), - - Some("fill") => output.place(&{ - let a = move|output: &mut O|state.interpret(output, &arg0).unwrap(); - match frags.next() { - Some("xy") | None => fill_wh(a), - Some("x") => fill_w(a), - Some("y") => fill_h(a), - frag => unimplemented!("fill/{frag:?}") + // Second `frags.next()` calls returns the namespace member. + Some("bsp") => eval_enum!( + "bsp", output, state, frags.next(), arg0, Split { + "n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below } - }), + ).half( + thunk(move|output: &mut O|state.interpret(output, &arg0)), + thunk(move|output: &mut O|state.interpret(output, &arg1)), + ).draw(output), - Some("exact") => output.place(&{ - let axis = frags.next(); - let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("exact: unsupported axis {axis:?}") }; - let cb = move|output: &mut O|state.interpret(output, &arg).unwrap(); - match axis { - Some("xy") | None => exact_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => exact_w(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => exact_h(state.namespace(arg0?)?.unwrap(), cb), - frag => unimplemented!("exact/{frag:?} ({expr:?}) ({head:?}) ({:?})", - head.src()?.unwrap_or_default().split("/").next()) + Some("align") => eval_enum!( + "align", output, state, frags.next(), arg0, Origin { + "c" => C, "n" => N, "s" => S, "e" => E, "w" => W, "x" => X, "y" => Y } - }), + ).align( + thunk(move|output: &mut O|state.interpret(output, &arg0)) + ).draw(output), - Some("min") => output.place(&{ - let axis = frags.next(); - let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("min: unsupported axis {axis:?}") }; - let cb = move|output: &mut O|state.interpret(output, &arg).unwrap(); - match axis { - Some("xy") | None => min_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => min_w(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => min_h(state.namespace(arg0?)?.unwrap(), cb), - frag => unimplemented!("min/{frag:?}") - } - }), - - Some("max") => output.place(&{ - let axis = frags.next(); - let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("max: unsupported axis {axis:?}") }; - let cb = move|output: &mut O|state.interpret(output, &arg).unwrap(); - match axis { - Some("xy") | None => max_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => max_w(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => max_h(state.namespace(arg0?)?.unwrap(), cb), - frag => unimplemented!("max/{frag:?}") - } - }), - - Some("push") => output.place(&{ - let axis = frags.next(); - let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("push: unsupported axis {axis:?}") }; - let cb = move|output: &mut O|state.interpret(output, &arg); - match axis { - Some("xy") | None => push_xy(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => push_x(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => push_y(state.namespace(arg0?)?.unwrap(), cb), - frag => unimplemented!("push/{frag:?}") - } - }), + Some("exact") => eval_xy!( + "exact", expr, head, output, state, frags.next(), arg0, arg1, arg2, wh_exact, w_exact, h_exact + ), + Some("min") => eval_xy!( + "min", expr, head, output, state, frags.next(), arg0, arg1, arg2, wh_min, w_min, h_min + ), + Some("max") => eval_xy!( + "max", expr, head, output, state, frags.next(), arg0, arg1, arg2, wh_max, w_max, h_max + ), + Some("push") => eval_xy!( + "push", expr, head, output, state, frags.next(), arg0, arg1, arg2, xy_push, x_push, y_push + ), _ => return Ok(false) - }; + }?; + Ok(true) } + /// Interpret TUI-specific layout operation. /// /// ``` -/// use tengri::{Namespace, Interpret, Tui, ratatui::prelude::Color}; +/// use tengri::{lang::{Namespace, Interpret}, term::Tui, ratatui::prelude::Color}; /// /// struct State; /// impl<'b> Namespace<'b, bool> for State {} @@ -189,8 +194,6 @@ pub fn eval_view_tui <'a, S> ( let arg0 = args.head(); let tail0 = args.tail(); let arg1 = tail0.head(); - let tail1 = tail0.tail(); - let arg2 = tail1.head(); match frags.next() { Some("text") => { diff --git a/src/lib.rs b/src/lib.rs index e7d22ac..c7fe21d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,22 @@ #![feature(anonymous_lifetime_in_impl_trait)] -#![feature(associated_type_defaults)] -#![feature(const_default)] -#![feature(const_option_ops)] +//#![feature(associated_type_defaults)] +//#![feature(const_default)] +//#![feature(const_option_ops)] #![feature(const_precise_live_drops)] #![feature(const_trait_impl)] -#![feature(impl_trait_in_assoc_type)] +//#![feature(impl_trait_in_assoc_type)] #![feature(step_trait)] -#![feature(trait_alias)] -#![feature(type_alias_impl_trait)] -#![feature(type_changing_struct_update)] +//#![feature(trait_alias)] +//#![feature(type_alias_impl_trait)] +//#![feature(type_changing_struct_update)] pub extern crate atomic_float; pub extern crate palette; pub extern crate better_panic; pub extern crate unicode_width; +#[cfg(feature = "sing")] pub extern crate jack; +#[cfg(feature = "term")] pub extern crate ratatui; +#[cfg(feature = "term")] pub extern crate crossterm; +#[cfg(feature = "lang")] pub extern crate dizzle as lang; #[cfg(test)] #[macro_use] pub extern crate proptest; pub(crate) use ::{ @@ -23,27 +27,30 @@ pub(crate) use ::{ std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::*}, }; -#[cfg(feature = "lang")] pub extern crate dizzle as lang; -#[cfg(feature = "lang")] pub use ::dizzle::{self, Usually, Perhaps, impl_default}; +#[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; - -#[cfg(feature = "sing")] pub extern crate jack; +/// 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; +/// Terminal keyboard input. #[cfg(feature = "term")] pub mod keys; -#[cfg(feature = "term")] pub extern crate ratatui; -#[cfg(feature = "term")] pub extern crate crossterm; /// Define a trait an implement it for various mutation-enabled wrapper types. */ #[macro_export] macro_rules! flex_trait_mut ( diff --git a/src/sing.rs b/src/sing.rs index a7903b8..7716129 100644 --- a/src/sing.rs +++ b/src/sing.rs @@ -25,7 +25,7 @@ pub trait Audio { /// Wraps [JackState], and through it [jack::Client] when connected. /// /// ``` -/// let jack = tengri::Jack::default(); +/// let jack = tengri::sing::Jack::default(); /// ``` #[derive(Clone, Debug, Default)] pub struct Jack<'j> ( pub(crate) Arc>> @@ -36,7 +36,7 @@ pub trait Audio { /// [jack::Client], which you can use to talk to the JACK API. /// /// ``` -/// let state = tengri::JackState::default(); +/// let state = tengri::sing::JackState::default(); /// ``` #[derive(Debug, Default)] pub enum JackState<'j> { /// Unused @@ -223,7 +223,7 @@ impl JackPerfModel for PerfModel { /// Things that can provide a [jack::Client] reference. /// /// ``` -/// use tengri::{Jack, HasJack}; +/// use tengri::sing::{Jack, HasJack}; /// /// let jack: &Jack = Jacked::default().jack(); /// diff --git a/src/space.rs b/src/space.rs index 2d2033f..a2f7010 100644 --- a/src/space.rs +++ b/src/space.rs @@ -1,144 +1,14 @@ use crate::{*, draw::*}; #[cfg(test)] use proptest_derive::Arbitrary; -/// 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 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 { 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)) - } - } -} +mod xywh; +pub use self::xywh::*; -/// 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!() - } -} +mod origin; +pub use self::origin::*; -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) -} - -/// 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) -} +mod split; +pub use self::split::*; /// A numeric type that can be used as coordinate. /// @@ -174,88 +44,6 @@ pub trait Coord: Send + Sync + Copy 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_at(area_b, &b); - to.place_at(area_b, &a); - }, - _ => { - to.place_at(area_a, &a); - to.place_at(area_a, &b); - } - } - 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; @@ -426,29 +214,6 @@ 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 { - ($head:expr $(,)?) => { $head }; - ($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) }; -} -#[macro_export] macro_rules! south { - ($head:expr $(,)?) => { $head }; - ($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) }; -} -#[macro_export] macro_rules! east { - ($head:expr $(,)?) => { $head }; - ($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) }; -} -#[macro_export] macro_rules! west { - ($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) }; -} -#[macro_export] macro_rules! above { - ($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) }; -} -#[macro_export] macro_rules! below { - ($head:expr $(,)?) => { $head }; - ($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) }; -} - /// Iterate over a collection of renderables: /// /// ``` diff --git a/src/space/origin.rs b/src/space/origin.rs new file mode 100644 index 0000000..256a7f6 --- /dev/null +++ b/src/space/origin.rs @@ -0,0 +1,100 @@ +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/split.rs b/src/space/split.rs new file mode 100644 index 0000000..fabcb6b --- /dev/null +++ b/src/space/split.rs @@ -0,0 +1,127 @@ +use super::*; + +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) +} + +/// Split along an axis. Direction determines order. +#[cfg_attr(test, derive(Arbitrary))] +#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split { + North, + South, + East, + West, + Above, + #[default] Below +} + +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_at(area_b, &b); + to.place_at(area_b, &a); + }, + _ => { + to.place_at(area_a, &a); + to.place_at(area_a, &b); + } + } + 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), + } + } + +} + +#[macro_export] macro_rules! north { + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) }; +} + +#[macro_export] macro_rules! south { + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) }; +} + +#[macro_export] macro_rules! east { + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) }; +} + +#[macro_export] macro_rules! west { + ($head:expr $(,)?) => { $head }; + ($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) }; +} + +#[macro_export] macro_rules! above { + ($head:expr $(,)?) => { $head }; + ($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) }; +} + +#[macro_export] macro_rules! below { + ($head:expr $(,)?) => { $head }; + ($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) }; +} diff --git a/src/space/xywh.rs b/src/space/xywh.rs new file mode 100644 index 0000000..a4047db --- /dev/null +++ b/src/space/xywh.rs @@ -0,0 +1,80 @@ +use super::*; + +/// 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 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 { + 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)) + } + } + +} From 42a1807c2b25c8c103d33f680e579362ce8e9dc7 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Fri, 24 Apr 2026 01:44:03 +0300 Subject: [PATCH 02/25] fix some more doctest errors --- src/draw.rs | 36 ++++++++++++++++++++++++++---------- src/eval.rs | 4 ++-- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/draw.rs b/src/draw.rs index 98ad3ef..67ee223 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,4 +1,20 @@ -use crate::{*, space::*}; +use crate::*; +pub use crate::space::*; + +/// Implement the [Draw] trait for a particular drawable and [Screen]. +/// +/// ``` +/// use tengri::{*, draw::*, term::*}; +/// struct MyDrawable; +/// impl_draw!(|self: MyDrawable, to: Tui|{ +/// todo!() +/// }); +/// ``` +#[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 +} }); /// Drawable that supports dynamic dispatch. /// @@ -13,12 +29,14 @@ use crate::{*, space::*}; /// a [Draw]able. /// /// ``` -/// use tengri::{*, draw::*, space::*}; +/// use tengri::{*, draw::*}; /// struct TestScreen(bool); /// impl Screen for TestScreen { type Unit = u16; } +/// impl X for TestScreen { fn x (&self) -> u16 { 0 } } +/// impl Y for TestScreen { fn y (&self) -> u16 { 0 } } /// struct TestWidget(bool); /// impl Draw for TestWidget { -/// fn draw (&self, screen: &mut TestScreen) -> Usually> { +/// fn draw (self, screen: &mut TestScreen) -> Usually> { /// screen.0 |= self.0; /// } /// } @@ -90,11 +108,11 @@ pub const fn either (condition: bool, a: impl Draw, b: impl Draw< /// Output target. /// /// ``` -/// use tengri::draw::*; +/// use tengri::{*, draw::*}; /// /// struct TestOut>(T); /// -/// impl> Screen for TestOut { +/// impl> Screen for TestOut { /// type Unit = u16; /// fn place_at + ?Sized> (&mut self, area: T, _: D) { /// println!("placed: {area:?}"); @@ -102,11 +120,9 @@ pub const fn either (condition: bool, a: impl Draw, b: impl Draw< /// } /// } /// -/// impl Draw for String { -/// fn draw (&self, to: &mut TestOut) -> Usually> { -/// to.area_mut().set_w(self.len() as u16); -/// } -/// } +/// 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; diff --git a/src/eval.rs b/src/eval.rs index 9e48051..c87eb56 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -59,7 +59,7 @@ macro_rules! eval_enum (( /// Interpret layout operation. /// /// ``` -/// # use tengri::{*, space::*}; +/// # use tengri::{lang::*, draw::*, eval::*}; /// struct Target { xywh: XYWH /*platform-specific*/} /// impl tengri::Out for Target { /// type Unit = u16; @@ -162,7 +162,7 @@ pub fn eval_view <'a, O: Screen + 'a, S> ( /// Interpret TUI-specific layout operation. /// /// ``` -/// use tengri::{lang::{Namespace, Interpret}, term::Tui, ratatui::prelude::Color}; +/// use tengri::{lang::*, term::*, eval::*, ratatui::prelude::Color}; /// /// struct State; /// impl<'b> Namespace<'b, bool> for State {} From 145047b7ff09eab9d796d52b78fd896aaf75d0c2 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Fri, 24 Apr 2026 02:06:04 +0300 Subject: [PATCH 03/25] more space/ and draw/ modules - 26 errors and 16 doctest fails - getting there, perpetually --- src/draw.rs | 117 +++---------------------------------------- src/draw/draw.rs | 60 ++++++++++++++++++++++ src/draw/screen.rs | 34 +++++++++++++ src/draw/thunk.rs | 16 ++++++ src/draw/view.rs | 8 +++ src/eval.rs | 10 ++-- src/sing.rs | 4 +- src/space.rs | 118 ++++++-------------------------------------- src/space/coord.rs | 35 +++++++++++++ src/space/iter.rs | 46 +++++++++++++++++ src/space/origin.rs | 2 +- src/space/size.rs | 28 +++++++++++ src/space/split.rs | 12 ++--- src/space/xywh.rs | 5 +- src/term.rs | 6 +-- 15 files changed, 269 insertions(+), 232 deletions(-) create mode 100644 src/draw/draw.rs create mode 100644 src/draw/screen.rs create mode 100644 src/draw/thunk.rs create mode 100644 src/draw/view.rs create mode 100644 src/space/coord.rs create mode 100644 src/space/iter.rs create mode 100644 src/space/size.rs diff --git a/src/draw.rs b/src/draw.rs index 67ee223..29dbac7 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,85 +1,17 @@ use crate::*; pub use crate::space::*; -/// Implement the [Draw] trait for a particular drawable and [Screen]. -/// -/// ``` -/// use tengri::{*, draw::*, term::*}; -/// struct MyDrawable; -/// impl_draw!(|self: MyDrawable, to: Tui|{ -/// todo!() -/// }); -/// ``` -#[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 -} }); +mod draw; +pub use self::draw::*; -/// Drawable that supports dynamic dispatch. -/// -/// Drawables are composable, e.g. the [when] and [either] conditionals -/// or the layout constraints. -/// -/// Drawables are consumable, i.e. the [Draw::draw] method receives an -/// owned `self` and does not return it, consuming the drawable. -/// -/// To draw a thing multiple times, instead of explicitly constructing it -/// every time, implement the [View] trait instead, which will construct -/// a [Draw]able. -/// -/// ``` -/// use tengri::{*, draw::*}; -/// struct TestScreen(bool); -/// impl Screen for TestScreen { type Unit = u16; } -/// impl X for TestScreen { fn x (&self) -> u16 { 0 } } -/// impl Y for TestScreen { fn y (&self) -> u16 { 0 } } -/// struct TestWidget(bool); -/// impl Draw for TestWidget { -/// fn draw (self, screen: &mut TestScreen) -> Usually> { -/// screen.0 |= self.0; -/// } -/// } -/// let mut screen = TestScreen(false); -/// TestWidget(false).draw(&mut screen); -/// TestWidget(true).draw(&mut screen); -/// TestWidget(false).draw(&mut screen); -/// ``` -pub trait Draw { - fn draw (self, to: &mut T) -> Usually>; -} -impl> Draw for &D { - fn draw (self, __: &mut T) -> Usually> { - todo!() - } -} -impl> Draw for Option { - fn draw (self, __: &mut T) -> Usually> { - todo!() - } -} +mod view; +pub use self::view::*; -/// Emit a [Draw]able. -/// -/// Speculative. How to avoid conflicts with [Draw] proper? -pub trait View { - fn view (&self) -> impl Draw; -} +mod thunk; +pub use self::thunk::*; -pub const fn thunk Usually>> (draw: F) -> Thunk { - Thunk(draw, std::marker::PhantomData) -} - -/// 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) - } -} +mod screen; +pub use self::screen::*; /// Only render when condition is true. /// @@ -104,36 +36,3 @@ pub const fn when (condition: bool, draw: impl Draw) -> impl Draw 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) }) } - -/// Output target. -/// -/// ``` -/// use tengri::{*, draw::*}; -/// -/// struct TestOut>(T); -/// -/// impl> Screen for TestOut { -/// type Unit = u16; -/// fn place_at + ?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 area. - fn place <'t, T: Draw + ?Sized> (&mut self, content: &'t T) { - self.place_at(self.xywh(), content) - } - /// Render drawable in subarea specified by `area` - fn place_at <'t, T: Draw + ?Sized> ( - &mut self, _area: XYWH, _content: &'t T - ) { - unimplemented!() - } -} diff --git a/src/draw/draw.rs b/src/draw/draw.rs new file mode 100644 index 0000000..8701084 --- /dev/null +++ b/src/draw/draw.rs @@ -0,0 +1,60 @@ +use super::*; + +/// Implement the [Draw] trait for a particular drawable and [Screen]. +/// +/// ``` +/// use tengri::{*, draw::*, term::*}; +/// struct MyDrawable; +/// impl_draw!(|self: MyDrawable, to: Tui|{ +/// todo!() +/// }); +/// ``` +#[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 +} }); + +/// Drawable that supports dynamic dispatch. +/// +/// Drawables are composable, e.g. the [when] and [either] conditionals +/// or the layout constraints. +/// +/// Drawables are consumable, i.e. the [Draw::draw] method receives an +/// owned `self` and does not return it, consuming the drawable. +/// +/// To draw a thing multiple times, instead of explicitly constructing it +/// every time, implement the [View] trait instead, which will construct +/// a [Draw]able. +/// +/// ``` +/// use tengri::{*, draw::*}; +/// struct MyScreen(bool); +/// impl Screen for MyScreen { type Unit = u16; } +/// impl X for MyScreen { fn x (&self) -> u16 { 0 } } +/// impl Y for MyScreen { fn y (&self) -> u16 { 0 } } +/// struct MyWidget(bool); +/// impl Draw for MyWidget { +/// fn draw (self, screen: &mut MyScreen) -> Usually> { +/// screen.0 |= self.0; +/// } +/// } +/// let mut screen = MyScreen(false); +/// MyWidget(false).draw(&mut screen); +/// MyWidget(true).draw(&mut screen); +/// MyWidget(false).draw(&mut screen); +/// ``` +pub trait Draw { + fn draw (self, to: &mut T) -> Usually>; +} +impl> Draw for &D { + fn draw (self, __: &mut T) -> Usually> { + todo!() + } +} +impl> Draw for Option { + fn draw (self, __: &mut T) -> Usually> { + todo!() + } +} + diff --git a/src/draw/screen.rs b/src/draw/screen.rs new file mode 100644 index 0000000..dc51744 --- /dev/null +++ b/src/draw/screen.rs @@ -0,0 +1,34 @@ +use super::*; + +/// Output target. +/// +/// ``` +/// use tengri::{*, draw::*}; +/// +/// struct TestOut>(T); +/// +/// impl> Screen for TestOut { +/// type Unit = u16; +/// fn place_at + ?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 area. + fn place <'t, T: Draw + ?Sized> (&mut self, content: &'t T) { + self.place_at(self.xywh(), content) + } + /// Render drawable in subarea specified by `area` + fn place_at <'t, T: Draw + ?Sized> ( + &mut self, _area: XYWH, _content: &'t T + ) { + unimplemented!() + } +} diff --git a/src/draw/thunk.rs b/src/draw/thunk.rs new file mode 100644 index 0000000..9b1d4b5 --- /dev/null +++ b/src/draw/thunk.rs @@ -0,0 +1,16 @@ +use super::*; + +pub const fn thunk Usually>> (draw: F) -> Thunk { + Thunk(draw, std::marker::PhantomData) +} + +/// 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) + } +} diff --git a/src/draw/view.rs b/src/draw/view.rs new file mode 100644 index 0000000..82fe908 --- /dev/null +++ b/src/draw/view.rs @@ -0,0 +1,8 @@ +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 c87eb56..4fc5934 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -172,11 +172,11 @@ pub fn eval_view <'a, O: Screen + 'a, S> ( /// # fn main () -> tengri::Usually<()> { /// let state = State; /// let mut out = Tui::default(); -/// tengri::eval_view_tui(&state, &mut out, "")?; -/// tengri::eval_view_tui(&state, &mut out, "text Hello world!")?; -/// tengri::eval_view_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?; -/// tengri::eval_view_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?; -/// tengri::eval_view_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?; +/// tengri::eval::eval_view_tui(&state, &mut out, "")?; +/// tengri::eval::eval_view_tui(&state, &mut out, "text Hello world!")?; +/// tengri::eval::eval_view_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?; +/// tengri::eval::eval_view_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?; +/// tengri::eval::eval_view_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?; /// # Ok(()) } /// ``` pub fn eval_view_tui <'a, S> ( diff --git a/src/sing.rs b/src/sing.rs index 7716129..cd76eb2 100644 --- a/src/sing.rs +++ b/src/sing.rs @@ -52,7 +52,7 @@ pub trait Audio { /// Event enum for JACK events. /// /// ``` -/// let event = tengri::JackEvent::XRun; +/// let event = tengri::sing::JackEvent::XRun; // kerpop /// ``` #[derive(Debug, Clone, PartialEq)] pub enum JackEvent { ThreadInit, @@ -70,7 +70,7 @@ pub trait Audio { /// Generic notification handler that emits [JackEvent] /// /// ``` -/// let notify = tengri::JackNotify(|_|{}); +/// let notify = tengri::sing::JackNotify(|_|{}); /// ``` pub struct JackNotify(pub T); diff --git a/src/space.rs b/src/space.rs index a2f7010..9a8650c 100644 --- a/src/space.rs +++ b/src/space.rs @@ -1,6 +1,9 @@ use crate::{*, draw::*}; #[cfg(test)] use proptest_derive::Arbitrary; +mod coord; +pub use self::coord::*; + mod xywh; pub use self::xywh::*; @@ -10,39 +13,11 @@ pub use self::origin::*; mod split; pub use self::split::*; -/// 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()) } -} +mod iter; +pub use self::iter::*; + +mod size; +pub use self::size::*; /// Horizontal axis. pub trait X { @@ -89,7 +64,7 @@ pub const fn w_exact (w: T::Unit, c: impl Draw) -> impl Draw { /// Shrink drawing area symmetrically. /// /// ``` -/// let padded = tengri::W(3).pad("Hello"); +/// 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!())) @@ -139,7 +114,7 @@ pub const fn h_exact (h: T::Unit, c: impl Draw) -> impl Draw { /// Shrink drawing area symmetrically. /// /// ``` -/// let padded = tengri::W::pad(3, "Hello"); +/// 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!())) @@ -170,7 +145,7 @@ pub const fn wh_pad (w: T::Unit, h: T::Unit, draw: impl Draw) /// Only draw content if area is above a certain size. /// /// ``` -/// let min = tengri::wh_min(3, 5, "Hello"); // 5x5 +/// let min = tengri::space::wh_min(3, 5, "Hello"); // 5x5 /// ``` pub const fn wh_min (w: Option, h: Option, draw: impl Draw) -> impl Draw @@ -181,7 +156,7 @@ pub const fn wh_min (w: Option, h: Option, draw: i /// Set the maximum width and/or height of the content. /// /// ``` -/// let max = tengri::wh_max(Some(3), Some(5), "Hello"); +/// 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 @@ -192,7 +167,7 @@ pub const fn wh_max (w: Option, h: Option, draw: i /// Set the maximum width and/or height of the content. /// /// ``` -/// let exact = tengri::wh_exact(Some(3), Some(5), "Hello"); +/// 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 @@ -202,7 +177,7 @@ pub const fn wh_exact (w: Option, h: Option, draw: /// Limit size of drawing area /// ``` -/// let clipped = tengri::wh_clip(Some(3), Some(5), "Hello"); +/// let clipped = tengri::space::wh_clip(Some(3), Some(5), "Hello"); /// ``` pub const fn wh_clip ( w: Option, h: Option, draw: impl Draw @@ -213,68 +188,3 @@ pub const fn wh_clip ( 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 } - -/// 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 < - S: Screen, - D: Draw, - V: Fn()->I, - I: Iterator, - F: Fn(&D)->dyn Draw, -> (_items: V, _cb: F) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - -pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( - _iter: impl Fn()->I, _draw: impl Fn(D)->U, -) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - -pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( - _iter: impl Fn()->I, _draw: impl Fn(D)->U, -) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - -pub fn iter_south <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( - _iter: impl Fn()->I, _draw: impl Fn(D)->U, -) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - -pub fn iter_west <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( - _iter: impl Fn()->I, _draw: impl Fn(D)->U, -) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - -#[derive(Default, Debug)] -pub struct Size(AtomicUsize, AtomicUsize); -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 { - pub const fn of (&self, of: impl Draw) -> impl Draw { - thunk(move|to: &mut T|{ - let area = of.draw(to)?; - self.0.store(area.w().into(), Relaxed); - self.1.store(area.h().into(), Relaxed); - Ok(area) - }) - } -} diff --git a/src/space/coord.rs b/src/space/coord.rs new file mode 100644 index 0000000..f5e6974 --- /dev/null +++ b/src/space/coord.rs @@ -0,0 +1,35 @@ +use super::*; + +/// 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()) } +} diff --git a/src/space/iter.rs b/src/space/iter.rs new file mode 100644 index 0000000..4bcfde8 --- /dev/null +++ b/src/space/iter.rs @@ -0,0 +1,46 @@ +use super::*; + +/// 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 < + S: Screen, + D: Draw, + V: Fn()->I, + I: Iterator, + F: Fn(&D)->dyn Draw, +> (_items: V, _cb: F) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +pub fn iter_south <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +pub fn iter_west <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + diff --git a/src/space/origin.rs b/src/space/origin.rs index 256a7f6..f3f7acc 100644 --- a/src/space/origin.rs +++ b/src/space/origin.rs @@ -73,7 +73,7 @@ impl> Draw for Anchor { /// /// ``` /// use tengri::draw::Origin; -/// let _ = Origin::NW.align(()) +/// let _ = Origin::NW.align(()); /// ``` #[cfg_attr(test, derive(Arbitrary))] #[derive(Debug, Copy, Clone, Default)] pub enum Origin { diff --git a/src/space/size.rs b/src/space/size.rs new file mode 100644 index 0000000..add71ae --- /dev/null +++ b/src/space/size.rs @@ -0,0 +1,28 @@ +use super::*; + +/// Uses [AtomicUsize] to measure size during\ +/// rendering (which is normally read-only). +#[derive(Default, Debug)] +pub struct Size(AtomicUsize, AtomicUsize); + +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 { + pub const fn of (&self, of: impl Draw) -> impl Draw { + thunk(move|to: &mut T|{ + let area = of.draw(to)?; + self.0.store(area.w().into(), Relaxed); + self.1.store(area.h().into(), Relaxed); + Ok(area) + }) + } +} + diff --git a/src/space/split.rs b/src/space/split.rs index fabcb6b..f6f53b2 100644 --- a/src/space/split.rs +++ b/src/space/split.rs @@ -39,12 +39,12 @@ impl Split { /// ``` /// use tengri::draw::Split::*; - /// let _ = Above.bsp((), ()); - /// let _ = Below.bsp((), ()); - /// let _ = North.bsp((), ()); - /// let _ = South.bsp((), ()); - /// let _ = East.bsp((), ()); - /// let _ = West.bsp((), ()); + /// let _ = Above.half("", ""); + /// let _ = Below.half("", ""); + /// let _ = North.half("", ""); + /// let _ = South.half("", ""); + /// let _ = East.half("", ""); + /// let _ = West.half("", ""); /// ``` pub const fn half (&self, a: impl Draw, b: impl Draw) -> impl Draw { thunk(move|to: &mut T|{ diff --git a/src/space/xywh.rs b/src/space/xywh.rs index a4047db..c403f5a 100644 --- a/src/space/xywh.rs +++ b/src/space/xywh.rs @@ -3,8 +3,9 @@ use super::*; /// Point with size. /// /// ``` -/// let xywh = tengri::XYWH(0u16, 0, 0, 0); -/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(20, 20)); +/// use tengri::space::{XY, XYWH}; +/// let xywh = XYWH(0u16, 0, 0, 0); +/// assert_eq!(XYWH(10u16, 10, 20, 20).center(), XY(20, 20)); /// ``` /// /// * [ ] TODO: origin field (determines at which corner/side is X0 Y0) diff --git a/src/term.rs b/src/term.rs index 2251432..4be440c 100644 --- a/src/term.rs +++ b/src/term.rs @@ -645,9 +645,9 @@ pub const fn border (on: bool, style: S, draw: impl Draw> (result: Usually) -> impl Draw { thunk(move|to: &mut Tui|match result { From be14173126664657e283c892fb8211e2de2d4c05 Mon Sep 17 00:00:00 2001 From: facile pop culture reference Date: Sat, 25 Apr 2026 00:02:58 +0300 Subject: [PATCH 04/25] well, one tui example works again --- examples/tui_00.rs | 2 +- src/color.rs | 4 +- src/draw.rs | 6 +- src/draw/screen.rs | 2 +- src/exit.rs | 1 - src/keys.rs | 85 +++++---- src/term.rs | 463 ++++++++++++++------------------------------- src/term/border.rs | 239 +++++++++++++++++++++++ 8 files changed, 440 insertions(+), 362 deletions(-) create mode 100644 src/term/border.rs diff --git a/examples/tui_00.rs b/examples/tui_00.rs index dc166b8..1ac73c5 100644 --- a/examples/tui_00.rs +++ b/examples/tui_00.rs @@ -10,7 +10,7 @@ tui_main!(State { }); tui_keys!(|self: State, input| { - todo!() + Ok(()) }); tui_view!(|self: State| { diff --git a/src/color.rs b/src/color.rs index f16358a..82bdc70 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,10 +1,10 @@ -use ::ratatui::style::Color; use crate::lang::impl_from; +use ::ratatui::style::Color; +use ::rand::distributions::uniform::UniformSampler; 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 { let term = Color::Rgb(r, g, b); diff --git a/src/draw.rs b/src/draw.rs index 29dbac7..d6462fc 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,6 +1,3 @@ -use crate::*; -pub use crate::space::*; - mod draw; pub use self::draw::*; @@ -13,6 +10,9 @@ pub use self::thunk::*; mod screen; pub use self::screen::*; +use crate::*; +pub use crate::space::*; + /// Only render when condition is true. /// /// ``` diff --git a/src/draw/screen.rs b/src/draw/screen.rs index dc51744..ea6b858 100644 --- a/src/draw/screen.rs +++ b/src/draw/screen.rs @@ -29,6 +29,6 @@ pub trait Screen: Space + Send + Sync + Sized { fn place_at <'t, T: Draw + ?Sized> ( &mut self, _area: XYWH, _content: &'t T ) { - unimplemented!() + unimplemented!("place_at") } } diff --git a/src/exit.rs b/src/exit.rs index 0b79708..afdc2ad 100644 --- a/src/exit.rs +++ b/src/exit.rs @@ -14,4 +14,3 @@ impl AsRef> for Exit { &self.0 } } - diff --git a/src/keys.rs b/src/keys.rs index 9cc54e1..ba56615 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -7,8 +7,17 @@ use ::crossterm::event::{ read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState }; +/// 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 thread which reads keys from the terminal. -pub fn tui_input > + Send + Sync + 'static> ( +pub fn tui_input > + Send + Sync + 'static> ( exited: &Arc, state: &Arc>, poll: Duration ) -> Result { let exited = exited.clone(); @@ -39,8 +48,11 @@ pub fn tui_input > + Send + Sync + 'static> ( /// 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) @@ -51,6 +63,7 @@ impl Ord for TuiEvent { /// 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 { @@ -67,43 +80,43 @@ impl TuiKey { 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('}'), + "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), + "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/term.rs b/src/term.rs index 4be440c..ef9ddd1 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,4 +1,7 @@ -use crate::{*, lang::*, draw::*, space::{*, Split::*}, color::*, text::*, task::*}; +mod border; +pub use self::border::*; + +use crate::{*, lang::*, draw::*, task::*}; //use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; //use rand::distributions::uniform::UniformSampler; use ::{ @@ -7,51 +10,84 @@ use ::{ time::Duration, ops::{Deref, DerefMut}, }, - better_panic::{Settings, Verbosity}, ratatui::{ - prelude::{Style, Buffer as ScreenBuffer, Position, Backend, Color}, + prelude::{Style, Position, Backend, Color}, style::{Modifier, Color::*}, backend::{CrosstermBackend, ClearType}, layout::{Size, Rect}, buffer::{Buffer, Cell}, + crossterm::{ + ExecutableCommand, + terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, + //event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, + } }, - crossterm::{ - ExecutableCommand, - terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, - //event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, - } }; #[macro_export] macro_rules! tui_main { ($state:expr) => { pub fn main () -> Usually<()> { - tengri::exit::Exit::run(|exit|{ - let state = Arc::new(RwLock::new($state)); - let input = ::tengri::keys::tui_input( - exit.as_ref(), - &state, - ::std::time::Duration::from_millis(100) - )?; - let output = ::tengri::term::tui_output( - stdout(), - exit.as_ref(), - &state, - ::std::time::Duration::from_millis(10) - )?; + use ::{ + std::sync::{ + Arc, + RwLock + }, + std::panic::{ + set_hook, + PanicHookInfo, + }, + std::time::{ + Duration + }, + better_panic::{ + Settings, + Verbosity + }, + ratatui::{ + backend::{ + Backend, + CrosstermBackend + }, + crossterm::{ + ExecutableCommand, + terminal::{ + disable_raw_mode, + LeaveAlternateScreen + } + } + }, + tengri::{ + exit::Exit, + keys::tui_input, + term::tui_output, + } + }; + let panic = Settings::auto() + .verbosity(Verbosity::Full) + .create_panic_handler(); + set_hook(Box::new(move |info: &PanicHookInfo|{ + stdout().execute(LeaveAlternateScreen).unwrap(); + CrosstermBackend::new(stdout()).show_cursor().unwrap(); + disable_raw_mode().unwrap(); + panic(info); + })); + Exit::run(|exit|{ + let state = Arc::new(RwLock::new($state)); + let scan = Duration::from_millis(100); + let input = tui_input(exit.as_ref(), &state, scan)?; + let frame = Duration::from_millis(10); + let output = tui_output(stdout(), exit.as_ref(), &state, frame)?; + output.join(); + stdout().execute(LeaveAlternateScreen)?; + CrosstermBackend::new(stdout()).show_cursor()?; + disable_raw_mode()?; Ok(()) }) } } } -#[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 { @@ -61,42 +97,94 @@ use ::{ } /// Spawn the TUI output thread which writes colored characters to the terminal. -pub fn tui_output + Send + Sync + 'static> ( +pub fn tui_output < + W: Write + Send + Sync + 'static, T: View + Send + Sync + 'static +> ( output: W, exited: &Arc, state: &Arc>, sleep: Duration ) -> Usually { let state = state.clone(); - tui_setup()?; + stdout().execute(EnterAlternateScreen)?; + CrosstermBackend::new(stdout()).hide_cursor()?; + enable_raw_mode()?; let mut backend = CrosstermBackend::new(output); let Size { width, height } = backend.size().expect("get size failed"); - let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height }); - let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height }); + let mut prev = Tui::new(width, height); + let mut next = Tui::new(width, height); Ok(Task::new_sleep(exited.clone(), sleep, move |perf| { let Size { width, height } = backend.size().expect("get size failed"); if let Ok(state) = state.try_read() { - tui_resize(&mut backend, &mut buffer_a, Rect { x: 0, y: 0, width, height }); - tui_redraw(&mut backend, &mut buffer_a, &mut buffer_b); + prev.resize(&mut backend, width, height); + state.view().draw(&mut next).expect("draw failed"); // TODO draw error + prev.redraw(&mut backend, &mut next); + //tui_redraw(&mut backend, &mut prev, &mut next); } let timer = format!("{:>3.3}ms", perf.used.load(Relaxed)); - buffer_a.set_string(0, 0, &timer, Style::default()); + prev.set_string(0, 0, &timer, Style::default()); })?) } -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 } } +pub fn tui_redraw <'b, W: Write> ( + back: &mut CrosstermBackend, + mut prev: &'b mut Buffer, + mut next: &'b mut Buffer +) { + let updates = prev.diff(&next); + back.draw(updates.into_iter()).expect("failed to render"); + Backend::flush(back).expect("failed to flush output new"); + std::mem::swap(&mut prev, &mut next); + next.reset(); +} + +/// Terminal output. +pub struct Tui( + /// Ratatui buffer; area is screen size + pub Buffer, + /// Current draw area + pub XYWH +); + +impl Tui { + fn new (width: u16, height: u16) -> Self { + Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height)) + } + fn resize (&mut self, back: &mut CrosstermBackend, width: u16, height: u16) { + let size = Rect { x: 0, y: 0, width, height }; + if self.0.area != size { + back.clear_region(ClearType::All).unwrap(); + self.0.resize(size); + self.0.reset(); + } + } + fn redraw <'b, W: Write> (&'b mut self, back: &mut CrosstermBackend, mut next: &'b mut Self) { + let updates = self.0.diff(&next.0); + back.draw(updates.into_iter()).expect("failed to render"); + Backend::flush(back).expect("failed to flush output new"); + std::mem::swap(self, &mut next); + next.0.reset(); + } +} + +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 { pub fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH { for row in 0..self.h() { @@ -127,6 +215,7 @@ impl Tui { } } } + /// Apply foreground color. pub const fn fg (fg: Color, draw: impl Draw) -> impl Draw { thunk(move|to: &mut Tui|{ @@ -134,6 +223,7 @@ pub const fn fg (fg: Color, draw: impl Draw) -> impl Draw { draw.draw(to) }) } + /// Apply background color. pub const fn bg (bg: Color, draw: impl Draw) -> impl Draw { thunk(move|to: &mut Tui|{ @@ -141,17 +231,20 @@ pub const fn bg (bg: Color, draw: impl Draw) -> impl Draw { draw.draw(to) }) } + pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw) -> impl Draw { thunk(move|to: &mut Tui|{ to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); }); draw.draw(to) }) } + pub const fn fill_char (c: char) -> impl Draw { thunk(move|to: &mut Tui|Ok(to.update(&|cell,_,_|{ cell.set_char(c); }))) } + /// Draw contents with modifier applied. pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw) -> impl Draw { thunk(move|to: &mut Tui|{ @@ -159,6 +252,7 @@ pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw) -> impl draw.draw(to) }) } + pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw { thunk(move|to: &mut Tui|Ok({ if on { @@ -168,10 +262,12 @@ pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw { } })) } + /// Draw contents with bold modifier applied. pub const fn bold (on: bool, draw: impl Draw) -> impl Draw { modify(on, Modifier::BOLD, draw) } + pub const fn fill_ul (color: Option) -> impl Draw { thunk(move|to: &mut Tui|Ok(if let Some(color) = color { to.update(&|cell,_,_|{ @@ -196,6 +292,7 @@ impl Coord for u16 { impl Draw for u64 { fn draw (self, _to: &mut Tui) -> Usually> { todo!() } } + impl Draw for f64 { fn draw (self, _to: &mut Tui) -> Usually> { todo!() } } @@ -288,216 +385,6 @@ pub const fn button_3 <'a> ( east(fg_bg(tui_g(224), tui_g(128), value), fg_bg(tui_g(128), Reset, "▌"), )))) } -macro_rules! border { - ($($T:ident { - $nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal - $($x:tt)* - }),+) => {$( - impl BorderStyle for $T { - const NW: &'static str = $nw; - const N: &'static str = $n; - const NE: &'static str = $ne; - const W: &'static str = $w; - const E: &'static str = $e; - const SW: &'static str = $sw; - const S: &'static str = $s; - const SE: &'static str = $se; - $($x)* - fn enabled (&self) -> bool { self.0 } - } - #[derive(Copy, Clone)] pub struct $T(pub bool, pub Style); - //impl Layout for $T {} - impl Draw for $T { - fn draw (self, to: &mut Tui) -> Usually> { - when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to))).draw(to) - } - } - )+} -} - -border! { - Square { - "┌" "─" "┐" - "│" "│" - "└" "─" "┘" fn style (&self) -> Option