tengri/src/layout.rs
facile pop culture reference 8a6eb19e27
Some checks failed
/ build (push) Has been cancelled
reenable full/xy mod
2026-08-08 23:37:06 +03:00

274 lines
9.3 KiB
Rust

use crate::*;
impl<S: Screen, T: Draw<S>> Layout<S> for T {}
pub trait Layout<S: Screen>: Draw<S> + Sized {
fn origin (self, azimuth: impl Into<Option<Azimuth>>) -> impl Draw<S> {
Origin(azimuth.into(), self)
}
fn origin_c (self) -> impl Draw<S> {
Origin(Some(Azimuth::C), self)
}
fn origin_x (self) -> impl Draw<S> {
Origin(Some(Azimuth::X), self)
}
fn origin_y (self) -> impl Draw<S> {
Origin(Some(Azimuth::Y), self)
}
fn origin_n (self) -> impl Draw<S> {
Origin(Some(Azimuth::N), self)
}
fn origin_s (self) -> impl Draw<S> {
Origin(Some(Azimuth::S), self)
}
fn origin_e (self) -> impl Draw<S> {
Origin(Some(Azimuth::E), self)
}
fn origin_w (self) -> impl Draw<S> {
Origin(Some(Azimuth::W), self)
}
fn origin_ne (self) -> impl Draw<S> {
Origin(Some(Azimuth::NE), self)
}
fn origin_se (self) -> impl Draw<S> {
Origin(Some(Azimuth::SE), self)
}
fn origin_nw (self) -> impl Draw<S> {
Origin(Some(Azimuth::NW), self)
}
fn origin_sw (self) -> impl Draw<S> {
Origin(Some(Azimuth::SW), self)
}
}
/// Uses [AtomicUsize] to measure size during\
/// rendering (which is normally read-only).
#[derive(Default, Debug, Clone)]
pub struct Sizer(
/// Width
pub Arc<AtomicUsize>,
/// Height
pub Arc<AtomicUsize>,
);
impl Xy<u16> for Sizer {
fn x (&self) -> u16 {
self.0.load(Relaxed) as u16
}
fn y (&self) -> u16 {
self.1.load(Relaxed) as u16
}
}
impl Wide<u16> for Sizer {
fn w (&self) -> u16 {
self.0.load(Relaxed) as u16
}
}
impl Tall<u16> for Sizer {
fn h (&self) -> u16 {
self.1.load(Relaxed) as u16
}
}
impl PartialEq for Sizer {
fn eq (&self, _: &Self) -> bool { todo!() }
}
impl Sizer {
pub const fn of <T: Screen> (&self, of: impl Draw<T>) -> impl Draw<T> {
draw(move|to: &mut T|{
let area = of.draw(to)?;
self.0.store(area.map(|a|a.w()).unwrap_or(T::Unit::zero()).into(), Relaxed);
self.1.store(area.map(|a|a.h()).unwrap_or(T::Unit::zero()).into(), Relaxed);
Ok(area)
})
}
}
/// Some layout operations exist in multiple variants that take a single argument.
/// Their handling in [eval_view] is uniform and goes like this:
#[macro_export] macro_rules! eval_enum ((
$name:literal, $output:ident, $state:ident, $value:expr, $Enum:ident {
$($v:literal => $V:ident),* $(,)?
}
) => {{
match $value {
$(Some($v) => $Enum::$V,)*
frag => return Err(format!("invalid variant: {}/{frag:?}", $name).into())
}
}});
/// Some layout operations exist in XY, X, and Y variants that take 3 or 2 arguments.
/// Their handling in [eval_view] is uniform and goes like this:
#[macro_export] macro_rules! eval_xy (
// Valueless variant:
// (fill/x ...)
// (fill/y ...)
// (fill/xy ...)
(
$name:expr => $expr:expr, $head:expr, $output:ident, $state:ident,
$variant:expr, $xy:ident, $x:ident, $y:ident, $arg:expr,
) => {{
// frags.next(): 2nd slash-delimited fragment: /x, /y, /xy
let variant = $variant;
let thunk = draw(move|screen|$state.interpret(screen, &$arg));
match variant {
// X variant
Some("x") => thunk.$x().draw($output),
// Y variant
Some("y") => thunk.$y().draw($output),
// XY variant (can be omitted)
Some("xy") | None => thunk.$xy().draw($output),
// Other namespace members are invalid
frag => invalid_variant($name, frag, $expr, $head)
}
}};
// Variadic variant:
// (push/x n ...)
// (push/y n ...)
// (push/xy n m ...)
(
$name:expr => $expr:expr, $head:expr, $output:ident, $state:ident,
$variant:expr, $xy:ident, $x:ident, $y:ident, $arg0:expr, $arg1:expr, $arg2:expr,
) => {{
// frags.next(): 2nd slash-delimited fragment: /x, /y, /xy
let variant = $variant;
let thunk = draw(move|screen|$state.interpret(screen, &match variant {
Some("x") | Some("y") => $arg1,
Some("xy") | None => $arg2,
_ => panic!("{}: unsupported axis {variant:?}; try /x, /y, /xy", $name)
}));
match variant {
// X variant
Some("x") => thunk.$x($state.namespace($arg0?)?)
.draw($output),
// Y variant
Some("y") => thunk.$y($state.namespace($arg0?)?)
.draw($output),
// XY variant (can be omitted)
Some("xy") | None => thunk.$xy($state.namespace($arg0?)?, $state.namespace($arg1?)?)
.draw($output),
// Other namespace members are invalid
frag => invalid_variant($name, frag, $expr, $head)
}
}};
);
#[macro_export] macro_rules! fn_kw_layout {
($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => {
#[cfg(all(feature = "draw", feature = "eval"))]
pub fn $name <O: Screen, S> (
$state: &S, $output: &mut O, $expr: &str
) -> Perhaps<XYWH<O::Unit>> where
S: Interpret<O, Option<XYWH<O::Unit>>>
+ for<'b> Namespace<'b, bool>
+ for<'b> Namespace<'b, O::Unit>
+ for<'b> Namespace<'b, Option<O::Unit>>
$body
}
}
#[macro_export] macro_rules! fn_kw_layout_tui {
($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => {
#[cfg(all(feature = "draw", feature = "eval", feature = "term"))]
pub fn $name <S> (
$state: &S, $output: &mut Tui, $expr: &str
) -> Perhaps<XYWH<u16>> where
S: Interpret<Tui, Option<XYWH<u16>>>
+ for<'b> Namespace<'b, bool>
+ for<'b> Namespace<'b, u16>
+ for<'b> Namespace<'b, Option<u16>>
+ for<'b> Namespace<'b, Color>
$body
}
}
#[macro_export] 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!(frags.next(), Some($name)).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())
});
}
}
#[macro_export] macro_rules! def_layout_modifier_flat {
(
$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 (self) -> $Struct<S, Self> { $Struct::$X(self) }
fn $fn_y (self) -> $Struct<S, Self> { $Struct::$Y(self) }
fn $fn_xy (self) -> $Struct<S, Self> { $Struct::$XY(self) }
}
pub enum $Struct<S: Screen, I: Draw<S>> {
__(PhantomData<S>), $X(I), $Y(I), $XY(I),
}
impl <S: Screen, I: Draw<S>> Draw<S> for $Struct<S, I> {
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!(frags.next(), Some($name)).then(||{
let args = expr.tail();
let arg0 = args.head();
eval_xy!(
$name => expr, head, output, state, frags.next(),
$fn_xy, $fn_x, $fn_y, arg0,
)
}).transpose()?.flatten())
});
}
}
mod area; pub use self::area::*;
mod axis; pub use self::axis::*;
mod azimuth; pub use self::azimuth::*;
mod cond; pub use self::cond::*;
mod iter; pub use self::iter::*;