From ca862b9802524ee1713c0088c7a525ad07f370a0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 8 Sep 2025 18:44:42 +0300 Subject: [PATCH] dsl, output, tui: add tests, examples, root dispatchers --- Justfile | 9 +- bacon.toml | 44 +++++ dsl/src/dsl_test.rs | 90 +++++----- output/src/layout/layout_cond.rs | 20 +-- output/src/layout/layout_map.rs | 10 +- output/src/output.rs | 51 +++--- output/src/{test.rs => output_test.rs} | 15 +- output/src/view.rs | 137 +++++++++++++++ tui/Cargo.toml | 3 + tui/examples/tui.rs | 226 ------------------------- tui/examples/tui_00.rs | 124 ++++++++++++++ tui/examples/tui_01.rs | 105 ++++++++++++ tui/src/lib.rs | 59 ------- tui/src/tui.rs | 78 +++++++++ tui/src/tui_content.rs | 8 + tui/src/tui_test.rs | 35 ++++ 16 files changed, 637 insertions(+), 377 deletions(-) create mode 100644 bacon.toml rename output/src/{test.rs => output_test.rs} (92%) create mode 100644 output/src/view.rs delete mode 100644 tui/examples/tui.rs create mode 100644 tui/examples/tui_00.rs create mode 100644 tui/examples/tui_01.rs delete mode 100644 tui/src/lib.rs create mode 100644 tui/src/tui.rs create mode 100644 tui/src/tui_test.rs diff --git a/Justfile b/Justfile index 3dbff77..1d5f794 100644 --- a/Justfile +++ b/Justfile @@ -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 diff --git a/bacon.toml b/bacon.toml new file mode 100644 index 0000000..8a5516f --- /dev/null +++ b/bacon.toml @@ -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 diff --git a/dsl/src/dsl_test.rs b/dsl/src/dsl_test.rs index 253b4c9..fb6d6f7 100644 --- a/dsl/src/dsl_test.rs +++ b/dsl/src/dsl_test.rs @@ -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(()) } diff --git a/output/src/layout/layout_cond.rs b/output/src/layout/layout_cond.rs index 103166f..aceb535 100644 --- a/output/src/layout/layout_cond.rs +++ b/output/src/layout/layout_cond.rs @@ -1,15 +1,15 @@ use crate::*; /// Show an item only when a condition is true. -pub struct When(pub bool, pub A); -impl When { +pub struct When(bool, T, PhantomData); +impl When { /// 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> Layout for When { - fn layout (&self, to: E::Area) -> E::Area { - let Self(cond, item) = self; - let mut area = E::Area::zero(); +impl> Layout for When { + fn layout (&self, to: O::Area) -> O::Area { + let Self(cond, item, ..) = self; + let mut area = O::Area::zero(); if *cond { let item_area = item.layout(to); area[0] = item_area.x(); @@ -20,9 +20,9 @@ impl> Layout for When { area.into() } } -impl> Draw for When { - fn draw (&self, to: &mut E) { - let Self(cond, item) = self; +impl> Draw for When { + fn draw (&self, to: &mut O) { + let Self(cond, item, ..) = self; if *cond { item.draw(to) } } } diff --git a/output/src/layout/layout_map.rs b/output/src/layout/layout_map.rs index cb230e2..f509dcc 100644 --- a/output/src/layout/layout_map.rs +++ b/output/src/layout/layout_map.rs @@ -80,16 +80,16 @@ impl<'a, O, A, B, I, F, G> Layout for Map 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 for Map where diff --git a/output/src/output.rs b/output/src/output.rs index 29ff6b2..00c7bb9 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -3,15 +3,9 @@ #![feature(impl_trait_in_assoc_type)] #![feature(const_precise_live_drops)] #![feature(type_changing_struct_update)] +#![feature(anonymous_lifetime_in_impl_trait)] //#![feature(non_lifetime_binders)] -mod content; pub use self::content::*; -mod draw; pub use self::draw::*; -mod group; pub use self::group::*; -mod layout; pub use self::layout::*; -mod space; pub use self::space::*; -mod thunk; pub use self::thunk::*; -mod widget; pub use self::widget::*; pub(crate) use self::Direction::*; pub(crate) use std::fmt::{Debug, Display}; pub(crate) use std::marker::PhantomData; @@ -19,38 +13,49 @@ pub(crate) use std::ops::{Add, Sub, Mul, Div}; pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::Relaxed}}; pub(crate) use tengri_core::*; -/// Draw target. +/// Drawing target. pub trait Out: Send + Sync + Sized { /// Unit of length type Unit: Coordinate; + /// Rectangle without offset type Size: Size; + /// Rectangle with offset type Area: Area; - /// 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 + ?Sized> (&mut self, area: Self::Area, content: &'t T); - fn place <'t, T: Draw + Layout + ?Sized> (&mut self, content: &'t T) { + /// Render drawable in area specified by `T::layout(self.area())` + #[inline] fn place <'t, T: Draw + Layout + ?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 + ?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 + Layout> Draw 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::*; diff --git a/output/src/test.rs b/output/src/output_test.rs similarity index 92% rename from output/src/test.rs rename to output/src/output_test.rs index ce859ed..958f485 100644 --- a/output/src/test.rs +++ b/output/src/output_test.rs @@ -89,8 +89,8 @@ macro_rules! test_op_transform { (None, Some(y)) => Some($Op::y(y, content)), _ => None } { - assert_eq!(Content::layout(&op, [x, y, w, h]), - Draw::layout(&op, [x, y, w, h])); + //assert_eq!(Content::layout(&op, [x, y, w, h]), + //Draw::layout(&op, [x, y, w, h])); } } } @@ -122,10 +122,10 @@ proptest! { h in u16::MIN..u16::MAX, ) { let bsp = Bsp(d, a, b); - assert_eq!( - Content::layout(&bsp, [x, y, w, h]), - Draw::layout(&bsp, [x, y, w, h]), - ); + //assert_eq!( + //Content::layout(&bsp, [x, y, w, h]), + //Draw::layout(&bsp, [x, y, w, h]), + //); } } @@ -142,7 +142,8 @@ proptest! { fn area_mut (&mut self) -> &mut [u16;4] { &mut self.0 } - fn place + ?Sized> (&mut self, _: [u16;4], _: &T) { + fn place_at + ?Sized> (&mut self, area: [u16;4], _: &T) { + println!("place_at: {area:?}"); () } } diff --git a/output/src/view.rs b/output/src/view.rs new file mode 100644 index 0000000..bd1cc51 --- /dev/null +++ b/output/src/view.rs @@ -0,0 +1,137 @@ +use crate::*; +use ::tengri_dsl::{Dsl, DslExpr, DslWord, DslNs}; + +pub trait View { + fn view_expr <'a> (&'a self, output: &mut O, expr: &'a impl DslExpr) -> Usually { + Err(format!("View::view_expr: no exprs defined: {expr:?}").into()) + } + fn view_word <'a> (&'a self, output: &mut O, word: &'a impl DslWord) -> Usually { + Err(format!("View::view_word: no words defined: {word:?}").into()) + } + fn view <'a> (&'a self, output: &mut O, dsl: &'a impl Dsl) -> Usually { + 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 where + S: View + + 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 Perhapss remain in scope. + match frags.next() { + + Some("when") => output.place(&When::new( + state.from(arg0?)?.unwrap(), + Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap()) + )), + + Some("either") => output.place(&Either::new( + state.from(arg0?)?.unwrap(), + Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap()), + Thunk::new(move|output: &mut O|state.view(output, &arg2).unwrap()) + )), + + Some("bsp") => output.place(&{ + let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap()); + let b = Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap()); + match frags.next() { + Some("n") => Bsp::n(a, b), + Some("s") => Bsp::s(a, b), + Some("e") => Bsp::e(a, b), + Some("w") => Bsp::w(a, b), + Some("a") => Bsp::a(a, b), + Some("b") => Bsp::b(a, b), + frag => unimplemented!("bsp/{frag:?}") + } + }), + + Some("align") => output.place(&{ + let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap()); + match frags.next() { + Some("n") => Align::n(a), + Some("s") => Align::s(a), + Some("e") => Align::e(a), + Some("w") => Align::w(a), + Some("x") => Align::x(a), + Some("y") => Align::y(a), + Some("c") => Align::c(a), + frag => unimplemented!("align/{frag:?}") + } + }), + + Some("fill") => output.place(&{ + let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap()); + match frags.next() { + Some("x") => Fill::X(a), + Some("y") => Fill::Y(a), + Some("xy") => Fill::XY(a), + frag => unimplemented!("fill/{frag:?}") + } + }), + + Some("fixed") => output.place(&{ + let axis = frags.next(); + let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!() }; + let cb = Thunk::new(move|output: &mut O|state.view(output, &arg).unwrap()); + match axis { + Some("x") => Fixed::X(state.from(arg0?)?.unwrap(), cb), + Some("y") => Fixed::Y(state.from(arg0?)?.unwrap(), cb), + Some("xy") => Fixed::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb), + frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})", + head.src()?.unwrap_or_default().split("/").next()) + } + }), + + Some("min") => output.place(&{ + let c = match frags.next() { + Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!() + }; + let cb = Thunk::new(move|output: &mut O|state.view(output, &c).unwrap()); + match frags.next() { + Some("x") => Min::X(state.from(arg0?)?.unwrap(), cb), + Some("y") => Min::Y(state.from(arg0?)?.unwrap(), cb), + Some("xy") => Min::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb), + frag => unimplemented!("min/{frag:?}") + } + }), + + Some("max") => output.place(&{ + let c = match frags.next() { + Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!() + }; + let cb = Thunk::new(move|output: &mut O|state.view(output, &c).unwrap()); + match frags.next() { + Some("x") => Max::X(state.from(arg0?)?.unwrap(), cb), + Some("y") => Max::Y(state.from(arg0?)?.unwrap(), cb), + Some("xy") => Max::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb), + frag => unimplemented!("max/{frag:?}") + } + }), + + _ => return Ok(false) + + }; + Ok(true) +} diff --git a/tui/Cargo.toml b/tui/Cargo.toml index 9dfa085..3e9ab61 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -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" ] diff --git a/tui/examples/tui.rs b/tui/examples/tui.rs deleted file mode 100644 index 2cc35b5..0000000 --- a/tui/examples/tui.rs +++ /dev/null @@ -1,226 +0,0 @@ -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)] pub struct Example( - usize, - Measure -); - -impl Example { - fn new () -> Arc> { - 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))))))) - }, - ]; -} - -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))) -}); - -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 { - state.0 = (state.0 + 1) % EXAMPLES.len(); - Ok(None) - } - fn prev (state: &mut Example) -> Perhaps { - state.0 = if state.0 > 0 { state.0 - 1 } else { EXAMPLES.len() - 1 }; - Ok(None) - } -} - -//#[tengri_proc::view(TuiOut)] -//impl Example { - //pub fn title (&self) -> impl Draw + 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 + use<'_> { - //Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", EXAMPLES[self.0])))).boxed() - //} - //pub fn hello (&self) -> impl Draw + use<'_> { - //Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed() - //} - //pub fn world (&self) -> impl Draw + use<'_> { - //Tui::bg(Color::Rgb(100, 10, 10), "world").boxed() - //} - //pub fn hello_world (&self) -> impl Draw + use<'_> { - //"Hello world!".boxed() - //} - //pub fn map_e (&self) -> impl Draw + use<'_> { - //Map::east(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() - //} - //pub fn map_s (&self) -> impl Draw + use<'_> { - //Map::south(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() - //} -//} -/* - - fn content (&self) -> dyn Draw { - 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(()) - //}))))) - //})) - } -*/ diff --git a/tui/examples/tui_00.rs b/tui/examples/tui_00.rs new file mode 100644 index 0000000..a4aa475 --- /dev/null +++ b/tui/examples/tui_00.rs @@ -0,0 +1,124 @@ +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); + +handle!(TuiIn: |self: Example, input|Ok(None)); +enum ExampleCommand { Next, Prev } +impl ExampleCommand { + fn eval (&self, state: &mut Example) -> Perhaps { + 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)) + } + } + } +} + +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 = 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 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> { + 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))))))) + }, + ]; +} diff --git a/tui/examples/tui_01.rs b/tui/examples/tui_01.rs new file mode 100644 index 0000000..58d505e --- /dev/null +++ b/tui/examples/tui_01.rs @@ -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 + 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 + use<'_> { + //Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", VIEWS[self.0])))).boxed() + //} + //pub fn hello (&self) -> impl Draw + use<'_> { + //Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed() + //} + //pub fn world (&self) -> impl Draw + use<'_> { + //Tui::bg(Color::Rgb(100, 10, 10), "world").boxed() + //} + //pub fn hello_world (&self) -> impl Draw + use<'_> { + //"Hello world!".boxed() + //} + //pub fn map_e (&self) -> impl Draw + use<'_> { + //Map::east(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() + //} + //pub fn map_s (&self) -> impl Draw + use<'_> { + //Map::south(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() + //} +//} + + //fn content (&self) -> dyn Draw { + //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(()) + ////}))))) + ////})) + //} diff --git a/tui/src/lib.rs b/tui/src/lib.rs deleted file mode 100644 index a2ca42b..0000000 --- a/tui/src/lib.rs +++ /dev/null @@ -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 for TestComponent { - fn content (&self) -> impl Draw { - Some(self.0.as_str()) - } - } - impl Handle for TestComponent { - fn handle (&mut self, _from: &TuiIn) -> Perhaps { - 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 )); -} diff --git a/tui/src/tui.rs b/tui/src/tui.rs new file mode 100644 index 0000000..c54a44e --- /dev/null +++ b/tui/src/tui.rs @@ -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 where + S: View + + 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::::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::::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) +} diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 184d7ea..90e6f3a 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -37,6 +37,14 @@ impl Tui { } }); +#[macro_export] macro_rules! tui_content ((|$self:ident:$Self:ty|$sexpr:expr)=>{ + impl Content for $Self { + fn content (&$self) -> impl Draw + Layout + '_ { + $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::*; diff --git a/tui/src/tui_test.rs b/tui/src/tui_test.rs new file mode 100644 index 0000000..9db2a6d --- /dev/null +++ b/tui/src/tui_test.rs @@ -0,0 +1,35 @@ +use crate::*; +#[test] fn test_tui_engine () -> Usually<()> { + //use std::sync::{Arc, RwLock}; + struct TestComponent(String); + impl Content for TestComponent { + fn content (&self) -> impl Draw + Layout { + Some(self.0.as_str()) + } + } + impl Handle for TestComponent { + fn handle (&mut self, _from: &TuiIn) -> Perhaps { + 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 )); +//}