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

@ -5,11 +5,12 @@ version = "0.15.0"
description = "UI metaframework."
[features]
default = ["lang", "sing", "midi", "draw", "play", "term", "text", "time", "rand", "okhsl", "eval"]
default = ["lang", "sing", "midi", "draw", "play", "term", "text", "time", "rand", "okhsl", "eval", "exit"]
bumpalo = ["dep:bumpalo"]
draw = []
gui = ["draw", "dep:winit"]
eval = []
exit = []
lang = ["dep:dizzle"]
midi = ["dep:midly"]
okhsl = ["dep:palette"]

View file

@ -2,7 +2,7 @@
use ::tengri::{
*,
lang::*,
dizzle::*,
crossterm::event::{KeyEvent, Event::*, KeyCode::*},
ratatui::style::Color,
};
@ -13,8 +13,8 @@ tui_app!(State {
});
tui_view!(self: State {
thunk(|to: &mut Tui|{
let _ = "DEMO [MODE 00]".align_sw().draw(to);
draw(|to: &mut Tui|{
let _ = "Demo [Mode 00]".align_sw().draw(to);
let _ = ShowSize.align_se().draw(to);
let _ = format!("Counter:\n{}", self.counter).align_c().draw(to);
Ok(Some(to.area()))

View file

@ -1,9 +1,8 @@
//! Mode 01: Direct view, actions with history
use itertools::Itertools;
use ::tengri::{
*,
lang::*,
dizzle::{*, itertools::Itertools},
crossterm::event::{Event::*, KeyEvent, KeyCode::*},
ratatui::style::Color,
};
@ -28,7 +27,7 @@ tui_keys!(self: State, input {
});
tui_view!(self: State {
let title = "Demo Mode 00";
let title = "Demo [Mode 01]";
let items = self.history.iter().take(10).map(|x|format!("{x:?}")).join("\n");
let history = format!("History: {}\n{items}", self.history.len());
let cursor = format!("Counter: {}", self.cursor);

View file

@ -2,7 +2,7 @@
use ::tengri::{
*,
lang::*,
dizzle::*,
crossterm::event::{Event::*, KeyEvent, KeyCode::*},
ratatui::style::Color,
};
@ -20,11 +20,15 @@ tui_view!(self: State {
let index = self.cursor + 1;
let wh = (self.size.w(), self.size.h());
let src = VIEWS.get(self.cursor).unwrap_or(&"");
let heading = format!("State {}/{} in {:?}", index, VIEWS.len(), &wh);
let title = bg(Color::Rgb(60, 10, 10), heading.align_n().push_xy(2, 1)).exact_h(3);
let code = bg(Color::Rgb(10, 60, 10), format!("Source:\n{}", src).align_n().push_xy(2, 1)).min_h(4);
let widget = thunk(move|to: &mut Tui|self.interpret(to, &src));
self.size.of(east(south(title, code).max_w(40), widget))
let heading = format!("Demo [Mode 02]\nExample {}/{}\nSize {:?}", index, VIEWS.len(), &wh);
let title = bg(Color::Rgb(60, 10, 10), heading);
let code = bg(Color::Rgb(10, 60, 10), format!("Source:\n{}", src).max_h(10));
let widget = draw(move|to: &mut Tui|self.interpret(to, &src)); // FIXME this forcefills
let sidebar = bg(Color::Rgb(20, 20, 20), north(code, title)).max_w(40);
let content = bg(Color::Rgb(64, 64, 64), widget);
east(sidebar, content)
//self.size.of(bg(Color::Rgb(10, 10, 10), south(east(sidebar, content), code)).full_w())
//self.size.of(bg(Color::Rgb(10, 10, 10), east(sidebar, content)).full_w())
});
impl Interpret<Tui, Option<XYWH<u16>>> for State {

View file

View file

@ -1,40 +0,0 @@
use super::*;
/// A numeric type that can be used as coordinate.
///
/// FIXME: Replace with `num` crate?
/// FIXME: Use AsRef/AsMut?
///
/// ```
/// use tengri::*;
/// let a: u16 = Coord::zero();
/// let b: u16 = a.plus(1);
/// let c: u16 = a.minus(2);
/// let d = a.atomic();
/// ```
pub trait Coord: Send + Sync + Copy
+ Add<Self, Output=Self>
+ Sub<Self, Output=Self>
+ Mul<Self, Output=Self>
+ Div<Self, Output=Self>
+ Ord + PartialEq + Eq
+ Debug + Display + Default
+ From<u16> + Into<u16>
+ Into<usize>
+ Into<f64>
//+ std::iter::Step
{
/// Zero in own type.
fn zero () -> Self { 0.into() }
/// Addition.
fn plus (self, other: Self) -> Self;
/// Saturating subtraction.
fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } }
/// Convert to [AtomicUsize].
fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) }
}
/// TUI works in u16 coordinates.
impl Coord for u16 {
fn plus (self, other: Self) -> Self { self.saturating_add(other) }
}

View file

View file

@ -1 +0,0 @@
use super::*;

View file

@ -1,47 +0,0 @@
use crate::{*, layout::*, draw::*};
use Azimuth::*;
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!()
}
}

View file

@ -1,31 +0,0 @@
use super::*;
use std::sync::atomic::Ordering;
/// 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(Ordering::Relaxed) as u16 }
fn y (&self) -> u16 { self.1.load(Ordering::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> {
thunk(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)
})
}
}

View file

@ -1,49 +0,0 @@
use crate::*;
use super::*;
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
pub struct Thunk<T: Screen, F>(pub F, std::marker::PhantomData<T>);
impl<T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> {
fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
(self.0)(to)
}
}
/// Basic [Draw]able closure.
///
/// ```
/// # use tengri::*;
/// # fn test () -> impl Draw<Tui> {
/// thunk(|to: &mut Tui|Ok(Some(to.1)))
/// # }
/// ```
pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> (
draw: F
) -> Thunk<T, F> {
Thunk(draw, 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, draw: impl Draw<T>) -> impl Draw<T> {
thunk(move|to: &mut T|if condition { draw.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> {
thunk(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) })
}

View file

@ -1,115 +0,0 @@
use super::*;
use Split::*;
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 {}

View file

@ -1,25 +0,0 @@
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
}
}

View file

@ -8,11 +8,13 @@ pub struct Align<T>(
impl<S: Screen, T: Draw<S>> Draw<S> for Align<T> {
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
let XYWH(x0, y0, w0, h0) = area;
Ok(align::<S>(area, self.1.layout(area)?, self.0))
}
fn draw (self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
Ok(self.layout(to.area())?.map(|area|to.clip(area, |to|self.1.draw(to))).transpose()?.flatten())
Ok(self.layout(to.area())?
.map(|area|to.clip(area, |to|self.1.draw(to)))
.transpose()?
.flatten())
}
}

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// ```
/// use tengri::*;

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Set size of of drawing area.
///
@ -8,22 +8,42 @@ use crate::{*, draw::*};
/// let _ = "".exact_h(1);
/// let _ = "".exact_wh(1, 1);
/// ```
pub enum Exact<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
__(PhantomData<T>),
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_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Exact<T, I, X>, to: T|{
let area: XYWH<T::Unit> = to.area();
let (item, area) = match self {
Self::W(item, w1) =>
(item, XYWH(area.0, area.1, w1.into().unwrap_or(area.2), area.3)),
Self::H(item, h1) =>
(item, XYWH(area.0, area.1, area.2, h1.into().unwrap_or(area.3))),
Self::WH(item, w1, h1) =>
(item, XYWH(area.0, area.1, w1.into().unwrap_or(area.2), h1.into().unwrap_or(area.3))),
_ => return Ok(None)
};
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, |to|item.draw(to))
});
}
}
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,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Use whole drawing area along one or both axes.
///

View file

@ -1,18 +1,17 @@
use super::*;
use crate::layout::*;
/// Iterate over a collection of the same kind of [Draw]able:
pub fn iter <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
thunk(move|_to: &mut S|{ todo!() })
draw(move|_to: &mut S|{ todo!() })
}
/// Iterate over a collection of the same kind of [Draw]able:
pub fn iter_once <'a, S: Screen, D: 'a, U: Draw<S>> (
_iter: impl Iterator<Item = D>, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
thunk(move|_to: &mut S|{ todo!() })
draw(move|_to: &mut S|{ todo!() })
}
/// Iterate over a collection of various [Draw]ables:
@ -23,43 +22,43 @@ pub fn iter_dyn <
I: Iterator<Item = D>, // Type of the iterator
F: Fn(&D, usize)->dyn Draw<S>, // Function that returns [Draw]able from iterator item
> (_items: V, _cb: F) -> impl Draw<S> {
thunk(move|_to: &mut S|{ todo!() })
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
thunk(move|_to: &mut S|{ todo!() })
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
thunk(move|_to: &mut S|{ todo!() })
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_east_fixed <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_height: S::Unit, _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
thunk(move|_to: &mut S|{ todo!() })
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_south <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
thunk(move|_to: &mut S|{ todo!() })
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_south_fixed <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_height: S::Unit, _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
thunk(move|_to: &mut S|{ todo!() })
draw(move|_to: &mut S|{ todo!() })
}
pub fn iter_west <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
_iter: impl Fn()->I, _draw: impl Fn(D, usize)->U,
) -> impl Draw<S> {
thunk(move|_to: &mut S|{ todo!() })
draw(move|_to: &mut S|{ todo!() })
}
pub fn row_south <S: Screen> (south: S::Unit, height: S::Unit, content: impl Draw<S>)

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Set maximum size of of drawing area.
///
@ -50,6 +50,6 @@ impl<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>> + Copy> Draw<T> for Max<T,
)),
_ => return Ok(None)
};
to.clip(area, draw(item))
to.clip(area, |to|item.draw(to))
}
}

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Only draw content if area is above a certain size.
///

View file

@ -1,58 +1,4 @@
use super::{*, layout::*};
#[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> {
Split::East.half(a, b)
}
pub const fn north <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Split::North.half(a, b)
}
pub const fn west <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Split::West.half(a, b)
}
pub const fn south <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Split::South.half(a, b)
}
pub const fn above <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Split::Above.half(a, b)
}
pub const fn below <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
Split::Below.half(a, b)
}
use super::*;
/// Split along an axis. Direction determines order.
#[cfg_attr(test, derive(Arbitrary))]
@ -65,6 +11,28 @@ pub const fn below <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw
#[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 {
/// ```
@ -77,11 +45,7 @@ impl Split {
/// let _ = Split::West.stack("", "");
/// ```
pub const fn stack <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: A, b: B) -> impl Draw<S> {
thunk(move|to: &mut S|{
let (area_a, area_b) = stack_areas(self, to.area(), &a, &b)?;
let (drawn_a, drawn_b) = draw_stacks(self, to, a, area_a, None, b, area_b, None)?;
Ok(stack_drawn(self, drawn_a, drawn_b))
})
Pair(*self, a, b, PhantomData)
}
/// ```
@ -94,7 +58,7 @@ impl Split {
/// let _ = Split::West.half("", "");
/// ```
pub const fn half <S: Screen, A: Draw<S>, B: Draw<S>> (&self, a: A, b: B) -> impl Draw<S> {
thunk(move|to: &mut 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)?;
@ -157,17 +121,22 @@ fn draw_stacks <S: Screen> (
area_b: impl Into<Option<XYWH<S::Unit>>>,
origin_b: impl Into<Option<Azimuth>>,
) -> Usually<(Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
Ok(match split {
Split::Below => {
let drawn_b = to.clip(area_b.into(), |to|b.align(origin_b.into()).draw(to))?;
let drawn_a = to.clip(area_a.into(), |to|a.align(origin_a.into()).draw(to))?;
let draw_a = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_a) = origin_a.into() {
to.clip(area_a.into(), |to|a.align(origin_a).draw(to))?
} else {
to.clip(area_a.into(), |to|a.draw(to))?
});
let draw_b = |to: &mut S|Ok::<_, Box<dyn Error>>(if let Some(origin_b) = origin_b.into() {
to.clip(area_b.into(), |to|b.align(origin_b).draw(to))?
} else {
to.clip(area_b.into(), |to|b.draw(to))?
});
Ok(if matches!(split, Split::Below) {
let drawn_b = draw_b(to)?;
let drawn_a = draw_a(to)?;
(drawn_a, drawn_b)
},
_ => {
let drawn_a = to.clip(area_a.into(), |to|a.align(origin_a.into()).draw(to))?;
let drawn_b = to.clip(area_b.into(), |to|b.align(origin_b.into()).draw(to))?;
(drawn_a, drawn_b)
}
} else {
(draw_a(to)?, draw_b(to)?)
})
}
@ -197,7 +166,7 @@ pub fn stack_areas <S: Screen> (
),
Split::North => (
if let Some(used) = area_a {
Some(XYWH(used.x(), area.y() + used.h(), used.w(), used.h()))
Some(XYWH(used.x(), (area.y() + area.h()).minus(used.h()), used.w(), used.h()))
} else {
None
},
@ -211,7 +180,7 @@ pub fn stack_areas <S: Screen> (
),
Split::West => (
if let Some(used) = area_a {
Some(XYWH(area.x() + used.w(), used.y(), used.w(), used.h()))
Some(XYWH((area.x() + area.w()).minus(used.w()), used.y(), used.w(), used.h()))
} else {
None
},
@ -253,18 +222,94 @@ fn stack_drawn <S: Coord> (
}
}
#[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(())
}
}

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,25 +1969,24 @@ 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 {
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,

View file

View file

@ -1,65 +0,0 @@
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()
}
}

View file

@ -5,9 +5,9 @@ use super::{*, draw::*};
/// ```
/// /// TODO
/// ```
pub fn border (on: bool, style: impl BorderStyle, draw: impl Draw<Tui>) -> impl Draw<Tui> {
let content = draw.pad_wh(1, 1);
let outline = when(on, thunk(move|to: &mut Tui|{
pub fn border (on: bool, style: impl BorderStyle, item: impl Draw<Tui>) -> impl Draw<Tui> {
let content = item.pad_wh(1, 1);
let outline = when(on, draw(move|to: &mut Tui|{
let XYWH(x, y, w, h) = to.1;
if w > 0 && h > 0 {
to.blit(&style.border_nw(), x, y, style.style());
@ -49,7 +49,7 @@ macro_rules! border {
//impl Layout<Tui> for $T {}
impl Draw<Tui> for $T {
fn draw (self, to: &mut Tui) -> Perhaps<XYWH<u16>> {
when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to).map(Some))).draw(to)
when(self.enabled(), draw(|to: &mut Tui|BorderStyle::draw(self, to).map(Some))).draw(to)
}
}
)+}

View file

View file

@ -2,7 +2,7 @@ use super::{*, draw::*};
use ratatui::{prelude::{Position}};
pub const fn x_repeat (c: &str) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|{
draw(move|to: &mut Tui|{
let XYWH(x, y, w, _h) = to.xywh();
for x in x..x+w {
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
@ -14,7 +14,7 @@ pub const fn x_repeat (c: &str) -> impl Draw<Tui> {
}
pub const fn y_repeat (c: &str) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|{
draw(move|to: &mut Tui|{
let XYWH(x, y, _w, h) = to.xywh();
for y in y..y+h {
if let Some(cell) = to.0.cell_mut(Position::from((x, y))) {
@ -26,7 +26,7 @@ pub const fn y_repeat (c: &str) -> impl Draw<Tui> {
}
pub const fn xy_repeat (c: &str) -> impl Draw<Tui> {
thunk(move|to: &mut Tui|{
draw(move|to: &mut Tui|{
let XYWH(x, y, w, h) = to.xywh();
let a = c.len();
for (_v, y) in (y..y+h).enumerate() {

View file

@ -7,7 +7,7 @@ pub const ICON_DEC_H: &[char] = &[' ', '🞀', ' '];
pub const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
pub fn x_scroll () -> impl Draw<Tui> {
thunk(|Tui(buf, XYWH(x1, y1, w, _h)): &mut Tui|{
draw(|Tui(buf, XYWH(x1, y1, w, _h)): &mut Tui|{
let x2 = *x1 + *w;
for (i, x) in (*x1..=x2).enumerate() {
if let Some(cell) = buf.cell_mut(Position::from((x, *y1))) {
@ -35,7 +35,7 @@ pub fn x_scroll () -> impl Draw<Tui> {
}
pub fn y_scroll () -> impl Draw<Tui> {
thunk(|Tui(buf, XYWH(x1, y1, _w, h)): &mut Tui|{
draw(|Tui(buf, XYWH(x1, y1, _w, h)): &mut Tui|{
let y2 = *y1 + *h;
for (i, y) in (*y1..=y2).enumerate() {
if let Some(cell) = buf.cell_mut(Position::from((*x1, y))) {

View file