output: remodularize

This commit is contained in:
🪞👃🪞 2025-09-02 22:36:34 +03:00
parent b98fccd98b
commit 4f1131744b
12 changed files with 1094 additions and 1124 deletions

View file

@ -1,991 +0,0 @@
//! Transform:
//! ```
//! use ::tengri::{output::*, tui::*};
//! let area: [u16;4] = [10, 10, 20, 20];
//! fn test (area: [u16;4], item: &impl Content<TuiOut>, expected: [u16;4]) {
//! assert_eq!(Content::layout(item, area), expected);
//! assert_eq!(Render::layout(item, area), expected);
//! };
//! test(area, &(), [20, 20, 0, 0]);
//!
//! test(area, &Fill::xy(()), area);
//! test(area, &Fill::x(()), [10, 20, 20, 0]);
//! test(area, &Fill::y(()), [20, 10, 0, 20]);
//!
//! //FIXME:test(area, &Fixed::x(4, ()), [18, 20, 4, 0]);
//! //FIXME:test(area, &Fixed::y(4, ()), [20, 18, 0, 4]);
//! //FIXME:test(area, &Fixed::xy(4, 4, unit), [18, 18, 4, 4]);
//! ```
//! Align:
//! ```
//! use ::tengri::{output::*, tui::*};
//! let area: [u16;4] = [10, 10, 20, 20];
//! fn test (area: [u16;4], item: &impl Content<TuiOut>, expected: [u16;4]) {
//! assert_eq!(Content::layout(item, area), expected);
//! assert_eq!(Render::layout(item, area), expected);
//! };
//!
//! let four = ||Fixed::xy(4, 4, "");
//! test(area, &Align::nw(four()), [10, 10, 4, 4]);
//! test(area, &Align::n(four()), [18, 10, 4, 4]);
//! test(area, &Align::ne(four()), [26, 10, 4, 4]);
//! test(area, &Align::e(four()), [26, 18, 4, 4]);
//! test(area, &Align::se(four()), [26, 26, 4, 4]);
//! test(area, &Align::s(four()), [18, 26, 4, 4]);
//! test(area, &Align::sw(four()), [10, 26, 4, 4]);
//! test(area, &Align::w(four()), [10, 18, 4, 4]);
//!
//! let two_by_four = ||Fixed::xy(4, 2, "");
//! test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
//! test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
//! test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
//! test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
//! test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
//! test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
//! test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
//! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
//! ```
use crate::*;
use Direction::*;
use std::marker::PhantomData;
use std::sync::{Arc, RwLock};
/// Stack things on top of each other,
#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) =>
{{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }});
/// Stack southward.
#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) =>
{{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }});
/// Stack northward.
#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) =>
{{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }});
/// Stack eastward.
#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) =>
{{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }});
/// Show an item only when a condition is true.
pub struct When<A>(pub bool, pub A);
impl<A> When<A> {
/// Create a binary condition.
pub const fn new (c: bool, a: A) -> Self { Self(c, a) }
}
impl<E: Output, A: Render<E>> Content<E> for When<A> {
fn layout (&self, to: E::Area) -> E::Area {
let Self(cond, item) = self;
let mut area = E::Area::zero();
if *cond {
let item_area = item.layout(to);
area[0] = item_area.x();
area[1] = item_area.y();
area[2] = item_area.w();
area[3] = item_area.h();
}
area.into()
}
fn render (&self, to: &mut E) {
let Self(cond, item) = self;
if *cond { item.render(to) }
}
}
/// Show one item if a condition is true and another if the condition is false
pub struct Either<A, B>(pub bool, pub A, pub B);
impl<A, B> Either<A, B> {
/// Create a ternary view condition.
pub const fn new (c: bool, a: A, b: B) -> Self { Self(c, a, b) }
}
impl<E: Output, A: Render<E>, B: Render<E>> Content<E> for Either<A, B> {
fn layout (&self, to: E::Area) -> E::Area {
let Self(cond, a, b) = self;
if *cond { a.layout(to) } else { b.layout(to) }
}
fn render (&self, to: &mut E) {
let Self(cond, a, b) = self;
if *cond { a.render(to) } else { b.render(to) }
}
}
/// 9th of area to place.
#[derive(Debug, Copy, Clone, Default)]
pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W }
pub struct Align<A>(Alignment, A);
impl<A> Align<A> {
#[inline] pub const fn c (a: A) -> Self { Self(Alignment::Center, a) }
#[inline] pub const fn x (a: A) -> Self { Self(Alignment::X, a) }
#[inline] pub const fn y (a: A) -> Self { Self(Alignment::Y, a) }
#[inline] pub const fn n (a: A) -> Self { Self(Alignment::N, a) }
#[inline] pub const fn s (a: A) -> Self { Self(Alignment::S, a) }
#[inline] pub const fn e (a: A) -> Self { Self(Alignment::E, a) }
#[inline] pub const fn w (a: A) -> Self { Self(Alignment::W, a) }
#[inline] pub const fn nw (a: A) -> Self { Self(Alignment::NW, a) }
#[inline] pub const fn sw (a: A) -> Self { Self(Alignment::SW, a) }
#[inline] pub const fn ne (a: A) -> Self { Self(Alignment::NE, a) }
#[inline] pub const fn se (a: A) -> Self { Self(Alignment::SE, a) }
}
impl<E: Output, A: Content<E>> Content<E> for Align<A> {
fn content (&self) -> impl Render<E> + '_ {
&self.1
}
fn layout (&self, on: E::Area) -> E::Area {
use Alignment::*;
let it = Render::layout(&self.content(), on).xywh();
let cx = on.x()+(on.w().minus(it.w())/2.into());
let cy = on.y()+(on.h().minus(it.h())/2.into());
let fx = (on.x()+on.w()).minus(it.w());
let fy = (on.y()+on.h()).minus(it.h());
let [x, y] = match self.0 {
Center => [cx, cy],
X => [cx, it.y()],
Y => [it.x(), cy],
NW => [on.x(), on.y()],
N => [cx, on.y()],
NE => [fx, on.y()],
W => [on.x(), cy],
E => [fx, cy],
SW => [on.x(), fy],
S => [cx, fy],
SE => [fx, fy],
}.into();
[x, y, it.w(), it.h()].into()
}
fn render (&self, to: &mut E) {
to.place(Content::layout(self, to.area()), &self.content())
}
}
/// A split or layer.
pub struct Bsp<A, B>(
pub(crate) Direction,
pub(crate) A,
pub(crate) B,
);
impl<A, B> Bsp<A, B> {
#[inline] pub const fn n (a: A, b: B) -> Self { Self(North, a, b) }
#[inline] pub const fn s (a: A, b: B) -> Self { Self(South, a, b) }
#[inline] pub const fn e (a: A, b: B) -> Self { Self(East, a, b) }
#[inline] pub const fn w (a: A, b: B) -> Self { Self(West, a, b) }
#[inline] pub const fn a (a: A, b: B) -> Self { Self(Above, a, b) }
#[inline] pub const fn b (a: A, b: B) -> Self { Self(Below, a, b) }
}
impl<E: Output, A: Content<E>, B: Content<E>> Content<E> for Bsp<A, B> {
fn layout (&self, outer: E::Area) -> E::Area { let [_, _, c] = self.areas(outer); c }
fn render (&self, to: &mut E) {
let [area_a, area_b, _] = self.areas(to.area());
let (a, b) = self.contents();
match self.0 {
Below => { to.place(area_a, a); to.place(area_b, b); },
_ => { to.place(area_b, b); to.place(area_a, a); }
}
}
}
impl<E: Output, A: Content<E>, B: Content<E>> BspAreas<E, A, B> for Bsp<A, B> {
fn direction (&self) -> Direction { self.0 }
fn contents (&self) -> (&A, &B) { (&self.1, &self.2) }
}
pub trait BspAreas<E: Output, A: Content<E>, B: Content<E>> {
fn direction (&self) -> Direction;
fn contents (&self) -> (&A, &B);
fn areas (&self, outer: E::Area) -> [E::Area;3] {
let direction = self.direction();
let [x, y, w, h] = outer.xywh();
let (a, b) = self.contents();
let [aw, ah] = a.layout(outer).wh();
let [bw, bh] = b.layout(match direction {
Above | Below => outer,
South => [x, y + ah, w, h.minus(ah)].into(),
North => [x, y, w, h.minus(ah)].into(),
East => [x + aw, y, w.minus(aw), h].into(),
West => [x, y, w.minus(aw), h].into(),
}).wh();
match direction {
Above | Below => {
let [x, y, w, h] = outer.center_xy([aw.max(bw), ah.max(bh)]);
let a = [(x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah];
let b = [(x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh];
[a.into(), b.into(), [x, y, w, h].into()]
},
South => {
let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]);
let a = [(x + w/2.into()).minus(aw/2.into()), y, aw, ah];
let b = [(x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh];
[a.into(), b.into(), [x, y, w, h].into()]
},
North => {
let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]);
let a = [(x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah];
let b = [(x + (w/2.into())).minus(bw/2.into()), y, bw, bh];
[a.into(), b.into(), [x, y, w, h].into()]
},
East => {
let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]);
let a = [x, (y + h/2.into()).minus(ah/2.into()), aw, ah];
let b = [x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh];
[a.into(), b.into(), [x, y, w, h].into()]
},
West => {
let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]);
let a = [x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah];
let b = [x, (y + h/2.into()).minus(bh/2.into()), bw, bh];
[a.into(), b.into(), [x, y, w, h].into()]
},
}
}
}
/// Defines an enum that transforms its content
/// along either the X axis, the Y axis, or both.
macro_rules! transform_xy {
($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$area:expr) => {
pub enum $Enum<A> { X(A), Y(A), XY(A) }
impl<A> $Enum<A> {
#[inline] pub const fn x (item: A) -> Self { Self::X(item) }
#[inline] pub const fn y (item: A) -> Self { Self::Y(item) }
#[inline] pub const fn xy (item: A) -> Self { Self::XY(item) }
}
impl<E: Output, T: Content<E>> Content<E> for $Enum<T> {
fn content (&self) -> impl Render<E> + '_ {
match self {
Self::X(item) => item,
Self::Y(item) => item,
Self::XY(item) => item,
}
}
fn layout (&$self, $to: <E as Output>::Area) -> <E as Output>::Area {
use $Enum::*;
$area
}
}
}
}
/// Defines an enum that parametrically transforms its content
/// along either the X axis, the Y axis, or both.
macro_rules! transform_xy_unit {
($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$layout:expr) => {
pub enum $Enum<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> $Enum<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self { Self::X(x, item) }
#[inline] pub const fn y (y: U, item: A) -> Self { Self::Y(y, item) }
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) }
}
impl<E: Output, T: Content<E>> Content<E> for $Enum<E::Unit, T> {
fn layout (&$self, $to: E::Area) -> E::Area {
$layout.into()
}
fn content (&self) -> impl Render<E> + '_ {
use $Enum::*;
Some(match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, })
}
}
impl<U: Coordinate, T> $Enum<U, T> {
#[inline] pub fn dx (&self) -> U {
use $Enum::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use $Enum::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
}
}
transform_xy!("fill/x" "fill/y" "fill/xy" |self: Fill, to|{
let [x0, y0, wmax, hmax] = to.xywh();
let [x, y, w, h] = self.content().layout(to).xywh();
match self {
X(_) => [x0, y, wmax, h],
Y(_) => [x, y0, w, hmax],
XY(_) => [x0, y0, wmax, hmax],
}.into()
});
transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{
let [x, y, w, h] = area.xywh();
let fixed_area = match self {
Self::X(fw, _) => [x, y, *fw, h],
Self::Y(fh, _) => [x, y, w, *fh],
Self::XY(fw, fh, _) => [x, y, *fw, *fh],
};
let [x, y, w, h] = Render::layout(&self.content(), fixed_area.into()).xywh();
let fixed_area = match self {
Self::X(fw, _) => [x, y, *fw, h],
Self::Y(fh, _) => [x, y, w, *fh],
Self::XY(fw, fh, _) => [x, y, *fw, *fh],
};
fixed_area
});
transform_xy_unit!("min/x" "min/y" "min/xy"|self: Min, area|{
let area = Render::layout(&self.content(), area);
match self {
Self::X(mw, _) => [area.x(), area.y(), area.w().max(*mw), area.h()],
Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)],
Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().max(*mw), area.h().max(*mh)],
}
});
transform_xy_unit!("max/x" "max/y" "max/xy"|self: Max, area|{
let [x, y, w, h] = area.xywh();
Render::layout(&self.content(), match self {
Self::X(fw, _) => [x, y, *fw, h],
Self::Y(fh, _) => [x, y, w, *fh],
Self::XY(fw, fh, _) => [x, y, *fw, *fh],
}.into())
});
transform_xy_unit!("shrink/x" "shrink/y" "shrink/xy"|self: Shrink, area|Render::layout(
&self.content(),
[area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())].into()));
transform_xy_unit!("expand/x" "expand/y" "expand/xy"|self: Expand, area|Render::layout(
&self.content(),
[area.x(), area.y(), area.w().plus(self.dx()), area.h().plus(self.dy())].into()));
transform_xy_unit!("push/x" "push/y" "push/xy"|self: Push, area|{
let area = Render::layout(&self.content(), area);
[area.x().plus(self.dx()), area.y().plus(self.dy()), area.w(), area.h()]
});
transform_xy_unit!("pull/x" "pull/y" "pull/xy"|self: Pull, area|{
let area = Render::layout(&self.content(), area);
[area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()]
});
transform_xy_unit!("margin/x" "margin/y" "margin/xy"|self: Margin, area|{
let area = Render::layout(&self.content(), area);
let dx = self.dx();
let dy = self.dy();
[area.x().minus(dx), area.y().minus(dy), area.w().plus(dy.plus(dy)), area.h().plus(dy.plus(dy))]
});
transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{
let area = Render::layout(&self.content(), area);
let dx = self.dx();
let dy = self.dy();
[area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))]
});
/// Enabling the `dsl` feature implements [FromDsl] for
/// the layout elements that are provided by this crate.
#[cfg(feature = "dsl")] mod ops_dsl {
use crate::*;
//use ::tengri_dsl::*;
//macro_rules! dsl {
//($(
//($Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$($arg:ident $(:$ty:ty)?),*] $expr:expr)
//)*) => {
//$(
//impl<S,$($($A),+)?> FromDsl<S> for $Struct$(<$($A),+>)? {
//fn try_from_dsl (
//state: &S, dsl: &impl Dsl
//) -> Perhaps<Self> {
//todo!()
//}
//}
//)*
//}
//}
macro_rules! dsl {
(
$Struct:ident $(<$($A:ident),+>)?
$op:literal $(/)? [$head: ident, $tail: ident] $expr:expr
) => {
impl<S,$($($A),+)?> FromDsl<S> for $Struct$(<$($A),+>)? {
fn from_dsl (
_state: &S, _dsl: &impl Dsl
) -> Perhaps<Self> {
todo!()
}
}
}
}
macro_rules! dsl_ns {
(
$Struct:ident $(<$($A:ident),+>)?
$op:literal $(/)? [$head: ident, $tail: ident] $expr:expr
) => {
impl<S,$($($A),+)?> FromDsl<S> for $Struct$(<$($A),+>)? {
fn from_dsl (
_state: &S, _dsl: &impl Dsl
) -> Perhaps<Self> {
todo!()
}
}
}
}
dsl!(When<A> "when" [_head, tail] Self(tail(0)?, tail(1)?));
dsl!(Either<A, B> "either" [_head, tail] Self(tail(0)?, tail(1)?, tail(2)?));
dsl_ns!(Align<A> "align/" [head, tail] match head {
"c" => Self::c(tail(0)?),
"x" => Self::x(tail(0)?),
"y" => Self::y(tail(0)?),
"n" => Self::n(tail(0)?),
"s" => Self::s(tail(0)?),
"e" => Self::e(tail(0)?),
"w" => Self::w(tail(0)?),
"nw" => Self::nw(tail(0)?),
"ne" => Self::ne(tail(0)?),
"sw" => Self::sw(tail(0)?),
"se" => Self::se(tail(0)?),
_ => return Err("invalid align variant".into()) });
dsl_ns!(Bsp<A, B> "bsp/" [head, tail] match head {
"n" => Self::n(tail(0)?, tail(1)?),
"s" => Self::s(tail(0)?, tail(1)?),
"e" => Self::e(tail(0)?, tail(1)?),
"w" => Self::w(tail(0)?, tail(1)?),
"a" => Self::a(tail(0)?, tail(1)?),
"b" => Self::b(tail(0)?, tail(1)?),
_ => return Err("invalid bsp variant".into()) });
dsl_ns!(Fill<A> "fill/" [head, tail] match x {
"x" => Self::x(tail(0)?),
"y" => Self::y(tail(0)?),
"xy" => Self::xy(tail(0)?),
_ => return Err("invalid fill variant".into()) });
dsl_ns!(Fixed<U, A> "fixed/" [head, tail] match x {
"x" => Self::x(tail(0)?, tail(1)?),
"y" => Self::y(tail(0)?, tail(1)?),
"xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?),
_ => return Err("invalid fill variant".into()) });
dsl_ns!(Min<U, A> "min/" [head, tail] match x {
"x" => Self::x(tail(0)?, tail(1)?),
"y" => Self::y(tail(0)?, tail(1)?),
"xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?),
_ => return Err("invalid min variant".into()) });
dsl_ns!(Max<U, A> "max/" [head, tail] match x {
"x" => Self::x(tail(0)?, tail(1)?),
"y" => Self::y(tail(0)?, tail(1)?),
"xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?),
_ => return Err("invalid max variant".into()) });
dsl_ns!(Shrink<U, A> "shrink/" [head, tail] match x {
"x" => Self::x(tail(0)?, tail(1)?),
"y" => Self::y(tail(0)?, tail(1)?),
"xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?),
_ => return Err("invalid min variant".into()) });
dsl_ns!(Expand<U, A> "expand/" [head, tail] match x {
"x" => Self::x(tail(0)?, tail(1)?),
"y" => Self::y(tail(0)?, tail(1)?),
"xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?),
_ => return Err("invalid max variant".into()) });
dsl_ns!(Pull<U, A> "pull/" [head, tail] match x {
"x" => Self::x(tail(0)?, tail(1)?),
"y" => Self::y(tail(0)?, tail(1)?),
"xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?),
_ => return Err("invalid max variant".into()) });
dsl_ns!(Push<U, A> "push/" [head, tail] match x {
"x" => Self::x(tail(0)?, tail(1)?),
"y" => Self::y(tail(0)?, tail(1)?),
"xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?),
_ => return Err("invalid max variant".into()) });
dsl_ns!(Margin<U, A> "margin/" [head, tail] match x {
"x" => Self::x(tail(0)?, tail(1)?),
"y" => Self::y(tail(0)?, tail(1)?),
"xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?),
_ => return Err("invalid max variant".into()) });
dsl_ns!(Padding<U, A> "padding/" [head, tail] match x {
"x" => Self::x(tail(0)?, tail(1)?),
"y" => Self::y(tail(0)?, tail(1)?),
"xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?),
_ => return Err("invalid max variant".into()) });
}
/// Lazily-evaluated [Render]able.
pub struct Thunk<E: Output, T: Render<E>, F: Fn()->T>(
PhantomData<E>,
F
);
impl<E: Output, T: Render<E>, F: Fn()->T> Thunk<E, T, F> {
pub const fn new (thunk: F) -> Self {
Self(PhantomData, thunk)
}
}
impl<E: Output, T: Render<E>, F: Fn()->T> Content<E> for Thunk<E, T, F> {
fn content (&self) -> impl Render<E> { (self.1)() }
}
pub struct ThunkBox<E: Output>(
PhantomData<E>,
Box<dyn Fn()->Box<dyn Render<E>>>,
);
impl<E: Output> ThunkBox<E> {
pub const fn new (thunk: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
Self(PhantomData, thunk)
}
}
impl<E: Output> Content<E> for ThunkBox<E> {
fn content (&self) -> impl Render<E> { (&self.1)() }
}
impl<E: Output> From<Box<dyn Fn()->Box<dyn Render<E>>>> for ThunkBox<E> {
fn from (f: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
Self(PhantomData, f)
}
}
//impl<'a, E: Output, F: Fn()->Box<dyn Render<E> + 'a> + 'a> From<F> for ThunkBox<'a, E> {
//fn from (f: F) -> Self {
//Self(Default::default(), Box::new(f))
//}
//}
pub struct ThunkRender<E: Output, F: Fn(&mut E)>(PhantomData<E>, F);
impl<E: Output, F: Fn(&mut E)> ThunkRender<E, F> {
pub fn new (render: F) -> Self { Self(PhantomData, render) }
}
impl<E: Output, F: Fn(&mut E)> Content<E> for ThunkRender<E, F> {
fn render (&self, to: &mut E) { (self.1)(to) }
}
pub struct ThunkLayout<
E: Output,
F1: Fn(E::Area)->E::Area,
F2: Fn(&mut E)
>(
PhantomData<E>,
F1,
F2
);
impl<E: Output, F1: Fn(E::Area)->E::Area, F2: Fn(&mut E)> ThunkLayout<E, F1, F2> {
pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) }
}
impl<E, F1, F2> Content<E> for ThunkLayout<E, F1, F2>
where
E: Output,
F1: Fn(E::Area)->E::Area,
F2: Fn(&mut E)
{
fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) }
fn render (&self, to: &mut E) { (self.2)(to) }
}
#[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T,
pub view: Arc<RwLock<U>>
}
impl<T: PartialEq, U> Memo<T, U> {
pub fn new (value: T, view: U) -> Self {
Self { value, view: Arc::new(view.into()) }
}
pub fn update <R> (
&mut self,
newval: T,
render: impl Fn(&mut U, &T, &T)->R
) -> Option<R> {
if newval != self.value {
let result = render(&mut*self.view.write().unwrap(), &newval, &self.value);
self.value = newval;
return Some(result);
}
None
}
}
/// Clear a pre-allocated buffer, then write into it.
#[macro_export] macro_rules! rewrite {
($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } }
}
pub struct Stack<'t, E, F1> {
__: PhantomData<&'t (E, F1)>,
direction: Direction,
callback: F1
}
impl<'t, 'u: 't, E, F1> Stack<'t, E, F1> where
Self: 't, E: Output, F1: Fn(&mut dyn FnMut(&dyn Render<E>)) + Send + Sync,
{
pub fn north (callback: F1) -> Self { Self::new(North, callback) }
pub fn south (callback: F1) -> Self { Self::new(South, callback) }
pub fn east (callback: F1) -> Self { Self::new(East, callback) }
pub fn west (callback: F1) -> Self { Self::new(West, callback) }
pub fn above (callback: F1) -> Self { Self::new(Above, callback) }
pub fn below (callback: F1) -> Self { Self::new(Below, callback) }
pub fn new (direction: Direction, callback: F1) -> Self {
Self { direction, callback, __: Default::default(), }
}
}
impl<'t, 'u, E, F1> Content<E> for Stack<'t, E, F1> where
Self: 't, E: Output, F1: Fn(&mut dyn FnMut(&dyn Render<E>)) + Send + Sync,
{
fn layout (&self, to: E::Area) -> E::Area {
let Self { direction, callback, .. } = self;
let (mut x, mut y) = (to.x(), to.y());
let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w());
let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h());
callback(&mut move|component: &dyn Render<E>|{
let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh();
match direction {
South => { y = y.plus(h);
h_used = h_used.plus(h);
h_remaining = h_remaining.minus(h);
w_used = w_used.max(w); },
East => { x = x.plus(w);
w_used = w_used.plus(w);
w_remaining = w_remaining.minus(w);
h_used = h_used.max(h); },
North | West => { todo!() },
Above | Below => {},
}
});
match direction {
North | West => { todo!() },
South | East => { [to.x(), to.y(), w_used.into(), h_used.into()].into() },
Above | Below => { [to.x(), to.y(), to.w(), to.h()].into() },
}
}
fn render (&self, to: &mut E) {
let Self { direction, callback, .. } = self;
let (mut x, mut y) = (to.x(), to.y());
let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w());
let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h());
callback(&mut move|component: &dyn Render<E>|{
let layout = component.layout([x, y, w_remaining, h_remaining].into());
match direction {
South => {
y = y.plus(layout.h());
h_remaining = h_remaining.minus(layout.h());
h_used = h_used.plus(layout.h()) },
East => {
x = x.plus(layout.w());
w_remaining = w_remaining.minus(layout.w());
w_used = w_used.plus(layout.h()) },
North | West => { todo!() },
Above | Below => {}
};
to.place(layout, component);
});
}
}
/// Renders items from an iterator.
pub struct Map<E, A, B, I, F, G>
where
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync,
{
__: PhantomData<(E, B)>,
/// Function that returns iterator over stacked components
get_iter: F,
/// Function that returns each stacked component
get_item: G,
}
impl<'a, E, A, B, I, F, G> Map<E, A, B, I, F, G> where
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a,
{
pub const fn new (get_iter: F, get_item: G) -> Self {
Self {
__: PhantomData,
get_iter,
get_item
}
}
}
macro_rules! impl_map_direction (($name:ident, $axis:ident, $align:ident)=>{
impl<'a, E, A, B, I, F> Map<
E, A, Push<E::Unit, Align<Fixed<E::Unit, Fill<B>>>>, I, F, fn(A, usize)->B
> where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a
{
pub const fn $name (
size: E::Unit,
get_iter: F,
get_item: impl Fn(A, usize)->B + Send + Sync
) -> Map<
E, A,
Push<E::Unit, Align<Fixed<E::Unit, B>>>,
I, F,
impl Fn(A, usize)->Push<E::Unit, Align<Fixed<E::Unit, B>>> + Send + Sync
> {
Map {
__: PhantomData,
get_iter,
get_item: move |item: A, index: usize|{
// FIXME: multiply
let mut push: E::Unit = E::Unit::from(0u16);
for _ in 0..index {
push = push + size;
}
Push::$axis(push, Align::$align(Fixed::$axis(size, get_item(item, index))))
}
}
}
}
});
impl_map_direction!(east, x, w);
impl_map_direction!(south, y, n);
impl_map_direction!(west, x, e);
impl_map_direction!(north, y, s);
impl<'a, E, A, B, I, F, G> Content<E> for Map<E, A, B, I, F, G> where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a,
G: Fn(A, usize)->B + Send + Sync
{
fn layout (&self, area: E::Area) -> E::Area {
let Self { get_iter, get_item, .. } = self;
let mut index = 0;
let [mut min_x, mut min_y] = area.center();
let [mut max_x, mut max_y] = area.center();
for item in get_iter() {
let [x,y,w,h] = get_item(item, index).layout(area).xywh();
min_x = min_x.min(x.into());
min_y = min_y.min(y.into());
max_x = max_x.max((x + w).into());
max_y = max_y.max((y + h).into());
index += 1;
}
let w = max_x - min_x;
let h = max_y - min_y;
//[min_x.into(), min_y.into(), w.into(), h.into()].into()
area.center_xy([w.into(), h.into()].into()).into()
}
fn render (&self, to: &mut E) {
let Self { get_iter, get_item, .. } = self;
let mut index = 0;
let area = Content::layout(self, to.area());
for item in get_iter() {
let item = get_item(item, index);
//to.place(area.into(), &item);
to.place(item.layout(area), &item);
index += 1;
}
}
}
#[inline] pub fn map_south<O: Output>(
item_offset: O::Unit,
item_height: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::y(item_offset, Fixed::y(item_height, Fill::x(item)))
}
#[inline] pub fn map_south_west<O: Output>(
item_offset: O::Unit,
item_height: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item))))
}
#[inline] pub fn map_east<O: Output>(
item_offset: O::Unit,
item_width: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item))))
}
///////////////////////////////////////////////////////////////////////////////
///// The syntagm `(when :condition :content)` corresponds to a [When] layout element.
//impl<S, A> FromDsl<S> for When<A> where bool: FromDsl<S>, A: FromDsl<S> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("when", |_, tail|Ok(Some(Self(
//FromDsl::<S>::provide(state,
//tail.nth(0, ||"no condition".into())?, ||"no condition".into())?,
//FromDsl::<S>::provide(state,
//tail.nth(1, ||"no content".into())?, ||"no content".into())?,
//))))
//}
//}
///// The syntagm `(either :condition :content1 :content2)` corresponds to an [Either] layout element.
//impl<S, A, B> FromDsl<S> for Either<A, B> where S: Eval<Ast, bool> + Eval<Ast, A> + Eval<Ast, B> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("either", |_, tail|Ok(Some(Self(
//state.eval(tail.nth(0, ||"no condition")?, ||"no condition")?,
//state.eval(tail.nth(1, ||"no content 1")?, ||"no content 1")?,
//state.eval(tail.nth(2, ||"no content 1")?, ||"no content 2")?,
//))))
//}
//}
///// The syntagm `(align/* :content)` corresponds to an [Align] layout element,
///// where `*` specifies the direction of the alignment.
//impl<S, A> FromDsl<S> for Align<A> where S: Eval<Option<Ast>, A> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("align/", |head, tail|Ok(Some(match head {
//"c" => Self::c(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"x" => Self::x(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"y" => Self::y(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"n" => Self::n(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"s" => Self::s(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"e" => Self::e(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"w" => Self::w(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"nw" => Self::nw(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"ne" => Self::ne(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"sw" => Self::sw(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"se" => Self::se(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//_ => return Err("invalid align variant".into())
//})))
//}
//}
///// The syntagm `(bsp/* :content1 :content2)` corresponds to a [Bsp] layout element,
///// where `*` specifies the direction of the split.
//impl<S, A, B> FromDsl<S> for Bsp<A, B> where S: Eval<Option<Ast>, A> + Eval<Option<Ast>, B> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("bsp/", |head, tail|Ok(Some(match head {
//"n" => Self::n(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"s" => Self::s(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"e" => Self::e(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"w" => Self::w(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"a" => Self::a(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"b" => Self::b(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//_ => return Ok(None),
//})))
//}
//}
//#[cfg(feature = "dsl")] take!($Enum<A>, A|state, words|Ok(
//if let Some(Token { value: Key(k), .. }) = words.peek() {
//let mut base = words.clone();
//let content = state.give_or_fail(words, ||format!("{k}: no content"))?;
//return Ok(Some(match words.next() {
//Some(Token{value: Key($x),..}) => Self::x(content),
//Some(Token{value: Key($y),..}) => Self::y(content),
//Some(Token{value: Key($xy),..}) => Self::xy(content),
//_ => unreachable!()
//}))
//} else {
//None
//}));
//#[cfg(feature = "dsl")] take!($Enum<U, A>, U, A|state, words|Ok(
//if let Some(Token { value: Key($x|$y|$xy), .. }) = words.peek() {
//let mut base = words.clone();
//Some(match words.next() {
//Some(Token { value: Key($x), .. }) => Self::x(
//state.give_or_fail(words, ||"x: no unit")?,
//state.give_or_fail(words, ||"x: no content")?,
//),
//Some(Token { value: Key($y), .. }) => Self::y(
//state.give_or_fail(words, ||"y: no unit")?,
//state.give_or_fail(words, ||"y: no content")?,
//),
//Some(Token { value: Key($x), .. }) => Self::xy(
//state.give_or_fail(words, ||"xy: no unit x")?,
//state.give_or_fail(words, ||"xy: no unit y")?,
//state.give_or_fail(words, ||"xy: no content")?
//),
//_ => unreachable!(),
//})
//} else {
//None
//}));
//if let Exp(_, exp) = source.value() {
//let mut rest = exp.clone();
//return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) {
//Some("bsp/n") => Self::n(
//state.eval(rest.next(), ||"bsp/n: no content 1")?,
//state.eval(rest.next(), ||"bsp/n: no content 2")?,
//),
//Some("bsp/s") => Self::s(
//state.eval(rest.next(), ||"bsp/s: no content 1")?,
//state.eval(rest.next(), ||"bsp/s: no content 2")?,
//),
//Some("bsp/e") => Self::e(
//state.eval(rest.next(), ||"bsp/e: no content 1")?,
//state.eval(rest.next(), ||"bsp/e: no content 2")?,
//),
//Some("bsp/w") => Self::w(
//state.eval(rest.next(), ||"bsp/w: no content 1")?,
//state.eval(rest.next(), ||"bsp/w: no content 2")?,
//),
//Some("bsp/a") => Self::a(
//state.eval(rest.next(), ||"bsp/a: no content 1")?,
//state.eval(rest.next(), ||"bsp/a: no content 2")?,
//),
//Some("bsp/b") => Self::b(
//state.eval(rest.next(), ||"bsp/b: no content 1")?,
//state.eval(rest.next(), ||"bsp/b: no content 2")?,
//),
//_ => return Ok(None),
//}))
//}
//Ok(None)
//if let Exp(_, source) = source.value() {
//let mut rest = source.clone();
//return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) {
//Some("align/c") => Self::c(state.eval(rest.next(), ||"align/c: no content")?),
//Some("align/x") => Self::x(state.eval(rest.next(), ||"align/x: no content")?),
//Some("align/y") => Self::y(state.eval(rest.next(), ||"align/y: no content")?),
//Some("align/n") => Self::n(state.eval(rest.next(), ||"align/n: no content")?),
//Some("align/s") => Self::s(state.eval(rest.next(), ||"align/s: no content")?),
//Some("align/e") => Self::e(state.eval(rest.next(), ||"align/e: no content")?),
//Some("align/w") => Self::w(state.eval(rest.next(), ||"align/w: no content")?),
//Some("align/nw") => Self::nw(state.eval(rest.next(), ||"align/nw: no content")?),
//Some("align/ne") => Self::ne(state.eval(rest.next(), ||"align/ne: no content")?),
//Some("align/sw") => Self::sw(state.eval(rest.next(), ||"align/sw: no content")?),
//Some("align/se") => Self::se(state.eval(rest.next(), ||"align/se: no content")?),
//_ => return Ok(None),
//}))
//}
//Ok(None)
//Ok(match source.exp_head().and_then(|e|e.key()) {
//Some("either") => Some(Self(
//source.exp_tail().and_then(|t|t.get(0)).map(|x|state.eval(x, ||"when: no condition"))?,
//source.exp_tail().and_then(|t|t.get(1)).map(|x|state.eval(x, ||"when: no content 1"))?,
//source.exp_tail().and_then(|t|t.get(2)).map(|x|state.eval(x, ||"when: no content 2"))?,
//)),
//_ => None
//})
//if let Exp(_, mut exp) = source.value()
//&& let Some(Ast(Key(id))) = exp.peek() && *id == *"either" {
//let _ = exp.next();
//return Ok(Some(Self(
//state.eval(exp.next().unwrap(), ||"either: no condition")?,
//state.eval(exp.next().unwrap(), ||"either: no content 1")?,
//state.eval(exp.next().unwrap(), ||"either: no content 2")?,
//)))
//}
//Ok(None)
//Ok(match source.exp_head().and_then(|e|e.key()) {
//Some("when") => Some(Self(
//source.exp_tail().and_then(|t|t.get(0)).map(|x|state.eval(x, ||"when: no condition"))?,
//source.exp_tail().and_then(|t|t.get(1)).map(|x|state.eval(x, ||"when: no content"))?,
//)),
//_ => None
//})
/*Stack::down(|add|{
let mut i = 0;
for (_, name) in self.dirs.iter() {
if i >= self.scroll {
add(&Tui::bold(i == self.index, name.as_str()))?;
}
i += 1;
}
for (_, name) in self.files.iter() {
if i >= self.scroll {
add(&Tui::bold(i == self.index, name.as_str()))?;
}
i += 1;
}
add(&format!("{}/{i}", self.index))?;
Ok(())
}));*/

View file

@ -0,0 +1,80 @@
//! ```
//! use ::tengri::{output::*, tui::*};
//! let area: [u16;4] = [10, 10, 20, 20];
//! fn test (area: [u16;4], item: &impl Content<TuiOut>, expected: [u16;4]) {
//! assert_eq!(Content::layout(item, area), expected);
//! assert_eq!(Render::layout(item, area), expected);
//! };
//!
//! let four = ||Fixed::xy(4, 4, "");
//! test(area, &Align::nw(four()), [10, 10, 4, 4]);
//! test(area, &Align::n(four()), [18, 10, 4, 4]);
//! test(area, &Align::ne(four()), [26, 10, 4, 4]);
//! test(area, &Align::e(four()), [26, 18, 4, 4]);
//! test(area, &Align::se(four()), [26, 26, 4, 4]);
//! test(area, &Align::s(four()), [18, 26, 4, 4]);
//! test(area, &Align::sw(four()), [10, 26, 4, 4]);
//! test(area, &Align::w(four()), [10, 18, 4, 4]);
//!
//! let two_by_four = ||Fixed::xy(4, 2, "");
//! test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
//! test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
//! test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
//! test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
//! test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
//! test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
//! test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
//! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
//! ```
use crate::*;
/// 9th of area to place.
#[derive(Debug, Copy, Clone, Default)]
pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W }
pub struct Align<A>(Alignment, A);
impl<A> Align<A> {
#[inline] pub const fn c (a: A) -> Self { Self(Alignment::Center, a) }
#[inline] pub const fn x (a: A) -> Self { Self(Alignment::X, a) }
#[inline] pub const fn y (a: A) -> Self { Self(Alignment::Y, a) }
#[inline] pub const fn n (a: A) -> Self { Self(Alignment::N, a) }
#[inline] pub const fn s (a: A) -> Self { Self(Alignment::S, a) }
#[inline] pub const fn e (a: A) -> Self { Self(Alignment::E, a) }
#[inline] pub const fn w (a: A) -> Self { Self(Alignment::W, a) }
#[inline] pub const fn nw (a: A) -> Self { Self(Alignment::NW, a) }
#[inline] pub const fn sw (a: A) -> Self { Self(Alignment::SW, a) }
#[inline] pub const fn ne (a: A) -> Self { Self(Alignment::NE, a) }
#[inline] pub const fn se (a: A) -> Self { Self(Alignment::SE, a) }
}
impl<E: Output, A: Content<E>> Content<E> for Align<A> {
fn content (&self) -> impl Render<E> + '_ {
&self.1
}
fn layout (&self, on: E::Area) -> E::Area {
use Alignment::*;
let it = Render::layout(&self.content(), on).xywh();
let cx = on.x()+(on.w().minus(it.w())/2.into());
let cy = on.y()+(on.h().minus(it.h())/2.into());
let fx = (on.x()+on.w()).minus(it.w());
let fy = (on.y()+on.h()).minus(it.h());
let [x, y] = match self.0 {
Center => [cx, cy],
X => [cx, it.y()],
Y => [it.x(), cy],
NW => [on.x(), on.y()],
N => [cx, on.y()],
NE => [fx, on.y()],
W => [on.x(), cy],
E => [fx, cy],
SW => [on.x(), fy],
S => [cx, fy],
SE => [fx, fy],
}.into();
[x, y, it.w(), it.h()].into()
}
fn render (&self, to: &mut E) {
to.place(Content::layout(self, to.area()), &self.content())
}
}

97
output/src/layout_bsp.rs Normal file
View file

@ -0,0 +1,97 @@
use crate::*;
use Direction::*;
/// A split or layer.
pub struct Bsp<A, B>(
pub(crate) Direction,
pub(crate) A,
pub(crate) B,
);
impl<A, B> Bsp<A, B> {
#[inline] pub const fn n (a: A, b: B) -> Self { Self(North, a, b) }
#[inline] pub const fn s (a: A, b: B) -> Self { Self(South, a, b) }
#[inline] pub const fn e (a: A, b: B) -> Self { Self(East, a, b) }
#[inline] pub const fn w (a: A, b: B) -> Self { Self(West, a, b) }
#[inline] pub const fn a (a: A, b: B) -> Self { Self(Above, a, b) }
#[inline] pub const fn b (a: A, b: B) -> Self { Self(Below, a, b) }
}
impl<E: Output, A: Content<E>, B: Content<E>> Content<E> for Bsp<A, B> {
fn layout (&self, outer: E::Area) -> E::Area { let [_, _, c] = self.areas(outer); c }
fn render (&self, to: &mut E) {
let [area_a, area_b, _] = self.areas(to.area());
let (a, b) = self.contents();
match self.0 {
Below => { to.place(area_a, a); to.place(area_b, b); },
_ => { to.place(area_b, b); to.place(area_a, a); }
}
}
}
impl<E: Output, A: Content<E>, B: Content<E>> BspAreas<E, A, B> for Bsp<A, B> {
fn direction (&self) -> Direction { self.0 }
fn contents (&self) -> (&A, &B) { (&self.1, &self.2) }
}
pub trait BspAreas<E: Output, A: Content<E>, B: Content<E>> {
fn direction (&self) -> Direction;
fn contents (&self) -> (&A, &B);
fn areas (&self, outer: E::Area) -> [E::Area;3] {
let direction = self.direction();
let [x, y, w, h] = outer.xywh();
let (a, b) = self.contents();
let [aw, ah] = a.layout(outer).wh();
let [bw, bh] = b.layout(match direction {
Above | Below => outer,
South => [x, y + ah, w, h.minus(ah)].into(),
North => [x, y, w, h.minus(ah)].into(),
East => [x + aw, y, w.minus(aw), h].into(),
West => [x, y, w.minus(aw), h].into(),
}).wh();
match direction {
Above | Below => {
let [x, y, w, h] = outer.center_xy([aw.max(bw), ah.max(bh)]);
let a = [(x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah];
let b = [(x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh];
[a.into(), b.into(), [x, y, w, h].into()]
},
South => {
let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]);
let a = [(x + w/2.into()).minus(aw/2.into()), y, aw, ah];
let b = [(x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh];
[a.into(), b.into(), [x, y, w, h].into()]
},
North => {
let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]);
let a = [(x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah];
let b = [(x + (w/2.into())).minus(bw/2.into()), y, bw, bh];
[a.into(), b.into(), [x, y, w, h].into()]
},
East => {
let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]);
let a = [x, (y + h/2.into()).minus(ah/2.into()), aw, ah];
let b = [x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh];
[a.into(), b.into(), [x, y, w, h].into()]
},
West => {
let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]);
let a = [x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah];
let b = [x, (y + h/2.into()).minus(bh/2.into()), bw, bh];
[a.into(), b.into(), [x, y, w, h].into()]
},
}
}
}
/// Stack things on top of each other,
#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) =>
{{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }});
/// Stack southward.
#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) =>
{{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }});
/// Stack northward.
#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) =>
{{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }});
/// Stack eastward.
#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) =>
{{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }});

214
output/src/layout_cond.rs Normal file
View file

@ -0,0 +1,214 @@
use crate::*;
/// Show an item only when a condition is true.
pub struct When<A>(pub bool, pub A);
impl<A> When<A> {
/// Create a binary condition.
pub const fn new (c: bool, a: A) -> Self { Self(c, a) }
}
impl<E: Output, A: Render<E>> Content<E> for When<A> {
fn layout (&self, to: E::Area) -> E::Area {
let Self(cond, item) = self;
let mut area = E::Area::zero();
if *cond {
let item_area = item.layout(to);
area[0] = item_area.x();
area[1] = item_area.y();
area[2] = item_area.w();
area[3] = item_area.h();
}
area.into()
}
fn render (&self, to: &mut E) {
let Self(cond, item) = self;
if *cond { item.render(to) }
}
}
/// Show one item if a condition is true and another if the condition is false
pub struct Either<A, B>(pub bool, pub A, pub B);
impl<A, B> Either<A, B> {
/// Create a ternary view condition.
pub const fn new (c: bool, a: A, b: B) -> Self { Self(c, a, b) }
}
impl<E: Output, A: Render<E>, B: Render<E>> Content<E> for Either<A, B> {
fn layout (&self, to: E::Area) -> E::Area {
let Self(cond, a, b) = self;
if *cond { a.layout(to) } else { b.layout(to) }
}
fn render (&self, to: &mut E) {
let Self(cond, a, b) = self;
if *cond { a.render(to) } else { b.render(to) }
}
}
///////////////////////////////////////////////////////////////////////////////
///// The syntagm `(when :condition :content)` corresponds to a [When] layout element.
//impl<S, A> FromDsl<S> for When<A> where bool: FromDsl<S>, A: FromDsl<S> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("when", |_, tail|Ok(Some(Self(
//FromDsl::<S>::provide(state,
//tail.nth(0, ||"no condition".into())?, ||"no condition".into())?,
//FromDsl::<S>::provide(state,
//tail.nth(1, ||"no content".into())?, ||"no content".into())?,
//))))
//}
//}
///// The syntagm `(either :condition :content1 :content2)` corresponds to an [Either] layout element.
//impl<S, A, B> FromDsl<S> for Either<A, B> where S: Eval<Ast, bool> + Eval<Ast, A> + Eval<Ast, B> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("either", |_, tail|Ok(Some(Self(
//state.eval(tail.nth(0, ||"no condition")?, ||"no condition")?,
//state.eval(tail.nth(1, ||"no content 1")?, ||"no content 1")?,
//state.eval(tail.nth(2, ||"no content 1")?, ||"no content 2")?,
//))))
//}
//}
///// The syntagm `(align/* :content)` corresponds to an [Align] layout element,
///// where `*` specifies the direction of the alignment.
//impl<S, A> FromDsl<S> for Align<A> where S: Eval<Option<Ast>, A> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("align/", |head, tail|Ok(Some(match head {
//"c" => Self::c(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"x" => Self::x(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"y" => Self::y(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"n" => Self::n(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"s" => Self::s(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"e" => Self::e(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"w" => Self::w(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"nw" => Self::nw(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"ne" => Self::ne(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"sw" => Self::sw(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"se" => Self::se(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//_ => return Err("invalid align variant".into())
//})))
//}
//}
///// The syntagm `(bsp/* :content1 :content2)` corresponds to a [Bsp] layout element,
///// where `*` specifies the direction of the split.
//impl<S, A, B> FromDsl<S> for Bsp<A, B> where S: Eval<Option<Ast>, A> + Eval<Option<Ast>, B> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("bsp/", |head, tail|Ok(Some(match head {
//"n" => Self::n(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"s" => Self::s(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"e" => Self::e(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"w" => Self::w(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"a" => Self::a(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"b" => Self::b(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//_ => return Ok(None),
//})))
//}
//}
//#[cfg(feature = "dsl")] take!($Enum<A>, A|state, words|Ok(
//if let Some(Token { value: Key(k), .. }) = words.peek() {
//let mut base = words.clone();
//let content = state.give_or_fail(words, ||format!("{k}: no content"))?;
//return Ok(Some(match words.next() {
//Some(Token{value: Key($x),..}) => Self::x(content),
//Some(Token{value: Key($y),..}) => Self::y(content),
//Some(Token{value: Key($xy),..}) => Self::xy(content),
//_ => unreachable!()
//}))
//} else {
//None
//}));
//#[cfg(feature = "dsl")] take!($Enum<U, A>, U, A|state, words|Ok(
//if let Some(Token { value: Key($x|$y|$xy), .. }) = words.peek() {
//let mut base = words.clone();
//Some(match words.next() {
//Some(Token { value: Key($x), .. }) => Self::x(
//state.give_or_fail(words, ||"x: no unit")?,
//state.give_or_fail(words, ||"x: no content")?,
//),
//Some(Token { value: Key($y), .. }) => Self::y(
//state.give_or_fail(words, ||"y: no unit")?,
//state.give_or_fail(words, ||"y: no content")?,
//),
//Some(Token { value: Key($x), .. }) => Self::xy(
//state.give_or_fail(words, ||"xy: no unit x")?,
//state.give_or_fail(words, ||"xy: no unit y")?,
//state.give_or_fail(words, ||"xy: no content")?
//),
//_ => unreachable!(),
//})
//} else {
//None
//}));
//if let Exp(_, exp) = source.value() {
//let mut rest = exp.clone();
//return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) {
//Some("bsp/n") => Self::n(
//state.eval(rest.next(), ||"bsp/n: no content 1")?,
//state.eval(rest.next(), ||"bsp/n: no content 2")?,
//),
//Some("bsp/s") => Self::s(
//state.eval(rest.next(), ||"bsp/s: no content 1")?,
//state.eval(rest.next(), ||"bsp/s: no content 2")?,
//),
//Some("bsp/e") => Self::e(
//state.eval(rest.next(), ||"bsp/e: no content 1")?,
//state.eval(rest.next(), ||"bsp/e: no content 2")?,
//),
//Some("bsp/w") => Self::w(
//state.eval(rest.next(), ||"bsp/w: no content 1")?,
//state.eval(rest.next(), ||"bsp/w: no content 2")?,
//),
//Some("bsp/a") => Self::a(
//state.eval(rest.next(), ||"bsp/a: no content 1")?,
//state.eval(rest.next(), ||"bsp/a: no content 2")?,
//),
//Some("bsp/b") => Self::b(
//state.eval(rest.next(), ||"bsp/b: no content 1")?,
//state.eval(rest.next(), ||"bsp/b: no content 2")?,
//),
//_ => return Ok(None),
//}))
//}
//Ok(None)
//if let Exp(_, source) = source.value() {
//let mut rest = source.clone();
//return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) {
//Some("align/c") => Self::c(state.eval(rest.next(), ||"align/c: no content")?),
//Some("align/x") => Self::x(state.eval(rest.next(), ||"align/x: no content")?),
//Some("align/y") => Self::y(state.eval(rest.next(), ||"align/y: no content")?),
//Some("align/n") => Self::n(state.eval(rest.next(), ||"align/n: no content")?),
//Some("align/s") => Self::s(state.eval(rest.next(), ||"align/s: no content")?),
//Some("align/e") => Self::e(state.eval(rest.next(), ||"align/e: no content")?),
//Some("align/w") => Self::w(state.eval(rest.next(), ||"align/w: no content")?),
//Some("align/nw") => Self::nw(state.eval(rest.next(), ||"align/nw: no content")?),
//Some("align/ne") => Self::ne(state.eval(rest.next(), ||"align/ne: no content")?),
//Some("align/sw") => Self::sw(state.eval(rest.next(), ||"align/sw: no content")?),
//Some("align/se") => Self::se(state.eval(rest.next(), ||"align/se: no content")?),
//_ => return Ok(None),
//}))
//}
//Ok(None)
//Ok(match source.exp_head().and_then(|e|e.key()) {
//Some("either") => Some(Self(
//source.exp_tail().and_then(|t|t.get(0)).map(|x|state.eval(x, ||"when: no condition"))?,
//source.exp_tail().and_then(|t|t.get(1)).map(|x|state.eval(x, ||"when: no content 1"))?,
//source.exp_tail().and_then(|t|t.get(2)).map(|x|state.eval(x, ||"when: no content 2"))?,
//)),
//_ => None
//})
//if let Exp(_, mut exp) = source.value()
//&& let Some(Ast(Key(id))) = exp.peek() && *id == *"either" {
//let _ = exp.next();
//return Ok(Some(Self(
//state.eval(exp.next().unwrap(), ||"either: no condition")?,
//state.eval(exp.next().unwrap(), ||"either: no content 1")?,
//state.eval(exp.next().unwrap(), ||"either: no content 2")?,
//)))
//}
//Ok(None)
//Ok(match source.exp_head().and_then(|e|e.key()) {
//Some("when") => Some(Self(
//source.exp_tail().and_then(|t|t.get(0)).map(|x|state.eval(x, ||"when: no condition"))?,
//source.exp_tail().and_then(|t|t.get(1)).map(|x|state.eval(x, ||"when: no content"))?,
//)),
//_ => None
//})

131
output/src/layout_map.rs Normal file
View file

@ -0,0 +1,131 @@
use crate::*;
/// Renders items from an iterator.
pub struct Map<E, A, B, I, F, G>
where
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync,
{
__: PhantomData<(E, B)>,
/// Function that returns iterator over stacked components
get_iter: F,
/// Function that returns each stacked component
get_item: G,
}
impl<'a, E, A, B, I, F, G> Map<E, A, B, I, F, G> where
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a,
{
pub const fn new (get_iter: F, get_item: G) -> Self {
Self {
__: PhantomData,
get_iter,
get_item
}
}
}
macro_rules! impl_map_direction (($name:ident, $axis:ident, $align:ident)=>{
impl<'a, E, A, B, I, F> Map<
E, A, Push<E::Unit, Align<Fixed<E::Unit, Fill<B>>>>, I, F, fn(A, usize)->B
> where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a
{
pub const fn $name (
size: E::Unit,
get_iter: F,
get_item: impl Fn(A, usize)->B + Send + Sync
) -> Map<
E, A,
Push<E::Unit, Align<Fixed<E::Unit, B>>>,
I, F,
impl Fn(A, usize)->Push<E::Unit, Align<Fixed<E::Unit, B>>> + Send + Sync
> {
Map {
__: PhantomData,
get_iter,
get_item: move |item: A, index: usize|{
// FIXME: multiply
let mut push: E::Unit = E::Unit::from(0u16);
for _ in 0..index {
push = push + size;
}
Push::$axis(push, Align::$align(Fixed::$axis(size, get_item(item, index))))
}
}
}
}
});
impl_map_direction!(east, x, w);
impl_map_direction!(south, y, n);
impl_map_direction!(west, x, e);
impl_map_direction!(north, y, s);
impl<'a, E, A, B, I, F, G> Content<E> for Map<E, A, B, I, F, G> where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a,
G: Fn(A, usize)->B + Send + Sync
{
fn layout (&self, area: E::Area) -> E::Area {
let Self { get_iter, get_item, .. } = self;
let mut index = 0;
let [mut min_x, mut min_y] = area.center();
let [mut max_x, mut max_y] = area.center();
for item in get_iter() {
let [x,y,w,h] = get_item(item, index).layout(area).xywh();
min_x = min_x.min(x.into());
min_y = min_y.min(y.into());
max_x = max_x.max((x + w).into());
max_y = max_y.max((y + h).into());
index += 1;
}
let w = max_x - min_x;
let h = max_y - min_y;
//[min_x.into(), min_y.into(), w.into(), h.into()].into()
area.center_xy([w.into(), h.into()].into()).into()
}
fn render (&self, to: &mut E) {
let Self { get_iter, get_item, .. } = self;
let mut index = 0;
let area = Content::layout(self, to.area());
for item in get_iter() {
let item = get_item(item, index);
//to.place(area.into(), &item);
to.place(item.layout(area), &item);
index += 1;
}
}
}
#[inline] pub fn map_south<O: Output>(
item_offset: O::Unit,
item_height: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::y(item_offset, Fixed::y(item_height, Fill::x(item)))
}
#[inline] pub fn map_south_west<O: Output>(
item_offset: O::Unit,
item_height: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item))))
}
#[inline] pub fn map_east<O: Output>(
item_offset: O::Unit,
item_width: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item))))
}

168
output/src/layout_stack.rs Normal file
View file

@ -0,0 +1,168 @@
use crate::*;
use Direction::*;
pub struct Stack<'x, E, F> {
__: PhantomData<&'x E>,
direction: Direction,
callback: F
}
impl<'x, E, F: Fn(&dyn Fn(&dyn Render<E>)) + 'x> Stack<'x, E, F> {
pub fn new (direction: Direction, callback: F) -> Self {
Self { direction, callback, __: Default::default(), }
}
pub fn above (callback: F) -> Self {
Self::new(Above, callback)
}
pub fn below (callback: F) -> Self {
Self::new(Below, callback)
}
pub fn north (callback: F) -> Self {
Self::new(North, callback)
}
pub fn south (callback: F) -> Self {
Self::new(South, callback)
}
pub fn east (callback: F) -> Self {
Self::new(East, callback)
}
pub fn west (callback: F) -> Self {
Self::new(West, callback)
}
}
impl<'x, E: Output, F: Fn(&dyn Fn(&dyn Render<E>)) + 'x> Content<E> for Stack<'x, E, F> {
fn layout (&self, to: E::Area) -> E::Area {
let state = StackLayoutState::<E>::new(self.direction, to);
let adder = &|component: &dyn Render<E>|{
let StackLayoutState { x, y, w_remaining, h_remaining, .. } = *state.borrow();
let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh();
state.borrow_mut().grow(w, h);
};
(self.callback)(&adder);
let StackLayoutState { w_used, h_used, .. } = *state.borrow();
match self.direction {
North | West => { todo!() },
South | East => { [to.x(), to.y(), w_used, h_used].into() },
_ => unreachable!(),
}
}
fn render (&self, to: &mut E) {
let state = StackLayoutState::<E>::new(self.direction, to.area());
let to = Rc::new(RefCell::new(to));
let adder = &|component: &dyn Render<E>|{
let StackLayoutState { x, y, w_remaining, h_remaining, .. } = *state.borrow();
let layout = component.layout([x, y, w_remaining, h_remaining].into());
state.borrow_mut().grow(layout.w(), layout.h());
to.borrow_mut().place(layout, component);
};
(self.callback)(&adder);
}
}
#[derive(Copy, Clone)]
struct StackLayoutState<E: Output> {
direction: Direction,
x: E::Unit,
y: E::Unit,
w_used: E::Unit,
h_used: E::Unit,
w_remaining: E::Unit,
h_remaining: E::Unit,
}
impl<E: Output> StackLayoutState<E> {
fn new (direction: Direction, area: E::Area) -> std::rc::Rc<std::cell::RefCell<Self>> {
let [x, y, w_remaining, h_remaining] = area.xywh();
std::rc::Rc::new(std::cell::RefCell::new(Self {
direction,
x, y, w_remaining, h_remaining,
w_used: E::Unit::zero(), h_used: E::Unit::zero()
}))
}
fn grow (&mut self, w: E::Unit, h: E::Unit) -> &mut Self {
match self.direction {
South => { self.y = self.y.plus(h);
self.h_used = self.h_used.plus(h);
self.h_remaining = self.h_remaining.minus(h);
self.w_used = self.w_used.max(w); },
East => { self.x = self.x.plus(w);
self.w_used = self.w_used.plus(w);
self.w_remaining = self.w_remaining.minus(w);
self.h_used = self.h_used.max(h); },
North | West => { todo!() },
Above | Below => {},
};
self
}
fn area_remaining (&self) -> E::Area {
[self.x, self.y, self.w_remaining, self.h_remaining].into()
}
}
//pub struct Stack<'a, E, F1> {
//__: PhantomData<&'a (E, F1)>,
//direction: Direction,
//callback: F1
//}
//impl<'a, E, F1> Stack<'a, E, F1> where
//E: Output, F1: Fn(&mut dyn FnMut(&'a dyn Render<E>)) + Send + Sync,
//{
//pub fn north (callback: F1) -> Self { Self::new(North, callback) }
//pub fn south (callback: F1) -> Self { Self::new(South, callback) }
//pub fn east (callback: F1) -> Self { Self::new(East, callback) }
//pub fn west (callback: F1) -> Self { Self::new(West, callback) }
//pub fn above (callback: F1) -> Self { Self::new(Above, callback) }
//pub fn below (callback: F1) -> Self { Self::new(Below, callback) }
//pub fn new (direction: Direction, callback: F1) -> Self {
//Self { direction, callback, __: Default::default(), }
//}
//}
//impl<'a, E, F1> Content<E> for Stack<'a, E, F1> where
//E: Output, F1: Fn(&mut dyn FnMut(&'a dyn Render<E>)) + Send + Sync,
//{
//fn layout (&self, to: E::Area) -> E::Area {
//let state = StackLayoutState::<E>::new(self.direction, to);
//let mut adder = {
//let state = state.clone();
//move|component: &dyn Render<E>|{
//let [w, h] = component.layout(state.borrow().area_remaining()).wh();
//state.borrow_mut().grow(w, h);
//}
//};
//(self.callback)(&mut adder);
//let StackLayoutState { w_used, h_used, .. } = *state.borrow();
//match self.direction {
//North | West => { todo!() },
//South | East => { [to.x(), to.y(), w_used, h_used].into() },
//Above | Below => { [to.x(), to.y(), to.w(), to.h()].into() },
//}
//}
//fn render (&self, to: &mut E) {
//let state = StackLayoutState::<E>::new(self.direction, to.area());
//let mut adder = {
//let state = state.clone();
//move|component: &dyn Render<E>|{
//let [x, y, w, h] = component.layout(state.borrow().area_remaining()).xywh();
//state.borrow_mut().grow(w, h);
//to.place([x, y, w, h].into(), component);
//}
//};
//(self.callback)(&mut adder);
//}
//}
/*Stack::down(|add|{
let mut i = 0;
for (_, name) in self.dirs.iter() {
if i >= self.scroll {
add(&Tui::bold(i == self.index, name.as_str()))?;
}
i += 1;
}
for (_, name) in self.files.iter() {
if i >= self.scroll {
add(&Tui::bold(i == self.index, name.as_str()))?;
}
i += 1;
}
add(&format!("{}/{i}", self.index))?;
Ok(())
}));*/

153
output/src/layout_xy.rs Normal file
View file

@ -0,0 +1,153 @@
//! Transform:
//! ```
//! use ::tengri::{output::*, tui::*};
//! let area: [u16;4] = [10, 10, 20, 20];
//! fn test (area: [u16;4], item: &impl Content<TuiOut>, expected: [u16;4]) {
//! assert_eq!(Content::layout(item, area), expected);
//! assert_eq!(Render::layout(item, area), expected);
//! };
//! test(area, &(), [20, 20, 0, 0]);
//!
//! test(area, &Fill::xy(()), area);
//! test(area, &Fill::x(()), [10, 20, 20, 0]);
//! test(area, &Fill::y(()), [20, 10, 0, 20]);
//!
//! //FIXME:test(area, &Fixed::x(4, ()), [18, 20, 4, 0]);
//! //FIXME:test(area, &Fixed::y(4, ()), [20, 18, 0, 4]);
//! //FIXME:test(area, &Fixed::xy(4, 4, unit), [18, 18, 4, 4]);
//! ```
use crate::*;
/// Defines an enum that transforms its content
/// along either the X axis, the Y axis, or both.
macro_rules! transform_xy {
($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$area:expr) => {
pub enum $Enum<A> { X(A), Y(A), XY(A) }
impl<A> $Enum<A> {
#[inline] pub const fn x (item: A) -> Self { Self::X(item) }
#[inline] pub const fn y (item: A) -> Self { Self::Y(item) }
#[inline] pub const fn xy (item: A) -> Self { Self::XY(item) }
}
impl<E: Output, T: Content<E>> Content<E> for $Enum<T> {
fn content (&self) -> impl Render<E> + '_ {
match self {
Self::X(item) => item,
Self::Y(item) => item,
Self::XY(item) => item,
}
}
fn layout (&$self, $to: <E as Output>::Area) -> <E as Output>::Area {
use $Enum::*;
$area
}
}
}
}
/// Defines an enum that parametrically transforms its content
/// along either the X axis, the Y axis, or both.
macro_rules! transform_xy_unit {
($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$layout:expr) => {
pub enum $Enum<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> $Enum<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self { Self::X(x, item) }
#[inline] pub const fn y (y: U, item: A) -> Self { Self::Y(y, item) }
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) }
}
impl<E: Output, T: Content<E>> Content<E> for $Enum<E::Unit, T> {
fn layout (&$self, $to: E::Area) -> E::Area {
$layout.into()
}
fn content (&self) -> impl Render<E> + '_ {
use $Enum::*;
Some(match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, })
}
}
impl<U: Coordinate, T> $Enum<U, T> {
#[inline] pub fn dx (&self) -> U {
use $Enum::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use $Enum::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
}
}
transform_xy!("fill/x" "fill/y" "fill/xy" |self: Fill, to|{
let [x0, y0, wmax, hmax] = to.xywh();
let [x, y, w, h] = self.content().layout(to).xywh();
match self {
X(_) => [x0, y, wmax, h],
Y(_) => [x, y0, w, hmax],
XY(_) => [x0, y0, wmax, hmax],
}.into()
});
transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{
let [x, y, w, h] = area.xywh();
let fixed_area = match self {
Self::X(fw, _) => [x, y, *fw, h],
Self::Y(fh, _) => [x, y, w, *fh],
Self::XY(fw, fh, _) => [x, y, *fw, *fh],
};
let [x, y, w, h] = Render::layout(&self.content(), fixed_area.into()).xywh();
let fixed_area = match self {
Self::X(fw, _) => [x, y, *fw, h],
Self::Y(fh, _) => [x, y, w, *fh],
Self::XY(fw, fh, _) => [x, y, *fw, *fh],
};
fixed_area
});
transform_xy_unit!("min/x" "min/y" "min/xy"|self: Min, area|{
let area = Render::layout(&self.content(), area);
match self {
Self::X(mw, _) => [area.x(), area.y(), area.w().max(*mw), area.h()],
Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)],
Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().max(*mw), area.h().max(*mh)],
}
});
transform_xy_unit!("max/x" "max/y" "max/xy"|self: Max, area|{
let [x, y, w, h] = area.xywh();
Render::layout(&self.content(), match self {
Self::X(fw, _) => [x, y, *fw, h],
Self::Y(fh, _) => [x, y, w, *fh],
Self::XY(fw, fh, _) => [x, y, *fw, *fh],
}.into())
});
transform_xy_unit!("shrink/x" "shrink/y" "shrink/xy"|self: Shrink, area|Render::layout(
&self.content(),
[area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())].into()));
transform_xy_unit!("expand/x" "expand/y" "expand/xy"|self: Expand, area|Render::layout(
&self.content(),
[area.x(), area.y(), area.w().plus(self.dx()), area.h().plus(self.dy())].into()));
transform_xy_unit!("push/x" "push/y" "push/xy"|self: Push, area|{
let area = Render::layout(&self.content(), area);
[area.x().plus(self.dx()), area.y().plus(self.dy()), area.w(), area.h()]
});
transform_xy_unit!("pull/x" "pull/y" "pull/xy"|self: Pull, area|{
let area = Render::layout(&self.content(), area);
[area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()]
});
transform_xy_unit!("margin/x" "margin/y" "margin/xy"|self: Margin, area|{
let area = Render::layout(&self.content(), area);
let dx = self.dx();
let dy = self.dy();
[area.x().minus(dx), area.y().minus(dy), area.w().plus(dy.plus(dy)), area.h().plus(dy.plus(dy))]
});
transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{
let area = Render::layout(&self.content(), area);
let dx = self.dx();
let dy = self.dy();
[area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))]
});

View file

@ -1,17 +1,29 @@
#![feature(step_trait)] #![feature(step_trait)]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_assoc_type)] #![feature(impl_trait_in_assoc_type)]
//#![feature(non_lifetime_binders)]
pub(crate) use std::marker::PhantomData; pub(crate) use std::cell::RefCell;
pub(crate) use std::fmt::{Debug, Display}; pub(crate) use std::fmt::{Debug, Display};
pub(crate) use std::ops::{Add, Sub, Mul, Div}; pub(crate) use std::marker::PhantomData;
pub(crate) use std::ops::{Add, Sub, Mul, Div, Deref};
pub(crate) use std::rc::Rc;
pub(crate) use std::sync::RwLock;
pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}}; pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}};
pub(crate) use tengri_core::*; pub(crate) use tengri_core::*;
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
mod space; pub use self::space::*; mod output; pub use self::output::*;
mod layout; pub use self::layout::*; mod output_render; pub use self::output_render::*;
mod output; pub use self::output::*; mod output_content; pub use self::output_content::*;
mod output_thunk; pub use self::output_thunk::*;
mod space; pub use self::space::*;
mod layout_align; pub use self::layout_align::*;
mod layout_bsp; pub use self::layout_bsp::*;
mod layout_cond; pub use self::layout_cond::*;
mod layout_map; pub use self::layout_map::*;
mod layout_stack; pub use self::layout_stack::*;
mod layout_xy; pub use self::layout_xy::*;
#[cfg(test)] mod test; #[cfg(test)] mod test;
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary; #[cfg(test)] pub(crate) use proptest_derive::Arbitrary;

View file

@ -21,131 +21,3 @@ pub trait Output: Send + Sync + Sized {
#[inline] fn h (&self) -> Self::Unit { self.area().h() } #[inline] fn h (&self) -> Self::Unit { self.area().h() }
#[inline] fn wh (&self) -> Self::Size { self.area().wh().into() } #[inline] fn wh (&self) -> Self::Size { self.area().wh().into() }
} }
/// Renderable with dynamic dispatch.
pub trait Render<E: Output> {
/// Compute layout.
fn layout (&self, area: E::Area) -> E::Area;
/// Write data to display.
fn render (&self, output: &mut E);
/// Perform type erasure, turning `self` into an opaque [RenderBox].
fn boxed <'a> (self) -> Box<dyn Render<E> + 'a> where Self: Sized + 'a {
Box::new(self) as Box<dyn Render<E> + 'a>
}
}
/// Most importantly, every [Content] is also a [Render].
///
/// However, the converse does not hold true.
/// Instead, the [Content::content] method returns an
/// opaque [Render] pointer.
impl<E: Output, C: Content<E>> Render<E> for C {
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
fn render (&self, output: &mut E) { Content::render(self, output) }
}
/// Opaque pointer to a renderable living on the heap.
///
/// Return this from [Content::content] to use dynamic dispatch.
pub type RenderBox<E> = Box<dyn Render<E>>;
/// You can render from a box.
impl<E: Output> Content<E> for RenderBox<E> {
fn content (&self) -> impl Render<E> + '_ { self.deref() }
//fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self }
}
/// You can render from an opaque pointer.
impl<E: Output> Content<E> for &dyn Render<E> where Self: Sized {
fn content (&self) -> impl Render<E> + '_ {
#[allow(suspicious_double_ref_op)]
self.deref()
}
fn layout (&self, area: E::Area) -> E::Area {
#[allow(suspicious_double_ref_op)]
Render::layout(self.deref(), area)
}
fn render (&self, output: &mut E) {
#[allow(suspicious_double_ref_op)]
Render::render(self.deref(), output)
}
}
/// Composable renderable with static dispatch.
pub trait Content<E: Output> {
/// Return a [Render]able of a specific type.
fn content (&self) -> impl Render<E> + '_ { () }
/// Perform layout. By default, delegates to [Self::content].
fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) }
/// Draw to output. By default, delegates to [Self::content].
fn render (&self, output: &mut E) { self.content().render(output) }
}
/// Every pointer to [Content] is a [Content].
impl<E: Output, C: Content<E>> Content<E> for &C {
fn content (&self) -> impl Render<E> + '_ { (*self).content() }
fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) }
fn render (&self, output: &mut E) { (*self).render(output) }
}
/// The platonic ideal unit of [Content]: total emptiness at dead center (e=1vg^sqrt(-1))
impl<E: Output> Content<E> for () {
fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() }
fn render (&self, _: &mut E) {}
}
impl<E: Output, T: Content<E>> Content<E> for Option<T> {
fn content (&self) -> impl Render<E> + '_ {
self.as_ref()
}
fn layout (&self, area: E::Area) -> E::Area {
self.as_ref()
.map(|content|content.layout(area))
.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
}
fn render (&self, output: &mut E) {
self.as_ref()
.map(|content|content.render(output));
}
}
/// Implement [Content] with composable content for a struct.
#[macro_export] macro_rules! content {
// Implement for all [Output]s.
(|$self:ident:$Struct:ty| $content:expr) => {
impl<E: Output> Content<E> for $Struct {
fn content (&$self) -> impl Render<E> + '_ { Some($content) }
}
};
// Implement for specific [Output].
($Output:ty:|
$self:ident:
$Struct:ident$(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?
|$content:expr) => {
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
for $Struct $(<$($($L)? $($T)?),+>)? {
fn content (&$self) -> impl Render<$Output> + '_ { $content }
}
};
}
/// Implement [Content] with custom rendering for a struct.
#[macro_export] macro_rules! render {
(|$self:ident:$Struct:ident $(<
$($L:lifetime),* $($T:ident $(:$Trait:path)?),*
>)?, $to:ident | $render:expr) => {
impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content<E>
for $Struct $(<$($L),* $($T),*>>)? {
fn render (&$self, $to: &mut E) { $render }
}
};
($Output:ty:|
$self:ident:
$Struct:ident $(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?, $to:ident
|$render:expr) => {
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
for $Struct $(<$($($L)? $($T)?),+>)? {
fn render (&$self, $to: &mut $Output) { $render }
}
};
}

View file

@ -0,0 +1,88 @@
use crate::*;
/// Composable renderable with static dispatch.
pub trait Content<E: Output> {
/// Return a [Render]able of a specific type.
fn content (&self) -> impl Render<E> + '_ {
()
}
/// Perform layout. By default, delegates to [Self::content].
fn layout (&self, area: E::Area) -> E::Area {
self.content().layout(area)
}
/// Draw to output. By default, delegates to [Self::content].
fn render (&self, output: &mut E) {
self.content().render(output)
}
}
/// Every pointer to [Content] is a [Content].
impl<E: Output, C: Content<E>> Content<E> for &C {
fn content (&self) -> impl Render<E> + '_ { (*self).content() }
fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) }
fn render (&self, output: &mut E) { (*self).render(output) }
}
/// The platonic ideal unit of [Content]: total emptiness at dead center (e=1vg^sqrt(-1))
impl<E: Output> Content<E> for () {
fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() }
fn render (&self, _: &mut E) {}
}
impl<E: Output, T: Content<E>> Content<E> for Option<T> {
fn content (&self) -> impl Render<E> + '_ {
self.as_ref()
}
fn layout (&self, area: E::Area) -> E::Area {
self.as_ref()
.map(|content|content.layout(area))
.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
}
fn render (&self, output: &mut E) {
self.as_ref()
.map(|content|content.render(output));
}
}
/// You can render from a box.
impl<E: Output> Content<E> for RenderBox<E> {
fn content (&self) -> impl Render<E> + '_ { self.deref() }
//fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self }
}
/// You can render from an opaque pointer.
impl<E: Output> Content<E> for &dyn Render<E> where Self: Sized {
fn content (&self) -> impl Render<E> + '_ {
#[allow(suspicious_double_ref_op)]
self.deref()
}
fn layout (&self, area: E::Area) -> E::Area {
#[allow(suspicious_double_ref_op)]
Render::layout(self.deref(), area)
}
fn render (&self, output: &mut E) {
#[allow(suspicious_double_ref_op)]
Render::render(self.deref(), output)
}
}
/// Implement [Content] with custom rendering for a struct.
#[macro_export] macro_rules! render {
(|$self:ident:$Struct:ident $(<
$($L:lifetime),* $($T:ident $(:$Trait:path)?),*
>)?, $to:ident | $render:expr) => {
impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content<E>
for $Struct $(<$($L),* $($T),*>>)? {
fn render (&$self, $to: &mut E) { $render }
}
};
($Output:ty:|
$self:ident:
$Struct:ident $(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?, $to:ident
|$render:expr) => {
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
for $Struct $(<$($($L)? $($T)?),+>)? {
fn render (&$self, $to: &mut $Output) { $render }
}
};
}

View file

@ -0,0 +1,47 @@
use crate::*;
/// Renderable with dynamic dispatch.
pub trait Render<E: Output> {
/// Compute layout.
fn layout (&self, area: E::Area) -> E::Area;
/// Write data to display.
fn render (&self, output: &mut E);
/// Perform type erasure, turning `self` into an opaque [RenderBox].
fn boxed <'a> (self) -> Box<dyn Render<E> + 'a> where Self: Sized + 'a {
Box::new(self) as Box<dyn Render<E> + 'a>
}
}
/// Every [Content] is also a [Render].
/// However, the converse does not hold true.
/// Instead, the [Content::content] method returns an
/// opaque [Render] pointer.
impl<E: Output, C: Content<E>> Render<E> for C {
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
fn render (&self, output: &mut E) { Content::render(self, output) }
}
/// Opaque pointer to a renderable living on the heap.
///
/// Return this from [Content::content] to use dynamic dispatch.
pub type RenderBox<E> = Box<dyn Render<E>>;
/// Implement [Content] with composable content for a struct.
#[macro_export] macro_rules! content {
// Implement for all [Output]s.
(|$self:ident:$Struct:ty| $content:expr) => {
impl<E: Output> Content<E> for $Struct {
fn content (&$self) -> impl Render<E> + '_ { Some($content) }
}
};
// Implement for specific [Output].
($Output:ty:|
$self:ident:
$Struct:ident$(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?
|$content:expr) => {
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
for $Struct $(<$($($L)? $($T)?),+>)? {
fn content (&$self) -> impl Render<$Output> + '_ { $content }
}
};
}

View file

@ -0,0 +1,99 @@
use crate::*;
/// Lazily-evaluated [Render]able.
pub struct Thunk<E: Output, T: Render<E>, F: Fn()->T>(
PhantomData<E>,
F
);
impl<E: Output, T: Render<E>, F: Fn()->T> Thunk<E, T, F> {
pub const fn new (thunk: F) -> Self {
Self(PhantomData, thunk)
}
}
impl<E: Output, T: Render<E>, F: Fn()->T> Content<E> for Thunk<E, T, F> {
fn content (&self) -> impl Render<E> { (self.1)() }
}
/// Lazily-evaluated [Render]able with
/// mandatory stack allocation and dynamic dispatch.
pub struct ThunkBox<E: Output>(
PhantomData<E>,
Box<dyn Fn()->Box<dyn Render<E>>>,
);
impl<E: Output> ThunkBox<E> {
pub const fn new (thunk: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
Self(PhantomData, thunk)
}
}
impl<E: Output> Content<E> for ThunkBox<E> {
fn content (&self) -> impl Render<E> { (&self.1)() }
}
impl<E: Output> From<Box<dyn Fn()->Box<dyn Render<E>>>> for ThunkBox<E> {
fn from (f: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
Self(PhantomData, f)
}
}
//impl<'a, E: Output, F: Fn()->Box<dyn Render<E> + 'a> + 'a> From<F> for ThunkBox<'a, E> {
//fn from (f: F) -> Self {
//Self(Default::default(), Box::new(f))
//}
//}
pub struct ThunkRender<E: Output, F: Fn(&mut E)>(PhantomData<E>, F);
impl<E: Output, F: Fn(&mut E)> ThunkRender<E, F> {
pub fn new (render: F) -> Self { Self(PhantomData, render) }
}
impl<E: Output, F: Fn(&mut E)> Content<E> for ThunkRender<E, F> {
fn render (&self, to: &mut E) { (self.1)(to) }
}
pub struct ThunkLayout<
E: Output,
F1: Fn(E::Area)->E::Area,
F2: Fn(&mut E)
>(
PhantomData<E>,
F1,
F2
);
impl<E: Output, F1: Fn(E::Area)->E::Area, F2: Fn(&mut E)> ThunkLayout<E, F1, F2> {
pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) }
}
impl<E, F1, F2> Content<E> for ThunkLayout<E, F1, F2>
where
E: Output,
F1: Fn(E::Area)->E::Area,
F2: Fn(&mut E)
{
fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) }
fn render (&self, to: &mut E) { (self.2)(to) }
}
#[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T,
pub view: Arc<RwLock<U>>
}
impl<T: PartialEq, U> Memo<T, U> {
pub fn new (value: T, view: U) -> Self {
Self { value, view: Arc::new(view.into()) }
}
pub fn update <R> (
&mut self,
newval: T,
render: impl Fn(&mut U, &T, &T)->R
) -> Option<R> {
if newval != self.value {
let result = render(&mut*self.view.write().unwrap(), &newval, &self.value);
self.value = newval;
return Some(result);
}
None
}
}
/// Clear a pre-allocated buffer, then write into it.
#[macro_export] macro_rules! rewrite {
($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } }
}