From 85ccb0737f63df4c1d8d7274e904aa28d4dfe6a0 Mon Sep 17 00:00:00 2001 From: same mf who else Date: Sat, 21 Feb 2026 16:18:52 +0200 Subject: [PATCH 1/4] fold tui under main crate --- Cargo.toml | 10 ++-- tengri/.scratch.rs | 15 ++++++ tengri/README.md | 7 +++ {tui => tengri}/examples/tui_00.rs | 0 {tui => tengri}/examples/tui_01.rs | 0 tengri/tengri.rs | 73 +++++++++++++++++------------- tui/Cargo.toml | 20 -------- tui/README.md | 4 -- tui/src/.scratch.rs | 0 tui/src/lib.rs | 0 tui/src/tui_test.rs | 16 ------- 11 files changed, 66 insertions(+), 79 deletions(-) rename {tui => tengri}/examples/tui_00.rs (100%) rename {tui => tengri}/examples/tui_01.rs (100%) delete mode 100644 tui/Cargo.toml delete mode 100644 tui/README.md delete mode 100644 tui/src/.scratch.rs delete mode 100644 tui/src/lib.rs delete mode 100644 tui/src/tui_test.rs diff --git a/Cargo.toml b/Cargo.toml index a3a6d54..34b703a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,9 @@ version = "0.14.0" edition = "2024" [workspace.dependencies] -dizzle = { path = "../dizzle" } - -tengri = { path = "./tengri" } -tengri_input = { path = "./input" } -tengri_output = { path = "./output" } -tengri_tui = { path = "./tui" } -tengri_proc = { path = "./proc" } +dizzle = { path = "../dizzle" } +tengri = { path = "./tengri" } +tengri_proc = { path = "./proc" } anyhow = { version = "1.0" } atomic_float = { version = "1" } diff --git a/tengri/.scratch.rs b/tengri/.scratch.rs index 55af234..0d0f833 100644 --- a/tengri/.scratch.rs +++ b/tengri/.scratch.rs @@ -881,3 +881,18 @@ impl> MenuItem { //} //self //} + +//#[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/tengri/README.md b/tengri/README.md index 0aec24e..41cfef5 100644 --- a/tengri/README.md +++ b/tengri/README.md @@ -47,3 +47,10 @@ it expresses the following notions: * sizing: `Fill`, `Fixed`, `Expand`, `Shrink`, `Min`, `Max` * implement custom components (that may be backend-dependent): [see `tui_content` in `tengri_tui`](../tui/src/tui_content) + +## TUI + +***tengri_tui*** implements [tengri_output](../output) and [tengri_input](../input) +on top of [ratatui](https://ratatui.rs/) and [crossterm](https://github.com/crossterm-rs/crossterm). + +tengri is published under [**AGPL3**](../LICENSE). diff --git a/tui/examples/tui_00.rs b/tengri/examples/tui_00.rs similarity index 100% rename from tui/examples/tui_00.rs rename to tengri/examples/tui_00.rs diff --git a/tui/examples/tui_01.rs b/tengri/examples/tui_01.rs similarity index 100% rename from tui/examples/tui_01.rs rename to tengri/examples/tui_01.rs diff --git a/tengri/tengri.rs b/tengri/tengri.rs index 87951d3..b1f8b58 100644 --- a/tengri/tengri.rs +++ b/tengri/tengri.rs @@ -10,43 +10,52 @@ #![feature(trait_alias)] #![feature(type_alias_impl_trait)] #![feature(type_changing_struct_update)] -//#![feature(non_lifetime_binders)] + +//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::*}, + backend::{Backend, CrosstermBackend, ClearType}, + layout::{Size, Rect}, + buffer::Cell +}; + +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::*; -//pub(crate) use quanta::Clock; -pub extern crate atomic_float; pub(crate) use atomic_float::AtomicF64; -pub extern crate ratatui; -pub extern crate crossterm; -pub extern crate palette; -pub extern crate better_panic; -extern crate dizzle; pub use dizzle::*; -#[cfg(test)] extern crate tengri_proc; + +#[macro_export] pub extern crate dizzle; +pub use dizzle::*; + use std::{time::Duration, thread::{spawn, JoinHandle}, io::Write}; -use unicode_width::*; -pub(crate) use ::{ - std::{ - io::{stdout, Stdout}, - sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}}, - fmt::{Debug, Display}, - ops::{Add, Sub, Mul, Div}, - marker::PhantomData, - }, - better_panic::{Settings, Verbosity}, - palette::{*, convert::*, okhsl::*}, - ratatui::{ - prelude::{Color, Style, Buffer, Position}, - style::{Stylize, Modifier, Color::*}, - backend::{Backend, CrosstermBackend, ClearType}, - layout::{Size, Rect}, - buffer::Cell - }, - crossterm::{ - ExecutableCommand, - terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, - event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, - } +pub(crate) use ::std::{ + io::{stdout, Stdout}, + sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}}, + fmt::{Debug, Display}, + ops::{Add, Sub, Mul, Div}, + marker::PhantomData, }; // Define macros first, so that private macros are available in private modules: diff --git a/tui/Cargo.toml b/tui/Cargo.toml deleted file mode 100644 index 51706b2..0000000 --- a/tui/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "tengri_tui" -description = "UI metaframework, Ratatui backend." -version = { workspace = true } -edition = { workspace = true } - -[features] -dsl = [ "tengri_output/dsl" ] -bumpalo = [ "dep:bumpalo" ] - -[dependencies] -dizzle = { workspace = true } - -tengri_input = { workspace = true } -tengri_output = { workspace = true } - - -[dev-dependencies] -tengri = { workspace = true, features = [ "dsl" ] } -tengri_proc = { workspace = true } diff --git a/tui/README.md b/tui/README.md deleted file mode 100644 index 8e227d8..0000000 --- a/tui/README.md +++ /dev/null @@ -1,4 +0,0 @@ -***tengri_tui*** is an implementation of [tengri_output](../output) and [tengri_input](../input) -on top of [ratatui](https://ratatui.rs/) and [crossterm](https://github.com/crossterm-rs/crossterm). - -tengri is published under [**AGPL3**](../LICENSE). diff --git a/tui/src/.scratch.rs b/tui/src/.scratch.rs deleted file mode 100644 index e69de29..0000000 diff --git a/tui/src/lib.rs b/tui/src/lib.rs deleted file mode 100644 index e69de29..0000000 diff --git a/tui/src/tui_test.rs b/tui/src/tui_test.rs deleted file mode 100644 index 53b9afe..0000000 --- a/tui/src/tui_test.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::*; - -//#[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 )); -//} From b294f2e62b970cf4e1cb133346132a0a113af575 Mon Sep 17 00:00:00 2001 From: same mf who else Date: Sat, 21 Feb 2026 18:13:21 +0200 Subject: [PATCH 2/4] add Tui::new, remove View --- tengri/tengri.rs | 40 +++++----------------------------------- tengri/tengri_impl.rs | 36 ++++++++++++++++++++++++++++++++++-- tengri/tengri_struct.rs | 1 + tengri/tengri_trait.rs | 20 -------------------- 4 files changed, 40 insertions(+), 57 deletions(-) diff --git a/tengri/tengri.rs b/tengri/tengri.rs index b1f8b58..e488c78 100644 --- a/tengri/tengri.rs +++ b/tengri/tengri.rs @@ -107,7 +107,7 @@ pub(crate) use ::std::{ #[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: View + S: Understand + for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, O::Unit> { @@ -282,7 +282,7 @@ pub(crate) use ::std::{ #[macro_export] macro_rules! tui_main { ($expr:expr) => { fn main () -> Usually<()> { - tengri::Tui::run(true, $expr)?; + tengri::Tui::new(stdout()).run(true, $expr)?; Ok(()) } }; @@ -323,37 +323,6 @@ macro_rules! border { )+} } -/// Run an app in the main loop. -pub fn tui_run + Handle + 'static> ( - join: bool, - state: &Arc> -) -> Usually>> { - let backend = CrosstermBackend::new(stdout()); - let Size { width, height } = backend.size()?; - let tui = Arc::new(RwLock::new(Tui { - exited: Arc::new(AtomicBool::new(false)), - buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }), - area: [0, 0, width, height], - perf: Default::default(), - backend, - })); - let _input_thread = tui_input(tui.clone(), state, Duration::from_millis(100)); - tui.write().unwrap().setup()?; - let render_thread = tui_output(tui.clone(), state, Duration::from_millis(10))?; - if join { - match render_thread.join() { - Ok(result) => { - tui.write().unwrap().teardown()?; - println!("\n\rRan successfully: {result:?}\n\r"); - }, - Err(error) => { - tui.write().unwrap().teardown()?; - panic!("\n\rDraw thread failed: error={error:?}.\n\r") - }, - } - } - Ok(tui) -} pub fn tui_setup ( backend: &mut CrosstermBackend @@ -487,7 +456,7 @@ pub fn tui_input + Send + Sync + 'static> ( #[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> ( state: &S, output: &mut TuiOut, expr: impl Expression + 'a ) -> Usually where - S: View + S: Understand + for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, u16> + for<'b>Namespace<'b, Color> @@ -909,7 +878,8 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef) -> u16 { Ok(None) } } - let engine = Tui::run(false, TestComponent("hello world".into()))?; + 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/tengri/tengri_impl.rs b/tengri/tengri_impl.rs index 3edc2b6..8387290 100644 --- a/tengri/tengri_impl.rs +++ b/tengri/tengri_impl.rs @@ -983,11 +983,38 @@ impl PerfModel { } impl Tui { + pub fn new (output: Stdout) -> Usually { + let backend = CrosstermBackend::new(output); + let Size { width, height } = backend.size()?; + Ok(Self { + exited: Arc::new(AtomicBool::new(false)), + buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }), + area: [0, 0, width, height], + perf: Default::default(), + backend, + }) + } /// Create and launch a terminal user interface. - pub fn run (join: bool, state: T) -> Usually>> where + pub fn run (self, join: bool, state: &Arc>) -> Usually>> where T: Handle + Draw + Send + Sync + 'static { - tui_run(join, &Arc::new(RwLock::new(state))) + let tui = Arc::new(RwLock::new(self)); + let _input_thread = tui_input(tui.clone(), state, Duration::from_millis(100)); + tui.write().unwrap().setup()?; + let render_thread = tui_output(tui.clone(), state, Duration::from_millis(10))?; + if join { + match render_thread.join() { + Ok(result) => { + tui.write().unwrap().teardown()?; + println!("\n\rRan successfully: {result:?}\n\r"); + }, + Err(error) => { + tui.write().unwrap().teardown()?; + panic!("\n\rDraw thread failed: error={error:?}.\n\r") + }, + } + } + Ok(tui) } /// True if done pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) } @@ -1021,6 +1048,11 @@ impl TuiEvent { Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self)) } } +impl From for TuiEvent { + fn from (c: char) -> Self { + Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))) + } +} impl TuiKey { const SPLIT: char = '/'; #[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Usually { diff --git a/tengri/tengri_struct.rs b/tengri/tengri_struct.rs index ae56877..59ae480 100644 --- a/tengri/tengri_struct.rs +++ b/tengri/tengri_struct.rs @@ -14,6 +14,7 @@ pub struct Tui { pub perf: PerfModel, } + #[derive(Debug, Clone)] pub struct TuiIn { /// Input event pub event: TuiEvent, diff --git a/tengri/tengri_trait.rs b/tengri/tengri_trait.rs index 8ac11ed..e03ce0f 100644 --- a/tengri/tengri_trait.rs +++ b/tengri/tengri_trait.rs @@ -112,26 +112,6 @@ pub trait Draw { fn draw (&self, to: &mut O); } -/// FIXME: This is a general implementation: should be called `Eval` and be part of [dizzle]. -/// Matches [Language] expressions to renderings for a given [Output] target. -pub trait View { - fn view_expr <'a> (&'a self, _output: &mut O, expr: &'a impl Expression) -> Usually { - Err(format!("View::view_expr: no exprs defined: {expr:?}").into()) - } - fn view_word <'a> (&'a self, _output: &mut O, word: &'a impl Symbol) -> Usually { - Err(format!("View::view_word: no words defined: {word:?}").into()) - } - fn view <'a> (&'a self, output: &mut O, dsl: &'a impl Language) -> Usually { - match (dsl.expr(), dsl.word()) { - (Ok(Some(e)), _ ) => self.view_expr(output, &e), - (_, Ok(Some(w))) => self.view_word(output, &w), - (Err(e), _ ) => Err(format!("invalid view expr:\n{dsl:?}\n{e}").into()), - (_, Err(w) ) => Err(format!("invalid view word:\n{dsl:?}\n{w}").into()), - (Ok(None), Ok(None) ) => Err(format!("empty view:\n{dsl:?}").into()), - } - } -} - /// Outputs combinator. pub trait Lay: Sized {} From 006cddcc16868aa626ecca6abf1c0f00523402fc Mon Sep 17 00:00:00 2001 From: same mf who else Date: Sat, 21 Feb 2026 18:21:50 +0200 Subject: [PATCH 3/4] view* -> understand* --- tengri/tengri.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tengri/tengri.rs b/tengri/tengri.rs index e488c78..e3310ec 100644 --- a/tengri/tengri.rs +++ b/tengri/tengri.rs @@ -80,10 +80,10 @@ pub(crate) use ::std::{ /// FIXME: This is generic: should be called `eval` and be part of [dizzle]. #[cfg(feature = "dsl")] #[macro_export] macro_rules! view { ($State:ident: $Output:ident: $namespaces:expr) => { - impl View<$Output, ()> for $State { - fn view_expr <'a> (&'a self, to: &mut $Output, expr: &'a impl Expression) -> Usually<()> { + impl Understand<$Output, ()> for $State { + fn understand_expr <'a> (&'a self, to: &mut $Output, expr: &'a impl Expression) -> Usually<()> { for namespace in $namespaces { if namespace(self, to, expr)? { return Ok(()) } } - Err(format!("{}::<{}, ()>::view_expr: unexpected: {expr:?}", + Err(format!("{}::<{}, ()>::understand_expr: unexpected: {expr:?}", stringify! { $State }, stringify! { $Output }).into()) } @@ -129,18 +129,18 @@ pub(crate) use ::std::{ Some("when") => output.place(&When::new( state.namespace(arg0?)?.unwrap(), - Thunk::new(move|output: &mut O|state.view(output, &arg1).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.view(output, &arg1).unwrap()), - Thunk::new(move|output: &mut O|state.view(output, &arg2).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.view(output, &arg0).unwrap()); - let b = Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap()); + 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), @@ -153,7 +153,7 @@ pub(crate) use ::std::{ }), Some("align") => output.place(&{ - let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap()); + 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), @@ -167,7 +167,7 @@ pub(crate) use ::std::{ }), Some("fill") => output.place(&{ - let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap()); + 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), @@ -179,7 +179,7 @@ pub(crate) use ::std::{ 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()); + 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), @@ -192,7 +192,7 @@ pub(crate) use ::std::{ 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()); + 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), @@ -204,7 +204,7 @@ pub(crate) use ::std::{ 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()); + 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), @@ -216,7 +216,7 @@ pub(crate) use ::std::{ 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()); + 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), @@ -478,7 +478,7 @@ pub fn tui_input + Send + Sync + 'static> ( let arg0 = arg0?.expect("fg: expected arg 0 (color)"); output.place(&Tui::fg( Namespace::::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")), - Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()), + Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap()), )) }, @@ -486,7 +486,7 @@ pub fn tui_input + Send + Sync + 'static> ( let arg0 = arg0?.expect("bg: expected arg 0 (color)"); output.place(&Tui::bg( Namespace::::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")), - Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()), + Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap()), )) }, From f34ceb2e889ac8ba98cf3622901ce7670fc7a7bd Mon Sep 17 00:00:00 2001 From: same mf who else Date: Sat, 21 Feb 2026 19:34:07 +0200 Subject: [PATCH 4/4] remove workspace --- Cargo.lock | 51 +++-------------- Cargo.toml | 74 ++++++++++++++----------- README.md | 57 ++++++++++++++++++- {tengri/examples => examples}/tui_00.rs | 0 {tengri/examples => examples}/tui_01.rs | 0 {tengri => src}/.scratch.rs | 0 {tengri => src}/README.md | 0 {tengri => src}/tengri.rs | 38 +++++++++---- {tengri => src}/tengri_impl.rs | 0 {tengri => src}/tengri_struct.rs | 0 {tengri => src}/tengri_trait.rs | 0 {tengri => src}/tengri_type.rs | 0 tengri/Cargo.toml | 38 ------------- 13 files changed, 133 insertions(+), 125 deletions(-) mode change 120000 => 100644 README.md rename {tengri/examples => examples}/tui_00.rs (100%) rename {tengri/examples => examples}/tui_01.rs (100%) rename {tengri => src}/.scratch.rs (100%) rename {tengri => src}/README.md (100%) rename {tengri => src}/tengri.rs (95%) rename {tengri => src}/tengri_impl.rs (100%) rename {tengri => src}/tengri_struct.rs (100%) rename {tengri => src}/tengri_trait.rs (100%) rename {tengri => src}/tengri_type.rs (100%) delete mode 100644 tengri/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index dd54cd4..177e248 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1166,35 +1166,20 @@ dependencies = [ name = "tengri" version = "0.14.0" dependencies = [ + "atomic_float", + "better-panic", + "bumpalo", "crossterm 0.29.0", "dizzle", - "tengri", - "tengri_input", - "tengri_output", - "tengri_proc", - "tengri_tui", -] - -[[package]] -name = "tengri_input" -version = "0.14.0" -dependencies = [ - "dizzle", - "tengri_tui", -] - -[[package]] -name = "tengri_output" -version = "0.14.0" -dependencies = [ - "atomic_float", - "bumpalo", - "dizzle", + "palette", "proptest", "proptest-derive", "quanta", + "rand 0.8.5", + "ratatui", "tengri", - "tengri_tui", + "tengri_proc", + "unicode-width 0.2.0", ] [[package]] @@ -1208,26 +1193,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tengri_tui" -version = "0.14.0" -dependencies = [ - "atomic_float", - "better-panic", - "bumpalo", - "crossterm 0.29.0", - "dizzle", - "palette", - "quanta", - "rand 0.8.5", - "ratatui", - "tengri", - "tengri_input", - "tengri_output", - "tengri_proc", - "unicode-width 0.2.0", -] - [[package]] name = "thiserror" version = "2.0.18" diff --git a/Cargo.toml b/Cargo.toml index 34b703a..2315f53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,43 @@ +[package] +name = "tengri" +edition = "2024" +version = "0.15.0" +description = "UI metaframework." + +[features] +default = [ "input", "output", "tui" ] +bumpalo = [ "dep:bumpalo" ] +input = [ ] +output = [ ] +tui = [ ] +dsl = [ ] + +[dependencies] +anyhow = { version = "1.0" } +atomic_float = { version = "1" } +better-panic = { version = "0.3.0" } +bumpalo = { version = "3.19.0", optional = true } +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" } + +[dev-dependencies] +crossterm = { version = "0.29.0" } +proptest = { version = "^1" } +proptest-derive = { version = "^0.5.1" } +tengri = { path = ".", features = [ "dsl" ] } +tengri_proc = { path = "./proc" } + +[lib] +path = "src/tengri.rs" + [profile.release] lto = true @@ -5,35 +45,5 @@ lto = true inherits = "test" lto = false -[workspace] -resolver = "2" -members = [ - "./tengri", - "./proc", -] - -[workspace.package] -version = "0.14.0" -edition = "2024" - -[workspace.dependencies] -dizzle = { path = "../dizzle" } -tengri = { path = "./tengri" } -tengri_proc = { path = "./proc" } - -anyhow = { version = "1.0" } -atomic_float = { version = "1" } -better-panic = { version = "0.3.0" } -bumpalo = { version = "3.19.0" } -crossterm = { version = "0.29.0" } -heck = { version = "0.5" } -palette = { version = "0.7.6", features = [ "random" ] } -proc-macro2 = { version = "1", features = ["span-locations"] } -proptest = { version = "^1" } -proptest-derive = { version = "^0.5.1" } -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" } +[target.'cfg(target_os = "linux")'] +rustflags = ["-C", "link-arg=-fuse-ld=mold"] diff --git a/README.md b/README.md deleted file mode 120000 index 7cad1cb..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -tengri/README.md \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..41cfef5 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +***tengri*** is a metaframework for building interactive applications with rust. (aren't we all?) + +tengri is developed as part of [***tek***](https://codeberg.org/unspeaker/tek), +a music program for terminals. + +tengri contains: +* [***dizzle***](./dsl), a framework for defining domain-specific languages +* [***output***](./output), an abstract UI layout framework +* [***input***](./input), an abstract UI event framework +* [***tui***](./tui), an implementation of tengri over [***ratatui***](https://ratatui.rs/). + +as well as: +* [***core***](./core), the shared definitions ("utils") module +* [***proc***](./proc), the space for procedural macros +* [***tengri***](./tengri), the top-level reexport crate + +tengri is published under [**AGPL3**](./LICENSE). + +## Input + +***tengri_input*** is where tengri's input handling is defined. + +the following items are provided: +* `Input` trait, for defining for input sources +* `Handle` trait and `handle!` macro, for defining input handlers +* `Command` trait and the `command!` macro, for defining commands that inputs may result in + +## Output + +***tengri_output*** is an abstract interface layout framework. + +it expresses the following notions: + +* [**space:**](./src/space.rs) `Direction`, `Coordinate`, `Area`, `Size`, `Measure` + +* [**output:**](./src/output.rs) `Out`, `Draw`, `Content` + * the layout operators are generic over `Draw` and/or `Content` + * the traits `Draw` and `Content` are generic over `Out` + * implement `Out` to bring a layout to a new backend: + [see `TuiOut` in `tengri_tui`](../tui/src/tui_engine/tui_output.rs) + +* [**layout:**](./src/layout.rs) + * conditionals: `When`, `Either` + * iteration: `Map` + * concatenation: `Bsp` + * positioning: `Align`, `Push`, `Pull` + * sizing: `Fill`, `Fixed`, `Expand`, `Shrink`, `Min`, `Max` + * implement custom components (that may be backend-dependent): + [see `tui_content` in `tengri_tui`](../tui/src/tui_content) + +## TUI + +***tengri_tui*** implements [tengri_output](../output) and [tengri_input](../input) +on top of [ratatui](https://ratatui.rs/) and [crossterm](https://github.com/crossterm-rs/crossterm). + +tengri is published under [**AGPL3**](../LICENSE). diff --git a/tengri/examples/tui_00.rs b/examples/tui_00.rs similarity index 100% rename from tengri/examples/tui_00.rs rename to examples/tui_00.rs diff --git a/tengri/examples/tui_01.rs b/examples/tui_01.rs similarity index 100% rename from tengri/examples/tui_01.rs rename to examples/tui_01.rs diff --git a/tengri/.scratch.rs b/src/.scratch.rs similarity index 100% rename from tengri/.scratch.rs rename to src/.scratch.rs diff --git a/tengri/README.md b/src/README.md similarity index 100% rename from tengri/README.md rename to src/README.md diff --git a/tengri/tengri.rs b/src/tengri.rs similarity index 95% rename from tengri/tengri.rs rename to src/tengri.rs index e3310ec..76fc703 100644 --- a/tengri/tengri.rs +++ b/src/tengri.rs @@ -453,6 +453,22 @@ pub fn tui_input + Send + Sync + 'static> ( }) } +/// Should be impl something or other... +/// +/// ``` +/// 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 {} +/// let state = State; +/// 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!)))")?; +/// ``` #[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> ( state: &S, output: &mut TuiOut, expr: impl Expression + 'a ) -> Usually where @@ -472,22 +488,22 @@ pub fn tui_input + Send + Sync + 'static> ( let _arg2 = tail1.head(); match frags.next() { - Some("text") => if let Some(src) = args?.src()? { output.place(&src) }, + 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( - Namespace::::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")), - Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap()), - )) + let arg0 = arg0?.expect("fg: expected arg 0 (color)"); + let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")); + let thunk = Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap()); + output.place(&Tui::fg(color, thunk)) }, Some("bg") => { - let arg0 = arg0?.expect("bg: expected arg 0 (color)"); - output.place(&Tui::bg( - Namespace::::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")), - Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap()), - )) + 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()); + output.place(&Tui::bg(color, thunk)) }, _ => return Ok(false) diff --git a/tengri/tengri_impl.rs b/src/tengri_impl.rs similarity index 100% rename from tengri/tengri_impl.rs rename to src/tengri_impl.rs diff --git a/tengri/tengri_struct.rs b/src/tengri_struct.rs similarity index 100% rename from tengri/tengri_struct.rs rename to src/tengri_struct.rs diff --git a/tengri/tengri_trait.rs b/src/tengri_trait.rs similarity index 100% rename from tengri/tengri_trait.rs rename to src/tengri_trait.rs diff --git a/tengri/tengri_type.rs b/src/tengri_type.rs similarity index 100% rename from tengri/tengri_type.rs rename to src/tengri_type.rs diff --git a/tengri/Cargo.toml b/tengri/Cargo.toml deleted file mode 100644 index f5927cb..0000000 --- a/tengri/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "tengri" -edition = "2024" -description = "UI metaframework." -version = { workspace = true } - -[features] -default = [ "input", "output", "tui" ] -bumpalo = [ "dep:bumpalo" ] -input = [ ] -output = [ ] -tui = [ ] -dsl = [ ] - -[dependencies] -atomic_float = { workspace = true } -better-panic = { workspace = true } -bumpalo = { workspace = true, optional = true } -crossterm = { workspace = true } -dizzle = { workspace = true } -palette = { workspace = true } -quanta = { workspace = true } -rand = { workspace = true } -ratatui = { workspace = true } -unicode-width = { workspace = true } - -[dev-dependencies] -tengri_proc = { workspace = true } -tengri = { workspace = true, features = [ "dsl" ] } -crossterm = { workspace = true } -proptest = { workspace = true } -proptest-derive = { workspace = true } - -[lib] -path = "tengri.rs" - -[target.'cfg(target_os = "linux")'] -rustflags = ["-C", "link-arg=-fuse-ld=mold"]