moar flat

This commit is contained in:
facile pop culture reference 2026-08-01 17:52:37 +03:00
parent fc01fd6ad3
commit a27dacff93
29 changed files with 649 additions and 582 deletions

View file

@ -46,7 +46,6 @@ macro_rules! features {
features! {
"time": [ time ],
"play": [ exit, task ],
"sing": [ sing ]
}
@ -192,7 +191,7 @@ macro_rules! eval_xy (
) => {{
// frags.next(): 2nd slash-delimited fragment: /x, /y, /xy
let variant = $variant;
let thunk = thunk(move|screen|$state.interpret(screen, &$arg));
let thunk = draw(move|screen|$state.interpret(screen, &$arg));
match variant {
// X variant
Some("x") => thunk.$x().draw($output),
@ -215,7 +214,7 @@ macro_rules! eval_xy (
) => {{
// frags.next(): 2nd slash-delimited fragment: /x, /y, /xy
let variant = $variant;
let thunk = thunk(move|screen|$state.interpret(screen, &match 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)
@ -236,9 +235,109 @@ macro_rules! eval_xy (
}};
);
#[cfg(feature = "exit")] pub use self::exit::*;
#[cfg(feature = "exit")] mod exit {
use crate::*;
use std::sync::{Arc, atomic::AtomicBool};
use crossterm::event::*;
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
impl Exit {
pub fn run <T> (run: impl FnOnce(Self)->Usually<T>) -> Usually<T> {
run(Self(Arc::new(AtomicBool::new(false))))
}
pub fn is (event: &Event) -> bool {
matches!(event, Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code: KeyCode::Char('c'),
kind: KeyEventKind::Press,
state: KeyEventState::NONE
}))
}
}
impl AsRef<Arc<AtomicBool>> for Exit {
fn as_ref (&self) -> &Arc<AtomicBool> {
&self.0
}
}
}
#[cfg(feature = "play")] pub use self::task::*;
#[cfg(feature = "play")] mod task {
use std::{
time::Duration,
sync::{Arc, atomic::{AtomicBool, Ordering::*}},
thread::{Builder, JoinHandle, sleep},
};
#[cfg(feature = "term")] use ::crossterm::event::poll;
use crate::time::PerfModel;
#[derive(Debug)] pub struct Task {
/// Exit flag.
pub exit: Arc<AtomicBool>,
/// Performance counter.
pub perf: Arc<PerfModel>,
/// Use this to wait for the thread to finish.
pub join: JoinHandle<()>,
}
impl Task {
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
pub fn new <F> (exit: Arc<AtomicBool>, mut call: F) -> Result<Self, std::io::Error>
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{
let perf = Arc::new(PerfModel::default());
Ok(Self {
exit: exit.clone(),
perf: perf.clone(),
join: Builder::new().name("tengri tui output".into()).spawn(move || {
while !exit.fetch_and(true, Relaxed) {
let _ = perf.cycle(&mut call);
}
})?.into()
})
}
/// Spawn a thread that runs `call` least one, then repeats
/// until `exit`, sleeping for `time` msec after every iteration.
pub fn new_sleep <F> (
exit: Arc<AtomicBool>, time: Duration, mut call: F
) -> Result<Self, std::io::Error>
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{
Self::new(exit, move |perf| {
let _ = call(perf);
sleep(time);
})
}
/// Spawn a thread that uses [crossterm::event::poll]
/// to run `call` every `time` msec.
#[cfg(feature = "term")] pub fn new_poll <F> (
exit: Arc<AtomicBool>, time: Duration, mut call: F
) -> Result<Self, std::io::Error>
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{
Self::new(exit, move |perf| {
if poll(time).is_ok() {
let _ = call(perf);
}
})
}
pub fn join (self) -> Result<(), Box<dyn std::any::Any + Send>> {
self.join.join()
}
}
}
#[cfg(feature = "draw")] pub use self::draw::*;
#[cfg(feature = "draw")] mod draw {
use crate::*;
use Azimuth::*;
use Split::*;
/// Output target.
///
@ -338,9 +437,82 @@ macro_rules! eval_xy (
}
}
/// The opposite of [thunk]?
pub fn draw <S: Screen, T: Draw<S>> (item: T) -> impl FnOnce(&mut S) -> Perhaps<XYWH<S::Unit>> {
move|to: &mut S|item.draw(to)
/// Emit a [Draw]able.
///
/// Speculative. How to avoid conflicts with [Draw] proper?
pub trait View<T: Screen> {
fn view (&self) -> impl Draw<T>;
}
impl<T: Screen> View<T> for () {
fn view (&self) -> impl Draw<T> {
()
}
}
/// Return a [Draw]able.
///
/// ```
/// # use tengri::*;
/// let _ = view::<Tui, _, _>(||"drawable");
/// let _ = view::<Tui, _, _>(||Some("drawable"));
/// ```
pub const fn view <S: Screen, T: Draw<S>, F: Fn()->T> (view: F) -> impl View<S> {
ViewThunk(view, PhantomData)
}
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
pub struct ViewThunk<S: Screen, F>(pub F, std::marker::PhantomData<S>);
impl<S: Screen, T: Draw<S>, F: Fn()->T> View<S> for ViewThunk<S, F> {
fn view (&self) -> impl Draw<S> {
self.0()
}
}
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
pub struct DrawThunk<S: Screen, F>(pub F, std::marker::PhantomData<S>);
impl<T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> Draw<T> for DrawThunk<T, F> {
fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
(self.0)(to)
}
}
/// Basic [Draw]able closure.
///
/// ```
/// # use tengri::*;
/// let _ = draw(|to: &mut Tui|Ok(Some(to.1)));
/// ```
pub const fn draw <T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> (
item: F
) -> DrawThunk<T, F> {
DrawThunk(item, std::marker::PhantomData)
}
/// 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()) })
}
/// 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) })
}
pub type Drawn<U> = Perhaps<XYWH<U>>;
@ -367,34 +539,168 @@ macro_rules! eval_xy (
//}
//}
/// Emit a [Draw]able.
///
/// Speculative. How to avoid conflicts with [Draw] proper?
pub trait View<T: Screen> {
fn view (&self) -> impl Draw<T>;
}
impl<T: Screen> View<T> for () {
fn view (&self) -> impl Draw<T> {
()
}
}
impl<T: Screen, V: View<T>> Draw<T> for &V {
fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
self.view().draw(to)
}
}
features! {
"draw": [
iter,
lrtb,
sizer,
split,
thunk,
xywh
]
pub trait Xy<N: Coord> {
fn x (&self) -> N;
fn y (&self) -> N;
}
pub trait Wh<N: Coord>: Wide<N> + Tall<N> {
fn wh (&self) -> [N;2];
}
pub trait Xywh<N: Coord>: Xy<N> + Wh<N> {
fn xywh (&self) -> XYWH<N> {
XYWH(self.x(), self.y(), self.w(), self.h())
}
}
pub trait Wide<N: Coord>: Xy<N> {
fn w (&self) -> N { N::zero() }
fn w_min (&self) -> N { self.w() }
fn w_max (&self) -> N { self.w() }
}
pub trait Tall<N: Coord> {
fn h (&self) -> N { N::zero() }
fn h_min (&self) -> N { self.h() }
fn h_max (&self) -> N { self.h() }
}
/// Point with size.
///
/// ```
/// # use tengri::*;
/// let xywh = XYWH(0u16, 0, 0, 0);
/// assert_eq!(XYWH(10u16, 10, 20, 20).center(), (20, 20));
/// ```
///
/// * [ ] TODO: origin field (determines at which corner/side is X0 Y0)
///
#[cfg_attr(test, derive(Arbitrary))] #[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct XYWH<N: Coord>(pub N, pub N, pub N, pub N);
impl<N: Coord> Xy<N> for XYWH<N> {
fn x (&self) -> N { self.0 }
fn y (&self) -> N { self.1 }
}
impl<N: Coord> Wide<N> for XYWH<N> { fn w (&self) -> N { self.2 } }
impl<N: Coord> Tall<N> for XYWH<N> { fn h (&self) -> N { self.3 } }
impl<N: Coord> XYWH<N> {
pub fn zero () -> Self {
Self(0.into(), 0.into(), 0.into(), 0.into())
}
pub fn center (&self) -> (N, N) {
let Self(x, y, w, h) = *self;
(x.plus(w/2.into()), y.plus(h/2.into()))
}
pub fn centered (&self) -> (N, N) {
let Self(x, y, w, h) = *self;
(x.minus(w/2.into()), y.minus(h/2.into()))
}
pub fn centered_x (&self, n: N) -> Self {
let Self(x, y, w, h) = *self;
let x_center = (x.plus(w / 2.into())).minus(n / 2.into());
let y_center = y.plus(h / 2.into());
XYWH(x_center, y_center, n, 1.into())
}
pub fn centered_y (&self, n: N) -> Self {
let Self(x, y, w, h) = *self;
let x_center = x.plus(w / 2.into());
let y_corner = (y.plus(h / 2.into())).minus(n / 2.into());
XYWH(x_center, y_corner, 1.into(), n)
}
pub fn centered_xy (&self, [n, m]: [N;2]) -> Self {
let Self(x, y, w, h) = *self;
let x_center = (x.plus(w / 2.into())).minus(n / 2.into());
let y_corner = (y.plus(h / 2.into())).minus(m / 2.into());
XYWH(x_center, y_corner, n, m)
}
pub fn split_half (&self, direction: &Split) -> (Self, Self) {
let XYWH(x, y, w, h) = self.xywh();
match direction {
South => (XYWH(x, y, w, h - h / 2.into()), XYWH(x, y + h / 2.into(), w, h / 2.into())),
East => (XYWH(x, y, w - w / 2.into(), h), XYWH(x + w / 2.into(), y, w / 2.into(), h)),
North => (XYWH(x, y + h / 2.into(), w, h - h / 2.into()), XYWH(x, y, w, h / 2.into())),
West => (XYWH(x + w / 2.into(), y, w - w / 2.into(), h), XYWH(x, y, w / 2.into(), h)),
Above | Below => (XYWH(x, y, w, h), XYWH(x, y, w, h))
}
}
}
impl From<&ratatui::prelude::Rect> for XYWH<u16> {
fn from (rect: &ratatui::prelude::Rect) -> Self {
Self(rect.x, rect.y, rect.width, rect.height)
}
}
impl<N: Coord, T: Wide<N> + Tall<N>> Wh<N> for T {
fn wh (&self) -> [N;2] {
[self.w(), self.h()]
}
}
impl<N: Coord, T: Xy<N> + Wh<N>> Xywh<N> for T {}
impl<N: Coord, T: Xywh<N>> Lrtb<N> for T {}
pub trait Lrtb<N: Coord>: Xywh<N> {
fn lrtb (&self) -> [N;4] {
// FIXME: factor origin
[self.x(), self.y(), self.x()+self.w(), self.y()+self.h()]
}
fn iter_x (&self) -> std::ops::Range<N> where Self: HasOrigin {
self.x_west()..self.x_east()
}
fn x_west (&self) -> N where Self: HasOrigin {
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => 0.into(), N|X|C|Y|S => w/2.into(), NE|E|SE => w };
self.x().minus(d)
}
fn x_east (&self) -> N where Self: HasOrigin {
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => w, N|X|C|Y|S => w/2.into(), NE|E|SE => 0.into() };
self.x().plus(d)
}
fn x_center (&self) -> N where Self: HasOrigin {
todo!()
}
fn iter_y (&self) -> std::ops::Range<N> where Self: HasOrigin {
self.y_north()..self.y_south()
}
fn y_north (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
let d = match a { NW|N|NE => 0.into(), W|X|C|Y|E => h/2.into(), SW|S|SE => h };
self.y().minus(d)
}
fn y_south (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
let d = match a { NW|N|NE => h, W|X|C|Y|E => h/2.into(), SW|S|SE => 0.into() };
self.y().plus(d)
}
fn y_center (&self) -> N where Self: HasOrigin {
todo!()
}
}
}
@ -444,7 +750,7 @@ macro_rules! eval_xy (
#[cfg(feature = "draw")] pub use self::layout::*;
#[cfg(feature = "draw")] mod layout {
use crate::{*, draw::*};
use crate::*;
impl<S: Screen, T: Draw<S>> Layout<S> for T {}
@ -613,21 +919,66 @@ macro_rules! eval_xy (
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
}
/// 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)
})
}
}
mod align; pub use self::align::*;
mod area; pub use self::area::*;
mod exact; pub use self::exact::*;
mod full; pub use self::full::*;
mod iter; pub use self::iter::*;
mod max; pub use self::max::*;
mod min; pub use self::min::*;
mod origin; pub use self::origin::*;
mod pad; pub use self::pad::*;
mod pull; pub use self::pull::*;
mod push; pub use self::push::*;
mod split; pub use self::split::*;
}
#[cfg(feature = "draw")] pub use self::color::*;
#[cfg(feature = "draw")] mod color {
use crate::{*, draw::*};
use crate::*;
use dizzle::LanguageError::*;
use ::ratatui::style::Color;
use ::rand::distributions::uniform::UniformSampler;
@ -814,8 +1165,10 @@ macro_rules! eval_xy (
}
}
#[cfg(feature = "text")]
mod text {
#[cfg(feature = "draw")] pub use self::text::*;
#[cfg(feature = "text")] mod text {
#![allow(unused)]
pub(crate) use ::unicode_width::*;
/// Displays an owned [str]-like with fixed maximum width.
@ -878,10 +1231,7 @@ mod text {
#[cfg(feature = "term")] pub use self::term::*;
#[cfg(feature = "term")] mod term {
use crate::*;
use crate::draw::*;
use crate::layout::*;
use Color::*;
#[cfg(feature = "text")] use crate::text::*;
#[macro_export] macro_rules! tui_app {
($Struct:ident { $($fields:tt)* }) => {
@ -982,6 +1332,7 @@ mod text {
impl Tall<u16> for Tui { fn h (&self) -> u16 { self.1.3 } }
impl HasOrigin for Tui { fn origin (&self) -> Azimuth { Azimuth::NW } }
impl Xy<u16> for Tui { fn x (&self) -> u16 { self.1.0 } fn y (&self) -> u16 { self.1.1 } }
/// Terminal output.
pub struct Tui(
/// Ratatui buffer; area is screen size
@ -989,6 +1340,7 @@ mod text {
/// Current draw area
pub XYWH<u16>
);
impl Tui {
pub fn setup_panic () {
use ::std::panic::{set_hook, PanicHookInfo};
@ -1151,7 +1503,7 @@ mod text {
/// }
/// ```
pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|match result {
draw(move|to: &mut Tui|match result {
Ok(content) => content.draw(to),
Err(e) => {
let err_fg = Color::Rgb(255,224,244);
@ -1166,7 +1518,7 @@ mod text {
/// Interpret TUI-specific layout operation.
///
/// ```
/// use tengri::{*, lang::*, ratatui::prelude::Color};
/// use tengri::{*, dizzle::*, ratatui::prelude::Color};
///
/// #[namespace(bool)]
/// #[namespace(u8)]
@ -1233,10 +1585,7 @@ mod text {
Some("fg") => {
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
if let Some(color) = Namespace::namespace(state, arg0)? {
fg(color, thunk(move|to: &mut Tui|{
state.interpret(to, &arg1)?;
Ok(Some(to.area().into())) // FIXME?: don't max out the used area?
})).draw(to)
fg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(to)
} else {
return Err(format!("fg: {arg0:?}: not a color").into())
}
@ -1245,10 +1594,7 @@ mod text {
Some("bg") => {
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
if let Some(color) = Namespace::namespace(state, arg0)? {
bg(color, thunk(move|to: &mut Tui|{
state.interpret(to, &arg1)?;
Ok(Some(to.area().into())) // FIXME?: don't max out the used area?
})).draw(to)
bg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(to)
} else {
return Err(format!("bg: {arg0:?}: not a color").into())
}
@ -1310,6 +1656,7 @@ mod text {
Ok(Some(XYWH(x0, y, string_width, 1)))
}
}
impl Screen for Tui {
type Unit = u16;
/// Render drawable in subarea specified by `area`
@ -1349,21 +1696,21 @@ mod text {
}
pub const fn fill_char (c: char) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|Ok(Some(to.update(&|cell,_,_|{
draw(move|to: &mut Tui|Ok(Some(to.update(&|cell,_,_|{
cell.set_char(c);
}))))
}
/// Draw contents with modifier applied.
pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|{
pub const fn modify (on: bool, modifier: Modifier, item: impl Draw<Tui>) -> impl Draw<Tui> {
draw(move|to: &mut Tui|{
fill_mod(on, modifier).draw(to)?;
draw.draw(to)
item.draw(to)
})
}
pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|Ok(Some({
draw(move|to: &mut Tui|Ok(Some({
if on {
to.update(&|cell,_,_|cell.modifier.insert(modifier))
} else {
@ -1373,8 +1720,8 @@ mod text {
}
/// Draw contents with bold modifier applied.
pub const fn bold (on: bool, draw: impl Draw<Tui>) -> impl Draw<Tui> {
modify(on, Modifier::BOLD, draw)
pub const fn bold (on: bool, item: impl Draw<Tui>) -> impl Draw<Tui> {
modify(on, Modifier::BOLD, item)
}
#[cfg(feature = "text")]
@ -1421,7 +1768,7 @@ mod text {
layout_text_u16(self.as_ref(), area)
}
fn draw (self, to: &mut Tui) -> Drawn<u16> {
let XYWH(x, y, w, ..) = to.1;
let XYWH(x, y, w, ..) = to.area();
let mut width: u16 = 1;
let mut chars = self.1.as_ref().chars();
while let Some(c) = chars.next() {
@ -1443,14 +1790,14 @@ mod text {
#[cfg(feature = "text")]
fn layout_text_u16 (text: &str, area: XYWH<u16>) -> Perhaps<XYWH<u16>> {
let XYWH(x, y, ..) = area;
let XYWH(x, y, w, h) = area;
let mut max_w = 0u16;
let mut max_h = 0u16;
for line in text.split("\n") {
max_h += 1;
max_w = max_w.max(line.len() as u16);
}
Ok(Some(XYWH(x, y, max_w, max_h)))
Ok(Some(XYWH(x, y, w.min(max_w), h.min(max_h))))
}
pub struct ShowSize;
@ -1467,31 +1814,56 @@ mod text {
}
}
pub struct ShowSizeOf<T>(pub T);
impl<T: Draw<Tui>> Draw<Tui> for ShowSizeOf<T> {
fn layout (&self, area: XYWH<u16>) -> Perhaps<XYWH<u16>> {
self.0.layout(area)
}
fn draw (self, to: &mut Tui) -> Drawn<u16> {
Ok(self.0.draw(to)?.map(|used|{
let _ = to.text(&format!("{used:?}"), used.0, used.1, u16::MAX);
used
}))
}
}
/// Apply foreground color.
pub const fn fg (fg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|{
pub const fn fg (fg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
draw(move|to: &mut Tui|{
to.update(&|cell,_,_|{ cell.set_fg(fg); });
draw.draw(to)
item.draw(to)
})
}
/// Apply background color.
pub const fn bg (bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|{
to.update(&|cell,_,_|{ cell.set_bg(bg); });
draw.draw(to)
})
pub const fn bg (bg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
Background(bg, item)
}
pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|{
pub struct Background<T>(Color, T);
impl<T: Draw<Tui>> Draw<Tui> for Background<T> {
fn layout (&self, area: XYWH<u16>) -> Drawn<u16> {
self.1.layout(area)
}
fn draw (self, to: &mut Tui) -> Drawn<u16> {
self.layout(to.area()).map(|area|to.clip(area, |to|{
to.update(&|cell,_,_|{ cell.set_bg(self.0); });
self.1.draw(to)
}))?
}
}
pub const fn fg_bg (fg: Color, bg: Color, item: impl Draw<Tui>) -> impl Draw<Tui> {
draw(move|to: &mut Tui|{
to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); });
draw.draw(to)
item.draw(to)
})
}
pub const fn fill_ul (color: Option<Color>) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|Ok(Some(if let Some(color) = color {
draw(move|to: &mut Tui|Ok(Some(if let Some(color) = color {
to.update(&|cell,_,_|{
cell.modifier.insert(Modifier::UNDERLINED);
cell.underline_color = color;
@ -1532,7 +1904,6 @@ mod text {
mod buffer; pub use self::buffer::*;
mod repeat; pub use self::repeat::*;
mod scroll; pub use self::scroll::*;
mod colors; pub use self::colors::*;
mod phat; pub use self::phat::*;
mod button; pub use self::button::*;
@ -1553,7 +1924,7 @@ mod text {
/// Interpret layout operation.
///
/// ```
/// # use tengri::{*, lang::*};
/// # use tengri::{*, dizzle::*};
///
/// struct State {/*app-specific*/}
/// impl<'b> Namespace<'b, u16> for State {}
@ -1598,29 +1969,28 @@ mod text {
Some("when") => when(
state.namespace(arg0?)?.unwrap(),
thunk(move|output: &mut O|{state.interpret(output, &arg1)})
draw(move|output: &mut O|{state.interpret(output, &arg1)})
).draw(output),
Some("either") => either(
state.namespace(arg0?)?.unwrap(),
thunk(move|output: &mut O|{state.interpret(output, &arg1)}),
thunk(move|output: &mut O|{state.interpret(output, &arg2)}),
draw(move|output: &mut O|{state.interpret(output, &arg1)}),
draw(move|output: &mut O|{state.interpret(output, &arg2)}),
).draw(output),
Some("bsp") => eval_enum!("bsp", output, state, frags.next(), arg0, Split {
"n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below
}).stack(
thunk(move|output: &mut O|{state.interpret(output, &arg0)}),
thunk(move|output: &mut O|{state.interpret(output, &arg1)}),
draw(move|output: &mut O|{state.interpret(output, &arg0)}),
draw(move|output: &mut O|{state.interpret(output, &arg1)}),
).draw(output),
Some("align") => thunk(move|output: &mut O|{
state.interpret(output, &arg0)
}).align(eval_enum!("align", output, state, frags.next(), arg0, 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),
Some("align") => draw(move|output: &mut O|{state.interpret(output, &arg0)})
.align(eval_enum!("align", output, state, frags.next(), arg0, 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),
Some("exact") => eval_xy!(
"exact" => expr, head, output, state, frags.next(), exact_wh, exact_w, exact_h, arg0, arg1, arg2,