support multipass layout, barely

This commit is contained in:
facile pop culture reference 2026-08-04 07:59:20 +03:00
parent 083ce8ba76
commit 7f9b7091e2
20 changed files with 1581 additions and 1783 deletions

View file

@ -1,45 +0,0 @@
use crate::*;
use Azimuth::*;
pub struct Align<T>(
pub(crate) Option<Azimuth>,
pub(crate) T,
);
impl<S: Screen, T: Draw<S>> Draw<S> for Align<T> {
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
Ok(align::<S>(area, self.1.layout(area)?, self.0))
}
fn draw (self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
Ok(if let Some(area) = self.layout(to.area())? {
to.clip(area, self.1)?
} else {
None
})
}
}
fn align <S: Screen> (
area0: XYWH<S::Unit>, area: Option<XYWH<S::Unit>>, azimuth: Option<Azimuth>
) -> Option<XYWH<S::Unit>> {
area.map(|XYWH(x, y, w, h)|{
let XYWH(x0, y0, w0, h0) = area0;
match azimuth {
Some(NW) => XYWH(x0, y0, w, h),
Some(N) => XYWH(x0 + w0.minus(w) / 2.into(), y0, w, h),
Some(NE) => XYWH((x0 + w0).minus(w), y0, w, h),
Some(W) => XYWH(x0, y0 + h0.minus(h) / 2.into(), w, h),
Some(C) => XYWH(x0 + w0.minus(w) / 2.into(), y0 + h0.minus(h) / 2.into(), w, h),
Some(E) => XYWH((x0 + w0).minus(w), y0 + h0.minus(h) / 2.into(), w, h),
Some(SW) => XYWH(x0, (y0 + h0).minus(h), w, h),
Some(S) => XYWH(x0 + w0.minus(w) / 2.into(), (y0 + h0).minus(h), w, h),
Some(SE) => XYWH((x0 + w0).minus(w), (y0 + h0).minus(h), w, h),
Some(X) => XYWH(x0 + w0.minus(w) / 2.into(), y, w, h),
Some(Y) => XYWH(x, y0 + h0.minus(h) / 2.into(), w, h),
None => XYWH(x, y, w, h)
}
})
}
#[cfg(test)] #[test] fn test_align () {
}

View file

@ -18,5 +18,5 @@ pub struct Area<S: Screen, T: Draw<S>>(
);
impl_draw!(<S: Screen, T: Draw<S>,>|self: Area<S, T>, to: S|{
to.clip(self.0, self.1)
to.draw(self.0, &self.1)
});

245
src/layout/axis.rs Normal file
View file

@ -0,0 +1,245 @@
use crate::*;
macro_rules! def_layout_modifier {
(
$Trait:ident ($fn_x:ident $fn_y:ident $fn_xy:ident),
$Struct:ident { $X:ident $Y:ident $XY:ident } $kw_name:ident $name:literal
|$self:ident, $to:ident| $body:block
) => {
impl<S: Screen, T: Draw<S>> $Trait<S> for T {}
pub trait $Trait<S: Screen>: Draw<S> + Sized {
fn $fn_x <N: Into<Option<S::Unit>> + Copy> (self, x: N)
-> $Struct<S, Self, N> { $Struct::$X(self, x) }
fn $fn_y <N: Into<Option<S::Unit>> + Copy> (self, y: N)
-> $Struct<S, Self, N> { $Struct::$Y(self, y) }
fn $fn_xy <N: Into<Option<S::Unit>> + Copy> (self, x: N, y: N)
-> $Struct<S, Self, N> { $Struct::$XY(self, x, y) }
}
pub enum $Struct<S: Screen, I: Draw<S>, X: Into<Option<S::Unit>>> {
__(PhantomData<S>), $X(I, X), $Y(I, X), $XY(I, X, X),
}
impl <S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for $Struct<S, I, X> {
fn draw (&$self, $to: &mut S) -> Perhaps<XYWH<S::Unit>> {
$body
}
}
fn_kw_layout!($kw_name |state, output, expr| {
let head = expr.head()?;
let mut frags = head.src()?.unwrap_or_default().split("/");
Ok(matches!(expr.head()?, Some("exact")).then(||{
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
eval_xy!(
$name => expr, head, output, state, frags.next(),
$fn_xy, $fn_x, $fn_y,
arg0, arg1, arg2,
)
}).transpose()?.flatten())
});
}
}
def_layout_modifier!(
CanExact (exact_w exact_h exact_wh),
Exact { W H WH } kw_exact "exact" |self, to| {
let XYWH(x, y, w0, h0) = to.area();
let (item, w, h) = match self {
Self::W(item, w) => (item, (*w).into().unwrap_or(w0), h0),
Self::H(item, h) => (item, w0, (*h).into().unwrap_or(h0)),
Self::WH(item, w, h) => (item, (*w).into().unwrap_or(h0), (*h).into().unwrap_or(h0)),
_ => unreachable!()
};
to.draw(XYWH(x, y, w0, h0), item)
}
);
def_layout_modifier!(
CanMin (min_w min_h min_wh),
Min { W H WH } kw_min "min" |self, to| {
match self {
Self::__(_) => unreachable!(),
Self::W(item, w1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
let w: S::Unit = (*w1).into().map(|w1|w.max(w1)).unwrap_or(w);
to.draw(XYWH(x, y, w, h), item)
},
Self::H(item, h1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
let h: S::Unit = (*h1).into().map(|h1|h.max(h1)).unwrap_or(h);
to.draw(XYWH(x, y, w, h), item)
},
Self::WH(item, w1, h1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
let w: S::Unit = (*w1).into().map(|w1|w.max(w1)).unwrap_or(w);
let h: S::Unit = (*h1).into().map(|h1|h.max(h1)).unwrap_or(h);
to.draw(XYWH(x, y, w, h), item)
},
_ => Ok(None)
}
}
);
def_layout_modifier!(
CanMax (max_w max_h max_wh),
Max { W H WH } kw_max "max" |self, to| {
let area: XYWH<S::Unit> = to.area();
let (item, area) = match self {
Self::W(item, max_w) => (item, XYWH(
area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2),
area.3
)),
Self::H(item, max_h) => (item, XYWH(
area.0, area.1, area.2,
(*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3)
)),
Self::WH(item, max_w, max_h) => (item, XYWH(
area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2),
(*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3),
)),
_ => return Ok(None)
};
to.draw(area, item)
}
);
def_layout_modifier!(
CanPad (pad_w pad_h pad_wh),
Pad { X Y XY } kw_pad "pad" |self, to| {
let area = to.area();
let (item, area) = match self {
Self::X(item, w1) => {
let w1: S::Unit = (*w1).into().unwrap_or_default();
(item, XYWH(area.0 + w1, area.1, area.2.minus(w1 + w1), area.3))
},
Self::Y(item, h1) => {
let h1: S::Unit = (*h1).into().unwrap_or_default();
(item, XYWH(area.0, area.1 + h1, area.2, area.3.minus(h1 + h1)))
},
Self::XY(item, w1, h1) => {
let w1: S::Unit = (*w1).into().unwrap_or_default();
let h1: S::Unit = (*h1).into().unwrap_or_default();
(item, XYWH(area.0 + w1, area.1 + h1, area.2.minus(w1 + w1), area.3.minus(h1 + h1)))
},
_ => return Ok(None)
};
item.draw(to)
}
);
def_layout_modifier!(
CanPush (push_x push_y push_xy),
Push { X Y XY } kw_push "push" |self, to| {
match self {
Self::__(_) => unreachable!(),
Self::X(item, x1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
to.draw(XYWH(x + (*x1).into().unwrap_or_default(), y, w, h), item)
},
Self::Y(item, y1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
to.draw(XYWH(x, y + (*y1).into().unwrap_or_default(), w, h), item)
},
Self::XY(item, x1, y1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
to.draw(XYWH(x + (*x1).into().unwrap_or_default(), y + (*y1).into().unwrap_or_default(), w, h), item)
},
_ => Ok(None)
}
}
);
def_layout_modifier!(
CanPull (pull_x pull_y pull_xy),
Pull { X Y XY } kw_pull "pull" |self, to| {
todo!()
}
);
/// Use whole drawing area along one or both axes.
///
/// ```
/// # fn doctest_layout_full () -> Result<(), Box<dyn std::error::Error>> {
/// use tengri::{Layout, Draw, XYWH};
/// let area = XYWH(0u16, 0, 80, 25);
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
/// assert_eq!("1".full_w().layout(area)?, Some(XYWH(0u16, 0, 80, 1)));
/// assert_eq!("1".full_h().layout(area)?, Some(XYWH(0u16, 0, 1, 25)));
/// assert_eq!("1".full_wh().layout(area)?, Some(XYWH(0u16, 0, 80, 25)));
/// # Ok(()) }
/// ```
pub enum Full<T: Screen, I: Draw<T>> {
__(PhantomData<T>),
W(I),
H(I),
WH(I),
}
impl_draw!(<T: Screen, I: Draw<T>,>|self: Full<T, I>, to: T|{
let XYWH(x0, y0, w0, h0) = to.area();
match self {
Self::W(item) => if let Some(XYWH(_, y, _, h)) = to.size(None, item)? {
to.draw(XYWH(x0, y, w0, h), item)
} else {
Ok(None)
},
Self::H(item) => if let Some(XYWH(x, _, w, _)) = to.size(None, item)? {
to.draw(XYWH(x, y0, w, h0), item)
} else {
Ok(None)
},
Self::WH(item) => if let Some(XYWH(..)) = to.size(None, item)? {
to.draw(XYWH(x0, y0, w0, h0), item)
} else {
Ok(None)
},
_ => unreachable!(),
}
});
/// Only draw content if area is above a certain size.
///
/// ```
/// # fn doctest_layout_min () -> Result<(), Box<dyn std::error::Error>> {
/// use tengri::{Layout, Draw, XYWH};
/// let area = XYWH(1u16, 1, 80, 25);
/// assert_eq!("1".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 5, 1)));
/// assert_eq!("1".min_h(5).layout(area)?, Some(XYWH(1u16, 1, 1, 5)));
/// assert_eq!("1".min_wh(5, 5).layout(area)?, Some(XYWH(1u16, 1, 5, 5)));
/// assert_eq!("123456".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 6, 1)));
/// # Ok(()) }
/// ```
/// Move content in the negative direction of one or both axes.
///
/// ```
/// # fn doctest_layout_pull () -> Result<(), Box<dyn std::error::Error>> {
/// use tengri::{Layout, Draw, XYWH};
/// let area = XYWH(1u16, 1, 80, 25);
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
/// assert_eq!("1".pull_x(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
/// assert_eq!("1".pull_y(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
/// assert_eq!("1".pull_xy(1, 1).layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
/// # Ok(()) }
/// ```
/// Move content in the positive direction of one or both axes.
///
/// ```
/// # fn doctest_layout_push () -> Result<(), Box<dyn std::error::Error>> {
/// use tengri::{Layout, Draw, XYWH};
/// let area = XYWH(0u16, 0, 80, 25);
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
/// assert_eq!("1".push_x(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
/// assert_eq!("1".push_y(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
/// assert_eq!("1".push_xy(1, 1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
/// # Ok(()) }
/// ```
#[cfg(test)] #[test] fn test_exact () -> Usually<()> {
let mut screen = Tui::Layout(XYWH(0, 0, 80, 25));
assert_eq!("FOOBAR\nKILROY".draw(&mut screen)?, Some(XYWH(0, 0, 6, 2)));
assert_eq!("FOOBAR\nKILROY".exact_w(3).draw(&mut screen)?, Some(XYWH(0, 0, 3, 2)));
assert_eq!("FOOBAR\nKILROY".exact_h(1).draw(&mut screen)?, Some(XYWH(0, 0, 6, 1)));
assert_eq!("FOOBAR\nKILROY".exact_wh(2, 1).draw(&mut screen)?, Some(XYWH(0, 0, 2, 1)));
Ok(())
}

455
src/layout/azimuth.rs Normal file
View file

@ -0,0 +1,455 @@
use crate::*;
use Azimuth::*;
/// Where is [0, 0] located?
///
/// ```
/// use tengri::*;
/// use Azimuth::*;
/// let _ = "".align(NW);
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Debug, Copy, Clone, Default)] pub enum Azimuth {
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
}
pub struct Origin<T>(
pub(crate) Option<Azimuth>,
pub(crate) T
);
impl_draw!(<S: Screen, T: Draw<S>,>|self: Origin<T>, _to: S|{
todo!()
});
/// Something that has `[0, 0]` at a particular point.
pub trait HasOrigin {
fn origin (&self) -> Azimuth;
}
impl<T: AsRef<Azimuth>> HasOrigin for T {
fn origin (&self) -> Azimuth {
*self.as_ref()
}
}
fn_kw_layout!(kw_align |state, output, expr| {
Ok(matches!(expr.head()?, Some("align")).then(||{
draw(move|output: &mut O|{state.interpret(output, &expr.tail().head())}).align(
eval_enum!("align", output, state,
expr.head().src()?.unwrap_or_default().split("/").skip(1).next(),
expr.tail().head(),
Azimuth {
"c" => C, "x" => X, "y" => Y,
"n" => N, "s" => S, "e" => E, "w" => W,
"nw" => NW, "sw" => SW, "ne" => NE, "se" => SE,
})
).draw(output)
}).transpose()?.flatten())
});
impl<S: Screen, T: Draw<S>> CanAlign<S> for T {}
pub trait CanAlign<S: Screen>: Draw<S> + Sized {
fn align (self, azimuth: impl Into<Option<Azimuth>>) -> Align<Self> {
Align(azimuth.into(), self)
}
fn align_c (self) -> Align<Self> {
Align(Some(Azimuth::C), self)
}
fn align_x (self) -> Align<Self> {
Align(Some(Azimuth::X), self)
}
fn align_y (self) -> Align<Self> {
Align(Some(Azimuth::Y), self)
}
fn align_n (self) -> Align<Self> {
Align(Some(Azimuth::N), self)
}
fn align_s (self) -> Align<Self> {
Align(Some(Azimuth::S), self)
}
fn align_e (self) -> Align<Self> {
Align(Some(Azimuth::E), self)
}
fn align_w (self) -> Align<Self> {
Align(Some(Azimuth::W), self)
}
fn align_ne (self) -> Align<Self> {
Align(Some(Azimuth::NE), self)
}
fn align_se (self) -> Align<Self> {
Align(Some(Azimuth::SE), self)
}
fn align_nw (self) -> Align<Self> {
Align(Some(Azimuth::NW), self)
}
fn align_sw (self) -> Align<Self> {
Align(Some(Azimuth::SW), self)
}
}
pub struct Align<T>(
pub(crate) Option<Azimuth>,
pub(crate) T,
);
impl<S: Screen, T: Draw<S>> Draw<S> for Align<T> {
fn draw (&self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
let area = to.area();
Ok(if let Some(area) = align::<S>(area, to.size(area, &self.1)?, self.0) {
to.draw(area, &self.1)?
} else {
None
})
}
}
fn align <S: Screen> (
area0: XYWH<S::Unit>, area: Option<XYWH<S::Unit>>, azimuth: Option<Azimuth>
) -> Option<XYWH<S::Unit>> {
area.map(|XYWH(x, y, w, h)|{
let XYWH(x0, y0, w0, h0) = area0;
match azimuth {
Some(NW) => XYWH(x0, y0, w, h),
Some(N) => XYWH(x0 + w0.minus(w) / 2.into(), y0, w, h),
Some(NE) => XYWH((x0 + w0).minus(w), y0, w, h),
Some(W) => XYWH(x0, y0 + h0.minus(h) / 2.into(), w, h),
Some(C) => XYWH(x0 + w0.minus(w) / 2.into(), y0 + h0.minus(h) / 2.into(), w, h),
Some(E) => XYWH((x0 + w0).minus(w), y0 + h0.minus(h) / 2.into(), w, h),
Some(SW) => XYWH(x0, (y0 + h0).minus(h), w, h),
Some(S) => XYWH(x0 + w0.minus(w) / 2.into(), (y0 + h0).minus(h), w, h),
Some(SE) => XYWH((x0 + w0).minus(w), (y0 + h0).minus(h), w, h),
Some(X) => XYWH(x0 + w0.minus(w) / 2.into(), y, w, h),
Some(Y) => XYWH(x, y0 + h0.minus(h) / 2.into(), w, h),
None => XYWH(x, y, w, h)
}
})
}
fn_kw_layout!(kw_split |state, output, expr| {
let head = expr.head();
let mut frags = head.src()?.unwrap_or_default().split("/");
Ok(matches!(frags.next(), Some("bsp")).then(||{
eval_enum!("bsp", output, state, frags.next(), expr.tail().head()?, Split {
"n" => North,
"s" => South,
"e" => East,
"w" => West,
"a" => Above,
"b" => Below
}).stack(
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().head()?)
}),
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().tail().head()?)
}),
).draw(output)
}).transpose()?.flatten())
});
/// Split along an axis. Direction determines order.
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split {
North,
South,
East,
West,
Above,
#[default] Below
}
pub struct Pair<S: Screen, A: Draw<S>, B: Draw<S>>(Split, A, B, PhantomData<S>);
pub fn split <S: Screen, A: Draw<S>, B: Draw<S>> (
split: Split, a: A, b: B
) -> Pair<S, A, B> {
Pair(split, a, b, PhantomData)
}
impl<S: Screen, A: Draw<S>, B: Draw<S>> Draw<S> for Pair<S, A, B> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
let Self(split, a, b, ..) = self;
let (area_a, area_b) = stack_areas(split, to, a, b)?;
let (drawn_a, drawn_b) = draw_stacks(split, to, a, area_a, None, b, area_b, None)?;
Ok(stack_drawn(split, drawn_a, drawn_b))
}
}
impl Split {
/// ```
/// use tengri::*;
/// let _ = Split::Above.stack("", "");
/// let _ = Split::Below.stack("", "");
/// let _ = Split::North.stack("", "");
/// let _ = Split::South.stack("", "");
/// let _ = Split::East.stack("", "");
/// let _ = Split::West.stack("", "");
/// ```
pub const fn stack <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: A, b: B) -> impl Draw<S> {
Pair(*self, a, b, PhantomData)
}
/// ```
/// use tengri::*;
/// let _ = Split::Above.half("", "");
/// let _ = Split::Below.half("", "");
/// let _ = Split::North.half("", "");
/// let _ = Split::South.half("", "");
/// let _ = Split::East.half("", "");
/// let _ = Split::West.half("", "");
/// ```
pub const fn half <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: &A, b: &B) -> impl Draw<S> {
draw(move|to: &mut S|{
let (area_a, area_b) = to.xywh().split_half(self);
let (origin_a, origin_b) = self.origins();
let (drawn_a, drawn_b) = draw_stacks(self, to, a, area_a, origin_a, b, area_b, origin_b)?;
Ok(stack_drawn(self, drawn_a, drawn_b))
})
}
/// Newly split areas begin at the center of the split
/// to maintain centeredness in the user's field of view.
///
/// Use [align] to override that and always start
/// at the top, bottom, etc.
///
/// ```
/// /*
///
/// Split east: Split south:
/// | | | | A |
/// | <-A|B-> | |---------|
/// | | | | B |
///
/// */
/// ```
const fn origins (&self) -> (Azimuth, Azimuth) {
use Azimuth::*;
match self {
Self::South => (S, N),
Self::East => (E, W),
Self::North => (N, S),
Self::West => (W, E),
Self::Above => (C, C),
Self::Below => (C, C),
}
}
///// ```
///// use tengri::*;
///// let _ = Split::Below.iter([
///// "Leftbar"
///// .min_w(10).max_w(15).align(Azimuth::NW),
///// "Rightbar"
///// .min_w(10).max_w(12).align(Azimuth::NE),
///// "Center"
///// .min_w(20).max_w(40).align(Azimuth::C),
///// ].iter());
///// ```
//pub fn iter <S: Screen, T: Draw<S>> (&self, _: impl Iterator<Item = T>) {
//todo!()
//}
}
fn draw_stacks <S: Screen> (
split: &Split,
to: &mut S,
a: impl Draw<S>,
area_a: impl Into<Option<XYWH<S::Unit>>>,
origin_a: impl Into<Option<Azimuth>>,
b: impl Draw<S>,
area_b: impl Into<Option<XYWH<S::Unit>>>,
origin_b: impl Into<Option<Azimuth>>,
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
let draw_a = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_a) = origin_a.into() {
to.draw(area_a.into(), a.align(origin_a))?
} else {
to.draw(area_a.into(), a)?
});
let draw_b = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_b) = origin_b.into() {
to.draw(area_b.into(), b.align(origin_b))?
} else {
to.draw(area_b.into(), b)?
});
Ok(if matches!(split, Split::Below) {
let drawn_b = draw_b(to)?;
let drawn_a = draw_a(to)?;
(drawn_a, drawn_b)
} else {
(draw_a(to)?, draw_b(to)?)
})
}
pub fn stack_areas <S: Screen> (
split: &Split,
to: &mut S,
a: impl Draw<S>,
b: impl Draw<S>,
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
let area_a = to.draw(None, a)?;
Ok(match split {
Split::South => (
area_a,
area_a
.map(|used|to.size(XYWH(to.x(), to.y() + used.h(), to.w(), to.h().minus(used.h())), b))
.transpose()?
.flatten()
),
Split::East => (
area_a,
area_a.map(|used|to.size(XYWH(to.x() + used.w(), to.y(), to.w().minus(used.w()), to.h()), b))
.transpose()?
.flatten()
),
Split::North => (
area_a.map(|used|XYWH(used.x(), (to.y() + to.h()).minus(used.h()), used.w(), used.h())),
if let Some(used) = area_a {
to.size(XYWH(to.x(), to.y(), to.w(), to.h().minus(used.h())), b)?
} else {
to.size(None, b)?
.map(|area_b|XYWH(area_b.x(), area_b.y() + area_b.h(), area_b.w(), area_b.h()))
}
),
Split::West => (
area_a.map(|used|XYWH((to.x() + to.w()).minus(used.w()), used.y(), used.w(), used.h())),
if let Some(used) = area_a {
to.size(XYWH(to.x(), to.y(), to.w().minus(used.w()), to.h()), b)?
} else {
to.size(None, b)?
.map(|area_b|XYWH(area_b.x() + area_b.w(), area_b.y(), area_b.w(), area_b.h()))
}
),
Split::Above | Split::Below => (
area_a,
to.size(None, b)?,
),
})
}
fn stack_drawn <S: Coord> (
split: &Split,
drawn_a: Option<XYWH<S>>,
drawn_b: Option<XYWH<S>>,
) -> Option<XYWH<S>> {
if let (Some(XYWH(xa, ya, wa, ha)), Some(XYWH(xb, yb, wb, hb))) = (drawn_a, drawn_b) {
match split {
Split::South => Some(XYWH(xa.min(xb), ya, wa.max(wb), ha + hb)),
Split::East => Some(XYWH(xa, ya.min(yb), wa + wb, ha.max(hb))),
Split::North => Some(XYWH(xa.min(xb), yb, wa.max(wb), ha + hb)),
Split::West => Some(XYWH(xb, ya.min(yb), wa + wb, ha.max(hb))),
Split::Above | Split::Below =>
Some(XYWH(xa.min(xb), ya.min(yb), wa.max(wb), ha.max(hb))),
}
} else if let Some(a) = drawn_a {
Some(a)
} else if let Some(b) = drawn_b {
Some(b)
} else {
None
}
}
#[macro_export] macro_rules! north {
($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) };
}
#[macro_export] macro_rules! south {
($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) };
}
#[macro_export] macro_rules! east {
($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) };
}
#[macro_export] macro_rules! west {
($head:expr $(,)?) => { $head };
($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) };
}
#[macro_export] macro_rules! above {
($head:expr $(,)?) => { $head };
($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) };
}
#[macro_export] macro_rules! below {
($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) };
}
pub const fn east <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::East, a, b, PhantomData)
}
pub const fn north <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::North, a, b, PhantomData)
}
pub const fn west <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::West, a, b, PhantomData)
}
pub const fn south <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::South, a, b, PhantomData)
}
pub const fn above <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::Above, a, b, PhantomData)
}
pub const fn below <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::Below, a, b, PhantomData)
}
#[cfg(test)] #[test] fn test_stack_areas () -> Usually<()> {
let area = XYWH(0u16, 0, 80, 25);
assert_eq!(stack_areas(&Split::East, &mut Tui::Layout(area), &"foo", &"bar")?, (
Some(XYWH(0u16, 0, 3, 1)),
Some(XYWH(3u16, 0, 3, 1)),
));
assert_eq!(stack_areas(&Split::South, &mut Tui::Layout(area), &"foo", &"bar")?, (
Some(XYWH(0u16, 0, 3, 1)),
Some(XYWH(0u16, 1, 3, 1)),
));
Ok(())
}
#[cfg(test)] #[test] fn test_split_stack () -> Usually<()> {
use Split::*;
fn size_of <A: Draw<Tui>, B: Draw<Tui>> (stack: &Pair<Tui, A, B>) -> Perhaps<XYWH<u16>> {
Tui::Layout(XYWH(0, 0, 80, 25)).size(None, stack)
}
assert_eq!(size_of(&split(East, "foo", "bar"))?,
Some(XYWH(0, 0, 6, 1)));
assert_eq!(size_of(&split(South, "foo", "bar"))?,
Some(XYWH(0, 0, 3, 2)));
assert_eq!(size_of(&split(South, split(East, "foo", "bar"), "baz"))?,
Some(XYWH(0, 0, 6, 2)));
assert_eq!(size_of(&split(East, split(South, "foo", "bar"), "baz"))?,
Some(XYWH(0, 0, 6, 2)));
return Ok(());
}
#[cfg(test)] #[test] fn test_align () -> Usually<()> {
let mut screen = Tui::Layout(XYWH(0, 0, 80, 25));
assert_eq!("FOOBAR\nKILROY".draw(&mut screen)?, Some(XYWH(0, 0, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_nw().draw(&mut screen)?, Some(XYWH(0, 0, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_n().draw(&mut screen)?, Some(XYWH(37, 0, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_ne().draw(&mut screen)?, Some(XYWH(74, 0, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_w().draw(&mut screen)?, Some(XYWH(0, 11, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_c().draw(&mut screen)?, Some(XYWH(37, 11, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_e().draw(&mut screen)?, Some(XYWH(74, 11, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_sw().draw(&mut screen)?, Some(XYWH(0, 23, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_s().draw(&mut screen)?, Some(XYWH(37, 23, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_se().draw(&mut screen)?, Some(XYWH(74, 23, 6, 2)));
Ok(())
}

46
src/layout/cond.rs Normal file
View file

@ -0,0 +1,46 @@
use crate::*;
fn_kw_layout!(kw_when |state, output, expr| {
Ok(matches!(expr.head()?, Some("when")).then(||{
when(state.namespace(expr.tail().head())?.unwrap(),
draw(move|output: &mut O|{state.interpret(output, &expr.tail().tail().head())})
).draw(output)
}).transpose()?.flatten())
});
/// Only render when condition is true.
///
/// ```
/// # use tengri::*;
/// # fn test () -> impl Draw<Tui> {
/// when(true, "Yes")
/// # }
/// ```
pub const fn when <T: Screen> (condition: bool, item: impl Draw<T>) -> impl Draw<T> {
draw(move|to: &mut T|if condition { item.draw(to) } else { Ok(Default::default()) })
}
fn_kw_layout!(kw_either |state, output, expr| {
Ok(matches!(expr.head()?, Some("either")).then(||{
either(state.namespace(expr.tail().head()?)?.unwrap(),
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().head()?)
}),
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().tail().head()?)
}),
).draw(output)
}).transpose()?.flatten())
});
/// Render one thing if a condition is true and another false.
///
/// ```
/// # use tengri::*;
/// # fn test () -> impl Draw<Tui> {
/// either(true, "Yes", "No")
/// # }
/// ```
pub const fn either <T: Screen> (condition: bool, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
draw(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) })
}

View file

@ -1,49 +0,0 @@
use crate::*;
/// Set size of of drawing area.
///
/// ```
/// use tengri::Layout;
/// let _ = "".exact_w(1);
/// let _ = "".exact_h(1);
/// let _ = "".exact_wh(1, 1);
/// ```
pub enum Exact<S: Screen, I: Draw<S>, X: Into<Option<S::Unit>>> {
__(PhantomData<S>),
W(I, X),
H(I, X),
WH(I, X, X),
}
impl <S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Exact<S, I, X> {
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
Ok(Some(layout_exact(self, area)))
}
fn draw (self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
let area = layout_exact(&self, to.area());
let item = match self { Self::W(i, ..) => i, Self::H(i, ..) => i, Self::WH(i, ..) => i, _ => unreachable!() };
to.clip(area, item)
}
}
fn layout_exact <S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> (
exact: &Exact<S, I, X>, area: XYWH<S::Unit>
) -> XYWH<S::Unit> {
let (w, h): (S::Unit, S::Unit) = match exact {
Exact::W(_, w) => {
let w: Option<S::Unit> = (*w).into();
(w.unwrap_or(area.2), area.3)
},
Exact::H(_, h) => {
let h: Option<S::Unit> = (*h).into();
(area.2, h.unwrap_or(area.3))
},
Exact::WH(_, w, h) => {
let w: Option<S::Unit> = (*w).into();
let h: Option<S::Unit> = (*h).into();
(w.unwrap_or(area.2), w.unwrap_or(area.3))
},
_ => unreachable!()
};
XYWH(area.0, area.1, w, h)
}

View file

@ -1,42 +0,0 @@
use crate::*;
/// Use whole drawing area along one or both axes.
///
/// ```
/// # fn doctest_layout_full () -> Result<(), Box<dyn std::error::Error>> {
/// use tengri::{Layout, Draw, XYWH};
/// let area = XYWH(0u16, 0, 80, 25);
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
/// assert_eq!("1".full_w().layout(area)?, Some(XYWH(0u16, 0, 80, 1)));
/// assert_eq!("1".full_h().layout(area)?, Some(XYWH(0u16, 0, 1, 25)));
/// assert_eq!("1".full_wh().layout(area)?, Some(XYWH(0u16, 0, 80, 25)));
/// # Ok(()) }
/// ```
pub enum Full<T: Screen, I: Draw<T>> {
__(PhantomData<T>),
W(I),
H(I),
WH(I),
}
impl_draw!(<T: Screen, I: Draw<T>,>|self: Full<T, I>, to: T|{
let XYWH(x0, y0, w0, h0) = to.area();
match self {
Self::W(item) => if let Some(XYWH(_, y, _, h)) = item.layout(to.area())? {
to.clip(XYWH(x0, y, w0, h), item)
} else {
Ok(None)
},
Self::H(item) => if let Some(XYWH(x, _, w, _)) = item.layout(to.area())? {
to.clip(XYWH(x, y0, w, h0), item)
} else {
Ok(None)
},
Self::WH(item) => if let Some(XYWH(..)) = item.layout(to.area())? {
to.clip(XYWH(x0, y0, w0, h0), item)
} else {
Ok(None)
},
_ => unreachable!(),
}
});

View file

@ -1,55 +0,0 @@
use crate::*;
/// Set maximum size of of drawing area.
///
/// ```
/// # fn doctest_layout_max () -> Result<(), Box<dyn std::error::Error>> {
/// use tengri::{Layout, Draw, XYWH};
/// let area = XYWH(1u16, 1, 80, 25);
/// assert_eq!("12345".max_w(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
/// assert_eq!("12345".max_h(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
/// assert_eq!("12345".max_wh(1, 1).layout(area)?, Some(XYWH(1u16, 1, 5, 1)));
/// # Ok(()) }
/// ```
pub enum Max<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>> + Copy> {
__(PhantomData<T>),
W(I, X),
H(I, X),
WH(I, X, X),
}
impl<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>> + Copy> Draw<T> for Max<T, I, X> {
fn layout (&self, area: XYWH<T::Unit>) -> Perhaps<XYWH<T::Unit>> {
Ok(Some(match self {
Self::W(_, max_w) => XYWH(
area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2),
area.3),
Self::H(_, max_h) => XYWH(
area.0, area.1, area.2,
(*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3)),
Self::WH(_, max_w, max_h) => XYWH(
area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2),
(*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3)),
_ => return Ok(None)
}))
}
fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
let area: XYWH<T::Unit> = to.area();
let (item, area) = match self {
Self::W(item, max_w) => (item, XYWH(
area.0, area.1, max_w.into().map(|max|max.min(area.2)).unwrap_or(area.2),
area.3
)),
Self::H(item, max_h) => (item, XYWH(
area.0, area.1, area.2,
max_h.into().map(|max|max.min(area.3)).unwrap_or(area.3)
)),
Self::WH(item, max_w, max_h) => (item, XYWH(
area.0, area.1, max_w.into().map(|max|max.min(area.2)).unwrap_or(area.2),
max_h.into().map(|max|max.min(area.3)).unwrap_or(area.3),
)),
_ => return Ok(None)
};
to.clip(area, item)
}
}

View file

@ -1,40 +0,0 @@
use crate::*;
/// Only draw content if area is above a certain size.
///
/// ```
/// # fn doctest_layout_min () -> Result<(), Box<dyn std::error::Error>> {
/// use tengri::{Layout, Draw, XYWH};
/// let area = XYWH(1u16, 1, 80, 25);
/// assert_eq!("1".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 5, 1)));
/// assert_eq!("1".min_h(5).layout(area)?, Some(XYWH(1u16, 1, 1, 5)));
/// assert_eq!("1".min_wh(5, 5).layout(area)?, Some(XYWH(1u16, 1, 5, 5)));
/// assert_eq!("123456".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 6, 1)));
/// # Ok(()) }
/// ```
pub enum Min<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
__(PhantomData<T>),
W(I, X),
H(I, X),
WH(I, X, X),
}
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Min<T, I, X>, to: T|{
match self {
Self::__(_) => unreachable!(),
Self::W(item, w1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
let w = w1.into().map(|w1|w.max(w1)).unwrap_or(w);
to.clip(XYWH(x, y, w, h), item)
},
Self::H(item, h1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
let h = h1.into().map(|h1|h.max(h1)).unwrap_or(h);
to.clip(XYWH(x, y, w, h), item)
},
Self::WH(item, w1, h1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
let w = w1.into().map(|w1|w.max(w1)).unwrap_or(w);
let h = h1.into().map(|h1|h.max(h1)).unwrap_or(h);
to.clip(XYWH(x, y, w, h), item)
},
_ => Ok(None)
}
});

View file

@ -1,21 +0,0 @@
use crate::*;
pub struct Origin<T>(
pub(crate) Option<Azimuth>,
pub(crate) T
);
impl_draw!(<S: Screen, T: Draw<S>,>|self: Origin<T>, _to: S|{
todo!()
});
/// Something that has `[0, 0]` at a particular point.
pub trait HasOrigin {
fn origin (&self) -> Azimuth;
}
impl<T: AsRef<Azimuth>> HasOrigin for T {
fn origin (&self) -> Azimuth {
*self.as_ref()
}
}

View file

@ -1,37 +0,0 @@
use crate::*;
/// Define inner drawing area.
///
/// ```
/// use tengri::Layout;
/// let _ = "".pad_w(1);
/// let _ = "".pad_h(1);
/// let _ = "".pad_wh(1, 1);
/// ```
pub enum Pad<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
__(PhantomData<T>),
W(I, X),
H(I, X),
WH(I, X, X),
}
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Pad<T, I, X>, to: T|{
let area = to.area();
let (item, area) = match self {
Self::W(item, w1) => {
let w1 = w1.into().unwrap_or(T::Unit::zero());
(item, XYWH(area.0 + w1, area.1, area.2.minus(w1 + w1), area.3))
},
Self::H(item, h1) => {
let h1 = h1.into().unwrap_or(T::Unit::zero());
(item, XYWH(area.0, area.1 + h1, area.2, area.3.minus(h1 + h1)))
},
Self::WH(item, w1, h1) => {
let w1 = w1.into().unwrap_or(T::Unit::zero());
let h1 = h1.into().unwrap_or(T::Unit::zero());
(item, XYWH(area.0 + w1, area.1 + h1, area.2.minus(w1 + w1), area.3.minus(h1 + h1)))
},
_ => return Ok(None)
};
item.draw(to)
});

View file

@ -1,24 +0,0 @@
use crate::*;
/// Move content in the negative direction of one or both axes.
///
/// ```
/// # fn doctest_layout_pull () -> Result<(), Box<dyn std::error::Error>> {
/// use tengri::{Layout, Draw, XYWH};
/// let area = XYWH(1u16, 1, 80, 25);
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
/// assert_eq!("1".pull_x(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
/// assert_eq!("1".pull_y(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
/// assert_eq!("1".pull_xy(1, 1).layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
/// # Ok(()) }
/// ```
pub enum Pull<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
__(PhantomData<T>),
X(I, X),
Y(I, X),
XY(I, X, X),
}
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Pull<T, I, X>, _to: T|{
todo!()
});

View file

@ -1,42 +0,0 @@
use crate::*;
/// Move content in the positive direction of one or both axes.
///
/// ```
/// # fn doctest_layout_push () -> Result<(), Box<dyn std::error::Error>> {
/// use tengri::{Layout, Draw, XYWH};
/// let area = XYWH(0u16, 0, 80, 25);
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
/// assert_eq!("1".push_x(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
/// assert_eq!("1".push_y(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
/// assert_eq!("1".push_xy(1, 1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
/// # Ok(()) }
/// ```
pub enum Push<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
__(PhantomData<T>),
X(I, X),
Y(I, X),
XY(I, X, X),
}
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Push<T, I, X>, to: T|{
match self {
Self::__(_) => unreachable!(),
Self::X(item, x1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
to.clip(XYWH(
x + x1.into().unwrap_or_default(), y, w, h
), item)
},
Self::Y(item, y1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
to.clip(XYWH(
x, y + y1.into().unwrap_or_default(), w, h
), item)
},
Self::XY(item, x1, y1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
to.clip(XYWH(
x + x1.into().unwrap_or_default(), y + y1.into().unwrap_or_default(), w, h
), item)
},
_ => Ok(None)
}
});

View file

@ -1,315 +0,0 @@
use super::*;
/// Split along an axis. Direction determines order.
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug, Default)] pub enum Split {
North,
South,
East,
West,
Above,
#[default] Below
}
pub struct Pair<S: Screen, A: Draw<S>, B: Draw<S>>(Split, A, B, PhantomData<S>);
pub fn split <S: Screen, A: Draw<S>, B: Draw<S>> (
split: Split, a: A, b: B
) -> Pair<S, A, B> {
Pair(split, a, b, PhantomData)
}
impl<S: Screen, A: Draw<S>, B: Draw<S>> Draw<S> for Pair<S, A, B> {
fn layout (&self, to: XYWH<S::Unit>) -> Drawn<S::Unit> {
let Self(split, a, b, ..) = self;
let (area_a, area_b) = stack_areas(split, to, a, b)?;
Ok(stack_drawn(split, area_a, area_b))
}
fn draw (self, to: &mut S) -> Drawn<S::Unit> {
let Self(ref split, a, b, ..) = self;
let (area_a, area_b) = stack_areas(split, to.area(), &a, &b)?;
let (drawn_a, drawn_b) = draw_stacks(split, to, a, area_a, None, b, area_b, None)?;
Ok(stack_drawn(split, drawn_a, drawn_b))
}
}
impl Split {
/// ```
/// use tengri::*;
/// let _ = Split::Above.stack("", "");
/// let _ = Split::Below.stack("", "");
/// let _ = Split::North.stack("", "");
/// let _ = Split::South.stack("", "");
/// let _ = Split::East.stack("", "");
/// let _ = Split::West.stack("", "");
/// ```
pub const fn stack <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: A, b: B) -> impl Draw<S> {
Pair(*self, a, b, PhantomData)
}
/// ```
/// use tengri::*;
/// let _ = Split::Above.half("", "");
/// let _ = Split::Below.half("", "");
/// let _ = Split::North.half("", "");
/// let _ = Split::South.half("", "");
/// let _ = Split::East.half("", "");
/// let _ = Split::West.half("", "");
/// ```
pub const fn half <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: A, b: B) -> impl Draw<S> {
draw(move|to: &mut S|{
let (area_a, area_b) = to.xywh().split_half(self);
let (origin_a, origin_b) = self.origins();
let (drawn_a, drawn_b) = draw_stacks(self, to, a, area_a, origin_a, b, area_b, origin_b)?;
Ok(stack_drawn(self, drawn_a, drawn_b))
})
}
/// Newly split areas begin at the center of the split
/// to maintain centeredness in the user's field of view.
///
/// Use [align] to override that and always start
/// at the top, bottom, etc.
///
/// ```
/// /*
///
/// Split east: Split south:
/// | | | | A |
/// | <-A|B-> | |---------|
/// | | | | B |
///
/// */
/// ```
const fn origins (&self) -> (Azimuth, Azimuth) {
use Azimuth::*;
match self {
Self::South => (S, N),
Self::East => (E, W),
Self::North => (N, S),
Self::West => (W, E),
Self::Above => (C, C),
Self::Below => (C, C),
}
}
///// ```
///// use tengri::*;
///// let _ = Split::Below.iter([
///// "Leftbar"
///// .min_w(10).max_w(15).align(Azimuth::NW),
///// "Rightbar"
///// .min_w(10).max_w(12).align(Azimuth::NE),
///// "Center"
///// .min_w(20).max_w(40).align(Azimuth::C),
///// ].iter());
///// ```
//pub fn iter <S: Screen, T: Draw<S>> (&self, _: impl Iterator<Item = T>) {
//todo!()
//}
}
fn draw_stacks <S: Screen> (
split: &Split,
to: &mut S,
a: impl Draw<S>,
area_a: impl Into<Option<XYWH<S::Unit>>>,
origin_a: impl Into<Option<Azimuth>>,
b: impl Draw<S>,
area_b: impl Into<Option<XYWH<S::Unit>>>,
origin_b: impl Into<Option<Azimuth>>,
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
let draw_a = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_a) = origin_a.into() {
to.clip(area_a.into(), a.align(origin_a))?
} else {
to.clip(area_a.into(), a)?
});
let draw_b = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_b) = origin_b.into() {
to.clip(area_b.into(), b.align(origin_b))?
} else {
to.clip(area_b.into(), b)?
});
Ok(if matches!(split, Split::Below) {
let drawn_b = draw_b(to)?;
let drawn_a = draw_a(to)?;
(drawn_a, drawn_b)
} else {
(draw_a(to)?, draw_b(to)?)
})
}
pub fn stack_areas <S: Screen> (
split: &Split,
area: XYWH<S::Unit>,
a: &impl Draw<S>,
b: &impl Draw<S>,
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
let area_a = a.layout(area)?;
Ok(match split {
Split::South => (
area_a,
if let Some(used) = area_a {
b.layout(XYWH(area.x(), area.y() + used.h(), area.w(), area.h().minus(used.h())))?
} else {
None
}
),
Split::East => (
area_a,
if let Some(used) = area_a {
b.layout(XYWH(area.x() + used.w(), area.y(), area.w().minus(used.w()), area.h()))?
} else {
None
}
),
Split::North => (
if let Some(used) = area_a {
Some(XYWH(used.x(), (area.y() + area.h()).minus(used.h()), used.w(), used.h()))
} else {
None
},
if let Some(used) = area_a {
b.layout(XYWH(area.x(), area.y(), area.w(), area.h().minus(used.h())))?
} else {
b.layout(area)?.map(|area_b|XYWH(
area_b.x(), area_b.y() + area_b.h(), area_b.w(), area_b.h()
))
}
),
Split::West => (
if let Some(used) = area_a {
Some(XYWH((area.x() + area.w()).minus(used.w()), used.y(), used.w(), used.h()))
} else {
None
},
if let Some(used) = area_a {
b.layout(XYWH(area.x(), area.y(), area.w().minus(used.w()), area.h()))?
} else {
b.layout(area)?.map(|area_b|XYWH(
area_b.x() + area_b.w(), area_b.y(), area_b.w(), area_b.h()
))
}
),
Split::Above | Split::Below => (
area_a,
b.layout(area)?,
),
})
}
fn stack_drawn <S: Coord> (
split: &Split,
drawn_a: Option<XYWH<S>>,
drawn_b: Option<XYWH<S>>,
) -> Option<XYWH<S>> {
if let (Some(XYWH(xa, ya, wa, ha)), Some(XYWH(xb, yb, wb, hb))) = (drawn_a, drawn_b) {
match split {
Split::South => Some(XYWH(xa.min(xb), ya, wa.max(wb), ha + hb)),
Split::East => Some(XYWH(xa, ya.min(yb), wa + wb, ha.max(hb))),
Split::North => Some(XYWH(xa.min(xb), yb, wa.max(wb), ha + hb)),
Split::West => Some(XYWH(xb, ya.min(yb), wa + wb, ha.max(hb))),
Split::Above | Split::Below =>
Some(XYWH(xa.min(xb), ya.min(yb), wa.max(wb), ha.max(hb))),
}
} else if let Some(a) = drawn_a {
Some(a)
} else if let Some(b) = drawn_b {
Some(b)
} else {
None
}
}
#[macro_export] macro_rules! north {
($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) };
}
#[macro_export] macro_rules! south {
($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) };
}
#[macro_export] macro_rules! east {
($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) };
}
#[macro_export] macro_rules! west {
($head:expr $(,)?) => { $head };
($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) };
}
#[macro_export] macro_rules! above {
($head:expr $(,)?) => { $head };
($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) };
}
#[macro_export] macro_rules! below {
($head:expr $(,)?) => { $head };
($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) };
}
pub const fn east <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::East, a, b, PhantomData)
}
pub const fn north <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::North, a, b, PhantomData)
}
pub const fn west <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::West, a, b, PhantomData)
}
pub const fn south <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::South, a, b, PhantomData)
}
pub const fn above <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::Above, a, b, PhantomData)
}
pub const fn below <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Pair(Split::Below, a, b, PhantomData)
}
#[cfg(test)] mod test {
use crate::*;
#[test] fn test_stack_areas () -> Usually<()> {
let area = XYWH(0u16, 0, 80, 25);
assert_eq!(stack_areas(&Split::East, area, &"foo", &"bar")?, (
Some(XYWH(0u16, 0, 3, 1)),
Some(XYWH(3u16, 0, 3, 1)),
));
assert_eq!(stack_areas(&Split::South, area, &"foo", &"bar")?, (
Some(XYWH(0u16, 0, 3, 1)),
Some(XYWH(0u16, 1, 3, 1)),
));
Ok(())
}
#[test] fn test_split_stack () -> Usually<()> {
use tengri::{*, Split::*};
let stack = split(East, "foo", "bar");
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 1)));
let stack = split(South, "foo", "bar");
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 3, 2)));
let stack = split(South, split(East, "foo", "bar"), "baz");
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 2)));
let stack = split(East, split(South, "foo", "bar"), "baz");
assert_eq!(stack.layout(XYWH(0, 0, 80, 25))?, Some(XYWH(0, 0, 6, 2)));
Ok(())
}
}