fix and coverage for layout modifiers
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
facile pop culture reference 2026-08-06 18:29:03 +03:00
parent cf67185ffd
commit 799e497622
10 changed files with 456 additions and 460 deletions

View file

@ -1,138 +1,125 @@
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 {}
/// Use whole drawing area along one or both axes.
pub enum Full<T: Screen, I: Draw<T>> {
__(PhantomData<T>),
W(I),
H(I),
WH(I),
}
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) }
}
impl_draw!(<T: Screen, I: Draw<T>,>|self: Full<T, I>, to: T|{
let XYWH(x0, y0, w0, h0) = to.area();
let item = match self {
Self::W(i) => i, Self::H(i) => i, Self::WH(i) => i, _ => unreachable!()
};
let (x1, y1, w1, h1) = match self {
Self::W(..) => (Some(x0), None, Some(w0), None),
Self::H(..) => (None, Some(y0), None, Some(h0)),
Self::WH(..) => (Some(x0), Some(y0), Some(w0), Some(h0)),
_ => unreachable!()
};
Ok(to.draw(to.area(), item)?.map(|XYWH(x2, y2, w2, h2)|XYWH(
x1.unwrap_or(x2), y1.unwrap_or(y2), w1.unwrap_or(w2), h1.unwrap_or(h2),
)))
});
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())
});
}
#[cfg(test)] #[test] fn test_layout_full () -> Usually<()> {
assert_eq!(check_layout("1")?, Some(XYWH(1u16, 1, 1, 1)));
assert_eq!(check_layout("1".full_w())?, Some(XYWH(1u16, 1, 80, 1)));
assert_eq!(check_layout("1".full_h())?, Some(XYWH(1u16, 1, 1, 25)));
assert_eq!(check_layout("1".full_wh())?, Some(XYWH(1u16, 1, 80, 25)));
Ok(())
}
def_layout_modifier!(
CanExact (exact_w exact_h exact_wh),
LayoutExact (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(), None),
Self::H(item, h) => (item, None, (*h).into()),
Self::WH(item, w, h) => (item, (*w).into(), (*h).into()),
_ => return Ok(None)
};
to.draw(XYWH(x, y, w.unwrap_or(w0), h.unwrap_or(h0)), item)
}
);
#[cfg(test)] #[test] fn test_layout_exact () -> Usually<()> {
assert_eq!(check_layout("FOOBAR\nKILROY")?, Some(XYWH(1, 1, 6, 2)));
assert_eq!(check_layout("FOOBAR\nKILROY".exact_w(3))?, Some(XYWH(1, 1, 3, 2)));
assert_eq!(check_layout("FOOBAR\nKILROY".exact_h(1))?, Some(XYWH(1, 1, 6, 1)));
assert_eq!(check_layout("FOOBAR\nKILROY".exact_wh(2, 1))?, Some(XYWH(1, 1, 2, 1)));
Ok(())
}
def_layout_modifier!(
LayoutMin (min_w min_h min_wh),
Min { W H WH } kw_min "min" |self, to| {
let XYWH(x0, y0, w0, h0) = to.area();
let (item, w1, h1) = match self {
Self::W(item, w1) => (item, (*w1).into(), None),
Self::H(item, h1) => (item, None, (*h1).into()),
Self::WH(item, w1, h1) => (item, (*w1).into(), (*h1).into()),
_ => return Ok(None)
};
Ok(to.draw(XYWH(
x0, y0,
w1.map(|w1|w1.max(w0)).unwrap_or(w0),
h1.map(|h1|h1.max(h0)).unwrap_or(h0)
), item)?.map(|XYWH(x2, y2, w2, h2)|XYWH(
x2, y2,
w1.map(|w1|w1.max(w2)).unwrap_or(w2),
h1.map(|h1|h1.max(h2)).unwrap_or(h2)
)))
}
);
#[cfg(test)] #[test] fn test_layout_min () -> Usually<()> {
assert_eq!(check_layout("1".min_w(5))?, Some(XYWH(1u16, 1, 5, 1)));
assert_eq!(check_layout("1".min_h(5))?, Some(XYWH(1u16, 1, 1, 5)));
assert_eq!(check_layout("1".min_wh(5, 5))?, Some(XYWH(1u16, 1, 5, 5)));
assert_eq!(check_layout("123456".min_w(5))?, Some(XYWH(1u16, 1, 6, 1)));
Ok(())
}
def_layout_modifier!(
LayoutMax (max_w max_h max_wh),
Max { W H WH } kw_max "max" |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, w, h), 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)
to.draw(XYWH(x, y, if w0 > w { w } else { w0 }, if h0 > h { h } else { h0 }), item)
}
);
def_layout_modifier!(
CanPad (pad_w pad_h pad_wh),
LayoutPad (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)))
},
let XYWH(x, y, w0, h0) = to.area();
let (item, w, h) = match self {
Self::X(item, w) => (item, (*w).into().unwrap_or_default(), Default::default()),
Self::Y(item, h) => (item, Default::default(), (*h).into().unwrap_or_default()),
Self::XY(item, w, h) => (item, (*w).into().unwrap_or_default(),
(*h).into().unwrap_or_default()),
_ => return Ok(None)
};
item.draw(to)
to.draw(XYWH(
x.plus(w),
y.plus(h),
w0.minus(w).minus(w),
h0.minus(h).minus(h),
), item)
}
);
def_layout_modifier!(
CanPush (push_x push_y push_xy),
LayoutPush (push_x push_y push_xy),
Push { X Y XY } kw_push "push" |self, to| {
match self {
Self::__(_) => unreachable!(),
@ -150,99 +137,47 @@ def_layout_modifier!(
}
);
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, Tui, Perhaps, Screen};
/// let area = XYWH(0u16, 0, 80, 25);
/// assert_eq!(layout("1")?, Some(XYWH(0u16, 0, 1, 1)));
/// assert_eq!(layout("1".full_w())?, Some(XYWH(0u16, 0, 80, 1)));
/// assert_eq!(layout("1".full_h())?, Some(XYWH(0u16, 0, 1, 25)));
/// assert_eq!(layout("1".full_wh())?, Some(XYWH(0u16, 0, 80, 25)));
/// fn layout (t: impl Draw<Tui>) -> Perhaps<XYWH<u16>> {
/// Tui::Layout(XYWH(0u16, 0, 80, 25)).size(None, t)
/// }
/// # 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)));
#[cfg(test)] #[test] fn test_layout_push () -> Usually<()> {
assert_eq!(check_layout("1")?, Some(XYWH(1u16, 1, 1, 1)));
assert_eq!(check_layout("1".push_x(1))?, Some(XYWH(2u16, 1, 1, 1)));
assert_eq!(check_layout("1".push_y(1))?, Some(XYWH(1u16, 2, 1, 1)));
assert_eq!(check_layout("1".push_xy(1, 1))?, Some(XYWH(2u16, 2, 1, 1)));
Ok(())
}
def_layout_modifier!(
LayoutPull (pull_x pull_y pull_xy),
Pull { X Y XY } kw_pull "pull" |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.minus((*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.minus((*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.minus((*x1).into().unwrap_or_default()),
y.minus((*y1).into().unwrap_or_default()),
w, h), item)
},
_ => Ok(None)
}
}
);
#[cfg(test)] #[test] fn test_layout_pull () -> Usually<()> {
assert_eq!(check_layout("1")?, Some(XYWH(1u16, 1, 1, 1)));
assert_eq!(check_layout("1".pull_x(1))?, Some(XYWH(0u16, 1, 1, 1)));
assert_eq!(check_layout("1".pull_y(1))?, Some(XYWH(1u16, 0, 1, 1)));
assert_eq!(check_layout("1".pull_xy(1, 1))?, Some(XYWH(0u16, 0, 1, 1)));
Ok(())
}
#[cfg(test)] fn check_layout (t: impl Draw<Tui>) -> Perhaps<XYWH<u16>> {
Tui::Layout(XYWH(1u16, 1, 80, 25)).size(None, t)
}