Compare commits

...

3 commits

Author SHA1 Message Date
3ebdf9e71f uuugh
Some checks failed
/ build (push) Has been cancelled
2025-09-09 01:07:19 +03:00
ca862b9802 dsl, output, tui: add tests, examples, root dispatchers 2025-09-08 19:42:44 +03:00
8dfe20a58c tui: update example 2025-09-08 17:48:56 +03:00
64 changed files with 1185 additions and 1426 deletions

View file

@ -2,6 +2,9 @@ export LLVM_PROFILE_FILE := "cov/cargo-test-%p-%m.profraw"
grcov-binary := "--binary-path ./target/coverage/deps/"
grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'"
default:
just -l
bacon:
bacon -s
@ -25,5 +28,7 @@ doc:
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' RUSTDOCFLAGS='-Cinstrument-coverage' \
cargo doc
example-tui:
cargo run -p tengri_tui --example tui
example-tui-00:
cargo run -p tengri_tui --example tui_00
example-tui-01:
cargo run -p tengri_tui --example tui_01

44
bacon.toml Normal file
View file

@ -0,0 +1,44 @@
default_job = "check-all"
env.CARGO_TERM_COLOR = "always"
[keybindings]
c = "job:check"
d = "job:doc-open"
t = "job:test"
n = "job:nextest"
l = "job:clippy"
[jobs]
[jobs.check]
command = ["cargo", "check"]
need_stdout = false
watch = ["core","dsl","editor","input","output","proc","tengri","tui"]
[jobs.clippy-all]
command = ["cargo", "clippy"]
need_stdout = false
watch = ["tek", "deps"]
[jobs.test]
command = ["cargo", "test"]
need_stdout = true
watch = ["tek", "deps"]
[jobs.doc]
command = ["cargo", "doc", "--no-deps"]
need_stdout = false
[jobs.doc-open]
command = ["cargo", "doc", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change
[skin]
status_fg = 251
status_bg = 200
key_fg = 11
status_key_fg = 11
project_name_badge_fg = 11
project_name_badge_bg = 69

View file

@ -10,22 +10,25 @@
)* };
);
/// Define a trait an implement it for read-only wrapper types. */
/// Define a trait and implement it for read-only wrapper types.
#[macro_export] macro_rules! flex_trait (
($Trait:ident $(<$($A:ident:$T:ident),+>)? $(:$dep:ident $(<$dtt:tt>)? $(+$dep2:ident $(<$dtt2:tt>)?)*)? {
$(fn $fn:ident (&$self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)*
$(fn $fn:ident $(<$($fl:lifetime),*>)? (& $($fl2:lifetime)* $self:ident $(, $arg:ident:$ty:ty)*) $(-> $ret:ty)? $body:block)*
}) => {
pub trait $Trait $(<$($A: $T),+>)? $(:$dep $(<$dtt>)? $(+$dep2 $(<$dtt2>)?)*)? {
$(fn $fn (&$self $(,$arg:$ty)*) -> $ret $body)*
$(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? $body)*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &_T_ {
$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })*
$(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (*$self).$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ {
$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (**$self).$fn($($arg),*) })*
$(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (**$self).$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<_T_> {
$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })*
$(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (*$self).$fn($($arg),*) })*
}
impl<$($($A: $T,)+)?> $Trait $(<$($A),+>)? for Box<dyn $Trait $(<$($A),+>)?> {
$(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (**$self).$fn($($arg),*) })*
}
//impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> {
//$(fn $fn (&$self $(,$arg:$ty)*) -> $ret {
@ -40,21 +43,24 @@
//}
});
/// Define a trait an implement it for read-only wrapper types. */
#[macro_export] macro_rules! flex_trait_unsized (
#[macro_export] macro_rules! flex_trait_sized (
($Trait:ident $(<$($A:ident:$T:ident),+>)? $(:$dep:ident $(<$dtt:tt>)? $(+$dep2:ident $(<$dtt2:tt>)?)*)? {
$(fn $fn:ident (&$self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)*
$(fn $fn:ident (&$self:ident $(, $arg:ident:$ty:ty)*) $(-> $ret:ty)? $body:block)*
}) => {
pub trait $Trait $(<$($A: $T),+>)? : $($dep $(<$dtt>+)? $($dep2 $(<$dtt2>)?)*+)? ?Sized {
$(fn $fn (&$self $(,$arg:$ty)*) -> $ret $body)*
pub trait $Trait $(<$($A: $T),+>)? : $($dep $(<$dtt>+)? $($dep2 $(<$dtt2>)?)*+)? Sized {
$(fn $fn (&$self $(,$arg:$ty)*) $(-> $ret)? $body)*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &_T_ {
$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })*
$(fn $fn (&$self $(,$arg:$ty)*) $(-> $ret)? { (*$self).$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ {
$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (**$self).$fn($($arg),*) })*
$(fn $fn (&$self $(,$arg:$ty)*) $(-> $ret)? { (**$self).$fn($($arg),*) })*
}
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<_T_> {
$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })*
$(fn $fn (&$self $(,$arg:$ty)*) $(-> $ret)? { (*$self).$fn($($arg),*) })*
}
impl<$($($A: $T,)+)?> $Trait $(<$($A),+>)? for Box<dyn $Trait $(<$($A),+>)?> {
$(fn $fn (&$self $(,$arg:$ty)*) $(-> $ret)? { (**$self).$fn($($arg),*) })*
}
//impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> {
//$(fn $fn (&$self $(,$arg:$ty)*) -> $ret {

View file

@ -1,16 +1,16 @@
use crate::*;
macro_rules!is_some(($exp:expr, $val:expr)=>{assert_eq!($exp, Ok(Some($val)))};);
macro_rules!is_none(($exp:expr)=>{assert_eq!($exp, Ok(None))};);
macro_rules!is_err(($exp:expr)=>{assert!($exp.is_err())};
($exp:expr, $err:expr)=>{assert_eq!($exp, Err($err))};);
#[test] fn test_exp () -> Result<(), DslError> {
let e0 = DslError::Unexpected('a');
let e1 = DslError::Unexpected('(');
let e2 = DslError::Unexpected('b');
let e3 = DslError::Unexpected('d');
let check = |src: &str, key, exp, head, tail|{
assert_eq!(src.key(), key, "{src}");
assert_eq!(src.exp(), exp, "{src}");
macro_rules!is_some(($expr:expr, $val:expr)=>{assert_eq!($expr, Ok(Some($val)))};);
macro_rules!is_none(($expr:expr)=>{assert_eq!($expr, Ok(None))};);
macro_rules!is_err(($expr:expr)=>{assert!($expr.is_err())};
($expr:expr, $err:expr)=>{assert_eq!($expr, Err($err))};);
#[test] fn test_expr () -> Result<(), DslError> {
let e0 = DslError::Unexpected('a', None, None);
let e1 = DslError::Unexpected('(', None, None);
let e2 = DslError::Unexpected('b', None, None);
let e3 = DslError::Unexpected('d', None, None);
let check = |src: &str, word, expr, head, tail|{
assert_eq!(src.word(), word, "{src}");
assert_eq!(src.expr(), expr, "{src}");
assert_eq!(src.head(), head, "{src}");
assert_eq!(src.tail(), tail, "{src}");
};
@ -21,43 +21,43 @@ macro_rules!is_err(($exp:expr)=>{assert!($exp.is_err())};
check("(a b c) d e f", Err(e1), Err(e3), Ok(Some("(a b c)")), Ok(Some("d e f")));
check("a (b c d) e f", Err(e1), Err(e0), Ok(Some("a")), Ok(Some("(b c d) e f")));
is_some!(sym_peek("\n :view/transport"), ":view/transport");
is_some!(word_peek("\n :view/transport"), ":view/transport");
assert!(is_whitespace(' '));
assert!(!is_key_start(' '));
assert!(is_key_start('f'));
assert!(is_space(' '));
assert!(!is_word_char(' '));
assert!(is_word_char('f'));
is_some!(key_seek_start("foo"), 0);
is_some!(key_seek_start("foo "), 0);
is_some!(key_seek_start(" foo "), 1);
is_some!(key_seek_length(&" foo "[1..]), 3);
is_some!(key_seek("foo"), (0, 3));
is_some!(key_peek("foo"), "foo");
is_some!(key_seek("foo "), (0, 3));
is_some!(key_peek("foo "), "foo");
is_some!(key_seek(" foo "), (1, 3));
is_some!(key_peek(" foo "), "foo");
is_some!(word_seek_start("foo"), 0);
is_some!(word_seek_start("foo "), 0);
is_some!(word_seek_start(" foo "), 1);
is_some!(word_seek_length(&" foo "[1..]), 3);
is_some!(word_seek("foo"), (0, 3));
is_some!(word_peek("foo"), "foo");
is_some!(word_seek("foo "), (0, 3));
is_some!(word_peek("foo "), "foo");
is_some!(word_seek(" foo "), (1, 3));
is_some!(word_peek(" foo "), "foo");
is_err!("(foo)".key());
is_err!("foo".exp());
is_err!("(foo)".word());
is_err!("foo".expr());
is_some!("(foo)".exp(), "foo");
is_some!("(foo)".expr(), "foo");
is_some!("(foo)".head(), "(foo)");
is_none!("(foo)".tail());
is_some!("(foo bar baz)".exp(), "foo bar baz");
is_some!("(foo bar baz)".expr(), "foo bar baz");
is_some!("(foo bar baz)".head(), "(foo bar baz)");
is_none!("(foo bar baz)".tail());
is_some!("(foo bar baz)".exp().head(), "foo");
is_some!("(foo bar baz)".exp().tail(), "bar baz");
is_some!("(foo bar baz)".exp().tail().head(), "bar");
is_some!("(foo bar baz)".exp().tail().tail(), "baz");
is_some!("(foo bar baz)".expr().head(), "foo");
is_some!("(foo bar baz)".expr().tail(), "bar baz");
is_some!("(foo bar baz)".expr().tail().head(), "bar");
is_some!("(foo bar baz)".expr().tail().tail(), "baz");
is_err!("foo".exp());
is_some!("foo".key(), "foo");
is_some!(" foo".key(), "foo");
is_some!(" foo ".key(), "foo");
is_err!("foo".expr());
is_some!("foo".word(), "foo");
is_some!(" foo".word(), "foo");
is_some!(" foo ".word(), "foo");
is_some!(" foo ".head(), "foo");
//assert_eq!(" foo ".head().head(), Ok(None));
@ -84,16 +84,16 @@ macro_rules!is_err(($exp:expr)=>{assert!($exp.is_err())};
is_some!(" (foo) (bar) ".tail(), " (bar) ");
is_some!(" (foo) (bar) ".tail().head(), "(bar)");
is_some!(" (foo) (bar) ".tail().head().head(), "(bar)");
is_some!(" (foo) (bar) ".tail().head().exp(), "bar");
is_some!(" (foo) (bar) ".tail().head().exp().head(), "bar");
is_some!(" (foo) (bar) ".tail().head().expr(), "bar");
is_some!(" (foo) (bar) ".tail().head().expr().head(), "bar");
is_some!(" (foo bar baz) ".head(), "(foo bar baz)");
is_some!(" (foo bar baz) ".head().head(), "(foo bar baz)");
is_some!(" (foo bar baz) ".exp(), "foo bar baz");
is_some!(" (foo bar baz) ".exp().head(), "foo");
is_some!(" (foo bar baz) ".exp().tail(), "bar baz");
is_some!(" (foo bar baz) ".exp().tail().head(), "bar");
is_some!(" (foo bar baz) ".exp().tail().tail(), "baz");
is_some!(" (foo bar baz) ".expr(), "foo bar baz");
is_some!(" (foo bar baz) ".expr().head(), "foo");
is_some!(" (foo bar baz) ".expr().tail(), "bar baz");
is_some!(" (foo bar baz) ".expr().tail().head(), "bar");
is_some!(" (foo bar baz) ".expr().tail().tail(), "baz");
is_none!(" (foo bar baz) ".tail());
Ok(())
}

View file

@ -1,40 +1,110 @@
use crate::*;
/// Composable renderable with static dispatch.
pub trait Content<E: Out>: Sized {
/// Return opaque [Draw]able.
fn content (&self) -> impl Draw<E> + Layout<E> + '_ { () }
}
/// The platonic ideal unit of [Content]:
/// total emptiness at dead center (e=1vg^sqrt(-1))
impl<E: Out> Content<E> for () {}
impl<E: Out, T: Draw<E> + Layout<E>> Content<E> for fn()->T {
fn content (&self) -> impl Draw<E> + Layout<E> + '_ {
self()
pub trait HasContent<O: Out> { fn content (&self) -> impl Content<O>; }
pub trait Content<O: Out>: Draw<O> + Layout<O> {}
impl<O: Out, T: Draw<O> + Layout<O>> Content<O> for T {}
impl<'a, O: Out> AsRef<dyn Draw<O> + 'a> for dyn Content<O> + 'a { fn as_ref (&self) -> &(dyn Draw<O> + 'a) { self } }
impl<'a, O: Out> AsRef<dyn Layout<O> + 'a> for dyn Content<O> + 'a { fn as_ref (&self) -> &(dyn Layout<O> + 'a) { self } }
/// Drawable with dynamic dispatch.
pub trait Draw<O: Out> { fn draw (&self, to: &mut O); }
impl<O: Out> Draw<O> for () { fn draw (&self, to: &mut O) {} }
impl<O: Out> Draw<O> for fn(&mut O) { fn draw (&self, to: &mut O) { (*self)(to) } }
impl<O: Out> Draw<O> for Box<dyn Draw<O>> { fn draw (&self, to: &mut O) { (**self).draw(to) } }
impl<O: Out, D: Draw<O>> Draw<O> for &D { fn draw (&self, to: &mut O) { (*self).draw(to) } }
impl<O: Out, D: Draw<O>> Draw<O> for &mut D { fn draw (&self, to: &mut O) { (**self).draw(to) } }
/// Drawable area of display.
pub trait Layout<O: Out> {
fn x (&self, to: O::Area) -> O::Unit { to.x() }
fn y (&self, to: O::Area) -> O::Unit { to.y() }
fn min_w (&self, to: O::Area) -> O::Unit { 0.into() }
fn max_w (&self, to: O::Area) -> O::Unit { to.w() }
fn w (&self, to: O::Area) -> O::Unit {
to.w().max(self.min_w(to)).min(self.max_w(to))
}
fn min_h (&self, to: O::Area) -> O::Unit { 0.into() }
fn max_h (&self, to: O::Area) -> O::Unit { to.h() }
fn h (&self, to: O::Area) -> O::Unit {
to.h().max(self.min_h(to)).min(self.max_h(to))
}
fn layout (&self, to: O::Area) -> O::Area {
[self.x(to), self.y(to), self.w(to), self.h(to)].into()
}
}
/// Implement composable content for a struct.
#[macro_export] macro_rules! content {
// Implement for all [Out]s.
(|$self:ident:$Struct:ty| $content:expr) => {
impl<E: Out> Content<E> for $Struct {
fn content (&$self) -> impl Draw<E> + Layout<E> + '_ { Some($content) }
}
};
// Implement for specific [Out].
($Out:ty:|
$self:ident:
$Struct:ident$(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?
|$content:expr) => {
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Out>
for $Struct $(<$($($L)? $($T)?),+>)? {
fn content (&$self) -> impl Draw<$Out> + Layout<$Out> + '_ { $content }
}
};
impl<O: Out> Layout<O> for () {
fn x (&self, a: O::Area) -> O::Unit { a.x() }
fn y (&self, a: O::Area) -> O::Unit { a.y() }
fn w (&self, _: O::Area) -> O::Unit { 0.into() }
fn min_w (&self, _: O::Area) -> O::Unit { 0.into() }
fn max_w (&self, _: O::Area) -> O::Unit { 0.into() }
fn h (&self, _: O::Area) -> O::Unit { 0.into() }
fn min_h (&self, _: O::Area) -> O::Unit { 0.into() }
fn max_h (&self, _: O::Area) -> O::Unit { 0.into() }
fn layout (&self, a: O::Area) -> O::Area { [a.x(), a.y(), 0.into(), 0.into()].into() }
}
impl<O: Out, L: Layout<O>> Layout<O> for &L {
fn x (&self, a: O::Area) -> O::Unit { (*self).x(a) }
fn y (&self, a: O::Area) -> O::Unit { (*self).y(a) }
fn w (&self, a: O::Area) -> O::Unit { (*self).w(a) }
fn min_w (&self, a: O::Area) -> O::Unit { (*self).min_w(a) }
fn max_w (&self, a: O::Area) -> O::Unit { (*self).max_w(a) }
fn h (&self, a: O::Area) -> O::Unit { (*self).h(a) }
fn min_h (&self, a: O::Area) -> O::Unit { (*self).min_h(a) }
fn max_h (&self, a: O::Area) -> O::Unit { (*self).max_h(a) }
fn layout (&self, a: O::Area) -> O::Area { (*self).layout(a) }
}
impl<O: Out, L: Layout<O>> Layout<O> for &mut L {
fn x (&self, a: O::Area) -> O::Unit { (**self).x(a) }
fn y (&self, a: O::Area) -> O::Unit { (**self).y(a) }
fn w (&self, a: O::Area) -> O::Unit { (**self).w(a) }
fn min_w (&self, a: O::Area) -> O::Unit { (**self).min_w(a) }
fn max_w (&self, a: O::Area) -> O::Unit { (**self).max_w(a) }
fn h (&self, a: O::Area) -> O::Unit { (**self).h(a) }
fn min_h (&self, a: O::Area) -> O::Unit { (**self).min_h(a) }
fn max_h (&self, a: O::Area) -> O::Unit { (**self).max_h(a) }
fn layout (&self, a: O::Area) -> O::Area { (**self).layout(a) }
}
impl<O: Out> Layout<O> for Box<dyn Layout<O>> {
fn x (&self, a: O::Area) -> O::Unit { (**self).x(a) }
fn y (&self, a: O::Area) -> O::Unit { (**self).y(a) }
fn w (&self, a: O::Area) -> O::Unit { (**self).w(a) }
fn min_w (&self, a: O::Area) -> O::Unit { (**self).min_w(a) }
fn max_w (&self, a: O::Area) -> O::Unit { (**self).max_w(a) }
fn h (&self, a: O::Area) -> O::Unit { (**self).h(a) }
fn min_h (&self, a: O::Area) -> O::Unit { (**self).min_h(a) }
fn max_h (&self, a: O::Area) -> O::Unit { (**self).max_h(a) }
fn layout (&self, a: O::Area) -> O::Area { (**self).layout(a) }
}
impl<O: Out, L: Layout<O>> Layout<O> for Option<L> {
fn x (&self, to: O::Area) -> O::Unit {
self.as_ref().map(|c|c.x(to)).unwrap_or(to.x())
}
fn y (&self, to: O::Area) -> O::Unit {
self.as_ref().map(|c|c.y(to)).unwrap_or(to.y())
}
fn min_w (&self, to: O::Area) -> O::Unit {
self.as_ref().map(|c|c.min_w(to)).unwrap_or(0.into())
}
fn max_w (&self, to: O::Area) -> O::Unit {
self.as_ref().map(|c|c.max_w(to)).unwrap_or(0.into())
}
fn w (&self, to: O::Area) -> O::Unit {
self.as_ref().map(|c|c.w(to)).unwrap_or(0.into())
}
fn min_h (&self, to: O::Area) -> O::Unit {
self.as_ref().map(|c|c.min_h(to)).unwrap_or(0.into())
}
fn max_h (&self, to: O::Area) -> O::Unit {
self.as_ref().map(|c|c.max_h(to)).unwrap_or(0.into())
}
fn h (&self, to: O::Area) -> O::Unit {
self.as_ref().map(|c|c.h(to)).unwrap_or(0.into())
}
fn layout (&self, to: O::Area) -> O::Area {
self.as_ref().map(|c|c.layout([self.x(to), self.y(to), self.w(to), self.h(to)].into()))
.unwrap_or([to.x(), to.y(), 0.into(), 0.into()].into())
}
}

View file

@ -1,94 +1,27 @@
use crate::*;
/// Drawable with dynamic dispatch.
pub trait Draw<O: Out> {
/// Write data to display.
fn draw (&self, to: &mut O);
pub struct Bound<O: Out, D>(pub O::Area, pub D);
impl<O: Out, D: Content<O>> HasContent<O> for Bound<O, D> {
fn content (&self) -> &impl Content<O> { &self.1 }
}
impl<O: Out> Draw<O> for fn(&mut O) {
impl<O: Out, T> Layout<O> for Bound<O, T> {
fn x (&self, _: O::Area) -> O::Unit { self.0.x() }
fn y (&self, _: O::Area) -> O::Unit { self.0.y() }
fn w (&self, _: O::Area) -> O::Unit { self.0.w() }
fn min_w (&self, _: O::Area) -> O::Unit { self.0.w() }
fn max_w (&self, _: O::Area) -> O::Unit { self.0.w() }
fn h (&self, _: O::Area) -> O::Unit { self.0.h() }
fn min_h (&self, _: O::Area) -> O::Unit { self.0.w() }
fn max_h (&self, _: O::Area) -> O::Unit { self.0.w() }
}
impl<O: Out, T: Draw<O>> Draw<O> for Bound<O, T> {
fn draw (&self, to: &mut O) {
self(to)
}
}
impl<O: Out> Draw<O> for () { fn draw (&self, _: &mut O) {} }
impl<O: Out, T: Draw<O>> Draw<O> for &T {
fn draw (&self, to: &mut O) { (*self).draw(to) }
}
impl<'x, O: Out> Draw<O> for &(dyn Draw<O> + 'x) {
fn draw (&self, to: &mut O) {
(*self).draw(to)
}
}
impl<'x, O: Out> Draw<O> for &mut (dyn Draw<O> + 'x) {
fn draw (&self, to: &mut O) {
(**self).draw(to)
}
}
impl<O: Out, T: Draw<O>> Draw<O> for RwLock<T> {
fn draw (&self, to: &mut O) { self.read().unwrap().draw(to) }
}
impl<O: Out, T: Draw<O>> Draw<O> for [T] {
fn draw (&self, to: &mut O) {
for draw in self.iter() {
draw.draw(to)
}
}
}
//impl<O: Out, T: Draw<O>> Draw<O> for &mut T {
//fn draw (&self, to: &mut O) {
//(**self).draw(to)
//}
//}
//impl<O: Out, T: Iterator<Item = U>, U: Draw<O>> Draw<O> for &mut T {
//fn draw (&self, to: &mut O) {
//for draw in *self {
//draw.draw(to)
//}
//}
//}
/// Implement custom drawing for a struct.
#[macro_export] macro_rules! draw {
// Implement for all [Out] backends.
(|$self:ident:$Struct:ident $(<
$($L:lifetime),* $($T:ident $(:$Trait:path)?),*
>)?, $to:ident | $draw:expr) => {
impl <$($($L),*)? O: Out, $($($T$(:$Trait)?),*)?> Draw<O>
for $Struct $(<$($L),* $($T),*>)? {
fn draw (&$self, $to: &mut O) { $draw }
}
};
// Implement for a specific [Out] backend.
($O:ty:|
$self:ident:
$Struct:ident $(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?, $to:ident
|$draw:expr) => {
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Draw<$O>
for $Struct $(<$($($L)? $($T)?),+>)? {
fn draw (&$self, $to: &mut $O) { $draw }
}
};
}
draw!(|self: Arc<T: Draw<O>>, to|(**self).draw(to));
draw!(|self: Box<T: Draw<O>>, to|(**self).draw(to));
//draw!(|self: Option<T: Draw<O>>, to|if let Some(draw) = self { draw.draw(to) });
impl<O: Out, T: Draw<O>> Draw<O> for Option<T> {
fn draw (&self, to: &mut O) {
if let Some(draw) = self {
draw.draw(to)
}
let area = to.area();
*to.area_mut() = self.0;
self.1.draw(to);
*to.area_mut() = area;
}
}

View file

@ -1,98 +1,10 @@
use crate::*;
pub trait Layout<O: Out> {
fn x (&self, area: O::Area) -> O::Unit {
area.x()
}
fn y (&self, area: O::Area) -> O::Unit {
area.y()
}
fn min_w (&self, _area: O::Area) -> O::Unit {
0.into()
}
fn max_w (&self, area: O::Area) -> O::Unit {
area.w()
}
fn min_h (&self, _area: O::Area) -> O::Unit {
0.into()
}
fn max_h (&self, area: O::Area) -> O::Unit {
area.h()
}
fn layout (&self, area: O::Area) -> O::Area {
O::Area::from([
self.x(area),
self.y(area),
area.w().max(self.min_w(area)).min(self.max_w(area)),
area.h().max(self.min_h(area)).min(self.max_h(area)),
])
}
}
#[macro_export] macro_rules! layout {
// Implement for all [Out] backends.
(|$self:ident:$Struct:ident $(<
$($L:lifetime),* $($T:ident $(:$Trait:path)?),*
>)?, $to:ident|$($method:ident = |$area:ident|$body:expr;)*) => {
impl <$($($L),*)? O: Out, $($($T$(:$Trait)?),*)?> Layout<O>
for $Struct $(<$($L),* $($T),*>)? {
$(fn $method (&$self, $area: O::Area) -> O::Area {
$body
})*
}
};
// Implement for a specific [Out] backend.
($O:ty:|
$self:ident:
$Struct:ident $(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?,
$to:ident
|$($method:ident = |$area:ident|$body:expr;)*) => {
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Layout<$O>
for $Struct $(<$($($L)? $($T)?),+>)? {
$(fn $method (&$self, $area: <$O as Out>::Area) -> <$O as Out>::Area {
$body
})*
}
};
}
impl<O: Out> Layout<O> for () {}
impl<O: Out, L: Layout<O>> Layout<O> for &L { /*FIXME*/ }
impl<O: Out, L: Layout<O>> Layout<O> for RwLock<L> { /*FIXME*/ }
impl<O: Out, L: Layout<O>> Layout<O> for Option<L> { /*FIXME*/ }
//impl<O: Out> Layout<O> for fn(&mut O) {}
impl<O: Out, L: Layout<O>> Layout<O> for Arc<L> {
fn layout (&self, to: O::Area) -> O::Area {
(**self).layout(to)
}
}
impl<'x, O: Out> Layout<O> for &(dyn Draw<O> + 'x) {
fn layout (&self, to: O::Area) -> O::Area {
Fill::xy(self).layout(to)
}
}
mod layout_align; pub use self::layout_align::*;
mod layout_bsp; pub use self::layout_bsp::*;
mod layout_cond; pub use self::layout_cond::*;
mod layout_expand; pub use self::layout_expand::*;
mod layout_fill; pub use self::layout_fill::*;
mod layout_fixed; pub use self::layout_fixed::*;
mod layout_map; pub use self::layout_map::*;
mod layout_margin; pub use self::layout_margin::*;
mod layout_max; pub use self::layout_max::*;
mod layout_min; pub use self::layout_min::*;
mod layout_padding; pub use self::layout_padding::*;
mod layout_pull; pub use self::layout_pull::*;
mod layout_push; pub use self::layout_push::*;
mod layout_shrink; pub use self::layout_shrink::*;
mod layout_stack; //pub use self::layout_stack::*;
mod layout_align; pub use self::layout_align::*;
mod layout_bsp; pub use self::layout_bsp::*;
mod layout_cond; pub use self::layout_cond::*;
mod layout_map; pub use self::layout_map::*;
mod layout_pad; pub use self::layout_pad::*;
mod layout_move; pub use self::layout_move::*;
mod layout_size; pub use self::layout_size::*;
mod layout_stack; //pub use self::layout_stack::*;

View file

@ -6,7 +6,7 @@
//! assert_eq!(Draw::layout(item, area), expected);
//! };
//!
//! let four = ||Fixed::xy(4, 4, "");
//! 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]);
@ -16,7 +16,7 @@
//! 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, "");
//! 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]);
@ -27,13 +27,12 @@
//! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
//! ```
use crate::*;
use Alignment::*;
/// 9th of area to place.
#[derive(Debug, Copy, Clone, Default)]
pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W }
pub struct Align<T>(Alignment, T);
impl<T> Align<T> {
#[inline] pub const fn c (a: T) -> Self { Self(Alignment::Center, a) }
#[inline] pub const fn x (a: T) -> Self { Self(Alignment::X, a) }
@ -47,39 +46,24 @@ impl<T> Align<T> {
#[inline] pub const fn ne (a: T) -> Self { Self(Alignment::NE, a) }
#[inline] pub const fn se (a: T) -> Self { Self(Alignment::SE, a) }
}
impl<E: Out, T: Layout<E>> Layout<E> for Align<T> {
fn layout (&self, on: E::Area) -> E::Area {
self.0.align(on, &self.1)
}
}
impl<E: Out, T: Draw<E> + Layout<E>> Draw<E> for Align<T> {
fn draw (&self, to: &mut E) {
to.place_at(self.layout(to.area()), &self.1)
}
}
impl Alignment {
fn align <E: Out> (&self, on: E::Area, content: &impl Layout<E>) -> E::Area {
use Alignment::*;
let it = content.layout(on).xywh();
let cx = on.x()+(on.w().minus(it.w())/2.into());
let cy = on.y()+(on.h().minus(it.h())/2.into());
let fx = (on.x()+on.w()).minus(it.w());
let fy = (on.y()+on.h()).minus(it.h());
let [x, y] = match self {
Center => [cx, cy],
X => [cx, it.y()],
Y => [it.x(), cy],
NW => [on.x(), on.y()],
N => [cx, on.y()],
NE => [fx, on.y()],
W => [on.x(), cy],
E => [fx, cy],
SW => [on.x(), fy],
S => [cx, fy],
SE => [fx, fy],
};
[x, y, it.w(), it.h()].into()
impl<O: Out, T: Content<O>> Draw<O> for Align<T> {
fn draw (&self, to: &mut O) { Bound(self.layout(to.area()), &self.1).draw(to) }
}
impl<O: Out, T: Layout<O>> Layout<O> for Align<T> {
fn x (&self, to: O::Area) -> O::Unit {
match self.0 {
NW | W | SW => to.x(),
N | Center | S => to.x().plus(to.w() / 2.into()).minus(self.1.w(to) / 2.into()),
NE | E | SE => to.x().plus(to.w()).minus(self.1.w(to)),
_ => todo!(),
}
}
fn y (&self, to: O::Area) -> O::Unit {
match self.0 {
NW | N | NE => to.y(),
W | Center | E => to.y().plus(to.h() / 2.into()).minus(self.1.h(to) / 2.into()),
SW | S | SE => to.y().plus(to.h()).minus(self.1.h(to)),
_ => todo!(),
}
}
}

View file

@ -1,7 +1,6 @@
use crate::*;
use Direction::*;
/// A split or layer.
/// A binary split or layer.
pub struct Bsp<Head, Tail>(
pub(crate) Direction,
/// First element.
@ -19,31 +18,79 @@ impl<Head, Tail> Bsp<Head, Tail> {
#[inline] pub const fn b (a: Head, b: Tail) -> Self { Self(Below, a, b) }
}
impl<
O: Out,
Head: Draw<O> + Layout<O>,
Tail: Draw<O> + Layout<O>
> Draw<O> for Bsp<Head, Tail> {
impl<O: Out, Head: Content<O>, Tail: Content<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);
if self.0 == Below {
to.place_at(a, &self.1);
to.place_at(b, &self.2);
} else {
to.place_at(b, &self.2);
to.place_at(a, &self.1);
match self.0 {
South => {
panic!("{}", self.1.h(to.area()));
let area_1 = self.1.layout(to.area());
let area_2 = self.2.layout([
to.area().x(),
to.area().y().plus(area_1.h()),
to.area().w(),
to.area().h().minus(area_1.h())
].into());
panic!("{area_1:?} {area_2:?}");
to.place_at(area_1, &self.1);
to.place_at(area_2, &self.2);
},
_ => todo!("{:?}", self.0)
}
//let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2);
//panic!("{a:?} {b:?}");
//if self.0 == Below {
//to.place_at(a, &self.1);
//to.place_at(b, &self.2);
//} else {
//to.place_at(b, &self.2);
//to.place_at(a, &self.1);
//}
}
}
impl<O: Out, Head: Layout<O>, Tail: Layout<O>> Layout<O> for Bsp<Head, Tail> {
fn w (&self, area: O::Area) -> O::Unit {
match self.0 {
North | South | Above | Below => self.1.w(area).max(self.2.w(area)),
East | West => self.1.min_w(area).plus(self.2.w(area)),
}
}
fn min_w (&self, area: O::Area) -> O::Unit {
match self.0 {
North | South | Above | Below => self.1.min_w(area).max(self.2.min_w(area)),
East | West => self.1.min_w(area).plus(self.2.min_w(area)),
}
}
fn max_w (&self, area: O::Area) -> O::Unit {
match self.0 {
North | South | Above | Below => self.1.max_w(area).max(self.2.max_w(area)),
East | West => self.1.max_w(area).plus(self.2.max_w(area)),
}
}
fn h (&self, area: O::Area) -> O::Unit {
match self.0 {
East | West | Above | Below => self.1.h(area).max(self.2.h(area)),
North | South => self.1.h(area).plus(self.2.h(area)),
}
}
fn min_h (&self, area: O::Area) -> O::Unit {
match self.0 {
East | West | Above | Below => self.1.min_h(area).max(self.2.min_h(area)),
North | South => self.1.min_h(area).plus(self.2.min_h(area)),
}
}
fn max_h (&self, area: O::Area) -> O::Unit {
match self.0 {
North | South | Above | Below => self.1.max_h(area).max(self.2.max_h(area)),
East | West => self.1.max_h(area).plus(self.2.max_h(area)),
}
}
fn layout (&self, area: O::Area) -> O::Area {
bsp_areas(area, self.0, &self.1, &self.2)[2]
}
}
fn bsp_areas <O: Out> (
area: O::Area, direction: Direction, a: &impl Layout<O>, b: &impl Layout<O>,
fn bsp_areas <O: Out, A: Layout<O>, B: Layout<O>> (
area: O::Area, direction: Direction, a: &A, b: &B,
) -> [O::Area;3] {
let [x, y, w, h] = area.xywh();
let [aw, ah] = a.layout(area).wh();

View file

@ -1,55 +1,46 @@
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: Out, T: Content<O>> 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();
if *cond {
let item_area = item.layout(to);
area[0] = item_area.x();
area[1] = item_area.y();
area[2] = item_area.w();
area[3] = item_area.h();
}
area.into()
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;
if *cond { item.layout(to) } else { O::Area::zero().into() }
}
}
impl<E: Out, A: Draw<E>> Draw<E> for When<A> {
fn draw (&self, to: &mut E) {
let Self(cond, item) = self;
if *cond { item.draw(to) }
impl<O: Out, T: Content<O>> Draw<O> for When<O, T> {
fn draw (&self, to: &mut O) {
let Self(cond, item, ..) = self;
if *cond { Bound(self.layout(to.area()), item).draw(to) }
}
}
/// Show one item if a condition is true and another if the condition is false
pub struct Either<E: Out, A: Draw<E> + Layout<E>, B: Draw<E> + Layout<E>>(pub bool, pub A, pub B, pub PhantomData<E>);
impl<E: Out, A: Draw<E> + Layout<E>, B: Draw<E> + Layout<E>> Either<E, A, B> {
pub struct Either<E: Out, A, B>(pub bool, pub A, pub B, pub PhantomData<E>);
impl<E: Out, A: Content<E>, B: Content<E>> Either<E, A, B> {
/// Create a ternary view condition.
pub const fn new (c: bool, a: A, b: B) -> Self {
Self(c, a, b, PhantomData)
}
}
impl<E: Out, A: Draw<E> + Layout<E>, B: Draw<E> + Layout<E>> Layout<E> for Either<E, A, B> {
impl<E: Out, A: Layout<E>, B: Layout<E>> Layout<E> for Either<E, A, B> {
fn layout (&self, to: E::Area) -> E::Area {
let Self(cond, a, b, ..) = self;
if *cond { a.layout(to) } else { b.layout(to) }
}
}
impl<E: Out, A: Draw<E> + Layout<E>, B: Draw<E> + Layout<E>> Draw<E> for Either<E, A, B> {
impl<E: Out, A: Content<E>, B: Content<E>> Draw<E> for Either<E, A, B> {
fn draw (&self, to: &mut E) {
let Self(cond, a, b, ..) = self;
if *cond { a.draw(to) } else { b.draw(to) }
let area = self.layout(to.area());
if *cond { Bound(area, a).draw(to) } else { Bound(area, b).draw(to) }
}
}
///////////////////////////////////////////////////////////////////////////////
@ -116,7 +107,7 @@ impl<E: Out, A: Draw<E> + Layout<E>, B: Draw<E> + Layout<E>> Draw<E> for Either<
//return Ok(Some(match words.next() {
//Some(Token{value: Key($x),..}) => Self::x(content),
//Some(Token{value: Key($y),..}) => Self::y(content),
//Some(Token{value: Key($xy),..}) => Self::xy(content),
//Some(Token{value: Key($xy),..}) => Self::XY(content),
//_ => unreachable!()
//}))
//} else {
@ -134,7 +125,7 @@ impl<E: Out, A: Draw<E> + Layout<E>, B: Draw<E> + Layout<E>> Draw<E> for Either<
//state.give_or_fail(words, ||"y: no unit")?,
//state.give_or_fail(words, ||"y: no content")?,
//),
//Some(Token { value: Key($x), .. }) => Self::xy(
//Some(Token { value: Key($x), .. }) => Self::XY(
//state.give_or_fail(words, ||"xy: no unit x")?,
//state.give_or_fail(words, ||"xy: no unit y")?,
//state.give_or_fail(words, ||"xy: no content")?

View file

@ -1,39 +0,0 @@
use crate::*;
pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Expand<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self { Self::X(x, item) }
#[inline] pub const fn y (y: U, item: A) -> Self { Self::Y(y, item) }
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) }
}
impl<O: Out, T: Draw<O> + Layout<O>> Content<O> for Expand<O::Unit, T> {
fn content (&self) -> impl Draw<O> + Layout<O> + '_ {
use Expand::*;
match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }
}
}
impl<O: Out, T: Draw<O> + Layout<O>> Draw<O> for Expand<O::Unit, T> {
fn draw (&self, to: &mut O) {
to.place_at(self.layout(to.area()), &self.content())
}
}
impl<U: Coordinate, T> Expand<U, T> {
#[inline] pub fn dx (&self) -> U {
use Expand::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use Expand::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
impl<O: Out, T: Layout<O>> Layout<O> for Expand<O::Unit, T> {
fn layout (&self, to: O::Area) -> O::Area {
[to.x(), to.y(), to.w().plus(self.dx()), to.h().plus(self.dy())].into()
}
}

View file

@ -1,55 +0,0 @@
use crate::*;
pub enum Fill<A> {
/// Use maximum width of area.
X(A),
/// Use maximum height of area.
Y(A),
/// Use maximum width and height of area.
XY(A)
}
impl<T> Fill<T> {
#[inline] pub const fn x (item: T) -> Self {
Self::X(item)
}
#[inline] pub const fn y (item: T) -> Self {
Self::Y(item)
}
#[inline] pub const fn xy (item: T) -> Self {
Self::XY(item)
}
#[inline] pub const fn has_x (&self) -> bool {
matches!(self, Self::X(_) | Self::XY(_))
}
#[inline] pub const fn has_y (&self) -> bool {
matches!(self, Self::Y(_) | Self::XY(_))
}
#[inline] pub const fn content (&self) -> &T {
use Fill::*;
match self { X(item) | Y(item) | XY(item) => item }
}
}
impl<O: Out, T: Draw<O>> Draw<O> for Fill<T> {
fn draw (&self, to: &mut O) {
to.place_at(self.layout(to.area()), &self.content())
}
}
impl<O: Out, T: Draw<O>> Layout<O> for Fill<T> {
fn min_w (&self, area: O::Area) -> O::Unit {
if self.has_x() {
area.w()
} else {
0.into()
}
}
fn min_h (&self, area: O::Area) -> O::Unit {
if self.has_y() {
area.h()
} else {
0.into()
}
}
}

View file

@ -1,47 +0,0 @@
use crate::*;
pub enum Fixed<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Fixed<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self {
Self::X(x, item)
}
#[inline] pub const fn y (y: U, item: A) -> Self {
Self::Y(y, item)
}
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self {
Self::XY(x, y, item)
}
#[inline] pub const fn content (&self) -> &A {
use Fixed::*;
match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }
}
}
impl<U: Coordinate, T> Fixed<U, T> {
#[inline] pub fn dx (&self) -> U {
use Fixed::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use Fixed::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
impl<O: Out, T: Draw<O>> Draw<O> for Fixed<O::Unit, T> {
fn draw (&self, to: &mut O) {
let area = Layout::<O>::layout(&self, to.area());
to.place_at(area, &self.content())
}
}
impl<O: Out, T> Layout<O> for Fixed<O::Unit, T> {
fn layout (&self, area: O::Area) -> O::Area {
[area.x(), area.y(), match self {
Fixed::X(w, _) | Fixed::XY(w, _, _) => *w, _ => area.w()
}, match self {
Fixed::Y(h, _) | Fixed::XY(_, h, _) => *h, _ => area.h()
}].into()
}
}

View file

@ -31,7 +31,7 @@ macro_rules! impl_map_direction (($name:ident, $axis:ident, $align:ident)=>{
O, A, Push<O::Unit, Align<Fixed<O::Unit, Fill<B>>>>, I, F, fn(A, usize)->B
> where
O: Out,
B: Draw<O> + Layout<O>,
B: Draw<O>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a
{
@ -61,14 +61,14 @@ macro_rules! impl_map_direction (($name:ident, $axis:ident, $align:ident)=>{
}
});
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_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: Out,
B: Draw<O> + Layout<O>,
B: Layout<O>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a,
G: Fn(A, usize)->B + Send + Sync
@ -80,21 +80,21 @@ 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
O: Out,
B: Draw<O> + Layout<O>,
B: Content<O>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a,
G: Fn(A, usize)->B + Send + Sync
@ -115,25 +115,25 @@ impl<'a, O, A, B, I, F, G> Draw<O> for Map<O, A, B, I, F, G> where
#[inline] pub fn map_south<O: Out>(
item_offset: O::Unit,
item_height: O::Unit,
item: impl Draw<O> + Layout<O>
) -> impl Draw<O> + Layout<O> {
Push::y(item_offset, Fixed::y(item_height, Fill::x(item)))
item: impl Content<O>
) -> impl Content<O> {
Push::Y(item_offset, Fixed::Y(item_height, Fill::X(item)))
}
#[inline] pub fn map_south_west<O: Out>(
item_offset: O::Unit,
item_height: O::Unit,
item: impl Draw<O> + Layout<O>
) -> impl Draw<O> + Layout<O> {
Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item))))
item: impl Content<O>
) -> impl Content<O> {
Push::Y(item_offset, Align::nw(Fixed::Y(item_height, Fill::X(item))))
}
#[inline] pub fn map_east<O: Out>(
item_offset: O::Unit,
item_width: O::Unit,
item: impl Draw<O> + Layout<O>
) -> impl Draw<O> + Layout<O> {
Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item))))
item: impl Content<O>
) -> impl Content<O> {
Push::X(item_offset, Align::w(Fixed::X(item_width, Fill::Y(item))))
}

View file

@ -1,50 +0,0 @@
use crate::*;
pub enum Margin<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Margin<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self {
Self::X(x, item)
}
#[inline] pub const fn y (y: U, item: A) -> Self {
Self::Y(y, item)
}
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self {
Self::XY(x, y, item)
}
#[inline] pub const fn content (&self) -> &A {
use Margin::*;
match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }
}
}
impl<E: Out, T: Draw<E> + Layout<E>> Draw<E> for Margin<E::Unit, T> {
fn draw (&self, to: &mut E) {
to.place_at(self.layout(to.area()), &self.content())
}
}
impl<U: Coordinate, T> Margin<U, T> {
#[inline] pub fn dx (&self) -> U {
use Margin::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use Margin::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
impl<E: Out, T: Layout<E>> Layout<E> for Margin<E::Unit, T> {
fn layout (&self, area: E::Area) -> E::Area {
let area = self.content().layout(area);
let dx = self.dx();
let dy = self.dy();
[
area.x().minus(dx),
area.y().minus(dy),
area.w().plus(dy.plus(dy)),
area.h().plus(dy.plus(dy)),
].into()
}
}

View file

@ -1,47 +0,0 @@
use crate::*;
pub enum Max<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Max<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self {
Self::X(x, item)
}
#[inline] pub const fn y (y: U, item: A) -> Self {
Self::Y(y, item)
}
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self {
Self::XY(x, y, item)
}
#[inline] pub const fn content (&self) -> &A {
use Max::*;
match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }
}
}
impl<E: Out, T: Draw<E> + Layout<E>> Draw<E> for Max<E::Unit, T> {
fn draw (&self, to: &mut E) {
to.place_at(self.layout(to.area()), &self.content())
}
}
impl<U: Coordinate, T> Max<U, T> {
#[inline] pub fn dx (&self) -> U {
use Max::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use Max::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
impl<E: Out, T: Layout<E>> Layout<E> for Max<E::Unit, T> {
fn layout (&self, area: E::Area) -> E::Area {
let [x, y, w, h] = self.content().layout(area).xywh();
match self {
Self::X(mw, _) => [x, y, w.min(*mw), h],
Self::Y(mh, _) => [x, y, w, h.min(*mh)],
Self::XY(mw, mh, _) => [x, y, w.min(*mw), h.min(*mh)],
}.into()
}
}

View file

@ -1,47 +0,0 @@
use crate::*;
pub enum Min<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Min<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self {
Self::X(x, item)
}
#[inline] pub const fn y (y: U, item: A) -> Self {
Self::Y(y, item)
}
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self {
Self::XY(x, y, item)
}
#[inline] pub const fn content (&self) -> &A {
use Min::*;
match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }
}
}
impl<E: Out, T: Draw<E> + Layout<E>> Draw<E> for Min<E::Unit, T> {
fn draw (&self, to: &mut E) {
to.place_at(self.layout(to.area()), &self.content())
}
}
impl<U: Coordinate, T> Min<U, T> {
#[inline] pub fn dx (&self) -> U {
use Min::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use Min::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
impl<E: Out, T: Layout<E>> Layout<E> for Min<E::Unit, T> {
fn layout (&self, area: E::Area) -> E::Area {
let [x, y, w, h] = self.content().layout(area).xywh();
match self {
Self::X(mw, _) => [x, y, w.max(*mw), h],
Self::Y(mh, _) => [x, y, w, h.max(*mh)],
Self::XY(mw, mh, _) => [x, y, w.max(*mw), h.max(*mh)],
}.into()
}
}

View file

@ -0,0 +1,33 @@
use crate::*;
/// Increment X and/or Y coordinate.
pub enum Push<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Push<U, A> {
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c, } }
}
impl<U: Coordinate, T> Push<U, T> {
#[inline] pub fn dx (&self) -> U { match self { Self::X(x, ..) | Self::XY(x, _, _) => *x, Self::Y(_, _) => 0.into() } }
#[inline] pub fn dy (&self) -> U { match self { Self::Y(y, ..) | Self::XY(_, y, _) => *y, Self::X(_, _) => 0.into() } }
}
impl<O: Out, T: Content<O>> Draw<O> for Push<O::Unit, T> {
fn draw (&self, to: &mut O) { Bound(self.layout(to.area()), self.inner()).draw(to) }
}
impl<O: Out, T: Layout<O>> Layout<O> for Push<O::Unit, T> {
fn x (&self, area: O::Area) -> O::Unit { area.x().plus(self.dx()) }
fn y (&self, area: O::Area) -> O::Unit { area.y().plus(self.dy()) }
}
/// Decrement X and/or Y coordinate.
pub enum Pull<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Pull<U, A> {
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c, } }
}
impl<U: Coordinate, T> Pull<U, T> {
#[inline] pub fn dx (&self) -> U { match self { Self::X(x, ..) | Self::XY(x, _, _) => *x, Self::Y(_, _) => 0.into() } }
#[inline] pub fn dy (&self) -> U { match self { Self::Y(y, ..) | Self::XY(_, y, _) => *y, Self::X(_, _) => 0.into() } }
}
impl<O: Out, T: Content<O>> Draw<O> for Pull<O::Unit, T> {
fn draw (&self, to: &mut O) { Bound(self.layout(to.area()), self.inner()).draw(to) }
}
impl<O: Out, T: Layout<O>> Layout<O> for Pull<O::Unit, T> {
fn x (&self, area: O::Area) -> O::Unit { area.x().minus(self.dx()) }
fn y (&self, area: O::Area) -> O::Unit { area.y().minus(self.dy()) }
}

View file

@ -0,0 +1,27 @@
use crate::*;
use Pad::*;
pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Pad<U, A> {
#[inline] pub const fn inner (&self) -> &A { match self { X(_, c) | Y(_, c) | XY(_, _, c) => c, } }
}
impl<U: Coordinate, T> Pad<U, T> {
#[inline] pub fn dx (&self) -> U { match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, } }
#[inline] pub fn dy (&self) -> U { match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, } }
}
impl<O: Out, T: Content<O>> Draw<O> for Pad<O::Unit, T> {
fn draw (&self, to: &mut O) { Bound(self.layout(to.area()), self.inner()).draw(to) }
}
impl<O: Out, T: Layout<O>> Layout<O> for Pad<O::Unit, T> {
fn x (&self, area: O::Area) -> O::Unit {
area.x().plus(self.dx())
}
fn y (&self, area: O::Area) -> O::Unit {
area.x().plus(self.dx())
}
fn w (&self, area: O::Area) -> O::Unit {
area.w().minus(self.dx() * 2.into())
}
fn h (&self, area: O::Area) -> O::Unit {
area.h().minus(self.dy() * 2.into())
}
}

View file

@ -1,50 +0,0 @@
use crate::*;
pub enum Padding<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Padding<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self {
Self::X(x, item)
}
#[inline] pub const fn y (y: U, item: A) -> Self {
Self::Y(y, item)
}
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self {
Self::XY(x, y, item)
}
#[inline] pub const fn content (&self) -> &A {
use Padding::*;
match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }
}
}
impl<E: Out, T: Draw<E> + Layout<E>> Draw<E> for Padding<E::Unit, T> {
fn draw (&self, to: &mut E) {
to.place_at(self.layout(to.area()), &self.content())
}
}
impl<U: Coordinate, T> Padding<U, T> {
#[inline] pub fn dx (&self) -> U {
use Padding::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use Padding::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
impl<E: Out, T: Layout<E>> Layout<E> for Padding<E::Unit, T> {
fn layout (&self, area: E::Area) -> E::Area {
let area = self.content().layout(area);
let dx = self.dx();
let dy = self.dy();
[
area.x().plus(dx),
area.y().plus(dy),
area.w().minus(dy.plus(dy)),
area.h().minus(dy.plus(dy))
].into()
}
}

View file

@ -1,48 +0,0 @@
use crate::*;
pub enum Pull<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Pull<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self {
Self::X(x, item)
}
#[inline] pub const fn y (y: U, item: A) -> Self {
Self::Y(y, item)
}
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self {
Self::XY(x, y, item)
}
#[inline] pub const fn content (&self) -> &A {
use Pull::*;
match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }
}
}
impl<E: Out, T: Draw<E> + Layout<E>> Draw<E> for Pull<E::Unit, T> {
fn draw (&self, to: &mut E) {
to.place_at(self.layout(to.area()), &self.content())
}
}
impl<U: Coordinate, T> Pull<U, T> {
#[inline] pub fn dx (&self) -> U {
use Pull::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use Pull::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
impl<E: Out, T: Layout<E>> Layout<E> for Pull<E::Unit, T> {
fn layout (&self, to: E::Area) -> E::Area {
let area = self.content().layout(to);
[
area.x().minus(self.dx()),
area.y().minus(self.dy()),
area.w(),
area.h()
].into()
}
}

View file

@ -1,43 +0,0 @@
use crate::*;
pub enum Push<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Push<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self {
Self::X(x, item)
}
#[inline] pub const fn y (y: U, item: A) -> Self {
Self::Y(y, item)
}
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self {
Self::XY(x, y, item)
}
#[inline] pub const fn content (&self) -> &A {
use Push::*;
match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }
}
}
impl<E: Out, T: Draw<E> + Layout<E>> Draw<E> for Push<E::Unit, T> {
fn draw (&self, to: &mut E) {
to.place_at(self.layout(to.area()), &self.content())
}
}
impl<U: Coordinate, T> Push<U, T> {
#[inline] pub fn dx (&self) -> U {
use Push::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use Push::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
impl<E: Out, T: Layout<E>> Layout<E> for Push<E::Unit, T> {
fn layout (&self, area: E::Area) -> E::Area {
let area = self.content().layout(area);
[area.x().plus(self.dx()), area.y().plus(self.dy()), area.w(), area.h()].into()
}
}

View file

@ -1,43 +0,0 @@
use crate::*;
pub enum Shrink<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Shrink<U, A> {
#[inline] pub const fn x (x: U, item: A) -> Self {
Self::X(x, item)
}
#[inline] pub const fn y (y: U, item: A) -> Self {
Self::Y(y, item)
}
#[inline] pub const fn xy (x: U, y: U, item: A) -> Self {
Self::XY(x, y, item)
}
#[inline] pub const fn content (&self) -> &A {
use Shrink::*;
match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }
}
}
impl<E: Out, T: Draw<E> + Layout<E>> Draw<E> for Shrink<E::Unit, T> {
fn draw (&self, to: &mut E) {
to.place_at(self.layout(to.area()), &self.content())
}
}
impl<U: Coordinate, T> Shrink<U, T> {
#[inline] pub fn dx (&self) -> U {
use Shrink::*;
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
}
#[inline] pub fn dy (&self) -> U {
use Shrink::*;
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
}
}
impl<E: Out, T: Layout<E>> Layout<E> for Shrink<E::Unit, T> {
fn layout (&self, to: E::Area) -> E::Area {
let area = self.content().layout(to);
[area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())].into()
}
}

View file

@ -0,0 +1,121 @@
use crate::*;
pub enum Fill<A> { X(A), Y(A), XY(A) }
impl<A> Fill<A> {
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(c) | Self::Y(c) | Self::XY(c) => c } }
#[inline] pub const fn dx (&self) -> bool { match self { Self::X(_) | Self::XY(_) => true, _ => false } }
#[inline] pub const fn dy (&self) -> bool { match self { Self::Y(_) | Self::XY(_) => true, _ => false } }
}
impl<O: Out, T: Content<O>> Draw<O> for Fill<T> {
fn draw (&self, to: &mut O) { Bound(self.layout(to.area()), self.inner()).draw(to) }
}
impl<O: Out, T: Layout<O>> Layout<O> for Fill<T> {
fn x (&self, area: O::Area) -> O::Unit { if self.dx() { area.x() } else { self.inner().x(area) } }
fn y (&self, area: O::Area) -> O::Unit { if self.dy() { area.y() } else { self.inner().y(area) } }
fn w (&self, area: O::Area) -> O::Unit { if self.dx() { area.w() } else { self.inner().w(area) } }
fn min_w (&self, area: O::Area) -> O::Unit { if self.dx() { area.w() } else { self.inner().min_w(area) } }
fn max_w (&self, area: O::Area) -> O::Unit { if self.dx() { area.w() } else { self.inner().max_w(area) } }
fn h (&self, area: O::Area) -> O::Unit { if self.dy() { area.h() } else { self.inner().h(area) } }
fn min_h (&self, area: O::Area) -> O::Unit { if self.dy() { area.h() } else { self.inner().min_h(area) } }
fn max_h (&self, area: O::Area) -> O::Unit { if self.dy() { area.h() } else { self.inner().max_h(area) } }
}
/// Set fixed size for content.
pub enum Fixed<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Fixed<U, A> {
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c } }
}
impl<U: Copy, A> Fixed<U, A> {
#[inline] pub const fn dx (&self) -> Option<U> { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } }
#[inline] pub const fn dy (&self) -> Option<U> { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } }
}
impl<O: Out, T: Content<O>> Draw<O> for Fixed<O::Unit, T> {
fn draw (&self, to: &mut O) { Bound(self.layout(to.area()), self.inner()).draw(to) }
}
impl<O: Out, T: Layout<O>> Layout<O> for Fixed<O::Unit, T> {
fn w (&self, area: O::Area) -> O::Unit { self.dx().unwrap_or(self.inner().w(area)) }
fn min_w (&self, area: O::Area) -> O::Unit { self.dx().unwrap_or(self.inner().min_w(area)) }
fn max_w (&self, area: O::Area) -> O::Unit { self.dx().unwrap_or(self.inner().max_w(area)) }
fn h (&self, area: O::Area) -> O::Unit { self.dy().unwrap_or(self.inner().h(area)) }
fn min_h (&self, area: O::Area) -> O::Unit { self.dy().unwrap_or(self.inner().min_h(area)) }
fn max_h (&self, area: O::Area) -> O::Unit { self.dy().unwrap_or(self.inner().max_h(area)) }
}
pub enum Max<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Max<U, A> {
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c } }
}
impl<U: Copy, A> Max<U, A> {
#[inline] pub const fn dx (&self) -> Option<U> { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } }
#[inline] pub const fn dy (&self) -> Option<U> { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } }
}
impl<O: Out, T: Content<O>> Draw<O> for Max<O::Unit, T> {
fn draw (&self, to: &mut O) { Bound(self.layout(to.area()), self.inner()).draw(to) }
}
impl<E: Out, T: Layout<E>> Layout<E> for Max<E::Unit, T> {
fn layout (&self, area: E::Area) -> E::Area {
let [x, y, w, h] = self.inner().layout(area).xywh();
match self {
Self::X(mw, _) => [x, y, w.min(*mw), h],
Self::Y(mh, _) => [x, y, w, h.min(*mh)],
Self::XY(mw, mh, _) => [x, y, w.min(*mw), h.min(*mh)],
}.into()
}
}
pub enum Min<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Min<U, A> {
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c } }
}
impl<U: Copy, A> Min<U, A> {
#[inline] pub const fn dx (&self) -> Option<U> { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } }
#[inline] pub const fn dy (&self) -> Option<U> { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } }
}
impl<O: Out, T: Content<O>> Draw<O> for Min<O::Unit, T> {
fn draw (&self, to: &mut O) { Bound(self.layout(to.area()), self.inner()).draw(to) }
}
impl<E: Out, T: Layout<E>> Layout<E> for Min<E::Unit, T> {
fn layout (&self, area: E::Area) -> E::Area {
let [x, y, w, h] = self.inner().layout(area).xywh();
match self {
Self::X(mw, _) => [x, y, w.max(*mw), h],
Self::Y(mh, _) => [x, y, w, h.max(*mh)],
Self::XY(mw, mh, _) => [x, y, w.max(*mw), h.max(*mh)],
}.into()
}
}
pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Expand<U, A> {
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c } }
}
impl<U: Copy + Default, A> Expand<U, A> {
#[inline] pub const fn dx (&self) -> Option<U> { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } }
#[inline] pub const fn dy (&self) -> Option<U> { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } }
}
impl<O: Out, T: Content<O>> Draw<O> for Expand<O::Unit, T> {
fn draw (&self, to: &mut O) { Bound(self.layout(to.area()), self.inner()).draw(to) }
}
impl<O: Out, T: Layout<O>> Layout<O> for Expand<O::Unit, T> {
fn w (&self, to: O::Area) -> O::Unit { self.inner().w(to).plus(self.dx().unwrap_or_default()) }
fn h (&self, to: O::Area) -> O::Unit { self.inner().w(to).plus(self.dy().unwrap_or_default()) }
}
pub enum Shrink<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
impl<U, A> Shrink<U, A> {
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c } }
}
impl<U: Copy, A> Shrink<U, A> {
#[inline] pub const fn dx (&self) -> Option<U> { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } }
#[inline] pub const fn dy (&self) -> Option<U> { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } }
}
impl<O: Out, T: Content<O>> Draw<O> for Shrink<O::Unit, T> {
fn draw (&self, to: &mut O) { Bound(self.layout(to.area()), self.inner()).draw(to) }
}
impl<E: Out, T: Layout<E>> Layout<E> for Shrink<E::Unit, T> {
fn layout (&self, to: E::Area) -> E::Area {
let area = self.inner().layout(to);
let dx = self.dx().unwrap_or_default();
let dy = self.dy().unwrap_or_default();
[area.x(), area.y(), area.w().minus(dx), area.h().minus(dy)].into()
}
}

View file

@ -3,15 +3,12 @@
#![feature(impl_trait_in_assoc_type)]
#![feature(const_precise_live_drops)]
#![feature(type_changing_struct_update)]
#![feature(anonymous_lifetime_in_impl_trait)]
#![feature(const_option_ops)]
#![feature(const_trait_impl)]
#![feature(const_default)]
//#![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 +16,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: Content<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

@ -84,13 +84,13 @@ macro_rules! test_op_transform {
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)),
(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]));
//assert_eq!(Content::layout(&op, [x, y, w, h]),
//Draw::layout(&op, [x, y, w, h]));
}
}
}
@ -104,8 +104,7 @@ 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_margin, Margin);
test_op_transform!(proptest_op_padding, Padding);
test_op_transform!(proptest_op_padding, Pad);
proptest! {
#[test] fn proptest_op_bsp (
@ -122,10 +121,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 +141,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:?}");
()
}
}

View file

@ -76,6 +76,6 @@ impl<E: Out> Measure<E> {
format!("{}x{}", self.w(), self.h()).into()
}
pub fn of <T: Draw<E>> (&self, item: T) -> Bsp<Fill<&Self>, T> {
Bsp::b(Fill::xy(self), item)
Bsp::b(Fill::XY(self), item)
}
}

View file

@ -1,43 +1,19 @@
use crate::*;
/// Lazily-evaluated [Draw]able.
pub struct Lazy<E, T, F>(PhantomData<(E, T)>, F);
impl<E: Out, T: Draw<E> + Layout<E>, F: Fn()->T> Lazy<E, T, F> {
pub const fn new (thunk: F) -> Self {
Self(PhantomData, thunk)
}
}
impl<E: Out, T: Draw<E> + Layout<E>, F: Fn()->T> Content<E> for Lazy<E, T, F> {
fn content (&self) -> impl Draw<E> + Layout<E> + '_ {
(self.1)()
}
}
pub struct Thunk<E: Out, F: Fn(&mut E)>(PhantomData<E>, F);
impl<E: Out, F: Fn(&mut E)> Thunk<E, F> {
pub const fn new (draw: F) -> Self { Self(PhantomData, draw) }
}
impl<E: Out, F: Fn(&mut E)> Draw<E> for Thunk<E, F> {
fn draw (&self, to: &mut E) { (self.1)(to) }
}
impl<E: Out, F: Fn(&mut E)> Layout<E> for Thunk<E, F> {
fn layout (&self, to: E::Area) -> E::Area { to }
}
#[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T,
pub view: Arc<RwLock<U>>
pub struct Lazy<O, T, F>(F, PhantomData<(O, T)>);
impl<O: Out, T: Content<O>, F: Fn()->T> Lazy<O, T, F> { pub const fn new (thunk: F) -> Self { Self(thunk, PhantomData) } }
pub struct Thunk<O: Out, F: Fn(&mut O)>(PhantomData<O>, F);
impl<O: Out, F: Fn(&mut O)> Thunk<O, F> { pub const fn new (draw: F) -> Self { Self(PhantomData, draw) } }
impl<O: Out, F: Fn(&mut O)> Layout<O> for Thunk<O, F> {}
impl<O: Out, F: Fn(&mut O)> Draw<O> for Thunk<O, F> {
fn draw (&self, to: &mut O) { (self.1)(to) }
}
#[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> {
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;
@ -48,6 +24,4 @@ impl<T: PartialEq, U> Memo<T, U> {
}
/// Clear a pre-allocated buffer, then write into it.
#[macro_export] macro_rules! rewrite {
($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } }
}
#[macro_export] macro_rules! rewrite { ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } }

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

@ -0,0 +1,147 @@
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("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.view(output, &arg).unwrap());
match axis {
Some("xy") | None => Fixed::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
Some("x") => Fixed::X(state.from(arg0?)?.unwrap(), cb),
Some("y") => Fixed::Y(state.from(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.view(output, &arg).unwrap());
match axis {
Some("xy") | None => Min::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
Some("x") => Min::X(state.from(arg0?)?.unwrap(), cb),
Some("y") => Min::Y(state.from(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.view(output, &arg).unwrap());
match axis {
Some("xy") | None => Max::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
Some("x") => Max::X(state.from(arg0?)?.unwrap(), cb),
Some("y") => Max::Y(state.from(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.view(output, &arg).unwrap());
match axis {
Some("xy") | None => Push::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
Some("x") => Push::X(state.from(arg0?)?.unwrap(), cb),
Some("y") => Push::Y(state.from(arg0?)?.unwrap(), cb),
frag => unimplemented!("push/{frag:?}")
}
}),
_ => return Ok(false)
};
Ok(true)
}

View file

@ -1,25 +1,11 @@
use crate::*;
pub struct FieldH<Theme, Label, Value>(pub Theme, pub Label, pub Value);
impl<O: Out, T, L: Draw<O>, V: Draw<O>> Layout<O> for FieldH<T, L, V> where Self: Content<O> {
fn layout (&self, to: O::Area) -> O::Area {
self.content().layout(to)
}
}
impl<O: Out, T, L: Draw<O>, V: Draw<O>> Draw<O> for FieldH<T, L, V> where Self: Content<O> {
fn draw (&self, to: &mut O) {
self.content().draw(to)
}
impl<O: Out, T, L: Content<O>, V: Content<O>> HasContent<O> for FieldH<T, L, V> {
fn content (&self) -> impl Content<O> { Bsp::e(&self.1, &self.2) }
}
pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
impl<O: Out, T, L: Draw<O>, V: Draw<O>> Layout<O> for FieldV<T, L, V> where Self: Content<O> {
fn layout (&self, to: O::Area) -> O::Area {
self.content().layout(to)
}
}
impl<O: Out, T, L: Draw<O>, V: Draw<O>> Draw<O> for FieldV<T, L, V> where Self: Content<O> {
fn draw (&self, to: &mut O) {
self.content().draw(to)
}
impl<O: Out, T, L: Content<O>, V: Content<O>> HasContent<O> for FieldV<T, L, V> {
fn content (&self) -> impl Content<O> { Bsp::s(&self.1, &self.2) }
}

View file

@ -4,6 +4,9 @@ description = "UI metaframework, Ratatui backend."
version = { workspace = true }
edition = { workspace = true }
[lib]
path = "src/tui.rs"
[features]
dsl = [ "dep:tengri_dsl", "tengri_output/dsl" ]
bumpalo = [ "dep:bumpalo" ]

View file

@ -1 +0,0 @@
:hello-world

View file

@ -1 +0,0 @@
(bsp/s :hello :world)

View file

@ -1 +0,0 @@
(fill/xy :hello-world)

View file

@ -1 +0,0 @@
(fixed/xy 20 10 :hello-world)

View file

@ -1 +0,0 @@
(bsp/s (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1 +0,0 @@
(bsp/e (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1 +0,0 @@
(bsp/n (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1 +0,0 @@
(bsp/w (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1 +0,0 @@
(bsp/a (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1 +0,0 @@
(bsp/b (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1,11 +0,0 @@
(bsp/s
(bsp/e (align/nw (fixed/xy 5 3 :hello))
(bsp/e (align/n (fixed/xy 5 3 :hello))
(align/ne (fixed/xy 5 3 :hello))))
(bsp/s
(bsp/e (align/w (fixed/xy 5 3 :hello))
(bsp/e (align/c (fixed/xy 5 3 :hello))
(align/e (fixed/xy 5 3 :hello))))
(bsp/e (align/sw (fixed/xy 5 3 :hello))
(bsp/e (align/s (fixed/xy 5 3 :hello))
(align/se (fixed/xy 5 3 :hello))))))

View file

@ -1,11 +0,0 @@
(bsp/s
(bsp/e (fixed/xy 8 5 (align/nw :hello))
(bsp/e (fixed/xy 8 5 (align/n :hello))
(fixed/xy 8 5 (align/ne :hello))))
(bsp/s
(bsp/e (fixed/xy 8 5 (align/w :hello))
(bsp/e (fixed/xy 8 5 (align/c :hello))
(fixed/xy 8 5 (align/e :hello))))
(bsp/e (fixed/xy 8 5 (align/sw :hello))
(bsp/e (fixed/xy 8 5 (align/s :hello))
(fixed/xy 8 5 (align/se :hello))))))

View file

@ -1,11 +0,0 @@
(bsp/s
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/nw :hello)))
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/n :hello)))
(grow/xy 1 1 (fixed/xy 8 5 (align/ne :hello)))))
(bsp/s
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/w :hello)))
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/c :hello)))
(grow/xy 1 1 (fixed/xy 8 5 (align/e :hello)))))
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/sw :hello)))
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/s :hello)))
(grow/xy 1 1 (fixed/xy 8 5 (align/se :hello)))))))

View file

@ -1 +0,0 @@
:map-e

View file

@ -1 +0,0 @@
(align/c :map-e)

View file

@ -1 +0,0 @@
:map-s

View file

@ -1 +0,0 @@
(align/c :map-s)

View file

@ -1,73 +0,0 @@
(align/c (bg/behind :bg0 (margin/xy 1 1 (col
(bg/behind :bg1 (border/around :border1 (margin/xy 2 1 :label1)))
(bg/behind :bg2 (border/around :border2 (margin/xy 4 2 :label2)))
(bg/behind :bg3 (border/around :border3 (margin/xy 6 3 :label3)))))))
fn content (&self) -> dyn Draw<Engine = Tui> {
let border_style = Style::default().fg(Color::Rgb(0,0,0));
Align::Center(Layers::new(move|add|{
add(&Background(Color::Rgb(0,128,128)))?;
add(&Margin::XY(1, 1, Stack::down(|add|{
add(&Layers::new(|add|{
add(&Background(Color::Rgb(128,96,0)))?;
add(&Border(Square(border_style)))?;
add(&Margin::XY(2, 1, "..."))?;
Ok(())
}).debug())?;
add(&Layers::new(|add|{
add(&Background(Color::Rgb(128,64,0)))?;
add(&Border(Lozenge(border_style)))?;
add(&Margin::XY(4, 2, "---"))?;
Ok(())
}).debug())?;
add(&Layers::new(|add|{
add(&Background(Color::Rgb(96,64,0)))?;
add(&Border(SquareBold(border_style)))?;
add(&Margin::XY(6, 3, "~~~"))?;
Ok(())
}).debug())?;
Ok(())
})).debug())?;
Ok(())
}))
//Align::Center(Margin::X(1, Layers::new(|add|{
//add(&Background(Color::Rgb(128,0,0)))?;
//add(&Stack::down(|add|{
//add(&Margin::Y(1, Layers::new(|add|{
//add(&Background(Color::Rgb(0,128,0)))?;
//add(&Align::Center("12345"))?;
//add(&Align::Center("FOO"))
//})))?;
//add(&Margin::XY(1, 1, Layers::new(|add|{
//add(&Align::Center("1234567"))?;
//add(&Align::Center("BAR"))?;
//add(&Background(Color::Rgb(0,0,128)))
//})))
//}))
//})))
//Align::Y(Layers::new(|add|{
//add(&Background(Color::Rgb(128,0,0)))?;
//add(&Margin::X(1, Align::Center(Stack::down(|add|{
//add(&Align::X(Margin::Y(1, Layers::new(|add|{
//add(&Background(Color::Rgb(0,128,0)))?;
//add(&Align::Center("12345"))?;
//add(&Align::Center("FOO"))
//})))?;
//add(&Margin::XY(1, 1, Layers::new(|add|{
//add(&Align::Center("1234567"))?;
//add(&Align::Center("BAR"))?;
//add(&Background(Color::Rgb(0,0,128)))
//})))?;
//Ok(())
//})))))
//}))
}

View file

@ -1,101 +0,0 @@
use tengri::{self, Usually, Perhaps, input::*, output::*, tui::*, dsl::*};
use std::sync::{Arc, RwLock};
//use crossterm::event::{*, KeyCode::*};
use crate::ratatui::style::Color;
fn main () -> Usually<()> {
let state = Arc::new(RwLock::new(Example(10, Measure::new())));
Tui::new().unwrap().run(&state)?;
Ok(())
}
#[derive(Debug)] pub struct Example(usize, Measure<TuiOut>);
const KEYMAP: &str = "(@left prev) (@right next)";
const EXAMPLES: &'static [&'static str] = &[
include_str!("edn/edn01.edn"),
include_str!("edn/edn02.edn"),
include_str!("edn/edn03.edn"),
include_str!("edn/edn04.edn"),
include_str!("edn/edn05.edn"),
include_str!("edn/edn06.edn"),
include_str!("edn/edn07.edn"),
include_str!("edn/edn08.edn"),
include_str!("edn/edn09.edn"),
include_str!("edn/edn10.edn"),
include_str!("edn/edn11.edn"),
include_str!("edn/edn12.edn"),
//include_str!("edn/edn13.edn"),
include_str!("edn/edn14.edn"),
include_str!("edn/edn15.edn"),
include_str!("edn/edn16.edn"),
include_str!("edn/edn17.edn"),
];
handle!(TuiIn: |self: Example, input|{
Ok(None)/*if let Some(command) = CstIter::new(KEYMAP).command::<_, ExampleCommand, _>(self, input) {
command.execute(self)?;
Some(true)
} else {
None
})*/
});
//#[tengri_proc::expose]
//impl Example {
//fn _todo_u16_stub (&self) -> u16 { todo!() }
//fn _todo_bool_stub (&self) -> bool { todo!() }
//fn _todo_usize_stub (&self) -> usize { todo!() }
////[bool] => {}
////[u16] => {}
////[usize] => {}
//}
#[tengri_proc::command(Example)]
impl ExampleCommand {
fn next (state: &mut Example) -> Perhaps<Self> {
state.0 = (state.0 + 1) % EXAMPLES.len();
Ok(None)
}
fn prev (state: &mut Example) -> Perhaps<Self> {
state.0 = if state.0 > 0 { state.0 - 1 } else { EXAMPLES.len() - 1 };
Ok(None)
}
}
tui_draw!(|self: Example, to|to.place(&self.content()));
content!(TuiOut: |self: Example|{
let index = self.0 + 1;
let wh = self.1.wh();
let src = EXAMPLES.get(self.0).unwrap_or(&"");
let heading = format!("Example {}/{} in {:?}", index, EXAMPLES.len(), &wh);
let title = Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(heading)));
let code = Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", src))));
let content = ();//Tui::bg(Color::Rgb(10, 10, 60), View(self, CstIter::new(src)));
self.1.of(Bsp::s(title, Bsp::n(""/*code*/, content)))
});
//#[tengri_proc::view(TuiOut)]
//impl Example {
//pub fn title (&self) -> impl Draw<TuiOut> + use<'_> {
//Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, EXAMPLES.len())))).boxed()
//}
//pub fn code (&self) -> impl Draw<TuiOut> + use<'_> {
//Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", EXAMPLES[self.0])))).boxed()
//}
//pub fn hello (&self) -> impl Draw<TuiOut> + use<'_> {
//Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed()
//}
//pub fn world (&self) -> impl Draw<TuiOut> + use<'_> {
//Tui::bg(Color::Rgb(100, 10, 10), "world").boxed()
//}
//pub fn hello_world (&self) -> impl Draw<TuiOut> + use<'_> {
//"Hello world!".boxed()
//}
//pub fn map_e (&self) -> impl Draw<TuiOut> + use<'_> {
//Map::east(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed()
//}
//pub fn map_s (&self) -> impl Draw<TuiOut> + use<'_> {
//Map::south(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed()
//}
//}

123
tui/examples/tui_00.rs Normal file
View file

@ -0,0 +1,123 @@
use tengri::{self, Usually, Perhaps, input::*, output::*, tui::*, dsl::*};
use std::sync::{Arc, RwLock};
use crate::ratatui::style::Color;
//use crossterm::event::{*, KeyCode::*};
fn main () -> Usually<()> {
let state = Example::new();
Tui::new().unwrap().run(&state)?;
Ok(())
}
#[derive(Debug)] struct Example(usize, Measure<TuiOut>);
handle!(TuiIn: |self: Example, input|Ok(None));
enum ExampleCommand { Next, Prev }
impl ExampleCommand {
fn eval (&self, state: &mut Example) -> Perhaps<Self> {
match self {
Self::Next => {
state.0 = (state.0 + 1) % Example::VIEWS.len();
Ok(Some(Self::Prev))
},
Self::Prev => {
state.0 = if state.0 > 0 { state.0 - 1 } else { Example::VIEWS.len() - 1 };
Ok(Some(Self::Next))
}
}
}
}
impl Draw<TuiOut> for Example {
fn content (&self) -> impl Draw<TuiOut> {
let index = self.0 + 1;
let wh = self.1.wh();
let src = Self::VIEWS.get(self.0).unwrap_or(&"");
let heading = format!("Example {}/{} in {:?}", index, Self::VIEWS.len(), &wh);
let title = Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(heading)));
let code = Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", src))));
let content = ();//Tui::bg(Color::Rgb(10, 10, 60), View(self, CstIter::new(src)));
self.1.of(Bsp::s(title, Bsp::n(""/*code*/, content)))
}
}
impl View<TuiOut, ()> for Example {
fn view_expr <'a> (&'a self, to: &mut TuiOut, expr: &'a impl DslExpr) -> Usually<()> {
if evaluate_output_expression(self, to, expr)?
|| evaluate_output_expression_tui(self, to, expr)? {
Ok(())
} else {
Err(format!("Example::view_expr: unexpected: {expr:?}").into())
}
}
}
impl Example {
fn new () -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(Example(10, Measure::new())))
}
const BINDS: &'static str = stringify! {
(@left prev)
(@right next)
};
const VIEWS: &'static [&'static str] = &[
stringify! { :hello-world },
stringify! { (fill/xy :hello-world) },
stringify! { (bsp/s :hello :world) },
stringify! { (fixed/xy 20 10 :hello-world) },
stringify! { (bsp/s (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
stringify! { (bsp/e (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
stringify! { (bsp/n (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
stringify! { (bsp/w (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
stringify! { (bsp/a (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
stringify! { (bsp/b (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world)) },
stringify! {
(bsp/s
(bsp/e (align/nw (fixed/xy 5 3 :hello))
(bsp/e (align/n (fixed/xy 5 3 :hello))
(align/ne (fixed/xy 5 3 :hello))))
(bsp/s
(bsp/e (align/w (fixed/xy 5 3 :hello))
(bsp/e (align/c (fixed/xy 5 3 :hello))
(align/e (fixed/xy 5 3 :hello))))
(bsp/e (align/sw (fixed/xy 5 3 :hello))
(bsp/e (align/s (fixed/xy 5 3 :hello))
(align/se (fixed/xy 5 3 :hello))))))
},
stringify! {
(bsp/s
(bsp/e (fixed/xy 8 5 (align/nw :hello))
(bsp/e (fixed/xy 8 5 (align/n :hello))
(fixed/xy 8 5 (align/ne :hello))))
(bsp/s
(bsp/e (fixed/xy 8 5 (align/w :hello))
(bsp/e (fixed/xy 8 5 (align/c :hello))
(fixed/xy 8 5 (align/e :hello))))
(bsp/e (fixed/xy 8 5 (align/sw :hello))
(bsp/e (fixed/xy 8 5 (align/s :hello))
(fixed/xy 8 5 (align/se :hello))))))
},
stringify! {
(bsp/s
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/nw :hello)))
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/n :hello)))
(grow/xy 1 1 (fixed/xy 8 5 (align/ne :hello)))))
(bsp/s
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/w :hello)))
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/c :hello)))
(grow/xy 1 1 (fixed/xy 8 5 (align/e :hello)))))
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/sw :hello)))
(bsp/e (grow/xy 1 1 (fixed/xy 8 5 (align/s :hello)))
(grow/xy 1 1 (fixed/xy 8 5 (align/se :hello)))))))
},
stringify! { :map-e },
stringify! { (align/c :map-e) },
stringify! { :map-s },
stringify! { (align/c :map-s) },
stringify! {
(align/c (bg/behind :bg0 (margin/xy 1 1 (col
(bg/behind :bg1 (border/around :border1 (margin/xy 2 1 :label1)))
(bg/behind :bg2 (border/around :border2 (margin/xy 4 2 :label2)))
(bg/behind :bg3 (border/around :border3 (margin/xy 6 3 :label3)))))))
},
];
}

105
tui/examples/tui_01.rs Normal file
View file

@ -0,0 +1,105 @@
fn main () {}
//#[tengri_proc::expose]
//impl Example {
//fn _todo_u16_stub (&self) -> u16 { todo!() }
//fn _todo_bool_stub (&self) -> bool { todo!() }
//fn _todo_usize_stub (&self) -> usize { todo!() }
////[bool] => {}
////[u16] => {}
////[usize] => {}
//}
//#[tengri_proc::view(TuiOut)]
//impl Example {
//pub fn title (&self) -> impl Draw<TuiOut> + use<'_> {
//Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, VIEWS.len())))).boxed()
//}
//pub fn code (&self) -> impl Draw<TuiOut> + use<'_> {
//Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", VIEWS[self.0])))).boxed()
//}
//pub fn hello (&self) -> impl Draw<TuiOut> + use<'_> {
//Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed()
//}
//pub fn world (&self) -> impl Draw<TuiOut> + use<'_> {
//Tui::bg(Color::Rgb(100, 10, 10), "world").boxed()
//}
//pub fn hello_world (&self) -> impl Draw<TuiOut> + use<'_> {
//"Hello world!".boxed()
//}
//pub fn map_e (&self) -> impl Draw<TuiOut> + use<'_> {
//Map::east(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed()
//}
//pub fn map_s (&self) -> impl Draw<TuiOut> + use<'_> {
//Map::south(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed()
//}
//}
//fn content (&self) -> dyn Draw<Engine = Tui> {
//let border_style = Style::default().fg(Color::Rgb(0,0,0));
//Align::Center(Layers::new(move|add|{
//add(&Background(Color::Rgb(0,128,128)))?;
//add(&Margin::XY(1, 1, Stack::down(|add|{
//add(&Layers::new(|add|{
//add(&Background(Color::Rgb(128,96,0)))?;
//add(&Border(Square(border_style)))?;
//add(&Margin::XY(2, 1, "..."))?;
//Ok(())
//}).debug())?;
//add(&Layers::new(|add|{
//add(&Background(Color::Rgb(128,64,0)))?;
//add(&Border(Lozenge(border_style)))?;
//add(&Margin::XY(4, 2, "---"))?;
//Ok(())
//}).debug())?;
//add(&Layers::new(|add|{
//add(&Background(Color::Rgb(96,64,0)))?;
//add(&Border(SquareBold(border_style)))?;
//add(&Margin::XY(6, 3, "~~~"))?;
//Ok(())
//}).debug())?;
//Ok(())
//})).debug())?;
//Ok(())
//}))
////Align::Center(Margin::X(1, Layers::new(|add|{
////add(&Background(Color::Rgb(128,0,0)))?;
////add(&Stack::down(|add|{
////add(&Margin::Y(1, Layers::new(|add|{
////add(&Background(Color::Rgb(0,128,0)))?;
////add(&Align::Center("12345"))?;
////add(&Align::Center("FOO"))
////})))?;
////add(&Margin::XY(1, 1, Layers::new(|add|{
////add(&Align::Center("1234567"))?;
////add(&Align::Center("BAR"))?;
////add(&Background(Color::Rgb(0,0,128)))
////})))
////}))
////})))
////Align::Y(Layers::new(|add|{
////add(&Background(Color::Rgb(128,0,0)))?;
////add(&Margin::X(1, Align::Center(Stack::down(|add|{
////add(&Align::X(Margin::Y(1, Layers::new(|add|{
////add(&Background(Color::Rgb(0,128,0)))?;
////add(&Align::Center("12345"))?;
////add(&Align::Center("FOO"))
////})))?;
////add(&Margin::XY(1, 1, Layers::new(|add|{
////add(&Align::Center("1234567"))?;
////add(&Align::Center("BAR"))?;
////add(&Background(Color::Rgb(0,0,128)))
////})))?;
////Ok(())
////})))))
////}))
//}

View file

@ -1,59 +0,0 @@
#![feature(type_changing_struct_update)]
mod tui_engine; pub use self::tui_engine::*;
mod tui_content; pub use self::tui_content::*;
pub(crate) use ::tengri_core::*;
#[cfg(feature = "dsl")] pub use ::tengri_dsl::*;
pub use ::tengri_input as input; pub(crate) use ::tengri_input::*;
pub use ::tengri_output as output; pub(crate) use ::tengri_output::*;
pub(crate) use atomic_float::AtomicF64;
pub use ::better_panic; pub(crate) use ::better_panic::{Settings, Verbosity};
pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*};
pub use ::crossterm; pub(crate) use ::crossterm::{
ExecutableCommand,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
};
pub use ::ratatui; pub(crate) use ratatui::{
prelude::{Color, Style, Buffer},
style::Modifier,
backend::{Backend, CrosstermBackend, ClearType},
layout::{Size, Rect},
buffer::Cell
};
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
pub(crate) use std::io::{stdout, Stdout};
#[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> {
use crate::*;
//use std::sync::{Arc, RwLock};
struct TestComponent(String);
impl Content<TuiOut> for TestComponent {
fn content (&self) -> impl Draw<TuiOut> {
Some(self.0.as_str())
}
}
impl Handle<TuiIn> for TestComponent {
fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> {
Ok(None)
}
}
let engine = Tui::new()?;
engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
let state = TestComponent("hello world".into());
let _state = std::sync::Arc::new(std::sync::RwLock::new(state));
//engine.run(&state)?;
Ok(())
}
#[cfg(test)] #[test] fn test_parse_key () {
//use KeyModifiers as Mods;
let _test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y)));
//test(":x",
//KeyEvent::new(KeyCode::Char('x'), Mods::NONE));
//test(":ctrl-x",
//KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL));
//test(":alt-x",
//KeyEvent::new(KeyCode::Char('x'), Mods::ALT));
//test(":shift-x",
//KeyEvent::new(KeyCode::Char('x'), Mods::SHIFT));
//test(":ctrl-alt-shift-x",
//KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT ));
}

78
tui/src/tui.rs Normal file
View file

@ -0,0 +1,78 @@
#![feature(type_changing_struct_update)]
#[cfg(test)] mod tui_test;
mod tui_engine; pub use self::tui_engine::*;
mod tui_content; pub use self::tui_content::*;
pub use ::{
tengri_input,
tengri_output,
ratatui,
crossterm,
palette,
better_panic
};
pub(crate) use ::{
tengri_core::*,
tengri_input::*,
tengri_output::*,
atomic_float::AtomicF64,
std::{io::{stdout, Stdout}, sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}},
better_panic::{Settings, Verbosity},
palette::{*, convert::*, okhsl::*},
ratatui::{
prelude::{Color, Style, Buffer},
style::Modifier,
backend::{Backend, CrosstermBackend, ClearType},
layout::{Size, Rect},
buffer::Cell
},
crossterm::{
ExecutableCommand,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
}
};
#[cfg(feature = "dsl")] use tengri_dsl::*;
#[cfg(feature = "dsl")]
pub fn evaluate_output_expression_tui <'a, S> (
state: &S, mut output: &mut TuiOut, expr: impl DslExpr + 'a
) -> Usually<bool> where
S: View<TuiOut, ()>
+ for<'b>DslNs<'b, bool>
+ for<'b>DslNs<'b, u16>
+ for<'b>DslNs<'b, Color>
{
// See `tengri_output::evaluate_output_expression`
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)");
output.place(&Tui::fg(
DslNs::<Color>::from(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")),
Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()),
))
},
Some("bg") => {
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
output.place(&Tui::bg(
DslNs::<Color>::from(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")),
Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()),
))
},
_ => return Ok(false)
};
Ok(true)
}

View file

@ -1,42 +1,12 @@
#[allow(unused)] use crate::*;
impl Tui {
pub const fn fg <T> (color: Color, w: T) -> TuiForeground<T> {
TuiForeground(Foreground(color, w))
}
pub const fn bg <T> (color: Color, w: T) -> TuiBackground<T> {
TuiBackground(Background(color, w))
}
pub const fn fg_bg <T> (fg: Color, bg: Color, w: T) -> TuiBackground<TuiForeground<T>> {
TuiBackground(Background(bg, TuiForeground(Foreground(fg, w))))
}
pub const fn modify <T> (enable: bool, modifier: Modifier, w: T) -> Modify<T> {
Modify(enable, modifier, w)
}
pub const fn bold <T> (enable: bool, w: T) -> Modify<T> {
Self::modify(enable, Modifier::BOLD, w)
}
pub const fn border <S, T> (enable: bool, style: S, w: T) -> Bordered<S, T> {
Bordered(enable, style, w)
}
pub const fn fg <T> (color: Color, w: T) -> Foreground<Color, T> { Foreground(color, w) }
pub const fn bg <T> (color: Color, w: T) -> Background<Color, T> { Background(color, w) }
pub const fn fg_bg <T> (fg: Color, bg: Color, w: T) -> Background<Color, Foreground<Color, T>> { Background(bg, Foreground(fg, w)) }
pub const fn modify <T> (enable: bool, modifier: Modifier, w: T) -> Modify<T> { Modify(enable, modifier, w) }
pub const fn bold <T> (enable: bool, w: T) -> Modify<T> { Self::modify(enable, Modifier::BOLD, w) }
pub const fn border <S, T> (enable: bool, style: S, w: T) -> Bordered<S, T> { Bordered(enable, style, w) }
}
#[macro_export] macro_rules! tui_layout ((|$self:ident:$Self:ty, $to:ident|$expr:expr)=>{
impl Layout<TuiOut> for $Self {
fn layout (&$self, $to: [u16;4]) -> [u16;4] {
$expr
}
}
});
#[macro_export] macro_rules! tui_draw ((|$self:ident:$Self:ty, $to:ident|$expr:expr)=>{
impl Draw<TuiOut> for $Self {
fn draw (&$self, $to: &mut TuiOut) {
$expr
}
}
});
mod tui_border; pub use self::tui_border::*;
mod tui_button; pub use self::tui_button::*;
mod tui_color; pub use self::tui_color::*;
@ -46,67 +16,41 @@ mod tui_phat; pub use self::tui_phat::*;
mod tui_repeat; pub use self::tui_repeat::*;
mod tui_scroll; pub use self::tui_scroll::*;
mod tui_string; pub use self::tui_string::*;
mod tui_number; //pub use self::tui_number::*;
mod tui_tryptich; //pub use self::tui_tryptich::*;
pub struct TuiForeground<T>(pub(crate) Foreground<Color, T>);
pub struct TuiBackground<T>(pub(crate) Background<Color, T>);
pub struct Modify<T>(pub bool, pub Modifier, pub T);
pub struct Styled<T>(pub Option<Style>, pub T);
impl<T: Layout<TuiOut>> Layout<TuiOut> for TuiForeground<T> {
fn layout (&self, to: [u16;4]) -> [u16;4] {
self.0.layout(to)
}
}
impl<T: Layout<TuiOut> + Draw<TuiOut>> Draw<TuiOut> for TuiForeground<T> {
impl<T: Draw<TuiOut>> Draw<TuiOut> for Foreground<Color, T> {
fn draw (&self, to: &mut TuiOut) {
let area = self.layout(to.area());
to.fill_fg(area, self.0.0);
to.place_at(area, &self.0.1);
}
}
impl<T: Layout<TuiOut>> Layout<TuiOut> for TuiBackground<T> {
fn layout (&self, to: [u16;4]) -> [u16;4] {
self.0.layout(to)
}
}
impl<T: Layout<TuiOut> + Draw<TuiOut>> Draw<TuiOut> for TuiBackground<T> {
impl<T: Draw<TuiOut>> Draw<TuiOut> for Background<Color, T> {
fn draw (&self, to: &mut TuiOut) {
let area = self.layout(to.area());
to.fill_bg(area, self.0.0);
to.place_at(area, &self.0.1);
}
}
impl<T: Layout<TuiOut>> Layout<TuiOut> for Modify<T> {
fn layout (&self, to: [u16;4]) -> [u16;4] {
self.2.layout(to)
}
}
impl<T: Draw<TuiOut>> Layout<TuiOut> for Modify<T> {}
impl<T: Draw<TuiOut>> Draw<TuiOut> for Modify<T> {
fn draw (&self, to: &mut TuiOut) {
to.fill_mod(to.area(), self.0, self.1);
self.2.draw(to)
}
}
impl<T: Layout<TuiOut>> Layout<TuiOut> for Styled<T> {
fn layout (&self, to: [u16;4]) -> [u16;4] {
self.1.layout(to)
}
}
impl<T: Layout<TuiOut> + Draw<TuiOut>> Draw<TuiOut> for Styled<T> {
impl<T: Draw<TuiOut>> Layout<TuiOut> for Styled<T> {}
impl<T: Draw<TuiOut>> Draw<TuiOut> for Styled<T> {
fn draw (&self, to: &mut TuiOut) {
to.place(&self.1);
// TODO write style over area
}
}
//impl<T: Draw<TuiOut> + Layout<TuiOut>> Content<TuiOut> for Result<T, Box<dyn std::error::Error>> {
//fn content (&self) -> impl Draw<TuiOut> + Layout<TuiOut> + '_ {
//impl<T: Draw<TuiOut>> Content<TuiOut> for Result<T, Box<dyn std::error::Error>> {
//fn content (&self) -> impl Draw<TuiOut> + '_ {
//Bsp::a(self.as_ref().ok(), self.as_ref().err().map(
//|e|Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(32,32,32), e.to_string())
//))

View file

@ -1,47 +1,45 @@
use crate::*;
impl<S: BorderStyle, W: Draw<TuiOut> + Layout<TuiOut>> Content<TuiOut> for Bordered<S, W> {
fn content (&self) -> impl Draw<TuiOut> + Layout<TuiOut> + '_ {
Fill::xy(lay!(
When::new(self.0, Border(self.0, self.1)),
Padding::xy(1, 1, &self.2)
))
impl<S: BorderStyle, W: Content<TuiOut>> HasContent<TuiOut> for Bordered<S, W> {
fn content (&self) -> impl Content<TuiOut> {
Fill::XY(lay!( When::new(self.0, Border(self.0, self.1)), Pad::XY(1, 1, &self.2) ))
}
}
impl<S: BorderStyle> Draw<TuiOut> for Border<S> {
fn draw (&self, to: &mut TuiOut) {
if self.0 {
let Border(enabled, style) = self.0;
if enabled {
let area = to.area();
if area.w() > 0 && area.y() > 0 {
to.blit(&self.1.nw(), area.x(), area.y(), self.1.style());
to.blit(&self.1.ne(), area.x() + area.w() - 1, area.y(), self.1.style());
to.blit(&self.1.sw(), area.x(), area.y() + area.h() - 1, self.1.style());
to.blit(&self.1.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.1.style());
to.blit(&style.border_nw(), area.x(), area.y(), style.style());
to.blit(&style.border_ne(), area.x() + area.w() - 1, area.y(), style.style());
to.blit(&style.border_sw(), area.x(), area.y() + area.h() - 1, style.style());
to.blit(&style.border_se(), area.x() + area.w() - 1, area.y() + area.h() - 1, style.style());
for x in area.x()+1..area.x()+area.w()-1 {
to.blit(&self.1.n(), x, area.y(), self.1.style());
to.blit(&self.1.s(), x, area.y() + area.h() - 1, self.1.style());
to.blit(&style.border_n(), x, area.y(), style.style());
to.blit(&style.border_s(), x, area.y() + area.h() - 1, style.style());
}
for y in area.y()+1..area.y()+area.h()-1 {
to.blit(&self.1.w(), area.x(), y, self.1.style());
to.blit(&self.1.e(), area.x() + area.w() - 1, y, self.1.style());
to.blit(&style.border_w(), area.x(), y, style.style());
to.blit(&style.border_e(), area.x() + area.w() - 1, y, style.style());
}
}
}
}
}
pub trait BorderStyle: Draw<TuiOut> + Layout<TuiOut> + Copy {
pub trait BorderStyle: Content<TuiOut> + Copy {
fn enabled (&self) -> bool;
fn enclose (self, w: impl Draw<TuiOut> + Layout<TuiOut>) -> impl Draw<TuiOut> + Layout<TuiOut> {
Bsp::b(Fill::xy(Border(self.enabled(), self)), w)
fn enclose (self, w: impl Draw<TuiOut>) -> impl Draw<TuiOut> {
Bsp::b(Fill::XY(Border(self.enabled(), self)), w)
}
fn enclose2 (self, w: impl Draw<TuiOut> + Layout<TuiOut>) -> impl Draw<TuiOut> + Layout<TuiOut> {
Bsp::b(Margin::xy(1, 1, Fill::xy(Border(self.enabled(), self))), w)
fn enclose2 (self, w: impl Draw<TuiOut>) -> impl Draw<TuiOut> {
Bsp::b(Pad::XY(1, 1, Fill::XY(Border(self.enabled(), self))), w)
}
fn enclose_bg (self, w: impl Draw<TuiOut> + Layout<TuiOut>) -> impl Draw<TuiOut> + Layout<TuiOut> {
fn enclose_bg (self, w: impl Draw<TuiOut>) -> impl Draw<TuiOut> {
Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
Bsp::b(Fill::xy(Border(self.enabled(), self)), w))
Bsp::b(Fill::XY(Border(self.enabled(), self)), w))
}
const NW: &'static str = "";
const N: &'static str = "";
@ -57,14 +55,14 @@ pub trait BorderStyle: Draw<TuiOut> + Layout<TuiOut> + Copy {
const W0: &'static str = "";
const E0: &'static str = "";
fn n (&self) -> &str { Self::N }
fn s (&self) -> &str { Self::S }
fn e (&self) -> &str { Self::E }
fn w (&self) -> &str { Self::W }
fn nw (&self) -> &str { Self::NW }
fn ne (&self) -> &str { Self::NE }
fn sw (&self) -> &str { Self::SW }
fn se (&self) -> &str { Self::SE }
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 }
#[inline] fn draw <'a> (
&self, to: &mut TuiOut
) -> Usually<()> {
@ -143,7 +141,6 @@ macro_rules! border {
fn enabled (&self) -> bool { self.0 }
}
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
impl Layout<TuiOut> for $T {}
impl Draw<TuiOut> for $T {
fn draw (&self, to: &mut TuiOut) {
if self.enabled() { let _ = BorderStyle::draw(self, to); }

View file

@ -1,26 +1,26 @@
use crate::{*, Color::*};
pub fn button_2 <'a> (
key: impl Draw<TuiOut> + Layout<TuiOut> + 'a,
label: impl Draw<TuiOut> + Layout<TuiOut> + 'a,
key: impl Draw<TuiOut> + 'a,
label: impl Draw<TuiOut> + 'a,
editing: bool,
) -> impl Draw<TuiOut> + Layout<TuiOut> + 'a {
) -> impl Draw<TuiOut> + 'a {
let key = Tui::fg_bg(Tui::orange(), Tui::g(0), Bsp::e(
Tui::fg(Tui::g(0), ""),
Bsp::e(key, Tui::fg(Tui::g(96), ""))
Tui::fg(Tui::g(0), &""),
Bsp::e(key, Tui::fg(Tui::g(96), &""))
));
let label = When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label));
Tui::bold(true, Bsp::e(key, label))
}
pub fn button_3 <'a> (
key: impl Draw<TuiOut> + Layout<TuiOut> + 'a,
label: impl Draw<TuiOut> + Layout<TuiOut> + 'a,
value: impl Draw<TuiOut> + Layout<TuiOut> + 'a,
key: impl Draw<TuiOut> + 'a,
label: impl Draw<TuiOut> + 'a,
value: impl Draw<TuiOut> + 'a,
editing: bool,
) -> impl Draw<TuiOut> + Layout<TuiOut> + 'a {
) -> impl Draw<TuiOut> + 'a {
let key = Tui::fg_bg(Tui::orange(), Tui::g(0),
Bsp::e(Tui::fg(Tui::g(0), ""), Bsp::e(key, Tui::fg(if editing {
Bsp::e(Tui::fg(Tui::g(0), &""), Bsp::e(key, Tui::fg(if editing {
Tui::g(128)
} else {
Tui::g(96)
@ -28,11 +28,11 @@ pub fn button_3 <'a> (
let label = Bsp::e(
When::new(!editing, Bsp::e(
Tui::fg_bg(Tui::g(255), Tui::g(96), label),
Tui::fg_bg(Tui::g(128), Tui::g(96), ""),
Tui::fg_bg(Tui::g(128), Tui::g(96), &""),
)),
Bsp::e(
Tui::fg_bg(Tui::g(224), Tui::g(128), value),
Tui::fg_bg(Tui::g(128), Reset, ""),
Tui::fg_bg(Tui::g(128), Reset, &""),
));
Tui::bold(true, Bsp::e(key, label))
}

View file

@ -1,37 +1,5 @@
use crate::*;
impl<
Label: Draw<TuiOut> + Layout<TuiOut>,
Value: Draw<TuiOut> + Layout<TuiOut>
> Content<TuiOut> for FieldH<ItemTheme, Label, Value> {
fn content (&self) -> impl Draw<TuiOut> + Layout<TuiOut> + '_ {
let Self(ItemTheme { darkest, dark, lightest, .. }, title, value) = self;
row!(
Tui::fg_bg(dark.rgb, darkest.rgb, ""),
Tui::fg_bg(lightest.rgb, dark.rgb, title),
Tui::fg_bg(dark.rgb, darkest.rgb, ""),
Tui::fg_bg(lightest.rgb, darkest.rgb, Tui::bold(true, value)),
)
}
}
impl<
Label: Draw<TuiOut> + Layout<TuiOut>,
Value: Draw<TuiOut> + Layout<TuiOut>
> Content<TuiOut> for FieldV<ItemTheme, Label, Value> {
fn content (&self) -> impl Draw<TuiOut> + Layout<TuiOut> + '_ {
let Self(ItemTheme { darkest, dark, lightest, .. }, title, value) = self;
Bsp::n(
Align::w(Tui::bg(darkest.rgb, Tui::fg(lightest.rgb, Tui::bold(true, value)))),
Fill::x(Align::w(row!(
Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "")),
Tui::bg(dark.rgb, Tui::fg(lightest.rgb, title)),
Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "")),
)))
)
}
}
// TODO:
pub struct Field<T, U> {
pub direction: Direction,
@ -44,8 +12,8 @@ pub struct Field<T, U> {
pub value_bg: Option<ItemColor>,
pub value_align: Option<Direction>,
}
impl<T: Draw<TuiOut>, U: Draw<TuiOut>> Content<TuiOut> for Field<T, U> {
fn content (&self) -> impl Draw<TuiOut> + Layout<TuiOut> + '_ {
impl<T: Content<TuiOut>, U: Content<TuiOut>> HasContent<TuiOut> for Field<T, U> {
fn content (&self) -> impl Content<TuiOut> {
"TODO"
}
}

View file

@ -13,21 +13,21 @@ impl<T> Phat<T> {
pub const LO: &'static str = "";
pub const HI: &'static str = "";
/// A phat line
pub fn lo (fg: Color, bg: Color) -> impl Draw<TuiOut> + Layout<TuiOut> {
Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(Self::LO)))
pub fn lo (fg: Color, bg: Color) -> impl Content<TuiOut> {
Fixed::Y(1, Tui::fg_bg(fg, bg, RepeatH(Self::LO)))
}
/// A phat line
pub fn hi (fg: Color, bg: Color) -> impl Draw<TuiOut> + Layout<TuiOut> {
Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(Self::HI)))
pub fn hi (fg: Color, bg: Color) -> impl Content<TuiOut> {
Fixed::Y(1, Tui::fg_bg(fg, bg, RepeatH(Self::HI)))
}
}
impl<T: Layout<TuiOut> + Draw<TuiOut>> Content<TuiOut> for Phat<T> {
fn content (&self) -> impl Draw<TuiOut> + Layout<TuiOut> + '_ {
impl<T: Content<TuiOut>> HasContent<TuiOut> for Phat<T> {
fn content (&self) -> impl Content<TuiOut> {
let [fg, bg, hi, lo] = self.colors;
let top = Fixed::y(1, Self::lo(bg, hi));
let low = Fixed::y(1, Self::hi(bg, lo));
let content = Tui::fg_bg(fg, bg, &self.content);
Min::xy(self.width, self.height, Bsp::s(top, Bsp::n(low, Fill::xy(content))))
Min::XY(self.width, self.height, Bsp::s(top, Bsp::n(low, Fill::XY(content))))
}
}

View file

@ -2,11 +2,6 @@ use crate::*;
use ratatui::prelude::Position;
pub struct Repeat<'a>(pub &'a str);
impl Layout<TuiOut> for Repeat<'_> {
fn layout (&self, to: [u16;4]) -> [u16;4] {
to
}
}
impl Draw<TuiOut> for Repeat<'_> {
fn draw (&self, to: &mut TuiOut) {
let [x, y, w, h] = to.area().xywh();
@ -23,11 +18,6 @@ impl Draw<TuiOut> for Repeat<'_> {
}
pub struct RepeatV<'a>(pub &'a str);
impl Layout<TuiOut> for RepeatV<'_> {
fn layout (&self, to: [u16;4]) -> [u16;4] {
to
}
}
impl Draw<TuiOut> for RepeatV<'_> {
fn draw (&self, to: &mut TuiOut) {
let [x, y, _w, h] = to.area().xywh();
@ -40,11 +30,6 @@ impl Draw<TuiOut> for RepeatV<'_> {
}
pub struct RepeatH<'a>(pub &'a str);
impl Layout<TuiOut> for RepeatH<'_> {
fn layout (&self, to: [u16;4]) -> [u16;4] {
to
}
}
impl Draw<TuiOut> for RepeatH<'_> {
fn draw (&self, to: &mut TuiOut) {
let [x, y, w, _h] = to.area().xywh();

View file

@ -1,18 +1,10 @@
use crate::*;
use crate::ratatui::prelude::Position;
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
tui_layout!(|self: &str, to|to.center_xy([width_chars_max(to.w(), self), 1]));
tui_draw!(|self: &str, to|{
let [x, y, w, ..] = self.layout(to.area());
to.text(&self, x, y, w)
});
tui_layout!(|self: Arc<str>, to|self.as_ref().layout(to));
tui_draw!(|self: Arc<str>, to|self.as_ref().draw(to));
tui_layout!(|self: String, to|self.as_str().layout(to));
tui_draw!(|self: String, to|self.as_str().draw(to));
impl Layout<TuiOut> for &str { fn layout (&self, to: [u16;4]) -> [u16;4] { to.center_xy([width_chars_max(to.w(), self), 1]) } }
impl Draw<TuiOut> for &str { fn draw (&self, to: &mut TuiOut) { let [x, y, w, ..] = self.layout(to.area()); to.text(&self, x, y, w) } }
impl Layout<TuiOut> for String { fn layout (&self, to: [u16;4]) -> [u16;4] { self.as_str().layout(to) } }
impl Draw<TuiOut> for String { fn draw (&self, to: &mut TuiOut) { self.as_str().draw(to) } }
fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
let mut width: u16 = 0;
@ -48,19 +40,13 @@ pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
impl<'a, T: AsRef<str>> TrimString<T> {
fn as_ref (&self) -> TrimStringRef<'_, T> {
TrimStringRef(self.0, &self.1)
}
}
impl<'a, T: AsRef<str>> Layout<TuiOut> for TrimString<T> {
fn layout (&self, to: [u16; 4]) -> [u16;4] {
Layout::layout(&self.as_ref(), to)
}
fn as_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
}
impl<'a, T: AsRef<str>> Draw<TuiOut> for TrimString<T> {
fn draw (&self, to: &mut TuiOut) {
Draw::draw(&self.as_ref(), to)
}
fn draw (&self, to: &mut TuiOut) { Draw::draw(&self.as_ref(), to) }
}
impl<'a, T: AsRef<str>> Layout<TuiOut> for TrimString<T> {
fn layout (&self, to: [u16; 4]) -> [u16;4] { Layout::layout(&self.as_ref(), to) }
}
/// Displays a borrowed [str]-like with fixed maximum width
@ -68,7 +54,7 @@ impl<'a, T: AsRef<str>> Draw<TuiOut> for TrimString<T> {
/// Width is computed using [unicode_width].
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
impl<T: AsRef<str>> Layout<TuiOut> for TrimStringRef<'_, T> {
impl<'a, T: AsRef<str>> Layout<TuiOut> for TrimStringRef<'a, T> {
fn layout (&self, to: [u16; 4]) -> [u16;4] {
[to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()]
}

View file

@ -1,11 +1,11 @@
use crate::*;
impl<
A: Draw<TuiOut> + Layout<TuiOut>,
B: Draw<TuiOut> + Layout<TuiOut>,
C: Draw<TuiOut> + Layout<TuiOut>,
> Content<TuiOut> for Tryptich<A, B, C> {
fn content (&self) -> impl Draw<TuiOut> + Layout<TuiOut> + '_ {
A: Content<TuiOut>,
B: Content<TuiOut>,
C: Content<TuiOut>,
> HasContent<TuiOut> for Tryptich<A, B, C> {
fn content (&self) -> impl Content<TuiOut> {
let Self { top, h, left: (w_a, ref a), middle: (w_b, ref b), right: (w_c, ref c) } = *self;
Fixed::y(h, if top {
Bsp::a(
@ -17,13 +17,12 @@ impl<
)
} else {
Bsp::a(
Fill::xy(Align::c(Fixed::x(w_b, Align::x(Tui::bg(Color::Reset, b))))),
Fill::XY(Align::c(Fixed::x(w_b, Align::x(Tui::bg(Color::Reset, b))))),
Bsp::a(
Fill::xy(Align::w(Fixed::x(w_a, Tui::bg(Color::Reset, a)))),
Fill::xy(Align::e(Fixed::x(w_c, Tui::bg(Color::Reset, c)))),
Fill::XY(Align::w(Fixed::x(w_a, Tui::bg(Color::Reset, a)))),
Fill::XY(Align::e(Fixed::x(w_c, Tui::bg(Color::Reset, c)))),
),
)
})
}
}

View file

@ -27,9 +27,6 @@ impl Out for TuiOut {
}
}
impl Layout<TuiOut> for fn(&mut TuiOut) {}
impl TuiOut {
/// Spawn the output thread.
pub fn run_output <T: Draw<TuiOut> + Send + Sync + 'static> (

35
tui/src/tui_test.rs Normal file
View file

@ -0,0 +1,35 @@
use crate::*;
#[test] fn test_tui_engine () -> Usually<()> {
//use std::sync::{Arc, RwLock};
struct TestComponent(String);
impl Content<TuiOut> for TestComponent {
fn content (&self) -> impl Draw<TuiOut> {
Some(self.0.as_str())
}
}
impl Handle<TuiIn> for TestComponent {
fn handle (&mut self, _from: &TuiIn) -> Perhaps<bool> {
Ok(None)
}
}
let engine = Tui::new()?;
engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
let state = TestComponent("hello world".into());
let _state = std::sync::Arc::new(std::sync::RwLock::new(state));
//engine.run(&state)?;
Ok(())
}
//#[test] fn test_parse_key () {
////use KeyModifiers as Mods;
//let _test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y)));
////test(":x",
////KeyEvent::new(KeyCode::Char('x'), Mods::NONE));
////test(":ctrl-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL));
////test(":alt-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::ALT));
////test(":shift-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::SHIFT));
////test(":ctrl-alt-shift-x",
////KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT ));
//}