Compare commits

..

2 commits

Author SHA1 Message Date
same mf who else
b0fbe3c173 wip: refactor: removing seams
Some checks are pending
/ build (push) Waiting to run
2026-03-19 17:22:47 +02:00
same mf who else
4e8d58d793 wip: refactor(tengri): routines! 2026-03-09 02:06:30 +02:00
20 changed files with 2569 additions and 2827 deletions

View file

@ -5,31 +5,35 @@ version = "0.15.0"
description = "UI metaframework."
[features]
default = ["lang", "sing", "draw", "tui"]
lang = ["dep:dizzle"]
sing = ["dep:jack"]
draw = []
tui = ["draw", "dep:ratatui", "dep:crossterm"]
gui = ["draw", "dep:winit"]
default = ["lang", "sing", "draw", "play", "tui", "text", "time", "rand", "okhsl"]
bumpalo = ["dep:bumpalo"]
draw = []
dsl = ["dep:dizzle"]
gui = ["draw", "dep:winit"]
lang = ["dep:dizzle"]
okhsl = ["dep:palette"]
play = []
rand = ["dep:rand"]
sing = ["dep:jack"]
text = ["dep:unicode-width"]
time = ["dep:quanta"]
tui = ["draw", "dep:ratatui", "dep:crossterm"]
[dependencies]
anyhow = { version = "1.0" }
atomic_float = { version = "1" }
better-panic = { version = "0.3.0" }
palette = { version = "0.7.6", features = [ "random" ] }
quanta = { version = "0.12.3" }
rand = { version = "0.8.5" }
unicode-width = { version = "0.2" }
dizzle = { optional = true, path = "./dizzle" }
jack = { optional = true, path = "./rust-jack" }
winit = { optional = true, version = "0.30.4", features = [ "x11" ]}
bumpalo = { optional = true, version = "3.19.0" }
crossterm = { optional = true, version = "0.29.0" }
ratatui = { optional = true, version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
bumpalo = { optional = true, version = "3.19.0" }
crossterm = { optional = true, version = "0.29.0" }
dizzle = { optional = true, path = "./dizzle" }
jack = { optional = true, path = "./rust-jack" }
palette = { optional = true, version = "0.7.6", features = [ "random" ] }
quanta = { optional = true, version = "0.12.3" }
rand = { optional = true, version = "0.8.5" }
ratatui = { optional = true, version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
unicode-width = { optional = true, version = "0.2" }
winit = { optional = true, version = "0.30.4", features = [ "x11" ]}
[dev-dependencies]
proptest = { version = "^1" }
@ -37,12 +41,6 @@ proptest-derive = { version = "^0.5.1" }
tengri = { path = ".", features = [ "dsl" ] }
#tengri_proc = { path = "./proc" }
[lib]
path = "src/tengri.rs"
[profile.release]
lto = true
[profile.coverage]
inherits = "test"
lto = false

View file

@ -13,15 +13,15 @@ connect to jack audio connection kit to process chunks of audio and midi.
### draw
abstract interface layout system for defining interface layouts abstractly.
backend-agnostic layout pattern.
### play
the input handling system.
### tui
### term
uses `ratatui` to run in a terminal.
uses `ratatui` to render in in a terminal.
### gui (todo-todo-todo)
@ -31,6 +31,14 @@ opens windows, runs shaders in them and/or delegates them (to e.g. plugin guis).
uses `dizzle` to let you livecode all of the above.
### time
performance counter.
### text
backend-agnostic text layout.
## license
here and now, the blessings of `tengri` are invokable under the [**`AGPL3`**](./LICENSE).

2
dizzle

@ -1 +1 @@
Subproject commit 6026aff29f9a0ca9ecafdfac13ea59ddd65d8f83
Subproject commit 068e26dd50699e51e9db01ac23fc0778074647bd

View file

@ -904,3 +904,458 @@ impl<E: Engine, S, C: Command<S>> MenuItem<E, S, C> {
////test(":ctrl-alt-shift-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT ));
//}
//#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
//#[cfg(test)] pub(crate) use proptest::{prelude::*, option::of};
//#[cfg(test)] use Direction::*;
//#[cfg(test)] use proptest_derive::Arbitrary;
//#[cfg(test)] proptest! {
//#[test] fn proptest_direction (
//d in prop_oneof![
//Just(North), Just(South),
//Just(East), Just(West),
//Just(Above), Just(Below)
//],
//x in u16::MIN..u16::MAX,
//y in u16::MIN..u16::MAX,
//w in u16::MIN..u16::MAX,
//h in u16::MIN..u16::MAX,
//a in u16::MIN..u16::MAX,
//) {
//let _ = d.split_fixed(XYWH(x, y, w, h), a);
//}
//}
//proptest! {
//#[test] fn proptest_area (
//x in u16::MIN..u16::MAX,
//y in u16::MIN..u16::MAX,
//w in u16::MIN..u16::MAX,
//h in u16::MIN..u16::MAX,
//a in u16::MIN..u16::MAX,
//b in u16::MIN..u16::MAX,
//) {
//let _: XYWH<u16> = XYWH::zero();
////let _: XYWH<u16> = XYWH::from_position([a, b]);
////let _: XYWH<u16> = XYWH::from_size([a, b]);
//let area: XYWH<u16> = XYWH(x, y, w, h);
////let _ = area.expect_min(a, b);
//let _ = area.xy();
//let _ = area.wh();
////let _ = area.xywh();
//let _ = area.clipped_h(a);
//let _ = area.clipped_w(b);
//let _ = area.clipped(WH(a, b));
////let _ = area.set_w(a);
////let _ = area.set_h(b);
//let _ = area.x2();
//let _ = area.y2();
//let _ = area.lrtb();
//let _ = area.center();
//let _ = area.centered();
//let _ = area.centered_x(a);
//let _ = area.centered_y(b);
//let _ = area.centered_xy([a, b]);
//}
//}
//proptest! {
//#[test] fn proptest_size (
//x in u16::MIN..u16::MAX,
//y in u16::MIN..u16::MAX,
//a in u16::MIN..u16::MAX,
//b in u16::MIN..u16::MAX,
//) {
//let size = WH(x, y);
//let _ = size.w();
//let _ = size.h();
//let _ = size.wh();
//let _ = size.clip_w(a);
//let _ = size.clip_h(b);
////let _ = size.expect_min(a, b);
////let _ = size.to_area_pos();
////let _ = size.to_area_size();
//}
//}
////macro_rules! test_op_transform {
////($fn:ident, $Op:ident) => {
////proptest! {
////#[test] fn $fn (
////op_x in of(u16::MIN..u16::MAX),
////op_y in of(u16::MIN..u16::MAX),
////content in "\\PC*",
////x in u16::MIN..u16::MAX,
////y in u16::MIN..u16::MAX,
////w in u16::MIN..u16::MAX,
////h in u16::MIN..u16::MAX,
////) {
////if let Some(op) = match (op_x, op_y) {
////(Some(x), Some(y)) => Some($Op::XY(x, y, content)),
////(Some(x), None) => Some($Op::X(x, content)),
////(None, Some(y)) => Some($Op::Y(y, content)),
////_ => None
////} {
//////assert_eq!(Content::layout(&op, [x, y, w, h]),
//////Draw::layout(&op, [x, y, w, h]));
////}
////}
////}
////}
////}
////test_op_transform!(proptest_op_fixed, Fixed);
////test_op_transform!(proptest_op_min, Min);
////test_op_transform!(proptest_op_max, Max);
////test_op_transform!(proptest_op_push, Push);
////test_op_transform!(proptest_op_pull, Pull);
////test_op_transform!(proptest_op_shrink, Shrink);
////test_op_transform!(proptest_op_expand, Expand);
////test_op_transform!(proptest_op_padding, Pad);
//proptest! {
//#[test] fn proptest_op_bsp (
//d in prop_oneof![
//Just(North), Just(South),
//Just(East), Just(West),
//Just(Above), Just(Below)
//],
//a in "\\PC*",
//b in "\\PC*",
//x in u16::MIN..u16::MAX,
//y in u16::MIN..u16::MAX,
//w in u16::MIN..u16::MAX,
//h in u16::MIN..u16::MAX,
//) {
//let bsp = Bsp(d, a, b);
////assert_eq!(
////Content::layout(&bsp, [x, y, w, h]),
////Draw::layout(&bsp, [x, y, w, h]),
////);
//}
//}
////#[test] fn test_tui_engine () -> Usually<()> {
//////use std::sync::{Arc, RwLock};
////struct TestComponent(String);
////impl Draw<TuiOut> for TestComponent {
////fn draw (&self, _to: &mut TuiOut) {}
////}
////impl Handle<TuiIn> for TestComponent {
////fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> { Ok(None) }
////}
////let engine = Tui::new(Box::<&mut Vec<u8>>::new(vec![0u8;0].as_mut()))?;
////let state = engine.run(false, &Arc::new(RwLock::new(TestComponent("hello world".into()))))?;
////state.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
////Ok(())
////}
////impl<O: Screen> PartialEq for Measure<O> { fn eq (&self, other: &Self) -> bool { self.w() == other.w() && self.h() == other.h() } }
////impl<N: Coord> W<N> for Measure<N> { fn w (&self) -> N { (self.x.load(Relaxed) as u16).into() } }
////impl<N: Coord> H<N> for Measure<N> { fn h (&self) -> N { (self.y.load(Relaxed) as u16).into() } }
////impl<N: Coord> Debug for Measure<N> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("Measure").field("width", &self.x).field("height", &self.y).finish() } }
////impl<N: Coord> Measure<N> {
////pub fn set_w (&self, w: impl Into<usize>) -> &Self { self.x.store(w.into(), Relaxed); self }
////pub fn set_h (&self, h: impl Into<usize>) -> &Self { self.y.store(h.into(), Relaxed); self }
////pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) -> &Self { self.set_w(w); self.set_h(h); self }
////pub fn format (&self) -> Arc<str> { format!("{}x{}", self.w(), self.h()).into() }
////pub fn of <T: Draw<O>, O: Screen<N>> (&self, item: T) -> Bsp<Fill<&Self>, T> { bsp_b(Fill::XY(self), item) }
////pub fn new (x: N, y: N) -> Self { Self { x: Arc::new(x.atomic()), y: Arc::new(y.atomic()), __: PhantomData::default(), } }
////}
////impl<N: Coord> From<WH<N>> for Measure<N> { fn from (WH(x, y): WH<O::Unit>) -> Self { Self::new(x, y) } }
////impl<O: Screen> Clone for Measure<O> {
////fn clone (&self) -> Self { Self { __: Default::default(), x: self.x.clone(), y: self.y.clone(), } }
////}
////impl<N: Coord, O: Screen<N>> Draw<O> for Measure<N> {
////fn draw (&self, to: &mut O) {
////// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
////self.x.store(to.area().w().into(), Relaxed);
////self.y.store(to.area().h().into(), Relaxed);
////}
////}
////use Alignment::*;
////let layout_x = match alignment {
////NW | W | SW => to.x(),
////N | C | S => to.x().plus(to.w() / 2.into()).minus(self.1.layout_w(to) / 2.into()),
////NE | E | SE => to.x().plus(to.w()).minus(self.1.layout_w(to)),
////_ => todo!(),
////};
////let layout_y = match alignment {
////NW | N | NE => to.y(),
////W | C | E => to.y().plus(to.h() / 2.into()).minus(self.1.layout_h(to) / 2.into()),
////SW | S | SE => to.y().plus(to.h()).minus(self.1.layout_h(to)),
////_ => todo!(),
////};
////draw.draw(to.clip(w, h))
////}
////todo!()
////impl<O: Screen, T: Layout<O>> Layout<O> for Align<T> {
////fn layout_x (&self, to: XYWH<O::Unit>) -> O::Unit {
////use Alignment::*;
////match self.0 {
////NW | W | SW => to.x(),
////N | C | S => to.x().plus(to.w() / 2.into()).minus(self.1.layout_w(to) / 2.into()),
////NE | E | SE => to.x().plus(to.w()).minus(self.1.layout_w(to)),
////_ => todo!(),
////}
////}
////fn layout_y (&self, to: XYWH<O::Unit>) -> O::Unit {
////use Alignment::*;
////match self.0 {
////NW | N | NE => to.y(),
////W | C | E => to.y().plus(to.h() / 2.into()).minus(self.1.layout_h(to) / 2.into()),
////SW | S | SE => to.y().plus(to.h()).minus(self.1.layout_h(to)),
////_ => todo!(),
////}
////}
////}
////impl<E: Screen, T: Layout<E>> Layout<E> for Min<E::Unit, T> {
////fn layout (&self, area: XYWH<E::Unit>) -> XYWH<E::Unit> {
////let XYWH(x, y, w, h) = self.inner().layout(area);
////match self {
////Self::X(mw, _) => XYWH(x, y, w.max(*mw), h),
////Self::Y(mh, _) => XYWH(x, y, w, h.max(*mh)),
////Self::XY(mw, mh, _) => XYWH(x, y, w.max(*mw), h.max(*mh)),
////}
////}
////}
////impl<E: Screen, T: Layout<E>> Layout<E> for Max<E::Unit, T> {
////fn layout (&self, area: XYWH<E::Unit>) -> XYWH<E::Unit> {
////let XYWH(x, y, w, h) = self.inner().layout(area);
////match self {
////Self::X(mw, _) => XYWH(x, y, w.min(*mw), h ),
////Self::Y(mh, _) => XYWH(x, y, w, h.min(*mh)),
////Self::XY(mw, mh, _) => XYWH(x, y, w.min(*mw), h.min(*mh)),
////}
////}
////}
////impl<'a, O, A, B, I, F, G> Map<O, A, B, I, F, G> where
////I: Iterator<Item = A> + Send + Sync + 'a,
////F: Fn() -> I + Send + Sync + 'a,
////{
////pub const fn new (get_iter: F, get_item: G) -> Self {
////Self {
////__: PhantomData,
////get_iter,
////get_item
////}
////}
////const fn to_south <S: Screen> (
////item_offset: S::Unit, item_height: S::Unit, item: impl Draw<S>
////) -> impl Draw<S> {
////Push::Y(item_offset, Fixed::Y(item_height, Fill::X(item)))
////}
////const fn to_south_west <S: Screen> (
////item_offset: S::Unit, item_height: S::Unit, item: impl Draw<S>
////) -> impl Draw<S> {
////Push::Y(item_offset, Align::nw(Fixed::Y(item_height, Fill::X(item))))
////}
////const fn to_east <S: Screen> (
////item_offset: S::Unit, item_width: S::Unit, item: impl Draw<S>
////) -> impl Draw<S> {
////Push::X(item_offset, Align::w(Fixed::X(item_width, Fill::Y(item))))
////}
////}
////macro_rules! impl_map_direction (($name:ident, $axis:ident, $align:ident)=>{
////impl<'a, O, A, B, I, F> Map<
////O, A, Push<O::Unit, Align<Fixed<O::Unit, Fill<B>>>>, I, F, fn(A, usize)->B
////> where
////O: Screen,
////B: Draw<O>,
////I: Iterator<Item = A> + Send + Sync + 'a,
////F: Fn() -> I + Send + Sync + 'a
////{
////pub const fn $name (
////size: O::Unit,
////get_iter: F,
////get_item: impl Fn(A, usize)->B + Send + Sync
////) -> Map<
////O, A,
////Push<O::Unit, Align<Fixed<O::Unit, B>>>,
////I, F,
////impl Fn(A, usize)->Push<O::Unit, Align<Fixed<O::Unit, B>>> + Send + Sync
////> {
////Map {
////__: PhantomData,
////get_iter,
////get_item: move |item: A, index: usize|{
////// FIXME: multiply
////let mut push: O::Unit = O::Unit::from(0u16);
////for _ in 0..index {
////push = push + size;
////}
////Push::$axis(push, Align::$align(Fixed::$axis(size, get_item(item, index))))
////}
////}
////}
////}
////});
////impl_map_direction!(east, X, w);
////impl_map_direction!(south, Y, n);
////impl_map_direction!(west, X, e);
////impl_map_direction!(north, Y, s);
////impl<'a, O, A, B, I, F, G> Layout<O> for Map<O, A, B, I, F, G> where
////O: Screen,
////B: Layout<O>,
////I: Iterator<Item = A> + Send + Sync + 'a,
////F: Fn() -> I + Send + Sync + 'a,
////G: Fn(A, usize)->B + Send + Sync
////{
////fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> {
////let Self { get_iter, get_item, .. } = self;
////let mut index = 0;
////let XY(mut min_x, mut min_y) = area.centered();
////let XY(mut max_x, mut max_y) = area.center();
////for item in get_iter() {
////let XYWH(x, y, w, h) = get_item(item, index).layout(area);
////min_x = min_x.min(x);
////min_y = min_y.min(y);
////max_x = max_x.max(x + w);
////max_y = max_y.max(y + h);
////index += 1;
////}
////let w = max_x - min_x;
////let h = max_y - min_y;
//////[min_x.into(), min_y.into(), w.into(), h.into()].into()
////area.centered_xy([w.into(), h.into()])
////}
////}
////impl<'a, O, A, B, I, F, G> Draw<O> for Map<O, A, B, I, F, G> where
////O: Screen,
////B: Draw<O>,
////I: Iterator<Item = A> + Send + Sync + 'a,
////F: Fn() -> I + Send + Sync + 'a,
////G: Fn(A, usize)->B + Send + Sync
////{
////fn draw (&self, to: &mut O) {
////let Self { get_iter, get_item, .. } = self;
////let mut index = 0;
////let area = self.layout(to.area());
////for item in get_iter() {
////let item = get_item(item, index);
//////to.show(area.into(), &item);
////to.show(item.layout(area), &item);
////index += 1;
////}
////}
////}
////fn split_cardinal <N: Coord> (
////direction: Direction, area: XYWH<N>, a: N
////) -> (XYWH<N>, XYWH<N>) {
////let XYWH(x, y, w, h) = area;
////match direction {
////North => (XYWH(x, y.plus(h).minus(a), w, a), XYWH(x, y, w, h.minus(a))),
////South => (XYWH(x, y, w, a), XYWH(x, y.plus(a), w, h.minus(a))),
////East => (XYWH(x, y, a, h), XYWH(x.plus(a), y, w.minus(a), h)),
////West => (XYWH(x.plus(w).minus(a), y, a, h), XYWH(x, y, w.minus(a), h)),
////Above | Below => (area, area)
////}
////}
////fn bsp_areas <N: Coord, A: TwoD<N>, B: TwoD<N>> (
////area: [N;4],
////direction: Direction,
////a: &A,
////b: &B,
////) -> [XYWH<N>;3] {
////let XYWH(x, y, w, h) = area;
////let WH(aw, ah) = a.layout(area).wh();
////let WH(bw, bh) = b.layout(match direction {
////South => XYWH(x, y + ah, w, h.minus(ah)),
////North => XYWH(x, y, w, h.minus(ah)),
////East => XYWH(x + aw, y, w.minus(aw), h),
////West => XYWH(x, y, w.minus(aw), h),
////Above => area,
////Below => area,
////}).wh();
////match direction {
////Above | Below => {
////let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah.max(bh)]);
////let a = XYWH((x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah);
////let b = XYWH((x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh);
////[a.into(), b.into(), XYWH(x, y, w, h)]
////},
////South => {
////let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah + bh]);
////let a = XYWH((x + w/2.into()).minus(aw/2.into()), y, aw, ah);
////let b = XYWH((x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh);
////[a.into(), b.into(), XYWH(x, y, w, h)]
////},
////North => {
////let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah + bh]);
////let a = XYWH((x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah);
////let b = XYWH((x + (w/2.into())).minus(bw/2.into()), y, bw, bh);
////[a.into(), b.into(), XYWH(x, y, w, h)]
////},
////East => {
////let XYWH(x, y, w, h) = area.centered_xy([aw + bw, ah.max(bh)]);
////let a = XYWH(x, (y + h/2.into()).minus(ah/2.into()), aw, ah);
////let b = XYWH(x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh);
////[a.into(), b.into(), XYWH(x, y, w, h)]
////},
////West => {
////let XYWH(x, y, w, h) = area.centered_xy([aw + bw, ah.max(bh)]);
////let a = XYWH(x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah);
////let b = XYWH(x, (y + h/2.into()).minus(bh/2.into()), bw, bh);
////[a.into(), b.into(), XYWH(x, y, w, h)]
////},
////}
////}
////pub fn iter
////impl<O: Screen, Head: Layout<O>, Tail: Layout<O>> Layout<O> for Bsp<Head, Tail> {
////fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit {
////match self.0 {
////Above | Below | North | South => self.1.layout_w(area).max(self.2.layout_w(area)),
////East | West => self.1.layout_w_min(area).plus(self.2.layout_w(area)),
////}
////}
////fn layout_w_min (&self, area: XYWH<O::Unit>) -> O::Unit {
////match self.0 {
////Above | Below | North | South => self.1.layout_w_min(area).max(self.2.layout_w_min(area)),
////East | West => self.1.layout_w_min(area).plus(self.2.layout_w_min(area)),
////}
////}
////fn layout_w_max (&self, area: XYWH<O::Unit>) -> O::Unit {
////match self.0 {
////Above | Below | North | South => self.1.layout_w_max(area).max(self.2.layout_w_max(area)),
////East | West => self.1.layout_w_max(area).plus(self.2.layout_w_max(area)),
////}
////}
////fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit {
////match self.0 {
////Above | Below | East | West => self.1.layout_h(area).max(self.2.layout_h(area)),
////North | South => self.1.layout_h(area).plus(self.2.layout_h(area)),
////}
////}
////fn layout_h_min (&self, area: XYWH<O::Unit>) -> O::Unit {
////match self.0 {
////Above | Below | East | West => self.1.layout_h_min(area).max(self.2.layout_h_min(area)),
////North | South => self.1.layout_h_min(area).plus(self.2.layout_h_min(area)),
////}
////}
////fn layout_h_max (&self, area: XYWH<O::Unit>) -> O::Unit {
////match self.0 {
////Above | Below | North | South => self.1.layout_h_max(area).max(self.2.layout_h_max(area)),
////East | West => self.1.layout_h_max(area).plus(self.2.layout_h_max(area)),
////}
////}
////fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> {
////bsp_areas(area, self.0, &self.1, &self.2)[2]
////}
////}
////impl<O: Screen, Head: Draw<O>, Tail: Draw<O>> Draw<O> for Bsp<Head, Tail> {
////fn draw (&self, to: &mut O) {
////let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2);
//////panic!("{a:?} {b:?}");
////if self.0 == Below {
////to.show(a, &self.1);
////to.show(b, &self.2);
////} else {
////to.show(b, &self.2);
////to.show(a, &self.1);
////}
////}
////}

19
src/color.rs Normal file
View file

@ -0,0 +1,19 @@
pub(crate) use ::palette::Okhsl;
pub trait HasColor { fn color (&self) -> ItemColor; }
pub struct ItemColor {}
pub struct ItemTheme {}
#[macro_export] macro_rules! has_color {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? {
fn color (&$self) -> ItemColor { $cb }
}
}
}
pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor {
todo!();
}

View file

@ -1,8 +1,233 @@
use crate::*;
use crate::{*, lang::*, color::*};
/// Drawable that supports dynamic dispatch.
///
/// ```
/// use tengri::draw::*;
/// struct TestScreen(bool);
/// struct TestWidget(bool);
/// impl Draw<TestScreen> for TestWidget {
/// fn draw (&self, screen: &mut T) {
/// screen.0 |= self.0;
/// }
/// }
/// let mut screen = TestScreen(false);
/// TestWidget(false).draw(&mut screen);
/// TestWidget(true).draw(&mut screen);
/// TestWidget(false).draw(&mut screen);
/// ```
pub trait Draw<T> {
fn draw (&self, to: &mut T);
}
impl<T> Draw<T> for () {
fn draw (&self, __: &mut T) {}
}
impl<T, F: Fn(&mut T)> Draw<T> for F {
fn draw (&self, to: &mut T) { self(to) }
}
//impl<T, D: Draw<T>> Draw<T> for &D {
//fn draw (&self, to: &mut T) { (*self).draw(to) }
//}
impl<T, D: Draw<T>> Draw<T> for Arc<D> {
fn draw (&self, to: &mut T) { (**self).draw(to) }
}
impl<T, D: Draw<T>> Draw<T> for RwLock<D> {
fn draw (&self, to: &mut T) { self.read().unwrap().draw(to) }
}
impl<T, D: Draw<T>> Draw<T> for Option<D> {
fn draw (&self, to: &mut T) { if let Some(draw) = self { draw.draw(to) } }
}
/// Only render when condition is true.
///
/// ```
/// # fn test () -> impl tengri::Draw<tengri::Tui> {
/// tengri::when(true, "Yes")
/// # }
/// ```
pub fn when <T> (condition: bool, draw: impl Draw<T>) -> impl Draw<T> {
move|to: &mut T|if condition { draw.draw(to); }
}
/// Render one thing if a condition is true and another false.
///
/// ```
/// # fn test () -> impl tengri::Draw<tengri::Tui> {
/// tengri::either(true, "Yes", "No")
/// # }
/// ```
pub fn either <T> (condition: bool, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) }
}
/// Output target.
///
/// ```
/// use tengri::*;
///
/// struct TestOut(impl TwoD<u16>);
///
/// impl Screen for TestOut {
/// type Unit = u16;
/// fn area (&self) -> impl TwoD<u16> { self.0 }
/// fn area_mut (&mut self) -> &mut impl TwoD<u16> { &mut self.0 }
/// fn show <T: Draw<Self> + ?Sized> (&mut self, area: impl TwoD<u16>, _: &T) {
/// println!("show: {area:?}");
/// ()
/// }
/// }
///
/// impl Draw<Screen> for String {
/// fn draw (&self, to: &mut TestOut) {
/// to.area_mut().set_w(self.len() as u16);
/// }
/// }
/// ```
pub trait Screen: Area<Self::Unit> + Aligned + Send + Sync + Sized {
type Unit: Coord;
/// Render drawable in area specified by `area`
fn show <'t, T: Draw<Self> + ?Sized> (&mut self, area: impl Area<Self::Unit>, content: &'t T);
fn clipped_mut (&mut self, w: Option<Self::Unit>, h: Option<Self::Unit>)
-> &mut Self;
fn padded_mut (&mut self, w: Self::Unit, h: Self::Unit)
-> &mut Self;
}
impl<O: Screen> X<O::Unit> for O { fn x (&self) -> O::Unit { self.x() } }
impl<O: Screen> Y<O::Unit> for O { fn y (&self) -> O::Unit { self.y() } }
impl<O: Screen> W<O::Unit> for O { fn w (&self) -> O::Unit { self.w() } }
impl<O: Screen> H<O::Unit> for O { fn h (&self) -> O::Unit { self.h() } }
/// Something that has area in 2D space.
pub trait Area<N: Coord>: W<N> + H<N> {
fn wh (&self) -> WH<N> {
WH(self.w(), self.h())
}
fn clip_w (&self, w: N) -> [N;2] {
[self.w().min(w), self.h()]
}
fn clip_h (&self, h: N) -> [N;2] {
[self.w(), self.h().min(h)]
}
fn at_least (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) }
Ok(self)
}
fn clip_wh (&self, w: Option<N>, h: Option<N>) -> [N;2] {
let w = w.map(|w|self.w().min(w)).unwrap_or(self.w());
let h = h.map(|h|self.h().min(h)).unwrap_or(self.h());
[w, h]
}
}
/// Something that has length along horizontal axis.
pub trait W<N: Coord> {
fn w (&self) -> N { N::zero() }
fn w_min (&self) -> N { self.w() }
fn w_max (&self) -> N { self.w() }
}
fn w_exact <N: Coord, T: Screen<Unit = N>> (_w: N, _c: impl Draw<T>) -> impl Draw<T> {
move|_to: &mut T|{ todo!() }
}
fn w_fill <N: Coord, T: Screen<Unit = N>> (_c: impl Draw<T>) -> impl Draw<T> {
move|_to: &mut T|{ todo!() }
}
fn w_min <N: Coord, T: Screen<Unit = N>> (w: N, x: impl Draw<T>) -> impl Draw<T> {
min(Some(w), None, x)
}
fn w_max <N: Coord, T> (w: N, x: impl Draw<T>) -> impl Draw<T> {
max(Some(w), None, x)
}
/// Something that has length along vertical axis.
pub trait H<N: Coord> {
fn h (&self) -> N { N::zero() }
fn h_min (&self) -> N { self.h() }
fn h_max (&self) -> N { self.h() }
}
fn h_exact <N: Coord, T: Screen<Unit = N>> (_h: N, _c: impl Draw<T>) -> impl Draw<T> {
move|_to: &mut T|{ todo!() }
}
fn h_fill <N: Coord, T: Screen<Unit = N>> (_c: impl Draw<T>) -> impl Draw<T> {
move|_to: &mut T|{ todo!() }
}
fn h_min <N: Coord, T: Screen<Unit = N>> (h: N, x: impl Draw<T>) -> impl Draw<T> {
min(None, Some(h), x)
}
fn h_max <N: Coord, T> (h: N, x: impl Draw<T>) -> impl Draw<T> {
max(None, Some(h), x)
}
/// Something that has a [Measure] of its rendered size.
pub trait Measured <N: Coord> {}
/// Point along (X, Y).
pub trait Point<N: Coord>: X<N> + Y<N> {
fn xy (&self) -> XY<N> { XY(self.x(), self.y()) }
}
/// Something that has `[0, 0]` at a particular point.
pub trait Anchored { fn anchor (&self) -> Align; }
/// Something that has a bounding box
///
/// ```
/// use tengri::{Bounded, XYWH};
/// let bounded: Bounded<tengri::Tui, _> = Bounded(0, 0 ,0 ,0 ,"");
/// ```
pub trait Bounding <N: Coord>: Point<N> + Area<N> + Anchored {
/// Iterate over every covered X coordinate.
fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
self.x_left()..self.x_right()
}
/// Iterate over every covered Y coordinate.
fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
self.y_top()..self.y_bottom()
}
fn x_left (&self) -> N {
use Align::*;
let w = self.w();
let a = self.anchor();
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_right (&self) -> N {
use Align::*;
let w = self.w();
let a = self.anchor();
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 y_top (&self) -> N {
use Align::*;
let a = self.anchor();
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_bottom (&self) -> N {
use Align::*;
let a = self.anchor();
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)
}
}
/// A cardinal direction.
///
/// ```
/// let direction = tengri::Direction::Above;
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction {
North, South, East, West, Above, Below
}
/// A numeric type that can be used as coordinate.
///
/// FIXME: Replace this ad-hoc trait with `num` crate.
/// FIXME: Replace with `num` crate?
/// FIXME: Use AsRef/AsMut?
pub trait Coord: Send + Sync + Copy
+ Add<Self, Output=Self>
+ Sub<Self, Output=Self>
@ -16,52 +241,45 @@ pub trait Coord: Send + Sync + Copy
{
fn zero () -> Self { 0.into() }
fn plus (self, other: Self) -> Self;
/// Saturating subtraction
fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } }
fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) }
}
/// Point along horizontal axis.
pub trait X<N: Coord> { fn x (&self) -> N { N::zero() } }
pub trait X<N: Coord> {
fn x (&self) -> N { N::zero() }
fn push <T> (a: impl Draw<T>) -> impl Draw<T> { a }
fn pull <T> (a: impl Draw<T>) -> impl Draw<T> { a }
}
/// Point along vertical axis.
pub trait Y<N: Coord> { fn y (&self) -> N { N::zero() } }
/// Length along horizontal axis.
pub trait W<N: Coord> { fn w (&self) -> N { N::zero() } }
/// Length along vertical axis.
pub trait H<N: Coord> { fn h (&self) -> N { N::zero() } }
/// Which corner/side of a box is 0, 0
pub trait Anchor { fn anchor (&self) -> Alignment; }
/// Area along (X, Y).
pub trait Area<N: Coord>: W<N> + H<N> { fn wh (&self) -> WH<N> { WH(self.w(), self.h()) } }
/// Point along (X, Y).
pub trait Point<N: Coord>: X<N> + Y<N> { fn xy (&self) -> XY<N> { XY(self.x(), self.y()) } }
// Something that has a 2D bounding box (X, Y, W, H).
pub trait Bounded<N: Coord>: Point<N> + Area<N> + Anchor<N> {
fn x2 (&self) -> N { self.x().plus(self.w()) }
fn y2 (&self) -> N { self.y().plus(self.h()) }
fn xywh (&self) -> [N;4] { [..self.xy(), ..self.wh()] }
fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
pub trait Y<N: Coord> {
fn y (&self) -> N { N::zero() }
fn push <T> (a: impl Draw<T>) -> impl Draw<T> { a }
fn pull <T> (a: impl Draw<T>) -> impl Draw<T> { a }
}
//impl<O: Screen, T: Draw<O>> Draw<O> for Bounded<O, T> {
//fn draw (&self, to: &mut O) {
//let area = to.area();
//*to.area_mut() = self.0;
//self.1.draw(to);
//*to.area_mut() = area;
//}
//}
/// A point (X, Y).
///
/// ```
/// let xy = tengri::XY(0u16, 0);
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XY<C: Coord>(pub C, pub C);
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct XY<C: Coord>(pub C, pub C);
impl<N: Coord> X<N> for XY<N> { fn x (&self) -> N { self.0 } }
impl<N: Coord> Y<N> for XY<N> { fn y (&self) -> N { self.1 } }
/// A size (Width, Height).
///
@ -69,7 +287,21 @@ pub trait Bounded<N: Coord>: Point<N> + Area<N> + Anchor<N> {
/// let wh = tengri::WH(0u16, 0);
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct WH<C: Coord>(pub C, pub C);
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct WH<C: Coord>(pub C, pub C);
impl<N: Coord> W<N> for WH<N> { fn w (&self) -> N { self.0 } }
impl<N: Coord> H<N> for WH<N> { fn h (&self) -> N { self.1 } }
impl<N: Coord> WH<N> {
fn min <T: Screen<Unit = N>> (&self, x: impl Draw<T>) -> impl Draw<T> {
min(Some(self.w()), Some(self.h()), x)
}
fn exact <T: Screen<Unit = N>> (&self, c: impl Draw<T>) -> impl Draw<T> {
move|_to: &mut T|{ todo!() }
}
fn fill <T: Screen<Unit = N>> (c: impl Draw<T>) -> impl Draw<T> {
move|_to: &mut T|{ todo!() }
}
}
/// Point with size.
///
@ -84,139 +316,144 @@ pub trait Bounded<N: Coord>: Point<N> + Area<N> + Anchor<N> {
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH<C: Coord>(
pub C, pub C, pub C, pub C
);
impl<N: Coord> X<N> for XYWH<N> { fn x (&self) -> N { self.0 } }
impl<N: Coord> Y<N> for XYWH<N> { fn y (&self) -> N { self.1 } }
impl<N: Coord> W<N> for XYWH<N> { fn w (&self) -> N { self.0 } }
impl<N: Coord> H<N> for XYWH<N> { fn h (&self) -> N { self.1 } }
/// A cardinal direction.
///
/// ```
/// let direction = tengri::Direction::Above;
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction {
North, South, East, West, Above, Below
impl<N: Coord> XYWH<N> {
pub fn zero () -> Self {
Self(0.into(), 0.into(), 0.into(), 0.into())
}
pub fn clipped_w (&self, w: N) -> XYWH<N> {
Self(self.x(), self.y(), self.w().min(w), self.h())
}
pub fn clipped_h (&self, h: N) -> XYWH<N> {
Self(self.x(), self.y(), self.w(), self.h().min(h))
}
pub fn clipped (&self, wh: WH<N>) -> XYWH<N> {
Self(self.x(), self.y(), wh.w(), wh.h())
}
pub fn center (&self) -> XY<N> {
XY(self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into()))
}
pub fn centered (&self) -> XY<N> {
let Self(x, y, w, h) = *self;
XY(x.minus(w/2.into()), y.minus(h/2.into()))
}
pub fn centered_x (&self, n: N) -> XYWH<N> {
let Self(x, y, w, h) = *self;
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into())
}
pub fn centered_y (&self, n: N) -> XYWH<N> {
let Self(x, y, w, h) = *self;
XYWH(x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n)
}
pub fn centered_xy (&self, [n, m]: [N;2]) -> XYWH<N> {
let Self(x, y, w, h) = *self;
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m)
}
}
/// Set maximum width and/or height of drawing area.
pub fn clip <T: Screen<Unit = N>, N: Coord> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> impl Draw<T> {
move|to: &mut T|draw.draw(to.clipped_mut(w, h))
}
/// Shrink drawing area symmetrically.
pub fn pad <T: Screen<Unit = N>, N: Coord> (w: N, h: N, draw: impl Draw<T>) -> impl Draw<T> {
move|to: &mut T|draw.draw(to.padded_mut(w, h))
}
pub fn pad_x <T: Screen<Unit = N>, N: Coord> (w: N, draw: impl Draw<T>) -> impl Draw<T> {
pad(w, N::zero(), draw)
}
pub fn pad_y <T: Screen<Unit = N>, N: Coord> (h: N, draw: impl Draw<T>) -> impl Draw<T> {
pad(N::zero(), h, draw)
}
pub fn pad_xy <T: Screen<Unit = N>, N: Coord> (p: N, draw: impl Draw<T>) -> impl Draw<T> {
pad(p, p, draw)
}
/// 9th of area to place.
///
/// ```
/// let alignment = tengri::Alignment::Center;
/// let alignment = tengri::Align::C;
/// use ::tengri::*;
/// let area = XYWH(10u16, 10, 20, 20);
/// fn test (area: XYWH<u16>, item: &impl Draw<Tui>, expected: [u16;4]) {
/// //assert_eq!(Lay::layout(item, area), expected);
/// //assert_eq!(Draw::layout(item, area), expected);
/// };
///
/// let four = exact_xy(4, 4, "foo");
/// test(area, &Align::nw(four()), [10, 10, 4, 4]);
/// test(area, &Align::n(four()), [18, 10, 4, 4]);
/// test(area, &Align::ne(four()), [26, 10, 4, 4]);
/// test(area, &Align::e(four()), [26, 18, 4, 4]);
/// test(area, &Align::se(four()), [26, 26, 4, 4]);
/// test(area, &Align::s(four()), [18, 26, 4, 4]);
/// test(area, &Align::sw(four()), [10, 26, 4, 4]);
/// test(area, &Align::w(four()), [10, 18, 4, 4]);
///
/// let two_by_four = exact_xy(4, 2, "foo");
/// test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
/// test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
/// test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
/// test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
/// test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
/// test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
/// test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
/// test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
/// pub struct Align<T>(pub Align, pub T);
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Debug, Copy, Clone, Default)] pub enum Alignment {
#[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W
#[derive(Debug, Copy, Clone, Default)] pub enum Align {
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
}
/// Drawable with dynamic dispatch.
pub trait Draw<T>: Fn(&mut T)->Usually<()> { fn draw (&self, to: &mut T) -> Usually<()>; }
/// Drawable thunk.
impl<T, F: Fn(&mut T)->()> Draw<T> for F { fn draw (&self, to: &mut T) -> Usually<()> { self(to) } }
impl<T> Draw<T> for () { fn draw (&self, _: &mut S) -> Usually<()> { Ok(()) } }
impl<T> Draw<T> for Box<dyn Draw<T>> { fn draw (&self, to: &mut S) -> Usually<()> { (**self).draw(to) } }
impl<T, D: Draw<T>> Draw<T> for &D { fn draw (&self, to: &mut S) -> Usually<()> { (*self).draw(to) } }
impl<T, D: Draw<T>> Draw<T> for &mut D { fn draw (&self, to: &mut S) -> Usually<()> { (**self).draw(to) } }
impl<T, D: Draw<T>> Draw<T> for Arc<D> { fn draw (&self, to: &mut S) -> Usually<()> { (**self).draw(to) } }
impl<T, D: Draw<T>> Draw<T> for RwLock<D> { fn draw (&self, to: &mut S) -> Usually<()> { self.read().unwrap().draw(to) } }
impl<T, D: Draw<T>> Draw<T> for Option<D> { fn draw (&self, to: &mut S) { if let Some(draw) = self { draw.draw(to) } } }
/// Draw the content or its error message.
///
/// ```
/// let _ = tengri::Catcher::<tengri::Tui, &'static str>::new(Ok(Some("hello")));
/// let _ = tengri::Catcher::<tengri::Tui, &'static str>::new(Ok(None));
/// let _ = tengri::Catcher::<tengri::Tui, &'static str>::new(Err("draw fail".into()));
/// ```
pub fn catcher <T, E> (error: Perhaps<E>, draw: impl Draw<T>) -> impl Draw<T> {
move|to|match self.0.as_ref() {
Ok(Some(content)) => draw(to),
Ok(None) => to.blit(&"<empty>", 0, 0, Some(Style::default().yellow())),
Err(e) => {
let err_fg = Color::Rgb(255,224,244);
let err_bg = Color::Rgb(96,24,24);
let title = Bsp::e(Tui::bold(true, "oopsie daisy. "), "rendering failed.");
let error = Bsp::e("\"why?\" ", Tui::bold(true, format!("{e}")));
to.place(&Tui::fg_bg(err_fg, err_bg, Bsp::s(title, error)))
}
}
pub trait Aligned {
fn aligned (&mut self, align: Align) -> &mut Self;
}
/// Only render when condition is true.
///
/// ```
/// fn test () -> impl tengri::Draw<tengri::Tui> {
/// tengri::when(true, "Yes")
/// }
/// ```
pub fn when <T> (condition: bool, draw: impl Draw<T>) -> impl Draw<T> {
move|to|Ok(if condition { draw(to)? } })
/// Set 0, 0 of drawing subarea.
pub fn align <T: Aligned> (alignment: Align, draw: impl Draw<T>) -> impl Draw<T> {
move|to: &mut T|draw.draw(to.aligned(alignment))
}
/// Render one thing if a condition is true and another false.
///
/// ```
/// fn test () -> impl tengri::Draw<tengri::Tui> {
/// tengri::either(true, "Yes", "No")
/// }
/// ```
pub fn either <T> (condition: bool, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
move|to|Ok(if condition { a(to)? } else { b(to)? })
}
/// Set maximum width and/or height of drawing area.
pub fn clip <T, N: Coord> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> impl Draw<T> {
move|to|draw(to.clip(w, h))
}
/// Shrink drawing area symmetrically.
pub fn pad <T, N: Coord> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> impl Draw<T> {
move|to|draw(to.pad(w, h))
impl Align {
pub fn n <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::N, x) }
pub fn s <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::S, x) }
pub fn e <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::E, x) }
pub fn w <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::W, x) }
pub fn nw <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::NW, x) }
pub fn ne <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::NE, x) }
pub fn sw <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::SW, x) }
pub fn se <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::SE, x) }
pub fn c <T: Aligned> (x: impl Draw<T>) -> impl Draw<T> { align(Align::C, x) }
}
/// Only draw content if area is above a certain size.
///
/// ```
/// let minimum = tengri::Min::XY(3, 5, "Hello"); // 5x5
/// let minimum = tengri::min_wh(3, 5, "Hello"); // 5x5
/// ```
pub fn min <T, N: Coord> (w: N, draw: impl Draw<T>) -> impl Draw<T> {
move|to|draw(to.min(w, h))
pub fn min <T, N: Coord> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> impl Draw<T> {
move|to: &mut T| { todo!() }//draw.draw(to.min(w, h))
}
/// Set the maximum width and/or height of the content.
///
/// ```
/// let maximum = tengri::Max::XY(3, 5, "Hello"); // 3x1
/// let maximum = tengri::max_wh(3, 5, "Hello"); // 3x1
/// ```
pub fn max <T, N: Coord> (x: N, draw: impl Draw<T>) -> impl Draw<T> {
move|to|draw(to.max(w, h))
pub fn max <T, N: Coord> (w: Option<N>, h: Option<N>, draw: impl Draw<T>) -> impl Draw<T> {
move|to: &mut T| { todo!() }// { todo!() }//draw.draw(to.max(w, h))
}
// pub fn displace ...
/// Shrink by amount on each axis.
///
/// ```
/// /// TODO
/// ```
pub fn border <T, S: BorderStyle> (on: bool, style: S, draw: impl Draw<T>) -> impl Draw<T> {
fill_wh(above(when(on, |output|{/*TODO*/}), pad(Some(1), Some(1), draw)))
}
/// Stackably padded.
///
/// ```
/// /// TODO
/// ```
pub fn phat <T, N: Coord> (
w: N, h: N, [fg, bg, hi, lo]: [Color;4], draw: impl Draw<T>
pub fn iter <T, U: Draw<T>, F: Fn(U)->dyn Draw<T>> (
_items: impl Iterator<Item = U>, _cb: F
) -> impl Draw<T> {
let top = exact_w(1, Self::lo(bg, hi));
let low = exact_h(1, Self::hi(bg, lo));
let draw = TuiOut::fg_bg(fg, bg, draw);
min_wh(w, h, bsp_s(top, bsp_n(low, fill_wh(draw))))
}
pub fn iter <T> (items: impl Iterator<Item = dyn Draw<T>>) -> impl Draw<T> {
todo!()
move|_to: &mut T|{ todo!() }
}
/// Split screen between two items, or layer them atop each other.
@ -230,129 +467,26 @@ pub fn iter <T> (items: impl Iterator<Item = dyn Draw<T>>) -> impl Draw<T> {
/// let _ = tengri::draw::bsp(East, (), ());
/// let _ = tengri::draw::bsp(West, (), ());
/// ```
pub fn bsp <T> (dir: Direction, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
move|to|{
//fn split_cardinal <N: Coord> (
//direction: Direction, area: XYWH<N>, a: N
//) -> (XYWH<N>, XYWH<N>) {
//let XYWH(x, y, w, h) = area;
//match direction {
//North => (XYWH(x, y.plus(h).minus(a), w, a), XYWH(x, y, w, h.minus(a))),
//South => (XYWH(x, y, w, a), XYWH(x, y.plus(a), w, h.minus(a))),
//East => (XYWH(x, y, a, h), XYWH(x.plus(a), y, w.minus(a), h)),
//West => (XYWH(x.plus(w).minus(a), y, a, h), XYWH(x, y, w.minus(a), h)),
//Above | Below => (area, area)
//}
//}
//fn bsp_areas <N: Coord, A: TwoD<N>, B: TwoD<N>> (
//area: [N;4],
//direction: Direction,
//a: &A,
//b: &B,
//) -> [XYWH<N>;3] {
//let XYWH(x, y, w, h) = area;
//let WH(aw, ah) = a.layout(area).wh();
//let WH(bw, bh) = b.layout(match direction {
//South => XYWH(x, y + ah, w, h.minus(ah)),
//North => XYWH(x, y, w, h.minus(ah)),
//East => XYWH(x + aw, y, w.minus(aw), h),
//West => XYWH(x, y, w.minus(aw), h),
//Above => area,
//Below => area,
//}).wh();
//match direction {
//Above | Below => {
//let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah.max(bh)]);
//let a = XYWH((x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah);
//let b = XYWH((x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh);
//[a.into(), b.into(), XYWH(x, y, w, h)]
//},
//South => {
//let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah + bh]);
//let a = XYWH((x + w/2.into()).minus(aw/2.into()), y, aw, ah);
//let b = XYWH((x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh);
//[a.into(), b.into(), XYWH(x, y, w, h)]
//},
//North => {
//let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah + bh]);
//let a = XYWH((x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah);
//let b = XYWH((x + (w/2.into())).minus(bw/2.into()), y, bw, bh);
//[a.into(), b.into(), XYWH(x, y, w, h)]
//},
//East => {
//let XYWH(x, y, w, h) = area.centered_xy([aw + bw, ah.max(bh)]);
//let a = XYWH(x, (y + h/2.into()).minus(ah/2.into()), aw, ah);
//let b = XYWH(x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh);
//[a.into(), b.into(), XYWH(x, y, w, h)]
//},
//West => {
//let XYWH(x, y, w, h) = area.centered_xy([aw + bw, ah.max(bh)]);
//let a = XYWH(x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah);
//let b = XYWH(x, (y + h/2.into()).minus(bh/2.into()), bw, bh);
//[a.into(), b.into(), XYWH(x, y, w, h)]
//},
//}
//}
//pub fn iter
}
pub fn bsp <T> (_dir: Direction, a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
move|_to: &mut T|{ todo!() }
}
/// 3-column layout with center priority.
///
/// ```
/// /// TODO
/// ```
pub fn tryptich <T, N: Coord> (
top: boolean, [(w_a, ref a), (w_b, ref b), (w_c, ref c)]: [(N, impl Draw<T>);3]
) -> impl Draw<T> {
let Self { top, h, left: (w_a, ref a), middle: (w_b, ref b), right: (w_c, ref c) } = *self;
let a = exact_w(w_a, a);
let b = exact_w(w_b, align_w(b));
let c = exact_w(w_c, c);
exact_h(h, if top {
bsp_above(fill_w(align_n(b)), bsp_above(fill_w(align_nw(a)), fill_w(align_ne(c))))
} else {
bsp_above(fill_wh(align_c(b)), bsp_above(fill_wh(align_w(a)), fill_wh(align_e(c))))
})
pub fn bsp_n <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
bsp(Direction::North, a, b)
}
impl<N: Unit, O: Screen<N>> Draw<O> for Measure<N> {
fn draw (&self, to: &mut O) {
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
self.x.store(to.area().w().into(), Relaxed);
self.y.store(to.area().h().into(), Relaxed);
}
pub fn bsp_s <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
bsp(Direction::South, a, b)
}
impl<O: Screen, T: Draw<O>> Draw<O> for Bounded<O, T> {
fn draw (&self, to: &mut O) {
let area = to.area();
*to.area_mut() = self.0;
self.1.draw(to);
*to.area_mut() = area;
}
pub fn bsp_e <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
bsp(Direction::East, a, b)
}
impl<O: Screen, T: Draw<O>> Draw<O> for Align<T> {
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), &self.1).draw(to) }
pub fn bsp_w <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
bsp(Direction::West, a, b)
}
impl<O: Screen, T: Draw<O>> Draw<O> for Pad<O::Unit, T> {
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
pub fn bsp_a <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
bsp(Direction::Above, a, b)
}
impl<O: Screen, Head: Draw<O>, Tail: Draw<O>> Draw<O> for Bsp<Head, Tail> {
fn draw (&self, to: &mut O) {
let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2);
//panic!("{a:?} {b:?}");
if self.0 == Below {
to.show(a, &self.1);
to.show(b, &self.2);
} else {
to.show(b, &self.2);
to.show(a, &self.1);
}
}
pub fn bsp_b <T> (a: impl Draw<T>, b: impl Draw<T>) -> impl Draw<T> {
bsp(Direction::Below, a, b)
}
/// Clear a pre-allocated buffer, then write into it.
@ -388,20 +522,44 @@ impl<O: Screen, Head: Draw<O>, Tail: Draw<O>> Draw<O> for Bsp<Head, Tail> {
/// Stack things on top of each other,
#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => {{
let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)* bsp
let bsp = (); $(let bsp = bsp_b(bsp, $expr);)* bsp
}});
/// Stack southward.
#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => {{
let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)* bsp
let bsp = (); $(let bsp = bsp_s(bsp, $expr);)* bsp
}});
/// Stack northward.
#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => {{
let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)* bsp
let bsp = (); $(let bsp = bsp_n(bsp, $expr);)* bsp
}});
/// Stack eastward.
#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{
let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)* bsp
let bsp = (); $(let bsp = bsp_e(bsp, $expr);)* bsp
}});
/// Memoize a rendering.
///
/// ```
/// let _ = tengri::Memo::new((), ());
/// ```
#[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T,
pub view: Arc<RwLock<U>>
}
impl<T: PartialEq, U> Memo<T, U> {
pub fn new (value: T, view: U) -> Self {
Self { value, view: Arc::new(view.into()) }
}
pub fn update <R> (&mut self, newval: T, draw: impl Fn(&mut U, &T, &T)->R) -> Option<R> {
if newval != self.value {
let result = draw(&mut*self.view.write().unwrap(), &newval, &self.value);
self.value = newval;
return Some(result);
}
None
}
}

220
src/eval.rs Normal file
View file

@ -0,0 +1,220 @@
use crate::*;
/// Interpret layout operation.
///
/// ```
/// # use tengri::*;
/// struct Target { xywh: XYWH<u16> /*platform-specific*/}
/// impl tengri::Out for Target {
/// type Unit = u16;
/// fn area (&self) -> XYWH<u16> { self.xywh }
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.xywh }
/// fn show <'t, T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, content: &'t T) {}
/// }
///
/// struct State {/*app-specific*/}
/// impl<'b> Namespace<'b, bool> for State {}
/// impl<'b> Namespace<'b, u16> for State {}
/// impl Understand<Target, ()> for State {}
///
/// # fn main () -> tengri::Usually<()> {
/// let state = State {};
/// let mut target = Target { xywh: Default::default() };
/// eval_view(&state, &mut target, &"")?;
/// eval_view(&state, &mut target, &"(when true (text hello))")?;
/// eval_view(&state, &mut target, &"(either true (text hello) (text world))")?;
/// // TODO test all
/// # Ok(()) }
/// ```
#[cfg(feature = "dsl")] pub fn eval_view <'a, O: Screen + 'a, S> (
state: &S, output: &mut O, expr: &'a impl Expression
) -> Usually<bool> where
S: Understand<O, ()>
+ for<'b>Namespace<'b, bool>
+ for<'b>Namespace<'b, O::Unit>
{
// First element of expression is used for dispatch.
// Dispatch is proto-namespaced using separator character
let head = expr.head()?;
let mut frags = head.src()?.unwrap_or_default().split("/");
// The rest of the tokens in the expr are arguments.
// Their meanings depend on the dispatched operation
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
// And we also have to do the above binding dance
// so that the Perhaps<token>s remain in scope.
match frags.next() {
Some("when") => output.place(&when(
state.namespace(arg0?)?.unwrap(),
move|output: &mut O|state.understand(output, &arg1)
)),
Some("either") => output.place(&either(
state.namespace(arg0?)?.unwrap(),
move|output: &mut O|state.understand(output, &arg1),
move|output: &mut O|state.understand(output, &arg2),
)),
Some("bsp") => output.place(&{
let a = move|output: &mut O|state.understand(output, &arg0);
let b = move|output: &mut O|state.understand(output, &arg1);
bsp(match frags.next() {
Some("n") => Alignment::N,
Some("s") => Alignment::S,
Some("e") => Alignment::E,
Some("w") => Alignment::W,
Some("a") => Alignment::A,
Some("b") => Alignment::B,
frag => unimplemented!("bsp/{frag:?}")
}, a, b)
}),
Some("align") => output.place(&{
let a = move|output: &mut O|state.understand(output, &arg0).unwrap();
align(match frags.next() {
Some("c") => Alignment::Center,
Some("n") => Alignment::N,
Some("s") => Alignment::S,
Some("e") => Alignment::E,
Some("w") => Alignment::W,
Some("x") => Alignment::X,
Some("y") => Alignment::Y,
frag => unimplemented!("align/{frag:?}")
}, a)
}),
Some("fill") => output.place(&{
let a = move|output: &mut O|state.understand(output, &arg0).unwrap();
match frags.next() {
Some("xy") | None => fill_wh(a),
Some("x") => fill_w(a),
Some("y") => fill_h(a),
frag => unimplemented!("fill/{frag:?}")
}
}),
Some("exact") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("exact: unsupported axis {axis:?}") };
let cb = move|output: &mut O|state.understand(output, &arg).unwrap();
match axis {
Some("xy") | None => exact_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => exact_w(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => exact_h(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("exact/{frag:?} ({expr:?}) ({head:?}) ({:?})",
head.src()?.unwrap_or_default().split("/").next())
}
}),
Some("min") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("min: unsupported axis {axis:?}") };
let cb = move|output: &mut O|state.understand(output, &arg).unwrap();
match axis {
Some("xy") | None => min_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => min_w(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => min_h(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("min/{frag:?}")
}
}),
Some("max") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("max: unsupported axis {axis:?}") };
let cb = move|output: &mut O|state.understand(output, &arg).unwrap();
match axis {
Some("xy") | None => max_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => max_w(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => max_h(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("max/{frag:?}")
}
}),
Some("push") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("push: unsupported axis {axis:?}") };
let cb = move|output: &mut O|state.understand(output, &arg);
match axis {
Some("xy") | None => push_xy(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => push_x(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => push_y(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("push/{frag:?}")
}
}),
_ => return Ok(false)
};
Ok(true)
}
/// Interpret TUI-specific layout operation.
///
/// ```
/// use tengri::{Namespace, Understand, Tui, ratatui::prelude::Color};
///
/// struct State;
/// impl<'b> Namespace<'b, bool> for State {}
/// impl<'b> Namespace<'b, u16> for State {}
/// impl<'b> Namespace<'b, Color> for State {}
/// impl Understand<Tui, ()> for State {}
/// # fn main () -> tengri::Usually<()> {
/// let state = State;
/// let mut out = TuiOut::default();
/// tengri::eval_view_tui(&state, &mut out, "")?;
/// tengri::eval_view_tui(&state, &mut out, "text Hello world!")?;
/// tengri::eval_view_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?;
/// tengri::eval_view_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?;
/// tengri::eval_view_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
/// # Ok(()) }
/// ```
pub fn eval_view_tui <'a, S> (
state: &S, output: &mut Buffer, expr: impl Expression + 'a
) -> Usually<bool> where
S: Understand<TuiOut, ()>
+ for<'b>Namespace<'b, bool>
+ for<'b>Namespace<'b, u16>
+ for<'b>Namespace<'b, Color>
{
// See `tengri::eval_view`
let head = expr.head()?;
let mut frags = head.src()?.unwrap_or_default().split("/");
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
match frags.next() {
Some("text") => {
if let Some(src) = args?.src()? { output.place(&src) }
},
Some("fg") => {
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color"));
let thunk = Thunk::new(move|output: &mut Buffer|state.understand(output, &arg1).unwrap());
output.place(&TuiOut::fg(color, thunk))
},
Some("bg") => {
//panic!("expr: {expr:?}\nhead: {head:?}\nfrags: {frags:?}\nargs: {args:?}\narg0: {arg0:?}\ntail0: {tail0:?}\narg1: {arg1:?}\ntail1: {tail1:?}\narg2: {arg2:?}");
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");;
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color"));
let thunk = Thunk::new(move|output: &mut Buffer|state.understand(output, &arg1).unwrap());
output.place(&TuiOut::bg(color, thunk))
},
_ => return Ok(false)
};
Ok(true)
}

71
src/lib.rs Normal file
View file

@ -0,0 +1,71 @@
#![feature(anonymous_lifetime_in_impl_trait)]
#![feature(associated_type_defaults)]
#![feature(const_default)]
#![feature(const_option_ops)]
#![feature(const_precise_live_drops)]
#![feature(const_trait_impl)]
#![feature(impl_trait_in_assoc_type)]
#![feature(step_trait)]
#![feature(trait_alias)]
#![feature(type_alias_impl_trait)]
#![feature(type_changing_struct_update)]
pub extern crate atomic_float;
pub extern crate palette;
pub extern crate better_panic;
pub extern crate unicode_width;
#[cfg(test)] #[macro_use] pub extern crate proptest;
pub(crate) use ::{
atomic_float::AtomicF64,
std::fmt::{Debug, Display},
std::ops::{Add, Sub, Mul, Div},
std::sync::{Arc, RwLock},
std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::*},
};
#[cfg(feature = "lang")] pub extern crate dizzle as lang;
#[cfg(feature = "lang")] pub use ::dizzle::{self, Usually, Perhaps, impl_default};
#[cfg(feature = "time")] pub mod time;
#[cfg(feature = "play")] pub mod play;
#[cfg(feature = "sing")] pub extern crate jack;
#[cfg(feature = "sing")] pub mod sing;
#[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
#[cfg(feature = "draw")] pub mod draw;
#[cfg(feature = "draw")] pub mod color;
#[cfg(feature = "text")] pub mod text;
#[cfg(feature = "tui")] pub mod tui;
#[cfg(feature = "tui")] pub extern crate ratatui;
#[cfg(feature = "tui")] pub extern crate crossterm;
/// Define a trait an implement it for various mutation-enabled wrapper types. */
#[macro_export] macro_rules! flex_trait_mut (
($Trait:ident $(<$($A:ident:$T:ident),+>)? {
$(fn $fn:ident (&mut $self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)*
})=>{
pub trait $Trait $(<$($A: $T),+>)? {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret $body)*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret {
if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) }
})*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.get_mut().unwrap().$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::Mutex<_T_>> {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.lock().unwrap().$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::RwLock<_T_>> {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })*
}
};
);

View file

@ -1,4 +1,65 @@
use crate::*;
use crate::{*, time::*, lang::*};
use ::std::{thread::JoinHandle, time::Duration};
#[cfg(feature = "tui")] use ::crossterm::event::poll;
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
impl Exit {
pub fn run <T> (run: impl Fn(Self)->Usually<T>) -> Usually<T> {
run(Self(Arc::new(AtomicBool::new(false))))
}
}
#[derive(Debug)] pub struct Thread {
/// 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 Thread {
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
pub fn new <F> (exit: Arc<AtomicBool>, call: F) -> Result<Self, std::io::Error>
where F: Fn(&PerfModel)->() + Send + Sync + 'static
{
let perf = Arc::new(PerfModel::default());
Ok(Self {
exit: exit.clone(),
perf: perf.clone(),
join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || {
while !exit.fetch_and(true, Relaxed) {
let _ = perf.cycle(&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, call: F
) -> Result<Self, std::io::Error>
where F: Fn(&PerfModel)->() + Send + Sync + 'static
{
Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); })
}
/// Spawn a thread that uses [crossterm::event::poll]
/// to run `call` every `time` msec.
#[cfg(feature = "tui")]pub fn new_poll <F> (
exit: Arc<AtomicBool>, time: Duration, call: F
) -> Result<Self, std::io::Error>
where F: Fn(&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()
}
}
/// Define an enum containing commands, and implement [Command] trait for over given `State`.
#[macro_export] macro_rules! def_command (

View file

@ -1,7 +1,27 @@
use crate::*;
use crate::{*, time::PerfModel};
use ::jack::*;
use JackState::*;
/// Trait for thing that has a JACK process callback.
pub trait Audio {
/// Handle a JACK event.
fn handle (&mut self, _event: JackEvent) {}
/// Projecss a JACK chunk.
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
/// The JACK process callback function passed to the server.
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}
/// Wraps [JackState], and through it [jack::Client] when connected.
///
/// ```
@ -199,3 +219,87 @@ impl JackPerfModel for PerfModel {
}
}
}
/// Things that can provide a [jack::Client] reference.
///
/// ```
/// use tengri::{Jack, HasJack};
///
/// let jack: &Jack = Jacked::default().jack();
///
/// #[derive(Default)] struct Jacked<'j>(Jack<'j>);
///
/// impl<'j> HasJack<'j> for Jacked<'j> {
/// fn jack (&self) -> &Jack<'j> { &self.0 }
/// }
/// ```
pub trait HasJack<'j>: Send + Sync {
/// Return the internal [jack::Client] handle
/// that lets you call the JACK API.
fn jack (&self) -> &Jack<'j>;
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
self.jack().with_client(op)
}
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
self.with_client(|client|client.port_by_name(name))
}
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
self.with_client(|c|c.port_by_id(id))
}
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
}
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->jack::contrib::Position)
-> Usually<()>
{
if enable {
self.with_client(|client|match client.register_timebase_callback(false, callback) {
Ok(_) => Ok(()),
Err(e) => Err(e)
})?
}
Ok(())
}
fn sync_follow (&self, _enable: bool) -> Usually<()> {
// TODO: sync follow
Ok(())
}
}
pub trait JackPerfModel {
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
}
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! impl_audio {
(|
$self1:ident:
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
}
};
($Struct:ident: $process:ident, $handle:ident) => {
impl Audio for $Struct {
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
$process(self, c, s)
}
#[inline] fn handle (&mut self, e: JackEvent) {
$handle(self, e)
}
}
};
($Struct:ident: $process:ident) => {
impl Audio for $Struct {
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
$process(self, c, s)
}
}
};
}

View file

@ -1,64 +0,0 @@
#![feature(anonymous_lifetime_in_impl_trait)]
#![feature(associated_type_defaults)]
#![feature(const_default)]
#![feature(const_option_ops)]
#![feature(const_precise_live_drops)]
#![feature(const_trait_impl)]
#![feature(impl_trait_in_assoc_type)]
#![feature(step_trait)]
#![feature(trait_alias)]
#![feature(type_alias_impl_trait)]
#![feature(type_changing_struct_update)]
pub extern crate atomic_float;
pub extern crate palette;
pub extern crate better_panic;
pub extern crate unicode_width;
mod tengri_macros;
mod tengri_struct; pub use self::tengri_struct::*;
mod tengri_trait; pub use self::tengri_trait::*;
mod tengri_impl; pub use self::tengri_impl::*;
mod tengri_fns; pub use self::tengri_fns::*;
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
pub(crate) use ::{
atomic_float::AtomicF64,
palette::{*, convert::*, okhsl::*},
better_panic::{Settings, Verbosity},
unicode_width::*,
std::{
io::{stdout, Stdout, Write},
sync::{Arc, Weak, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
fmt::{Debug, Display},
ops::{Add, Sub, Mul, Div},
marker::PhantomData,
time::Duration,
thread::{spawn, JoinHandle}
}
};
#[cfg(feature = "lang")] extern crate dizzle;
#[cfg(feature = "lang")] pub use ::dizzle::self as lang;
#[cfg(feature = "draw")] pub mod draw;
#[cfg(feature = "tui")] pub extern crate ratatui;
#[cfg(feature = "tui")] pub extern crate crossterm;
#[cfg(feature = "tui")] pub(crate) use ::{
ratatui::{
prelude::{Color, Style, Buffer, Position},
style::{Stylize, Modifier, Color::*},
backend::{Backend, CrosstermBackend, ClearType},
layout::{Size, Rect},
buffer::Cell
},
crossterm::{
ExecutableCommand,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
}
};
#[cfg(feature = "sing")] pub extern crate jack;
#[cfg(feature = "sing")] pub mod sing;
#[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};

View file

@ -1,182 +0,0 @@
use crate::*;
/// Interpret layout operation.
///
/// ```
/// # use tengri::*;
/// struct Target { xywh: XYWH<u16> /*platform-specific*/}
/// impl tengri::Out for Target {
/// type Unit = u16;
/// fn area (&self) -> XYWH<u16> { self.xywh }
/// fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.xywh }
/// fn show <'t, T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, content: &'t T) {}
/// }
///
/// struct State {/*app-specific*/}
/// impl<'b> Namespace<'b, bool> for State {}
/// impl<'b> Namespace<'b, u16> for State {}
/// impl Understand<Target, ()> for State {}
///
/// # fn main () -> tengri::Usually<()> {
/// let state = State {};
/// let mut target = Target { xywh: Default::default() };
/// eval_view(&state, &mut target, &"")?;
/// eval_view(&state, &mut target, &"(when true (text hello))")?;
/// eval_view(&state, &mut target, &"(either true (text hello) (text world))")?;
/// // TODO test all
/// # Ok(()) }
/// ```
#[cfg(feature = "dsl")] pub fn eval_view <'a, O: Screen + 'a, S> (
state: &S, output: &mut O, expr: &'a impl Expression
) -> Usually<bool> where
S: Understand<O, ()>
+ for<'b>Namespace<'b, bool>
+ for<'b>Namespace<'b, O::Unit>
{
// First element of expression is used for dispatch.
// Dispatch is proto-namespaced using separator character
let head = expr.head()?;
let mut frags = head.src()?.unwrap_or_default().split("/");
// The rest of the tokens in the expr are arguments.
// Their meanings depend on the dispatched operation
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
// And we also have to do the above binding dance
// so that the Perhaps<token>s remain in scope.
match frags.next() {
Some("when") => output.place(&When::new(
state.namespace(arg0?)?.unwrap(),
Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap())
)),
Some("either") => output.place(&Either::new(
state.namespace(arg0?)?.unwrap(),
Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()),
Thunk::new(move|output: &mut O|state.understand(output, &arg2).unwrap())
)),
Some("bsp") => output.place(&{
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
let b = Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap());
match frags.next() {
Some("n") => Bsp::n(a, b),
Some("s") => Bsp::s(a, b),
Some("e") => Bsp::e(a, b),
Some("w") => Bsp::w(a, b),
Some("a") => Bsp::a(a, b),
Some("b") => Bsp::b(a, b),
frag => unimplemented!("bsp/{frag:?}")
}
}),
Some("align") => output.place(&{
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
match frags.next() {
Some("n") => Align::n(a),
Some("s") => Align::s(a),
Some("e") => Align::e(a),
Some("w") => Align::w(a),
Some("x") => Align::x(a),
Some("y") => Align::y(a),
Some("c") => Align::c(a),
frag => unimplemented!("align/{frag:?}")
}
}),
Some("fill") => output.place(&{
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
match frags.next() {
Some("xy") | None => Fill::XY(a),
Some("x") => Fill::X(a),
Some("y") => Fill::Y(a),
frag => unimplemented!("fill/{frag:?}")
}
}),
Some("fixed") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
match axis {
Some("xy") | None => Fixed::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => Fixed::X(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => Fixed::Y(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})",
head.src()?.unwrap_or_default().split("/").next())
}
}),
Some("min") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
match axis {
Some("xy") | None => Min::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => Min::X(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => Min::Y(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("min/{frag:?}")
}
}),
Some("max") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
match axis {
Some("xy") | None => Max::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => Max::X(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => Max::Y(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("max/{frag:?}")
}
}),
Some("push") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap());
match axis {
Some("xy") | None => Push::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => Push::X(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => Push::Y(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("push/{frag:?}")
}
}),
_ => return Ok(false)
};
Ok(true)
}
/// Trim string with [unicode_width].
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
let input = input.as_ref();
let mut output = Vec::with_capacity(input.len());
let mut width: usize = 1;
let mut chars = input.chars();
while let Some(c) = chars.next() {
if width > max_width {
break
}
output.push(c);
width += c.width().unwrap_or(0);
}
return output.into_iter().collect()
}
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
let mut width: u16 = 0;
let mut chars = text.as_ref().chars();
while let Some(c) = chars.next() {
width += c.width().unwrap_or(0) as u16;
if width > max {
break
}
}
return width
}

File diff suppressed because it is too large Load diff

View file

@ -1,60 +0,0 @@
use crate::*;
/// Define an enum containing commands, and implement [Command] trait for over given `State`.
#[macro_export] macro_rules! def_command (
($Command:ident: |$state:ident: $State:ty| {
// FIXME: support attrs (docstrings)
$($Variant:ident$({$($arg:ident:$Arg:ty),+ $(,)?})?=>$body:expr),* $(,)?
})=>{
#[derive(Debug)] pub enum $Command {
// FIXME: support attrs (docstrings)
$($Variant $({ $($arg: $Arg),* })?),*
}
impl ::tengri::Command<$State> for $Command {
fn execute (&self, $state: &mut $State) -> Perhaps<Self> {
match self {
$(Self::$Variant $({ $($arg),* })? => $body,)*
_ => unimplemented!("Command<{}>: {self:?}", stringify!($State)),
}
}
}
});
/// Implement [Handle] for given `State` and `handler`.
#[macro_export] macro_rules! handle {
(|$self:ident:$State:ty,$input:ident|$handler:expr) => {
impl<E: Engine> ::tengri::Handle<E> for $State {
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
$handler
}
}
};
($E:ty: |$self:ident:$State:ty,$input:ident|$handler:expr) => {
impl ::tengri::Handle<$E> for $State {
fn handle (&mut $self, $input: &$E) ->
Perhaps<<$E as ::tengri::Input>::Handled>
{
$handler
}
}
}
}
#[macro_export] macro_rules! tui_main {
($expr:expr) => {
fn main () -> Usually<()> {
let engine = ::tengri::Tui::new(Box::new(stdout()))?;
let state = ::std::sync::Arc::new(std::sync::RwLock::new($expr));
engine.run(true, &state)?;
Ok(())
}
};
}
#[macro_export] macro_rules! has_color {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? {
fn color (&$self) -> ItemColor { $cb }
}
}
}

View file

@ -1,256 +0,0 @@
use crate::*;
/// Memoize a rendering.
///
/// ```
/// let _ = tengri::Memo::new((), ());
/// ```
#[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T,
pub view: Arc<RwLock<U>>
}
pub use self::spatial::*; mod spatial {
use crate::*;
/// A widget that tracks its rendered width and height.
///
/// ```
/// let measure = tengri::Measure::<u16>::default();
/// ```
#[derive(Default)] pub struct Measure<N: Coord> {
pub x: Arc<AtomicUsize>,
pub y: Arc<AtomicUsize>,
pub __: PhantomData<N>,
}
/// Increment X and/or Y coordinate.
///
/// ```
/// let pushed = tengri::Push::XY(2, 2, "Hello");
/// ```
pub enum Push<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Decrement X and/or Y coordinate.
///
/// ```
/// let pulled = tengri::Pull::XY(2, 2, "Hello");
/// ```
pub enum Pull<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the content to fill the container.
///
/// ```
/// let filled = tengri::Fill::XY("Hello");
/// ```
pub enum Fill<A> { X(A), Y(A), XY(A) }
/// Set fixed size for content.
///
/// ```
/// let fixed = tengri::Fixed::XY(3, 5, "Hello"); // 3x5
/// ```
pub enum Fixed<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Decrease the width and/or height of the content.
///
/// ```
/// let shrunk = tengri::Shrink::XY(2, 0, "Hello"); // 1x1
/// ```
pub enum Shrink<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Increaase the width and/or height of the content.
///
/// ```
/// let expanded = tengri::Expand::XY(5, 3, "HELLO"); // 15x3
/// ```
pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Align position of inner area to middle, side, or corner of outer area.
///
///
/// ```
/// use ::tengri::*;
/// let area = XYWH(10u16, 10, 20, 20);
/// fn test (area: XYWH<u16>, item: &impl Draw<Tui>, expected: [u16;4]) {
/// //assert_eq!(Lay::layout(item, area), expected);
/// //assert_eq!(Draw::layout(item, area), expected);
/// };
///
/// let four = ||Fixed::XY(4, 4, "");
/// test(area, &Align::nw(four()), [10, 10, 4, 4]);
/// test(area, &Align::n(four()), [18, 10, 4, 4]);
/// test(area, &Align::ne(four()), [26, 10, 4, 4]);
/// test(area, &Align::e(four()), [26, 18, 4, 4]);
/// test(area, &Align::se(four()), [26, 26, 4, 4]);
/// test(area, &Align::s(four()), [18, 26, 4, 4]);
/// test(area, &Align::sw(four()), [10, 26, 4, 4]);
/// test(area, &Align::w(four()), [10, 18, 4, 4]);
///
/// let two_by_four = ||Fixed::XY(4, 2, "");
/// test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
/// test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
/// test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
/// test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
/// test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
/// test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
/// test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
/// test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
/// ```
pub struct Align<T>(pub Alignment, pub T);
// TODO DOCUMENTME
pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
// TODO DOCUMENTME
pub struct Bordered<S, W>(pub bool, pub S, pub W);
// TODO DOCUMENTME
pub struct Border<S>(pub bool, pub S);
/// TODO DOCUMENTME
///
/// ```
/// use tengri::{Bounded, XYWH};
/// let bounded: Bounded<tengri::Tui, _> = Bounded(0, 0 ,0 ,0 ,"");
/// ```
pub struct Bounded<N: Coord, D>(N, N, N, N, pub D);
/// Draws items from an iterator.
///
/// ```
/// // FIXME let map = tengri::Map(||[].iter(), |_|{});
/// ```
pub struct Map<O, A, B, I, F, G>
where
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync,
{
/// Function that returns iterator over stacked components
pub get_iter: F,
/// Function that returns each stacked component
pub get_item: G,
pub __: PhantomData<(O, B)>,
}
/// A color in OKHSL and RGB representations.
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
pub okhsl: Okhsl<f32>,
#[cfg(feature = "tui")] pub rgb: Color,
}
/// A color in OKHSL and RGB with lighter and darker variants.
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemTheme {
pub base: ItemColor,
pub light: ItemColor,
pub lighter: ItemColor,
pub lightest: ItemColor,
pub dark: ItemColor,
pub darker: ItemColor,
pub darkest: ItemColor,
}
// TODO DOCUMENTME
pub struct FieldH<Theme, Label, Value>(pub Theme, pub Label, pub Value);
// TODO DOCUMENTME
pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
// TODO:
pub struct Field<C, T, U> {
pub direction: Direction,
pub label: Option<T>,
pub label_fg: Option<C>,
pub label_bg: Option<C>,
pub label_align: Option<Direction>,
pub value: Option<U>,
pub value_fg: Option<C>,
pub value_bg: Option<C>,
pub value_align: Option<Direction>,
}
}
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
impl Exit {
pub fn run <T> (run: impl Fn()->Usually<T>) -> Usually<T> {
run(Self(Arc::new(AtomicBool::new(false))))
}
}
#[derive(Debug)] pub struct Thread {
/// Exit flag.
pub exit: Arc<AtomicBool>,
/// Performance counter.
pub perf: Arc<PerfModel>,
/// Use this to wait for the thread to finish.
pub join: JoinHandle<()>,
}
#[cfg(feature = "tui")] pub use self::terminal::*;
#[cfg(feature = "tui")] mod terminal {
use crate::*;
/// TUI input loop event.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiEvent(pub Event);
/// TUI key spec.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiKey(pub Option<KeyCode>, pub KeyModifiers);
/// TUI buffer sized by `usize` instead of `u16`.
#[derive(Default)] pub struct BigBuffer {
pub width: usize,
pub height: usize,
pub content: Vec<Cell>
}
pub struct Foreground<Color, Item>(pub Color, pub Item);
pub struct Background<Color, Item>(pub Color, pub Item);
pub struct Modify<T>(pub bool, pub Modifier, pub T);
pub struct Styled<T>(pub Option<Style>, pub T);
/// A cell that takes up 3 rows on its own,
/// but stacks, giving (N+1)*2 rows per N cells.
pub struct Phat<T> { pub width: u16, pub height: u16, pub content: T, pub colors: [Color;4], }
/// A three-column layout.
pub struct Tryptich<A, B, C> {
pub top: bool,
pub h: u16,
pub left: (u16, A),
pub middle: (u16, B),
pub right: (u16, C),
}
/// Repeat a string, e.g. for background fill
pub enum Repeat<'a> { X(&'a str), Y(&'a str), XY(&'a str) }
/// Scroll indicator
pub enum Scrollbar {
/// Horizontal scrollbar
X { offset: usize, length: usize, total: usize, },
/// Vertical scrollbar
Y { offset: usize, length: usize, total: usize, }
}
}
pub use self::textual::*;
mod textual {
/// Displays an owned [str]-like with fixed maximum width.
///
/// Width is computed using [unicode_width].
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
/// Displays a borrowed [str]-like with fixed maximum width
///
/// Width is computed using [unicode_width].
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
}
#[cfg(feature = "jack")] pub use self::aural::*;
#[cfg(feature = "jack")] mod aural {
}
pub use self::temporal::*; mod temporal {
use crate::*;
/// Performance counter
#[derive(Debug)]
pub struct PerfModel {
pub clock: quanta::Clock,
/// Measurement has a small cost. Disable it here.
pub enabled: bool,
// In nanoseconds. Time used by last iteration.
pub used: AtomicF64,
// In microseconds. Max prescribed time for iteration (frame, chunk...).
pub window: AtomicF64,
}
}

View file

@ -1,144 +0,0 @@
use crate::*;
use Direction::*;
use proptest::{prelude::*, option::of};
use proptest_derive::Arbitrary;
proptest! {
#[test] fn proptest_direction (
d in prop_oneof![
Just(North), Just(South),
Just(East), Just(West),
Just(Above), Just(Below)
],
x in u16::MIN..u16::MAX,
y in u16::MIN..u16::MAX,
w in u16::MIN..u16::MAX,
h in u16::MIN..u16::MAX,
a in u16::MIN..u16::MAX,
) {
let _ = d.split_fixed(XYWH(x, y, w, h), a);
}
}
proptest! {
#[test] fn proptest_area (
x in u16::MIN..u16::MAX,
y in u16::MIN..u16::MAX,
w in u16::MIN..u16::MAX,
h in u16::MIN..u16::MAX,
a in u16::MIN..u16::MAX,
b in u16::MIN..u16::MAX,
) {
let _: XYWH<u16> = XYWH::zero();
//let _: XYWH<u16> = XYWH::from_position([a, b]);
//let _: XYWH<u16> = XYWH::from_size([a, b]);
let area: XYWH<u16> = XYWH(x, y, w, h);
//let _ = area.expect_min(a, b);
let _ = area.xy();
let _ = area.wh();
//let _ = area.xywh();
let _ = area.clipped_h(a);
let _ = area.clipped_w(b);
let _ = area.clipped(WH(a, b));
//let _ = area.set_w(a);
//let _ = area.set_h(b);
let _ = area.x2();
let _ = area.y2();
let _ = area.lrtb();
let _ = area.center();
let _ = area.centered();
let _ = area.centered_x(a);
let _ = area.centered_y(b);
let _ = area.centered_xy([a, b]);
}
}
proptest! {
#[test] fn proptest_size (
x in u16::MIN..u16::MAX,
y in u16::MIN..u16::MAX,
a in u16::MIN..u16::MAX,
b in u16::MIN..u16::MAX,
) {
let size = WH(x, y);
let _ = size.w();
let _ = size.h();
let _ = size.wh();
let _ = size.clip_w(a);
let _ = size.clip_h(b);
//let _ = size.expect_min(a, b);
//let _ = size.to_area_pos();
//let _ = size.to_area_size();
}
}
macro_rules! test_op_transform {
($fn:ident, $Op:ident) => {
proptest! {
#[test] fn $fn (
op_x in of(u16::MIN..u16::MAX),
op_y in of(u16::MIN..u16::MAX),
content in "\\PC*",
x in u16::MIN..u16::MAX,
y in u16::MIN..u16::MAX,
w in u16::MIN..u16::MAX,
h in u16::MIN..u16::MAX,
) {
if let Some(op) = match (op_x, op_y) {
(Some(x), Some(y)) => Some($Op::XY(x, y, content)),
(Some(x), None) => Some($Op::X(x, content)),
(None, Some(y)) => Some($Op::Y(y, content)),
_ => None
} {
//assert_eq!(Content::layout(&op, [x, y, w, h]),
//Draw::layout(&op, [x, y, w, h]));
}
}
}
}
}
test_op_transform!(proptest_op_fixed, Fixed);
test_op_transform!(proptest_op_min, Min);
test_op_transform!(proptest_op_max, Max);
test_op_transform!(proptest_op_push, Push);
test_op_transform!(proptest_op_pull, Pull);
test_op_transform!(proptest_op_shrink, Shrink);
test_op_transform!(proptest_op_expand, Expand);
test_op_transform!(proptest_op_padding, Pad);
proptest! {
#[test] fn proptest_op_bsp (
d in prop_oneof![
Just(North), Just(South),
Just(East), Just(West),
Just(Above), Just(Below)
],
a in "\\PC*",
b in "\\PC*",
x in u16::MIN..u16::MAX,
y in u16::MIN..u16::MAX,
w in u16::MIN..u16::MAX,
h in u16::MIN..u16::MAX,
) {
let bsp = Bsp(d, a, b);
//assert_eq!(
//Content::layout(&bsp, [x, y, w, h]),
//Draw::layout(&bsp, [x, y, w, h]),
//);
}
}
//#[test] fn test_tui_engine () -> Usually<()> {
////use std::sync::{Arc, RwLock};
//struct TestComponent(String);
//impl Draw<TuiOut> for TestComponent {
//fn draw (&self, _to: &mut TuiOut) {}
//}
//impl Handle<TuiIn> for TestComponent {
//fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> { Ok(None) }
//}
//let engine = Tui::new(Box::<&mut Vec<u8>>::new(vec![0u8;0].as_mut()))?;
//let state = engine.run(false, &Arc::new(RwLock::new(TestComponent("hello world".into()))))?;
//state.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
//Ok(())
//}

View file

@ -1,341 +0,0 @@
use crate::*;
/// Output target.
///
/// ```
/// use tengri::*;
///
/// struct TestOut(impl TwoD<u16>);
///
/// impl tengri::Out for TestOut {
/// type Unit = u16;
/// fn area (&self) -> impl TwoD<u16> { self.0 }
/// fn area_mut (&mut self) -> &mut impl TwoD<u16> { &mut self.0 }
/// fn show <T: Draw<Self> + ?Sized> (&mut self, area: impl TwoD<u16>, _: &T) {
/// println!("show: {area:?}");
/// ()
/// }
/// }
///
/// impl tengri::Draw<TestOut> for String {
/// fn draw (&self, to: &mut TestOut) {
/// //to.area_mut().set_w(self.len() as u16);
/// }
/// }
/// ```
pub trait Screen: W<Self::Unit> + H<Self::Unit> + Send + Sync + Sized {
type Unit: Coord;
/// Render drawable in area specified by `area`
fn show <'t, T: Draw<Self> + ?Sized> (&mut self, area: TwoD<Self::Unit>, content: &'t T);
}
pub trait HasColor {
fn color (&self) -> ItemColor;
}
// Something that has a [Measure] of its rendered size.
pub trait Measured<O: Screen> {
fn measure (&self) -> &Measure<O>;
fn measure_width (&self) -> O::Unit { self.measure().w() }
fn measure_height (&self) -> O::Unit { self.measure().h() }
}
#[cfg(feature = "tui")] pub trait TuiOut: Screen {
fn tui_out (&mut self) -> &mut Buffer;
fn update (&mut self, area: impl TwoD<u16>, callback: &impl Fn(&mut Cell, u16, u16)) {
tui_update(self.buffer(), area, callback);
}
fn fill_char (&mut self, area: impl TwoD<u16>, c: char) {
self.update(area, &|cell,_,_|{cell.set_char(c);})
}
fn fill_bg (&mut self, area: impl TwoD<u16>, color: Color) {
self.update(area, &|cell,_,_|{cell.set_bg(color);})
}
fn fill_fg (&mut self, area: impl TwoD<u16>, color: Color) {
self.update(area, &|cell,_,_|{cell.set_fg(color);})
}
fn fill_mod (&mut self, area: impl TwoD<u16>, on: bool, modifier: Modifier) {
if on {
self.update(area, &|cell,_,_|cell.modifier.insert(modifier))
} else {
self.update(area, &|cell,_,_|cell.modifier.remove(modifier))
}
}
fn fill_bold (&mut self, area: impl TwoD<u16>, on: bool) {
self.fill_mod(area, on, Modifier::BOLD)
}
fn fill_reversed (&mut self, area: impl TwoD<u16>, on: bool) {
self.fill_mod(area, on, Modifier::REVERSED)
}
fn fill_crossed_out (&mut self, area: impl TwoD<u16>, on: bool) {
self.fill_mod(area, on, Modifier::CROSSED_OUT)
}
fn fill_ul (&mut self, area: impl TwoD<u16>, color: Option<Color>) {
if let Some(color) = color {
self.update(area, &|cell,_,_|{
cell.modifier.insert(ratatui::prelude::Modifier::UNDERLINED);
cell.underline_color = color;
})
} else {
self.update(area, &|cell,_,_|{
cell.modifier.remove(ratatui::prelude::Modifier::UNDERLINED);
})
}
}
fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
for cell in self.buffer().content.iter_mut() {
cell.fg = fg;
cell.bg = bg;
cell.modifier = modifier;
}
}
fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
let text = text.as_ref();
let style = style.unwrap_or(Style::default());
let buf = self.buffer();
if x < buf.area.width && y < buf.area.height {
buf.set_string(x, y, text, style);
}
}
/// Write a line of text
///
/// TODO: do a paragraph (handle newlines)
fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) {
let text = text.as_ref();
let buf = self.buffer();
let mut string_width: u16 = 0;
for character in text.chars() {
let x = x0 + string_width;
let character_width = character.width().unwrap_or(0) as u16;
string_width += character_width;
if string_width > max_width {
break
}
if let Some(cell) = buf.write().unwrap().cell_mut(ratatui::prelude::Position { x, y }) {
cell.set_char(character);
} else {
break
}
}
}
}
#[cfg(feature = "tui")] pub trait BorderStyle: Draw<Buffer> + Copy {
fn enabled (&self) -> bool;
fn border_n (&self) -> &str { Self::N }
fn border_s (&self) -> &str { Self::S }
fn border_e (&self) -> &str { Self::E }
fn border_w (&self) -> &str { Self::W }
fn border_nw (&self) -> &str { Self::NW }
fn border_ne (&self) -> &str { Self::NE }
fn border_sw (&self) -> &str { Self::SW }
fn border_se (&self) -> &str { Self::SE }
fn enclose (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
Bsp::b(Fill::XY(Border(self.enabled(), self)), w)
}
fn enclose2 (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
Bsp::b(Pad::XY(1, 1, Fill::XY(Border(self.enabled(), self))), w)
}
fn enclose_bg (self, w: impl Draw<Buffer>) -> impl Draw<Buffer> {
TuiOut::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
Bsp::b(Fill::XY(Border(self.enabled(), self)), w))
}
#[inline] fn draw <'a> (&self, to: &mut impl TuiOut) -> Usually<()> {
if self.enabled() {
self.draw_h(to, None)?;
self.draw_v(to, None)?;
self.draw_c(to, None)?;
}
Ok(())
}
#[inline] fn draw_h (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl TwoD<u16>> {
let area = to.area();
let style = style.or_else(||self.style_horizontal());
let [x, x2, y, y2] = area.lrtb();
for x in x..x2.saturating_sub(1) {
to.blit(&Self::N, x, y, style);
to.blit(&Self::S, x, y2.saturating_sub(1), style)
}
Ok(area)
}
#[inline] fn draw_v (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl TwoD<u16>> {
let area = to.area();
let style = style.or_else(||self.style_vertical());
let [x, x2, y, y2] = area.lrtb();
let h = y2 - y;
if h > 1 {
for y in y..y2.saturating_sub(1) {
to.blit(&Self::W, x, y, style);
to.blit(&Self::E, x2.saturating_sub(1), y, style);
}
} else if h > 0 {
to.blit(&Self::W0, x, y, style);
to.blit(&Self::E0, x2.saturating_sub(1), y, style);
}
Ok(area)
}
#[inline] fn draw_c (&self, to: &mut impl TuiOut, style: Option<Style>) -> Usually<impl TwoD<u16>> {
let area = to.area();
let style = style.or_else(||self.style_corners());
let XYWH(x, y, w, h) = area;
if w > 1 && h > 1 {
to.blit(&Self::NW, x, y, style);
to.blit(&Self::NE, x + w - 1, y, style);
to.blit(&Self::SW, x, y + h- 1, style);
to.blit(&Self::SE, x + w - 1, y + h - 1, style);
}
Ok(area)
}
#[inline] fn style (&self) -> Option<Style> { None }
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
#[inline] fn style_corners (&self) -> Option<Style> { self.style() }
const NW: &'static str = "";
const N: &'static str = "";
const NE: &'static str = "";
const E: &'static str = "";
const SE: &'static str = "";
const S: &'static str = "";
const SW: &'static str = "";
const W: &'static str = "";
const N0: &'static str = "";
const S0: &'static str = "";
const W0: &'static str = "";
const E0: &'static str = "";
}
/// Things that can provide a [jack::Client] reference.
///
/// ```
/// use tengri::{Jack, HasJack};
///
/// let jack: &Jack = Jacked::default().jack();
///
/// #[derive(Default)] struct Jacked<'j>(Jack<'j>);
///
/// impl<'j> HasJack<'j> for Jacked<'j> {
/// fn jack (&self) -> &Jack<'j> { &self.0 }
/// }
/// ```
#[cfg(feature = "jack")] pub trait HasJack<'j>: Send + Sync {
/// Return the internal [jack::Client] handle
/// that lets you call the JACK API.
fn jack (&self) -> &Jack<'j>;
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
self.jack().with_client(op)
}
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
self.with_client(|client|client.port_by_name(name))
}
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
self.with_client(|c|c.port_by_id(id))
}
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
}
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> {
if enable {
self.with_client(|client|match client.register_timebase_callback(false, callback) {
Ok(_) => Ok(()),
Err(e) => Err(e)
})?
}
Ok(())
}
fn sync_follow (&self, _enable: bool) -> Usually<()> {
// TODO: sync follow
Ok(())
}
}
/// Trait for thing that has a JACK process callback.
#[cfg(feature = "jack")] pub trait Audio {
/// Handle a JACK event.
fn handle (&mut self, _event: JackEvent) {}
/// Projecss a JACK chunk.
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
/// The JACK process callback function passed to the server.
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}
/// Implement [Audio]: provide JACK callbacks.
#[cfg(feature = "jack")] #[macro_export] macro_rules! impl_audio {
(|
$self1:ident:
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
}
};
($Struct:ident: $process:ident, $handle:ident) => {
impl Audio for $Struct {
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
$process(self, c, s)
}
#[inline] fn handle (&mut self, e: JackEvent) {
$handle(self, e)
}
}
};
($Struct:ident: $process:ident) => {
impl Audio for $Struct {
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
$process(self, c, s)
}
}
};
}
#[cfg(feature = "jack")] pub trait JackPerfModel {
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
}
/// Define a trait an implement it for various mutation-enabled wrapper types. */
#[macro_export] macro_rules! flex_trait_mut (
($Trait:ident $(<$($A:ident:$T:ident),+>)? {
$(fn $fn:ident (&mut $self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)*
})=>{
pub trait $Trait $(<$($A: $T),+>)? {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret $body)*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret {
if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) }
})*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.get_mut().unwrap().$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::Mutex<_T_>> {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.lock().unwrap().$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::RwLock<_T_>> {
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })*
}
};
);

43
src/text.rs Normal file
View file

@ -0,0 +1,43 @@
pub(crate) use ::unicode_width::*;
/// Displays an owned [str]-like with fixed maximum width.
///
/// Width is computed using [unicode_width].
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
/// Displays a borrowed [str]-like with fixed maximum width
///
/// Width is computed using [unicode_width].
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
impl<'a, T: AsRef<str>> TrimString<T> {
fn to_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
}
/// Trim string with [unicode_width].
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
let input = input.as_ref();
let mut output = Vec::with_capacity(input.len());
let mut width: usize = 1;
let mut chars = input.chars();
while let Some(c) = chars.next() {
if width > max_width {
break
}
output.push(c);
width += c.width().unwrap_or(0);
}
return output.into_iter().collect()
}
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
let mut width: u16 = 0;
let mut chars = text.as_ref().chars();
while let Some(c) = chars.next() {
width += c.width().unwrap_or(0) as u16;
if width > max {
break
}
}
return width
}

63
src/time.rs Normal file
View file

@ -0,0 +1,63 @@
use crate::*;
/// Performance counter
#[derive(Debug)]
pub struct PerfModel {
pub clock: quanta::Clock,
/// Measurement has a small cost. Disable it here.
pub enabled: bool,
// In nanoseconds. Time used by last iteration.
pub used: AtomicF64,
// In microseconds. Max prescribed time for iteration (frame, chunk...).
pub window: AtomicF64,
}
impl_default!(PerfModel: Self {
enabled: true,
clock: quanta::Clock::new(),
used: Default::default(),
window: Default::default(),
});
impl PerfModel {
pub fn get_t0 (&self) -> Option<u64> {
if self.enabled {
Some(self.clock.raw())
} else {
None
}
}
pub fn get_t1 (&self, t0: Option<u64>) -> Option<std::time::Duration> {
if let Some(t0) = t0 {
if self.enabled {
Some(self.clock.delta(t0, self.clock.raw()))
} else {
None
}
} else {
None
}
}
pub fn update (&self, t0: Option<u64>, microseconds: f64) {
if let Some(t0) = t0 {
let t1 = self.clock.raw();
self.used.store(self.clock.delta_as_nanos(t0, t1) as f64, Relaxed);
self.window.store(microseconds, Relaxed,);
}
}
pub fn percentage (&self) -> Option<f64> {
let window = self.window.load(Relaxed) * 1000.0;
if window > 0.0 {
let used = self.used.load(Relaxed);
Some(100.0 * used / window)
} else {
None
}
}
pub fn cycle <F: Fn(&Self)->T, T> (&self, call: &F) -> T {
let t0 = self.get_t0();
let result = call(self);
let _t1 = self.get_t1(t0).unwrap();
result
}
}

1206
src/tui.rs

File diff suppressed because it is too large Load diff