//! Transform: //! ``` //! use ::tengri::{output::*, tui::*}; //! let area: [u16;4] = [10, 10, 20, 20]; //! fn test (area: [u16;4], item: &impl Content, 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, 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(pub bool, pub A); impl When { /// Create a binary condition. pub const fn new (c: bool, a: A) -> Self { Self(c, a) } } impl> Content for When { 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(pub bool, pub A, pub B); impl Either { /// Create a ternary view condition. pub const fn new (c: bool, a: A, b: B) -> Self { Self(c, a, b) } } impl, B: Render> Content for Either { 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(Alignment, A); impl Align { #[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> Content for Align { fn content (&self) -> impl Render + '_ { &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( pub(crate) Direction, pub(crate) A, pub(crate) B, ); impl Bsp { #[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, B: Content> Content for Bsp { 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, B: Content> BspAreas for Bsp { fn direction (&self) -> Direction { self.0 } fn contents (&self) -> (&A, &B) { (&self.1, &self.2) } } pub trait BspAreas, B: Content> { 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 { X(A), Y(A), XY(A) } impl $Enum { #[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> Content for $Enum { fn content (&self) -> impl Render + '_ { match self { Self::X(item) => item, Self::Y(item) => item, Self::XY(item) => item, } } fn layout (&$self, $to: ::Area) -> ::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 { X(U, A), Y(U, A), XY(U, U, A), } impl $Enum { #[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> Content for $Enum { fn layout (&$self, $to: E::Area) -> E::Area { $layout.into() } fn content (&self) -> impl Render + '_ { use $Enum::*; Some(match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }) } } impl $Enum { #[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 FromDsl for $Struct$(<$($A),+>)? { //fn try_from_dsl ( //state: &S, dsl: &impl Dsl //) -> Perhaps { //todo!() //} //} //)* //} //} macro_rules! dsl { ( $Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { impl FromDsl for $Struct$(<$($A),+>)? { fn from_dsl ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { todo!() } } } } macro_rules! dsl_ns { ( $Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { impl FromDsl for $Struct$(<$($A),+>)? { fn from_dsl ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { todo!() } } } } dsl!(When "when" [_head, tail] Self(tail(0)?, tail(1)?)); dsl!(Either "either" [_head, tail] Self(tail(0)?, tail(1)?, tail(2)?)); dsl_ns!(Align "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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, F: Fn()->T>( PhantomData, F ); impl, F: Fn()->T> Thunk { pub const fn new (thunk: F) -> Self { Self(PhantomData, thunk) } } impl, F: Fn()->T> Content for Thunk { fn content (&self) -> impl Render { (self.1)() } } pub struct ThunkBox( PhantomData, BoxBox>>, ); impl ThunkBox { pub const fn new (thunk: BoxBox>>) -> Self { Self(PhantomData, thunk) } } impl Content for ThunkBox { fn content (&self) -> impl Render { (&self.1)() } } impl FromBox>>> for ThunkBox { fn from (f: BoxBox>>) -> Self { Self(PhantomData, f) } } //impl<'a, E: Output, F: Fn()->Box + 'a> + 'a> From for ThunkBox<'a, E> { //fn from (f: F) -> Self { //Self(Default::default(), Box::new(f)) //} //} pub struct ThunkRender(PhantomData, F); impl ThunkRender { pub fn new (render: F) -> Self { Self(PhantomData, render) } } impl Content for ThunkRender { 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, F1, F2 ); implE::Area, F2: Fn(&mut E)> ThunkLayout { pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) } } impl Content for ThunkLayout 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 { pub value: T, pub view: Arc> } impl Memo { pub fn new (value: T, view: U) -> Self { Self { value, view: Arc::new(view.into()) } } pub fn update ( &mut self, newval: T, render: impl Fn(&mut U, &T, &T)->R ) -> Option { 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)) + 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 for Stack<'t, E, F1> where Self: 't, E: Output, F1: Fn(&mut dyn FnMut(&dyn Render)) + 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|{ 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|{ 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 where I: Iterator + 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 where I: Iterator + 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>>>, I, F, fn(A, usize)->B > where E: Output, B: Render, I: Iterator + 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>>, I, F, impl Fn(A, usize)->Push>> + 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 for Map where E: Output, B: Render, I: Iterator + 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( item_offset: O::Unit, item_height: O::Unit, item: impl Content ) -> impl Content { Push::y(item_offset, Fixed::y(item_height, Fill::x(item))) } #[inline] pub fn map_south_west( item_offset: O::Unit, item_height: O::Unit, item: impl Content ) -> impl Content { Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item)))) } #[inline] pub fn map_east( item_offset: O::Unit, item_width: O::Unit, item: impl Content ) -> impl Content { 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 FromDsl for When where bool: FromDsl, A: FromDsl { //fn try_provide (state: &S, source: &DslVal) -> Perhaps { //source.exp_match("when", |_, tail|Ok(Some(Self( //FromDsl::::provide(state, //tail.nth(0, ||"no condition".into())?, ||"no condition".into())?, //FromDsl::::provide(state, //tail.nth(1, ||"no content".into())?, ||"no content".into())?, //)))) //} //} ///// The syntagm `(either :condition :content1 :content2)` corresponds to an [Either] layout element. //impl FromDsl for Either where S: Eval + Eval + Eval { //fn try_provide (state: &S, source: &DslVal) -> Perhaps { //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 FromDsl for Align where S: Eval, A> { //fn try_provide (state: &S, source: &DslVal) -> Perhaps { //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 FromDsl for Bsp where S: Eval, A> + Eval, B> { //fn try_provide (state: &S, source: &DslVal) -> Perhaps { //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|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|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(()) }));*/