diff --git a/Cargo.lock b/Cargo.lock index be9dae7..177e248 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1164,9 +1164,8 @@ dependencies = [ [[package]] name = "tengri" -version = "0.15.0" +version = "0.14.0" dependencies = [ - "anyhow", "atomic_float", "better-panic", "bumpalo", @@ -1185,7 +1184,7 @@ dependencies = [ [[package]] name = "tengri_proc" -version = "0.15.0" +version = "0.14.0" dependencies = [ "dizzle", "heck", diff --git a/Cargo.toml b/Cargo.toml index 13ad326..e9e0667 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,14 @@ atomic_float = { version = "1" } better-panic = { version = "0.3.0" } bumpalo = { version = "3.19.0", optional = true } crossterm = { version = "0.29.0" } +heck = { version = "0.5" } palette = { version = "0.7.6", features = [ "random" ] } +proc-macro2 = { version = "1", features = ["span-locations"] } quanta = { version = "0.12.3" } +quote = { version = "1" } rand = { version = "0.8.5" } ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] } +syn = { version = "2", features = ["full", "extra-traits"] } unicode-width = { version = "0.2" } dizzle = { path = "../dizzle" } diff --git a/examples/tui_00.rs b/examples/tui_00.rs index 94bf069..a94b936 100644 --- a/examples/tui_00.rs +++ b/examples/tui_00.rs @@ -1,4 +1,5 @@ -use ::{std::{io::stdout, sync::{Arc, RwLock}}, ratatui::style::Color, tengri::*}; +use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, + tengri::{*, input::*, output::*}, tengri_tui::*}; tui_main!(State { cursor: 10, ..Default::default() }); namespace!(State: bool {}); namespace!(State: u16 {}); @@ -24,7 +25,7 @@ fn draw_example (state: &State, to: &mut TuiOut) { 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(state, CstIter::new(src))); - state.size.of(Bsp::s(title, Bsp::n(code, state.understand(to, &src).unwrap()))).draw(to) + state.size.of(Bsp::s(title, Bsp::n(code, state.view(to, &src).unwrap()))).draw(to) } impl Action { const BINDS: &'static str = stringify! { (@left prev) (@right next) }; diff --git a/examples/tui_01.rs b/examples/tui_01.rs index 9fa7a93..9d62bdf 100644 --- a/examples/tui_01.rs +++ b/examples/tui_01.rs @@ -1,4 +1,3 @@ -use ::{std::sync::{Arc, RwLock}, ratatui::style::Color, tengri::*}; fn main () {} //#[tengri_proc::expose] diff --git a/proc/Cargo.toml b/proc/Cargo.toml index c509384..839b1c6 100644 --- a/proc/Cargo.toml +++ b/proc/Cargo.toml @@ -1,18 +1,18 @@ [package] name = "tengri_proc" description = "UI metaframework, procedural macros." -version = "0.15.0" -edition = "2024" +version = { workspace = true } +edition = { workspace = true } [lib] proc-macro = true [dependencies] dizzle = { path = "../../dizzle" } -quote = { version = "1" } -syn = { version = "2", features = ["full", "extra-traits"] } -heck = { version = "0.5" } -proc-macro2 = { version = "1", features = ["span-locations"] } +syn = { workspace = true } +quote = { workspace = true } +proc-macro2 = { workspace = true } +heck = { workspace = true } [target.'cfg(target_os = "linux")'] rustflags = ["-C", "link-arg=-fuse-ld=mold"] diff --git a/proc/README.md b/proc/README.md deleted file mode 100644 index 48f19c8..0000000 --- a/proc/README.md +++ /dev/null @@ -1 +0,0 @@ -currently unused. diff --git a/src/tengri.rs b/src/tengri.rs index e542ea2..76fc703 100644 --- a/src/tengri.rs +++ b/src/tengri.rs @@ -4,20 +4,18 @@ #![feature(const_option_ops)] #![feature(const_precise_live_drops)] #![feature(const_trait_impl)] +#![feature(if_let_guard)] #![feature(impl_trait_in_assoc_type)] #![feature(step_trait)] #![feature(trait_alias)] #![feature(type_alias_impl_trait)] #![feature(type_changing_struct_update)] -mod tengri_impl; -mod tengri_trait; pub use self::tengri_trait::*; -mod tengri_struct; pub use self::tengri_struct::*; -#[cfg(test)] pub(crate) use proptest_derive::Arbitrary; -pub extern crate dizzle; pub use dizzle::*; -pub extern crate atomic_float; pub(crate) use atomic_float::AtomicF64; -pub extern crate palette; pub(crate) use ::palette::{*, convert::*, okhsl::*}; -pub extern crate better_panic; pub(crate) use better_panic::{Settings, Verbosity}; -pub extern crate unicode_width; pub(crate) use unicode_width::*; + +//pub(crate) use quanta::Clock; + +pub extern crate atomic_float; +pub(crate) use atomic_float::AtomicF64; + pub extern crate ratatui; pub(crate) use ::ratatui::{ prelude::{Color, Style, Buffer, Position}, style::{Stylize, Modifier, Color::*}, @@ -25,19 +23,39 @@ pub extern crate ratatui; pub(crate) use ::ratatui::{ layout::{Size, Rect}, buffer::Cell }; -pub extern crate crossterm; pub(crate) use ::crossterm::{ + +pub extern crate crossterm; +pub(crate) use ::crossterm::{ ExecutableCommand, terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, }; + +pub extern crate palette; +pub(crate) use ::palette::{*, convert::*, okhsl::*}; + +pub extern crate better_panic; +pub(crate) use better_panic::{Settings, Verbosity}; + +pub extern crate unicode_width; +pub(crate) use unicode_width::*; + +//#[cfg(test)] extern crate tengri_proc; +mod tengri_impl; +mod tengri_type; pub use self::tengri_type::*; +mod tengri_trait; pub use self::tengri_trait::*; +mod tengri_struct; pub use self::tengri_struct::*; + +#[macro_export] pub extern crate dizzle; +pub use dizzle::*; + +use std::{time::Duration, thread::{spawn, JoinHandle}, io::Write}; pub(crate) use ::std::{ - io::{stdout, Stdout, Write}, + io::{stdout, Stdout}, sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}}, fmt::{Debug, Display}, ops::{Add, Sub, Mul, Div}, marker::PhantomData, - time::Duration, - thread::{spawn, JoinHandle} }; // Define macros first, so that private macros are available in private modules: @@ -74,24 +92,144 @@ pub(crate) use ::std::{ } /// Stack things on top of each other, -#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp -}}); +#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }}); /// Stack southward. -#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp -}}); +#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}); /// Stack northward. -#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp -}}); +#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }}); /// Stack eastward. -#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{ - let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp -}}); +#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}); + +/// Define layout operation. +#[cfg(feature = "dsl")] pub fn evaluate_output_expression <'a, O: Out + 'a, S> ( + state: &S, output: &mut O, expr: &'a impl Expression +) -> Usually where + S: Understand + + for<'b>Namespace<'b, bool> + + for<'b>Namespace<'b, O::Unit> +{ + // First element of expression is used for dispatch. + // Dispatch is proto-namespaced using separator character + let head = expr.head()?; + let mut frags = head.src()?.unwrap_or_default().split("/"); + // The rest of the tokens in the expr are arguments. + // Their meanings depend on the dispatched operation + let args = expr.tail(); + let arg0 = args.head(); + let tail0 = args.tail(); + let arg1 = tail0.head(); + let tail1 = tail0.tail(); + let arg2 = tail1.head(); + // And we also have to do the above binding dance + // so that the Perhapss remain in scope. + match frags.next() { + + Some("when") => output.place(&When::new( + state.namespace(arg0?)?.unwrap(), + Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()) + )), + + Some("either") => output.place(&Either::new( + state.namespace(arg0?)?.unwrap(), + Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()), + Thunk::new(move|output: &mut O|state.understand(output, &arg2).unwrap()) + )), + + Some("bsp") => output.place(&{ + let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap()); + let b = Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()); + match frags.next() { + Some("n") => Bsp::n(a, b), + Some("s") => Bsp::s(a, b), + Some("e") => Bsp::e(a, b), + Some("w") => Bsp::w(a, b), + Some("a") => Bsp::a(a, b), + Some("b") => Bsp::b(a, b), + frag => unimplemented!("bsp/{frag:?}") + } + }), + + Some("align") => output.place(&{ + let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap()); + match frags.next() { + Some("n") => Align::n(a), + Some("s") => Align::s(a), + Some("e") => Align::e(a), + Some("w") => Align::w(a), + Some("x") => Align::x(a), + Some("y") => Align::y(a), + Some("c") => Align::c(a), + frag => unimplemented!("align/{frag:?}") + } + }), + + Some("fill") => output.place(&{ + let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap()); + match frags.next() { + Some("xy") | None => Fill::XY(a), + Some("x") => Fill::X(a), + Some("y") => Fill::Y(a), + frag => unimplemented!("fill/{frag:?}") + } + }), + + Some("fixed") => output.place(&{ + let axis = frags.next(); + let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") }; + let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); + match axis { + Some("xy") | None => Fixed::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), + Some("x") => Fixed::X(state.namespace(arg0?)?.unwrap(), cb), + Some("y") => Fixed::Y(state.namespace(arg0?)?.unwrap(), cb), + frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})", + head.src()?.unwrap_or_default().split("/").next()) + } + }), + + Some("min") => output.place(&{ + let axis = frags.next(); + let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") }; + let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); + match axis { + Some("xy") | None => Min::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), + Some("x") => Min::X(state.namespace(arg0?)?.unwrap(), cb), + Some("y") => Min::Y(state.namespace(arg0?)?.unwrap(), cb), + frag => unimplemented!("min/{frag:?}") + } + }), + + Some("max") => output.place(&{ + let axis = frags.next(); + let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") }; + let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); + match axis { + Some("xy") | None => Max::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), + Some("x") => Max::X(state.namespace(arg0?)?.unwrap(), cb), + Some("y") => Max::Y(state.namespace(arg0?)?.unwrap(), cb), + frag => unimplemented!("max/{frag:?}") + } + }), + + Some("push") => output.place(&{ + let axis = frags.next(); + let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") }; + let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); + match axis { + Some("xy") | None => Push::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), + Some("x") => Push::X(state.namespace(arg0?)?.unwrap(), cb), + Some("y") => Push::Y(state.namespace(arg0?)?.unwrap(), cb), + frag => unimplemented!("push/{frag:?}") + } + }), + + _ => return Ok(false) + + }; + Ok(true) +} /// Implement [Command] for given `State` and `handler` #[macro_export] macro_rules! command { @@ -144,9 +282,7 @@ pub(crate) use ::std::{ #[macro_export] macro_rules! tui_main { ($expr:expr) => { fn main () -> Usually<()> { - let engine = ::tengri::Tui::new(Box::new(stdout()))?; - let state = ::std::sync::Arc::new(std::sync::RwLock::new($expr)); - engine.run(true, &state)?; + tengri::Tui::new(stdout()).run(true, $expr)?; Ok(()) } }; @@ -317,178 +453,21 @@ pub fn tui_input + Send + Sync + 'static> ( }) } -/// Define layout operation. -/// -/// ``` -/// # use tengri::*; -/// struct Target { xywh: XYWH /*platform-specific*/} -/// impl tengri::Out for Target { -/// type Unit = u16; -/// fn area (&self) -> XYWH { self.xywh } -/// fn area_mut (&mut self) -> &mut XYWH { &mut self.xywh } -/// fn place_at <'t, T: Draw + ?Sized> (&mut self, area: XYWH, content: &'t T) {} -/// } -/// -/// struct State {/*app-specific*/} -/// impl<'b> Namespace<'b, bool> for State {} -/// impl<'b> Namespace<'b, u16> for State {} -/// impl Understand for State {} -/// -/// # fn main () -> tengri::Usually<()> { -/// let state = State {}; -/// let mut target = Target { xywh: Default::default() }; -/// evaluate_output_expression(&state, &mut target, &"")?; -/// evaluate_output_expression(&state, &mut target, &"(when true (text hello))")?; -/// evaluate_output_expression(&state, &mut target, &"(either true (text hello) (text world))")?; -/// // TODO test all -/// # Ok(()) } -/// ``` -#[cfg(feature = "dsl")] pub fn evaluate_output_expression <'a, O: Out + 'a, S> ( - state: &S, output: &mut O, expr: &'a impl Expression -) -> Usually where - S: Understand - + for<'b>Namespace<'b, bool> - + for<'b>Namespace<'b, O::Unit> -{ - // First element of expression is used for dispatch. - // Dispatch is proto-namespaced using separator character - let head = expr.head()?; - let mut frags = head.src()?.unwrap_or_default().split("/"); - // The rest of the tokens in the expr are arguments. - // Their meanings depend on the dispatched operation - let args = expr.tail(); - let arg0 = args.head(); - let tail0 = args.tail(); - let arg1 = tail0.head(); - let tail1 = tail0.tail(); - let arg2 = tail1.head(); - // And we also have to do the above binding dance - // so that the Perhapss remain in scope. - match frags.next() { - - Some("when") => output.place(&When::new( - state.namespace(arg0?)?.unwrap(), - Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()) - )), - - Some("either") => output.place(&Either::new( - state.namespace(arg0?)?.unwrap(), - Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()), - Thunk::new(move|output: &mut O|state.understand(output, &arg2).unwrap()) - )), - - Some("bsp") => output.place(&{ - let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap()); - let b = Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()); - match frags.next() { - Some("n") => Bsp::n(a, b), - Some("s") => Bsp::s(a, b), - Some("e") => Bsp::e(a, b), - Some("w") => Bsp::w(a, b), - Some("a") => Bsp::a(a, b), - Some("b") => Bsp::b(a, b), - frag => unimplemented!("bsp/{frag:?}") - } - }), - - Some("align") => output.place(&{ - let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap()); - match frags.next() { - Some("n") => Align::n(a), - Some("s") => Align::s(a), - Some("e") => Align::e(a), - Some("w") => Align::w(a), - Some("x") => Align::x(a), - Some("y") => Align::y(a), - Some("c") => Align::c(a), - frag => unimplemented!("align/{frag:?}") - } - }), - - Some("fill") => output.place(&{ - let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap()); - match frags.next() { - Some("xy") | None => Fill::XY(a), - Some("x") => Fill::X(a), - Some("y") => Fill::Y(a), - frag => unimplemented!("fill/{frag:?}") - } - }), - - Some("fixed") => output.place(&{ - let axis = frags.next(); - let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") }; - let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); - match axis { - Some("xy") | None => Fixed::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => Fixed::X(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => Fixed::Y(state.namespace(arg0?)?.unwrap(), cb), - frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})", - head.src()?.unwrap_or_default().split("/").next()) - } - }), - - Some("min") => output.place(&{ - let axis = frags.next(); - let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") }; - let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); - match axis { - Some("xy") | None => Min::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => Min::X(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => Min::Y(state.namespace(arg0?)?.unwrap(), cb), - frag => unimplemented!("min/{frag:?}") - } - }), - - Some("max") => output.place(&{ - let axis = frags.next(); - let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") }; - let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); - match axis { - Some("xy") | None => Max::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => Max::X(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => Max::Y(state.namespace(arg0?)?.unwrap(), cb), - frag => unimplemented!("max/{frag:?}") - } - }), - - Some("push") => output.place(&{ - let axis = frags.next(); - let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") }; - let cb = Thunk::new(move|output: &mut O|state.understand(output, &arg).unwrap()); - match axis { - Some("xy") | None => Push::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb), - Some("x") => Push::X(state.namespace(arg0?)?.unwrap(), cb), - Some("y") => Push::Y(state.namespace(arg0?)?.unwrap(), cb), - frag => unimplemented!("push/{frag:?}") - } - }), - - _ => return Ok(false) - - }; - Ok(true) -} - /// Should be impl something or other... /// /// ``` -/// use tengri::{Namespace, Understand, TuiOut, ratatui::prelude::Color}; -/// /// struct State; /// impl<'b> Namespace<'b, bool> for State {} /// impl<'b> Namespace<'b, u16> for State {} /// impl<'b> Namespace<'b, Color> for State {} /// impl Understand for State {} -/// # fn main () -> tengri::Usually<()> { /// let state = State; -/// let mut out = TuiOut::default(); +/// let out = TuiOut::default(); /// tengri::evaluate_output_expression_tui(&state, &mut out, "")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "text Hello world!")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?; /// tengri::evaluate_output_expression_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?; -/// # Ok(()) } /// ``` #[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> ( state: &S, output: &mut TuiOut, expr: impl Expression + 'a @@ -506,7 +485,7 @@ pub fn tui_input + Send + Sync + 'static> ( let tail0 = args.tail(); let arg1 = tail0.head(); let tail1 = tail0.tail(); - let arg2 = tail1.head(); + let _arg2 = tail1.head(); match frags.next() { Some("text") => { @@ -521,9 +500,6 @@ pub fn tui_input + Send + Sync + 'static> ( }, Some("bg") => { - //panic!("expr: {expr:?}\nhead: {head:?}\nfrags: {frags:?}\nargs: {args:?}\narg0: {arg0:?}\ntail0: {tail0:?}\narg1: {arg1:?}\ntail1: {tail1:?}\narg2: {arg2:?}"); - //panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");; - //panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}"); let arg0 = arg0?.expect("bg: expected arg 0 (color)"); let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")); let thunk = Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap()); @@ -581,8 +557,8 @@ pub fn named_key (token: &str) -> Option { } /// ``` -/// let _ = tengri::button_2("", "", true); -/// let _ = tengri::button_2("", "", false); +/// let _ = button_2("", "", true); +/// let _ = button_2("", "", false); /// ``` pub fn button_2 <'a> (key: impl Content, label: impl Content, editing: bool) -> impl Content { Tui::bold(true, Bsp::e( @@ -591,8 +567,8 @@ pub fn button_2 <'a> (key: impl Content, label: impl Content, ed } /// ``` -/// let _ = tengri::button_3("", "", "", true); -/// let _ = tengri::button_3("", "", "", false); +/// let _ = button_3("", "", "", true); +/// let _ = button_3("", "", "", false); /// ``` pub fn button_3 <'a> ( key: impl Content, label: impl Content, value: impl Content, editing: bool, @@ -780,7 +756,6 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef) -> u16 { use proptest::{prelude::*, option::of}; use proptest_derive::Arbitrary; use crate::*; - use Direction::*; proptest! { #[test] fn proptest_direction ( @@ -907,18 +882,22 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef) -> u16 { } } - //#[test] fn test_tui_engine () -> Usually<()> { - ////use std::sync::{Arc, RwLock}; - //struct TestComponent(String); - //impl Draw for TestComponent { - //fn draw (&self, _to: &mut TuiOut) {} - //} - //impl Handle for TestComponent { - //fn handle (&mut self, _from: &TuiIn) -> Perhaps { Ok(None) } - //} - //let engine = Tui::new(Box::<&mut Vec>::new(vec![0u8;0].as_mut()))?; - //let state = engine.run(false, &Arc::new(RwLock::new(TestComponent("hello world".into()))))?; - //state.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed); - //Ok(()) - //} + #[test] fn test_tui_engine () -> Usually<()> { + //use std::sync::{Arc, RwLock}; + struct TestComponent(String); + impl Draw for TestComponent { + fn draw (&self, _to: &mut TuiOut) { + } + } + impl Handle for TestComponent { + fn handle (&mut self, _from: &TuiIn) -> Perhaps { + Ok(None) + } + } + let mut output = String::new(); + let engine = Tui::new(&mut output).run(false, TestComponent("hello world".into()))?; + engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed); + //engine.run(&state)?; + Ok(()) + } } diff --git a/src/tengri_impl.rs b/src/tengri_impl.rs index 40d444f..8387290 100644 --- a/src/tengri_impl.rs +++ b/src/tengri_impl.rs @@ -270,11 +270,15 @@ impl> Draw for Option { //} impl, F: Fn()->T> Lazy { - pub const fn new (thunk: F) -> Self { Self(thunk, PhantomData) } + pub const fn new (thunk: F) -> Self { + Self(thunk, PhantomData) + } } impl Thunk { - pub const fn new (draw: F) -> Self { Self(PhantomData, draw) } + pub const fn new (draw: F) -> Self { + Self(PhantomData, draw) + } } impl Layout for Thunk {} @@ -668,15 +672,29 @@ impl Bsp { impl, Tail: Content> Draw for Bsp { fn draw (&self, to: &mut O) { - let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2); - //panic!("{a:?} {b:?}"); - if self.0 == Below { - to.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(XYWH( + to.area().x(), to.area().y().plus(area_1.h()), + to.area().w(), to.area().h().minus(area_1.h()) + )); + //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); + //} } } @@ -965,7 +983,7 @@ impl PerfModel { } impl Tui { - pub fn new (output: Box) -> Usually { + pub fn new (output: Stdout) -> Usually { let backend = CrosstermBackend::new(output); let Size { width, height } = backend.size()?; Ok(Self { @@ -1015,8 +1033,6 @@ impl Input for TuiIn { type Event = TuiEvent; type Handled = bool; fn event (&self) -> &TuiEvent { &self.event } -} -impl Done for TuiIn { fn done (&self) { self.exited.store(true, Relaxed); } fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) } } @@ -1032,11 +1048,6 @@ impl TuiEvent { Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self)) } } -impl From for TuiEvent { - fn from (e: Event) -> Self { - Self(e) - } -} impl From for TuiEvent { fn from (c: char) -> Self { Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))) diff --git a/src/tengri_struct.rs b/src/tengri_struct.rs index 234cded..59ae480 100644 --- a/src/tengri_struct.rs +++ b/src/tengri_struct.rs @@ -1,418 +1,394 @@ -pub use self::logical::*; mod logical { - use crate::*; +#[cfg(test)] use proptest_derive::Arbitrary; +use crate::*; - /// Thunks can be natural error boundaries! - pub struct ErrorBoundary>( - pub std::marker::PhantomData, - pub Perhaps - ); - - // TODO DOCUMENTME - pub struct Lazy( - pub F, - pub PhantomData<(O, T)> - ); - - // TODO DOCUMENTME - pub struct Thunk( - pub PhantomData, - pub F - ); - - // TODO DOCUMENTME - #[derive(Debug, Default)] pub struct Memo { - pub value: T, - pub view: Arc> - } +/// The `Tui` struct (the *engine*) implements the +/// `tengri_input::Input` and `tengri_output::Out` traits. +/// At launch, the `Tui` engine spawns two threads, the render thread and the input thread. +/// the application may further spawn other threads. All threads communicate using shared ownership: +/// `Arc>` and `Arc`. Thus, at launch the engine and application instances are expected to be wrapped in `Arc`. +pub struct Tui { + pub exited: Arc, + pub backend: CrosstermBackend, + pub buffer: Buffer, + pub area: [u16;4], + pub perf: PerfModel, } -pub use self::temporal::*; mod temporal { - use crate::*; - /// Performance counter - #[derive(Debug)] - pub struct PerfModel { - pub enabled: bool, - - pub clock: quanta::Clock, - // In nanoseconds. Time used by last iteration. - pub used: AtomicF64, - // In microseconds. Max prescribed time for iteration (frame, chunk...). - pub window: AtomicF64, - } +#[derive(Debug, Clone)] pub struct TuiIn { + /// Input event + pub event: TuiEvent, + /// Exit flag + pub exited: Arc, } -pub use self::spatial::*; mod spatial { - use crate::*; +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent( + pub Event +); - // TODO DOCUMENTME - pub struct Bordered(pub bool, pub S, pub W); +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey( + pub Option, + pub KeyModifiers +); - // TODO DOCUMENTME - pub struct Border(pub bool, pub S); - - /// A binary split or layer. - pub struct Bsp( - /// Direction of split - pub(crate) Direction, - /// First element. - pub(crate) Head, - /// Second element. - pub(crate) Tail, - ); - - /// A point (X, Y). - /// - /// ``` - /// let xy = tengri::XY(0u16, 0); - /// ``` - #[cfg_attr(test, derive(Arbitrary))] - #[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XY( - pub C, pub C - ); - - /// A size (Width, Height). - /// - /// ``` - /// let wh = tengri::WH(0u16, 0); - /// ``` - #[cfg_attr(test, derive(Arbitrary))] - #[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct WH( - pub C, pub C - ); - - /// Point with size. - /// - /// ``` - /// let xywh = tengri::XYWH(0u16, 0, 0, 0); - /// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(20, 20)); - /// ``` - /// - /// * [ ] TODO: anchor field (determines at which corner/side is X0 Y0) - /// - #[cfg_attr(test, derive(Arbitrary))] - #[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH( - pub C, pub C, pub C, pub C - ); - - /// A cardinal direction. - /// - /// ``` - /// let direction = tengri::Direction::Above; - /// ``` - #[cfg_attr(test, derive(Arbitrary))] - #[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction { - North, South, East, West, Above, Below - } - - /// 9th of area to place. - /// - /// ``` - /// let alignment = tengri::Alignment::Center; - /// ``` - #[cfg_attr(test, derive(Arbitrary))] - #[derive(Debug, Copy, Clone, Default)] pub enum Alignment { - #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W - } - - /// A widget that tracks its rendered width and height. - /// - /// ``` - /// let measure = tengri::Measure::::default(); - /// ``` - #[derive(Default)] pub struct Measure { - pub __: PhantomData, - pub x: Arc, - pub y: Arc, - } - - /// Show an item only when a condition is true. - /// - /// ``` - /// fn test () -> impl tengri::Draw { - /// tengri::when(true, "Yes") - /// } - /// ``` - pub struct When(pub bool, pub T, pub PhantomData); - pub const fn when(condition: bool, content: T) -> When { - When(condition, content, PhantomData) - } - - /// Show one item if a condition is true and another if the condition is false. - /// - /// ``` - /// fn test () -> impl tengri::Draw { - /// tengri::either(true, "Yes", "No") - /// } - /// ``` - pub struct Either(pub bool, pub A, pub B, pub PhantomData); - pub const fn either(condition: bool, content_a: A, content_b: B) -> Either { - Either(condition, content_a, content_b, PhantomData) - } - - /// Increment X and/or Y coordinate. - /// - /// ``` - /// let pushed = tengri::Push::XY(2, 2, "Hello"); - /// ``` - pub enum Push { X(U, A), Y(U, A), XY(U, U, A), } - - /// Decrement X and/or Y coordinate. - /// - /// ``` - /// let pulled = tengri::Pull::XY(2, 2, "Hello"); - /// ``` - pub enum Pull { X(U, A), Y(U, A), XY(U, U, A), } - - /// Set the content to fill the container. - /// - /// ``` - /// let filled = tengri::Fill::XY("Hello"); - /// ``` - pub enum Fill { X(A), Y(A), XY(A) } - - /// Set fixed size for content. - /// - /// ``` - /// let fixed = tengri::Fixed::XY(3, 5, "Hello"); // 3x5 - /// ``` - pub enum Fixed { X(U, A), Y(U, A), XY(U, U, A), } - - /// Set the maximum width and/or height of the content. - /// - /// ``` - /// let maximum = tengri::Min::XY(3, 5, "Hello"); // 3x1 - /// ``` - pub enum Max { X(U, A), Y(U, A), XY(U, U, A), } - - /// Set the minimum width and/or height of the content. - /// - /// ``` - /// let minimam = tengri::Min::XY(3, 5, "Hello"); // 5x5 - /// ``` - pub enum Min { X(U, A), Y(U, A), XY(U, U, A), } - - /// Decrease the width and/or height of the content. - /// - /// ``` - /// let shrunk = tengri::Shrink::XY(2, 0, "Hello"); // 1x1 - /// ``` - pub enum Shrink { X(U, A), Y(U, A), XY(U, U, A), } - - /// Increaase the width and/or height of the content. - /// - /// ``` - /// let expanded = tengri::Expand::XY(5, 3, "HELLO"); // 15x3 - /// ``` - pub enum Expand { X(U, A), Y(U, A), XY(U, U, A), } - - /// Align position of inner area to middle, side, or corner of outer area. - /// - /// - /// ``` - /// use ::tengri::*; - /// let area = XYWH(10u16, 10, 20, 20); - /// fn test (area: XYWH, item: &impl Draw, expected: [u16;4]) { - /// //assert_eq!(Lay::layout(item, area), expected); - /// //assert_eq!(Draw::layout(item, area), expected); - /// }; - /// - /// let four = ||Fixed::XY(4, 4, ""); - /// test(area, &Align::nw(four()), [10, 10, 4, 4]); - /// test(area, &Align::n(four()), [18, 10, 4, 4]); - /// test(area, &Align::ne(four()), [26, 10, 4, 4]); - /// test(area, &Align::e(four()), [26, 18, 4, 4]); - /// test(area, &Align::se(four()), [26, 26, 4, 4]); - /// test(area, &Align::s(four()), [18, 26, 4, 4]); - /// test(area, &Align::sw(four()), [10, 26, 4, 4]); - /// test(area, &Align::w(four()), [10, 18, 4, 4]); - /// - /// let two_by_four = ||Fixed::XY(4, 2, ""); - /// test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]); - /// test(area, &Align::n(two_by_four()), [18, 10, 4, 2]); - /// test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]); - /// test(area, &Align::e(two_by_four()), [26, 19, 4, 2]); - /// test(area, &Align::se(two_by_four()), [26, 28, 4, 2]); - /// test(area, &Align::s(two_by_four()), [18, 28, 4, 2]); - /// test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]); - /// test(area, &Align::w(two_by_four()), [10, 19, 4, 2]); - /// ``` - pub struct Align(pub Alignment, pub T); - - // TODO DOCUMENTME - pub enum Pad { X(U, A), Y(U, A), XY(U, U, A), } - - /// TODO DOCUMENTME - /// - /// ``` - /// use tengri::{Bounded, XYWH}; - /// let area = XYWH(0, 0, 0, 0); - /// let content = ""; - /// let bounded: Bounded = Bounded(area, content); - /// ``` - pub struct Bounded(pub XYWH, pub D); - - /// Draws items from an iterator. - /// - /// ``` - /// // FIXME let map = tengri::Map(||[].iter(), |_|{}); - /// ``` - pub struct Map - where - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, - { - /// Function that returns iterator over stacked components - pub get_iter: F, - /// Function that returns each stacked component - pub get_item: G, - - pub __: PhantomData<(O, B)>, - } - - /// A color in OKHSL and RGB representations. - #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor { - pub okhsl: Okhsl, - pub rgb: Color, - } - /// A color in OKHSL and RGB with lighter and darker variants. - #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemTheme { - pub base: ItemColor, - pub light: ItemColor, - pub lighter: ItemColor, - pub lightest: ItemColor, - pub dark: ItemColor, - pub darker: ItemColor, - pub darkest: ItemColor, - } +#[derive(Default)] pub struct TuiOut { + pub buffer: Buffer, + pub area: XYWH, } -pub use self::terminal::*; mod terminal { - use crate::*; - - /// The TUI engine. - /// - /// ``` - /// # fn main () -> tengri::Usually<()> { - /// let tui = tengri::Tui::new(Box::new(vec![0u8;0]))?; - /// # Ok(()) } - /// ``` - pub struct Tui { - pub exited: Arc, - pub backend: CrosstermBackend>, - pub buffer: Buffer, - pub area: [u16;4], - pub perf: PerfModel, - } - - /// The TUI input event source. - /// - /// ``` - /// use ::{tengri::{TuiIn, TuiEvent}, std::sync::Arc}; - /// let tui_in = TuiIn { event: 'f'.into(), exited: Arc::new(false.into()) }; - /// ``` - #[derive(Debug, Clone)] pub struct TuiIn { - /// Input event - pub event: TuiEvent, - /// Exit flag - pub exited: Arc, - } - - #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent( - pub Event - ); - - #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey( - pub Option, - pub KeyModifiers - ); - - /// The TUI output render target. - /// - /// ``` - /// let tui_out = tengri::TuiOut::default(); - /// ``` - #[derive(Default)] pub struct TuiOut { - pub buffer: Buffer, - pub area: XYWH, - } - - /// TUI buffer sized by `usize` instead of `u16`. - #[derive(Default)] pub struct BigBuffer { - pub width: usize, - pub height: usize, - pub content: Vec - } - - // TODO DOCUMENTME - pub struct Foreground(pub Color, pub Item); - - // TODO DOCUMENTME - pub struct Background(pub Color, pub Item); - - pub struct Modify(pub bool, pub Modifier, pub T); - - pub struct Styled(pub Option