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] 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)) + } + } + +}