dsl, output, tui: add tests, examples, root dispatchers

This commit is contained in:
🪞👃🪞 2025-09-08 18:44:42 +03:00
parent 8dfe20a58c
commit ca862b9802
16 changed files with 637 additions and 377 deletions

View file

@ -1,15 +1,15 @@
use crate::*;
/// Show an item only when a condition is true.
pub struct When<A>(pub bool, pub A);
impl<A> When<A> {
pub struct When<O, T>(bool, T, PhantomData<O>);
impl<O, T> When<O, T> {
/// Create a binary condition.
pub const fn new (c: bool, a: A) -> Self { Self(c, a) }
pub const fn new (c: bool, a: T) -> Self { Self(c, a, PhantomData) }
}
impl<E: Out, A: Layout<E>> Layout<E> for When<A> {
fn layout (&self, to: E::Area) -> E::Area {
let Self(cond, item) = self;
let mut area = E::Area::zero();
impl<O: Out, T: Layout<O>> Layout<O> for When<O, T> {
fn layout (&self, to: O::Area) -> O::Area {
let Self(cond, item, ..) = self;
let mut area = O::Area::zero();
if *cond {
let item_area = item.layout(to);
area[0] = item_area.x();
@ -20,9 +20,9 @@ impl<E: Out, A: Layout<E>> Layout<E> for When<A> {
area.into()
}
}
impl<E: Out, A: Draw<E>> Draw<E> for When<A> {
fn draw (&self, to: &mut E) {
let Self(cond, item) = self;
impl<O: Out, T: Draw<O>> Draw<O> for When<O, T> {
fn draw (&self, to: &mut O) {
let Self(cond, item, ..) = self;
if *cond { item.draw(to) }
}
}

View file

@ -80,16 +80,16 @@ impl<'a, O, A, B, I, F, G> Layout<O> for Map<O, A, B, I, F, G> where
let [mut max_x, mut max_y] = area.center();
for item in get_iter() {
let [x,y,w,h] = get_item(item, index).layout(area).xywh();
min_x = min_x.min(x.into());
min_y = min_y.min(y.into());
max_x = max_x.max((x + w).into());
max_y = max_y.max((y + h).into());
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.center_xy([w.into(), h.into()].into()).into()
area.center_xy([w.into(), h.into()]).into()
}
}
impl<'a, O, A, B, I, F, G> Draw<O> for Map<O, A, B, I, F, G> where

View file

@ -3,15 +3,9 @@
#![feature(impl_trait_in_assoc_type)]
#![feature(const_precise_live_drops)]
#![feature(type_changing_struct_update)]
#![feature(anonymous_lifetime_in_impl_trait)]
//#![feature(non_lifetime_binders)]
mod content; pub use self::content::*;
mod draw; pub use self::draw::*;
mod group; pub use self::group::*;
mod layout; pub use self::layout::*;
mod space; pub use self::space::*;
mod thunk; pub use self::thunk::*;
mod widget; pub use self::widget::*;
pub(crate) use self::Direction::*;
pub(crate) use std::fmt::{Debug, Display};
pub(crate) use std::marker::PhantomData;
@ -19,38 +13,49 @@ pub(crate) use std::ops::{Add, Sub, Mul, Div};
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::Relaxed}};
pub(crate) use tengri_core::*;
/// Draw target.
/// Drawing target.
pub trait Out: Send + Sync + Sized {
/// Unit of length
type Unit: Coordinate;
/// Rectangle without offset
type Size: Size<Self::Unit>;
/// Rectangle with offset
type Area: Area<Self::Unit>;
/// Current output area
fn area (&self) -> Self::Area;
/// Mutable pointer to area
fn area_mut (&mut self) -> &mut Self::Area;
/// Draw widget in area
fn place_at <'t, T: Draw<Self> + ?Sized> (&mut self, area: Self::Area, content: &'t T);
fn place <'t, T: Draw<Self> + Layout<Self> + ?Sized> (&mut self, content: &'t T) {
/// Render drawable in area specified by `T::layout(self.area())`
#[inline] fn place <'t, T: Draw<Self> + Layout<Self> + ?Sized> (
&mut self, content: &'t T
) {
self.place_at(content.layout(self.area()), content)
}
/// Render drawable in area specified by `area`
fn place_at <'t, T: Draw<Self> + ?Sized> (&mut self, area: Self::Area, content: &'t T);
/// Current output area
fn area (&self) -> Self::Area;
#[inline] fn x (&self) -> Self::Unit { self.area().x() }
#[inline] fn y (&self) -> Self::Unit { self.area().y() }
#[inline] fn w (&self) -> Self::Unit { self.area().w() }
#[inline] fn h (&self) -> Self::Unit { self.area().h() }
#[inline] fn wh (&self) -> Self::Size { self.area().wh().into() }
/// Mutable pointer to area.
fn area_mut (&mut self) -> &mut Self::Area;
}
#[cfg(test)] mod test;
#[cfg(test)] mod output_test;
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
//impl<E: Out, C: Content<E> + Layout<E>> Draw<E> for C { // if only
//fn draw (&self, to: &mut E) {
//if let Some(content) = self.content() {
//to.place_at(self.layout(to.area()), &content);
//}
//}
//}
mod content; pub use self::content::*;
mod draw; pub use self::draw::*;
mod group; pub use self::group::*;
mod layout; pub use self::layout::*;
mod space; pub use self::space::*;
mod thunk; pub use self::thunk::*;
mod widget; pub use self::widget::*;
#[cfg(feature = "dsl")] mod view;
#[cfg(feature = "dsl")] pub use self::view::*;

View file

@ -89,8 +89,8 @@ macro_rules! test_op_transform {
(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]));
//assert_eq!(Content::layout(&op, [x, y, w, h]),
//Draw::layout(&op, [x, y, w, h]));
}
}
}
@ -122,10 +122,10 @@ proptest! {
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]),
);
//assert_eq!(
//Content::layout(&bsp, [x, y, w, h]),
//Draw::layout(&bsp, [x, y, w, h]),
//);
}
}
@ -142,7 +142,8 @@ proptest! {
fn area_mut (&mut self) -> &mut [u16;4] {
&mut self.0
}
fn place <T: Draw<Self> + ?Sized> (&mut self, _: [u16;4], _: &T) {
fn place_at <T: Draw<Self> + ?Sized> (&mut self, area: [u16;4], _: &T) {
println!("place_at: {area:?}");
()
}
}

137
output/src/view.rs Normal file
View file

@ -0,0 +1,137 @@
use crate::*;
use ::tengri_dsl::{Dsl, DslExpr, DslWord, DslNs};
pub trait View<O, U> {
fn view_expr <'a> (&'a self, output: &mut O, expr: &'a impl DslExpr) -> Usually<U> {
Err(format!("View::view_expr: no exprs defined: {expr:?}").into())
}
fn view_word <'a> (&'a self, output: &mut O, word: &'a impl DslWord) -> Usually<U> {
Err(format!("View::view_word: no words defined: {word:?}").into())
}
fn view <'a> (&'a self, output: &mut O, dsl: &'a impl Dsl) -> Usually<U> {
if let Ok(Some(expr)) = dsl.expr() {
self.view_expr(output, &expr)
} else if let Ok(Some(word)) = dsl.word() {
self.view_word(output, &word)
} else {
panic!("{dsl:?}: invalid")
}
}
}
pub fn evaluate_output_expression <'a, O: Out + 'a, S> (
state: &S, output: &mut O, expr: &'a impl DslExpr
) -> Usually<bool> where
S: View<O, ()>
+ for<'b>DslNs<'b, bool>
+ for<'b>DslNs<'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.from(arg0?)?.unwrap(),
Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap())
)),
Some("either") => output.place(&Either::new(
state.from(arg0?)?.unwrap(),
Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap()),
Thunk::new(move|output: &mut O|state.view(output, &arg2).unwrap())
)),
Some("bsp") => output.place(&{
let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap());
let b = Thunk::new(move|output: &mut O|state.view(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.view(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.view(output, &arg0).unwrap());
match frags.next() {
Some("x") => Fill::X(a),
Some("y") => Fill::Y(a),
Some("xy") => Fill::XY(a),
frag => unimplemented!("fill/{frag:?}")
}
}),
Some("fixed") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!() };
let cb = Thunk::new(move|output: &mut O|state.view(output, &arg).unwrap());
match axis {
Some("x") => Fixed::X(state.from(arg0?)?.unwrap(), cb),
Some("y") => Fixed::Y(state.from(arg0?)?.unwrap(), cb),
Some("xy") => Fixed::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})",
head.src()?.unwrap_or_default().split("/").next())
}
}),
Some("min") => output.place(&{
let c = match frags.next() {
Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!()
};
let cb = Thunk::new(move|output: &mut O|state.view(output, &c).unwrap());
match frags.next() {
Some("x") => Min::X(state.from(arg0?)?.unwrap(), cb),
Some("y") => Min::Y(state.from(arg0?)?.unwrap(), cb),
Some("xy") => Min::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
frag => unimplemented!("min/{frag:?}")
}
}),
Some("max") => output.place(&{
let c = match frags.next() {
Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!()
};
let cb = Thunk::new(move|output: &mut O|state.view(output, &c).unwrap());
match frags.next() {
Some("x") => Max::X(state.from(arg0?)?.unwrap(), cb),
Some("y") => Max::Y(state.from(arg0?)?.unwrap(), cb),
Some("xy") => Max::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
frag => unimplemented!("max/{frag:?}")
}
}),
_ => return Ok(false)
};
Ok(true)
}