From 4f1131744bdc2760efa5169aaa59ea45ad29c5f3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 2 Sep 2025 22:36:34 +0300 Subject: [PATCH] output: remodularize --- output/src/layout.rs | 991 ----------------------------------- output/src/layout_align.rs | 80 +++ output/src/layout_bsp.rs | 97 ++++ output/src/layout_cond.rs | 214 ++++++++ output/src/layout_map.rs | 131 +++++ output/src/layout_stack.rs | 168 ++++++ output/src/layout_xy.rs | 153 ++++++ output/src/lib.rs | 22 +- output/src/output.rs | 128 ----- output/src/output_content.rs | 88 ++++ output/src/output_render.rs | 47 ++ output/src/output_thunk.rs | 99 ++++ 12 files changed, 1094 insertions(+), 1124 deletions(-) delete mode 100644 output/src/layout.rs create mode 100644 output/src/layout_align.rs create mode 100644 output/src/layout_bsp.rs create mode 100644 output/src/layout_cond.rs create mode 100644 output/src/layout_map.rs create mode 100644 output/src/layout_stack.rs create mode 100644 output/src/layout_xy.rs create mode 100644 output/src/output_content.rs create mode 100644 output/src/output_render.rs create mode 100644 output/src/output_thunk.rs diff --git a/output/src/layout.rs b/output/src/layout.rs deleted file mode 100644 index 57eb080..0000000 --- a/output/src/layout.rs +++ /dev/null @@ -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, 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(()) -}));*/ diff --git a/output/src/layout_align.rs b/output/src/layout_align.rs new file mode 100644 index 0000000..59eda80 --- /dev/null +++ b/output/src/layout_align.rs @@ -0,0 +1,80 @@ +//! ``` +//! 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::*; + +/// 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()) + } +} diff --git a/output/src/layout_bsp.rs b/output/src/layout_bsp.rs new file mode 100644 index 0000000..0272d17 --- /dev/null +++ b/output/src/layout_bsp.rs @@ -0,0 +1,97 @@ +use crate::*; +use Direction::*; + +/// 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()] + }, + } + } +} + +/// 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 }}); diff --git a/output/src/layout_cond.rs b/output/src/layout_cond.rs new file mode 100644 index 0000000..1da4925 --- /dev/null +++ b/output/src/layout_cond.rs @@ -0,0 +1,214 @@ +use crate::*; + +/// 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) } + } +} + + + +/////////////////////////////////////////////////////////////////////////////// + + + ///// 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 + //}) diff --git a/output/src/layout_map.rs b/output/src/layout_map.rs new file mode 100644 index 0000000..a194c06 --- /dev/null +++ b/output/src/layout_map.rs @@ -0,0 +1,131 @@ +use crate::*; + +/// 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)))) +} + + diff --git a/output/src/layout_stack.rs b/output/src/layout_stack.rs new file mode 100644 index 0000000..4e45e4b --- /dev/null +++ b/output/src/layout_stack.rs @@ -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)) + '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)) + 'x> Content for Stack<'x, E, F> { + fn layout (&self, to: E::Area) -> E::Area { + let state = StackLayoutState::::new(self.direction, to); + let adder = &|component: &dyn Render|{ + 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::::new(self.direction, to.area()); + let to = Rc::new(RefCell::new(to)); + let adder = &|component: &dyn Render|{ + 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 { + 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 StackLayoutState { + fn new (direction: Direction, area: E::Area) -> std::rc::Rc> { + 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)) + 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 for Stack<'a, E, F1> where + //E: Output, F1: Fn(&mut dyn FnMut(&'a dyn Render)) + Send + Sync, +//{ + //fn layout (&self, to: E::Area) -> E::Area { + //let state = StackLayoutState::::new(self.direction, to); + //let mut adder = { + //let state = state.clone(); + //move|component: &dyn Render|{ + //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::::new(self.direction, to.area()); + //let mut adder = { + //let state = state.clone(); + //move|component: &dyn Render|{ + //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(()) +}));*/ diff --git a/output/src/layout_xy.rs b/output/src/layout_xy.rs new file mode 100644 index 0000000..93d2eea --- /dev/null +++ b/output/src/layout_xy.rs @@ -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, 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 { 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))] +}); diff --git a/output/src/lib.rs b/output/src/lib.rs index 8576d23..0a61edb 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -1,17 +1,29 @@ #![feature(step_trait)] #![feature(type_alias_impl_trait)] #![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::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 tengri_core::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; -mod space; pub use self::space::*; -mod layout; pub use self::layout::*; -mod output; pub use self::output::*; +mod output; pub use self::output::*; +mod output_render; pub use self::output_render::*; +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)] pub(crate) use proptest_derive::Arbitrary; diff --git a/output/src/output.rs b/output/src/output.rs index 324d7a5..4eb1036 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -21,131 +21,3 @@ pub trait Output: Send + Sync + Sized { #[inline] fn h (&self) -> Self::Unit { self.area().h() } #[inline] fn wh (&self) -> Self::Size { self.area().wh().into() } } - -/// Renderable with dynamic dispatch. -pub trait Render { - /// 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 + 'a> where Self: Sized + 'a { - Box::new(self) as Box + '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> Render 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 = Box>; - -/// You can render from a box. -impl Content for RenderBox { - fn content (&self) -> impl Render + '_ { self.deref() } - //fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self } -} - -/// You can render from an opaque pointer. -impl Content for &dyn Render where Self: Sized { - fn content (&self) -> impl Render + '_ { - #[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 { - /// Return a [Render]able of a specific type. - fn content (&self) -> impl Render + '_ { () } - /// 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> Content for &C { - fn content (&self) -> impl Render + '_ { (*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 Content for () { - fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() } - fn render (&self, _: &mut E) {} -} - -impl> Content for Option { - fn content (&self) -> impl Render + '_ { - 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 Content for $Struct { - fn content (&$self) -> impl Render + '_ { 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 - 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 } - } - }; -} diff --git a/output/src/output_content.rs b/output/src/output_content.rs new file mode 100644 index 0000000..ef199db --- /dev/null +++ b/output/src/output_content.rs @@ -0,0 +1,88 @@ +use crate::*; + +/// Composable renderable with static dispatch. +pub trait Content { + /// Return a [Render]able of a specific type. + fn content (&self) -> impl Render + '_ { + () + } + /// 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> Content for &C { + fn content (&self) -> impl Render + '_ { (*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 Content for () { + fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() } + fn render (&self, _: &mut E) {} +} + +impl> Content for Option { + fn content (&self) -> impl Render + '_ { + 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 Content for RenderBox { + fn content (&self) -> impl Render + '_ { self.deref() } + //fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self } +} + +/// You can render from an opaque pointer. +impl Content for &dyn Render where Self: Sized { + fn content (&self) -> impl Render + '_ { + #[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 + 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 } + } + }; +} diff --git a/output/src/output_render.rs b/output/src/output_render.rs new file mode 100644 index 0000000..3027b0c --- /dev/null +++ b/output/src/output_render.rs @@ -0,0 +1,47 @@ +use crate::*; + +/// Renderable with dynamic dispatch. +pub trait Render { + /// 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 + 'a> where Self: Sized + 'a { + Box::new(self) as Box + '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> Render 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 = Box>; + +/// 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 Content for $Struct { + fn content (&$self) -> impl Render + '_ { 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 } + } + }; +} diff --git a/output/src/output_thunk.rs b/output/src/output_thunk.rs new file mode 100644 index 0000000..5a2cb5c --- /dev/null +++ b/output/src/output_thunk.rs @@ -0,0 +1,99 @@ +use crate::*; + +/// 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)() } +} + +/// Lazily-evaluated [Render]able with +/// mandatory stack allocation and dynamic dispatch. +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)*) } } +}