fixes and refactors

- use macros for the evals
- move some space stuff into submodules
- partial update to doctests
This commit is contained in:
i do not exist 2026-04-24 01:34:43 +03:00
parent b44dc02f33
commit e074712459
9 changed files with 464 additions and 380 deletions

View file

@ -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,
};

View file

@ -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<TestScreen> for TestWidget {
/// fn draw (&self, screen: &mut T) -> Usually<XYWH<u16>> {
/// fn draw (&self, screen: &mut TestScreen) -> Usually<XYWH<u16>> {
/// screen.0 |= self.0;
/// }
/// }
@ -31,12 +31,12 @@ pub trait Draw<T: Screen> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>>;
}
impl<T: Screen, D: Draw<T>> Draw<T> for &D {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
fn draw (self, __: &mut T) -> Usually<XYWH<T::Unit>> {
todo!()
}
}
impl<T: Screen, D: Draw<T>> Draw<T> for Option<D> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
fn draw (self, __: &mut T) -> Usually<XYWH<T::Unit>> {
todo!()
}
}
@ -66,8 +66,9 @@ impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T,
/// Only render when condition is true.
///
/// ```
/// # fn test () -> impl tengri::Draw<tengri::Tui> {
/// tengri::when(true, "Yes")
/// # use tengri::draw::*;
/// # fn test () -> impl tengri::draw::Draw<tengri::term::Tui> {
/// when(true, "Yes")
/// # }
/// ```
pub const fn when <T: Screen> (condition: bool, draw: impl Draw<T>) -> impl Draw<T> {
@ -77,8 +78,9 @@ pub const fn when <T: Screen> (condition: bool, draw: impl Draw<T>) -> impl Draw
/// Render one thing if a condition is true and another false.
///
/// ```
/// # fn test () -> impl tengri::Draw<tengri::Tui> {
/// tengri::either(true, "Yes", "No")
/// # use tengri::draw::*;
/// # fn test () -> impl tengri::draw::Draw<tengri::term::Tui> {
/// either(true, "Yes", "No")
/// # }
/// ```
pub const fn either <T: Screen> (condition: bool, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
@ -88,13 +90,13 @@ pub const fn either <T: Screen> (condition: bool, a: impl Draw<T>, b: impl Draw<
/// Output target.
///
/// ```
/// use tengri::*;
/// use tengri::draw::*;
///
/// struct TestOut(impl TwoD<u16>);
/// struct TestOut<T: Screen<Unit = u16>>(T);
///
/// impl Screen for TestOut {
/// impl<T: Screen<Unit = u16>> Screen<T> for TestOut {
/// type Unit = u16;
/// fn place_at <T: Draw<Self> + ?Sized> (&mut self, area: impl TwoD<u16>, _: &T) {
/// fn place_at <D: Draw<Self> + ?Sized> (&mut self, area: T, _: D) {
/// println!("placed: {area:?}");
/// ()
/// }
@ -114,7 +116,7 @@ pub trait Screen: Space<Self::Unit> + Send + Sync + Sized {
}
/// Render drawable in subarea specified by `area`
fn place_at <'t, T: Draw<Self> + ?Sized> (
&mut self, area: XYWH<Self::Unit>, content: &'t T
&mut self, _area: XYWH<Self::Unit>, _content: &'t T
) {
unimplemented!()
}

View file

@ -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<u16> /*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<bool> where
S: Interpret<O, ()>
+ for<'b>Namespace<'b, bool>
+ for<'b>Namespace<'b, O::Unit>
S: Interpret<O, XYWH<O::Unit>>
+ for<'b> Namespace<'b, bool>
+ for<'b> Namespace<'b, O::Unit>
+ for<'b> Namespace<'b, Option<O::Unit>>
{
// 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 Perhaps<token>s 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") => {

View file

@ -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 (

View file

@ -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<RwLock<JackState<'j>>>
@ -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();
///

View file

@ -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<N: Coord>(pub N, pub N, pub N, pub N);
impl From<&ratatui::prelude::Rect> for XYWH<u16> {
fn from (rect: &ratatui::prelude::Rect) -> Self {
Self(rect.x, rect.y, rect.width, rect.height)
}
}
impl<N: Coord> X<N> for XYWH<N> { fn x (&self) -> N { self.0 } fn w (&self) -> N { self.2 } }
impl<N: Coord> Y<N> for XYWH<N> { fn y (&self) -> N { self.0 } fn h (&self) -> N { self.2 } }
impl<N: Coord> XYWH<N> {
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<T: AsRef<Origin>> HasOrigin for T { fn origin (&self) -> Origin { *self.as_ref() } }
pub struct Anchor<T>(Origin, T);
impl<T> AsRef<Origin> for Anchor<T> {
fn as_ref (&self) -> &Origin {
&self.0
}
}
impl<T: Screen, U: Draw<T>> Draw<T> for Anchor<U> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
todo!()
}
}
mod origin;
pub use self::origin::*;
pub const fn origin_c <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::C, a)
}
pub const fn origin_x <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::X, a)
}
pub const fn origin_y <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::Y, a)
}
pub const fn origin_nw <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::NW, a)
}
pub const fn origin_n <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::N, a)
}
pub const fn origin_ne <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::NE, a)
}
pub const fn origin_w <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::W, a)
}
pub const fn origin_e <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::E, a)
}
pub const fn origin_sw <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::SW, a)
}
pub const fn origin_s <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::S, a)
}
pub const fn origin_se <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
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 <T: Screen> (&self, a: impl Draw<T>) -> impl Draw<T> {
align(*self, a)
}
}
/// ```
/// use tengri::draw::{align, Origin::*};
/// let _ = align(NW, "test");
/// let _ = align(SE, "test");
/// ```
pub fn align <T: Screen> (origin: Origin, a: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T| { todo!() })
}
pub fn align_n <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
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 <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::East.half(a, b)
}
pub const fn north <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::North.half(a, b)
}
pub const fn west <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::West.half(a, b)
}
pub const fn south <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::South.half(a, b)
}
pub const fn above <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::Above.half(a, b)
}
pub const fn below <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
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 <T: Screen> (&self, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
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<N: Coord> {
fn x (&self) -> N;
@ -426,29 +214,6 @@ pub const fn wh_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
pub const fn w_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { a }
pub const fn h_full <T: Screen> (a: impl Draw<T>) -> impl Draw<T> { 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:
///
/// ```

100
src/space/origin.rs Normal file
View file

@ -0,0 +1,100 @@
use super::*;
pub const fn origin_c <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::C, a)
}
pub const fn origin_x <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::X, a)
}
pub const fn origin_y <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::Y, a)
}
pub const fn origin_nw <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::NW, a)
}
pub const fn origin_n <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::N, a)
}
pub const fn origin_ne <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::NE, a)
}
pub const fn origin_w <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::W, a)
}
pub const fn origin_e <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::E, a)
}
pub const fn origin_sw <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::SW, a)
}
pub const fn origin_s <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::S, a)
}
pub const fn origin_se <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
Anchor(Origin::SE, a)
}
/// Something that has `[0, 0]` at a particular point.
pub trait HasOrigin {
fn origin (&self) -> Origin;
}
impl<T: AsRef<Origin>> HasOrigin for T {
fn origin (&self) -> Origin {
*self.as_ref()
}
}
pub struct Anchor<T>(Origin, T);
impl<T> AsRef<Origin> for Anchor<T> {
fn as_ref (&self) -> &Origin {
&self.0
}
}
impl<T: Screen, U: Draw<T>> Draw<T> for Anchor<U> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
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 <T: Screen> (&self, a: impl Draw<T>) -> impl Draw<T> {
align(*self, a)
}
}
/// ```
/// use tengri::draw::{align, Origin::*};
/// let _ = align(NW, "test");
/// let _ = align(SE, "test");
/// ```
pub fn align <T: Screen> (origin: Origin, a: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T| { todo!() })
}
pub fn align_n <T: Screen> (a: impl Draw<T>) -> impl Draw<T> {
align(Origin::N, a)
}

127
src/space/split.rs Normal file
View file

@ -0,0 +1,127 @@
use super::*;
pub const fn east <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::East.half(a, b)
}
pub const fn north <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::North.half(a, b)
}
pub const fn west <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::West.half(a, b)
}
pub const fn south <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::South.half(a, b)
}
pub const fn above <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
Split::Above.half(a, b)
}
pub const fn below <T: Screen> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
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 <T: Screen> (&self, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
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,)*)) };
}

80
src/space/xywh.rs Normal file
View file

@ -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<N: Coord>(pub N, pub N, pub N, pub N);
impl From<&ratatui::prelude::Rect> for XYWH<u16> {
fn from (rect: &ratatui::prelude::Rect) -> Self {
Self(rect.x, rect.y, rect.width, rect.height)
}
}
impl<N: Coord> X<N> for XYWH<N> {
fn x (&self) -> N { self.0 }
fn w (&self) -> N { self.2 }
}
impl<N: Coord> Y<N> for XYWH<N> {
fn y (&self) -> N { self.0 }
fn h (&self) -> N { self.2 }
}
impl<N: Coord> XYWH<N> {
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))
}
}
}