mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-04-04 05:50:44 +02:00
Compare commits
No commits in common. "f34ceb2e889ac8ba98cf3622901ce7670fc7a7bd" and "c1011ddb7f8061c5fe242b6eab62a66537ae671b" have entirely different histories.
f34ceb2e88
...
c1011ddb7f
18 changed files with 272 additions and 250 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
|
@ -1166,20 +1166,35 @@ dependencies = [
|
||||||
name = "tengri"
|
name = "tengri"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic_float",
|
|
||||||
"better-panic",
|
|
||||||
"bumpalo",
|
|
||||||
"crossterm 0.29.0",
|
"crossterm 0.29.0",
|
||||||
"dizzle",
|
"dizzle",
|
||||||
"palette",
|
"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",
|
||||||
"proptest",
|
"proptest",
|
||||||
"proptest-derive",
|
"proptest-derive",
|
||||||
"quanta",
|
"quanta",
|
||||||
"rand 0.8.5",
|
|
||||||
"ratatui",
|
|
||||||
"tengri",
|
"tengri",
|
||||||
"tengri_proc",
|
"tengri_tui",
|
||||||
"unicode-width 0.2.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1193,6 +1208,26 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.18"
|
version = "2.0.18"
|
||||||
|
|
|
||||||
78
Cargo.toml
78
Cargo.toml
|
|
@ -1,43 +1,3 @@
|
||||||
[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]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
||||||
|
|
@ -45,5 +5,39 @@ lto = true
|
||||||
inherits = "test"
|
inherits = "test"
|
||||||
lto = false
|
lto = false
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")']
|
[workspace]
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
resolver = "2"
|
||||||
|
members = [
|
||||||
|
"./tengri",
|
||||||
|
"./proc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
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" }
|
||||||
|
|
||||||
|
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" }
|
||||||
|
|
|
||||||
56
README.md
56
README.md
|
|
@ -1,56 +0,0 @@
|
||||||
***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).
|
|
||||||
1
README.md
Symbolic link
1
README.md
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
tengri/README.md
|
||||||
|
|
@ -881,18 +881,3 @@ impl<E: Engine, S, C: Command<S>> MenuItem<E, S, C> {
|
||||||
//}
|
//}
|
||||||
//self
|
//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 ));
|
|
||||||
//}
|
|
||||||
38
tengri/Cargo.toml
Normal file
38
tengri/Cargo.toml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
[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"]
|
||||||
|
|
@ -47,10 +47,3 @@ it expresses the following notions:
|
||||||
* sizing: `Fill`, `Fixed`, `Expand`, `Shrink`, `Min`, `Max`
|
* sizing: `Fill`, `Fixed`, `Expand`, `Shrink`, `Min`, `Max`
|
||||||
* implement custom components (that may be backend-dependent):
|
* implement custom components (that may be backend-dependent):
|
||||||
[see `tui_content` in `tengri_tui`](../tui/src/tui_content)
|
[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).
|
|
||||||
|
|
@ -10,52 +10,43 @@
|
||||||
#![feature(trait_alias)]
|
#![feature(trait_alias)]
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
#![feature(type_changing_struct_update)]
|
#![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_impl;
|
||||||
mod tengri_type; pub use self::tengri_type::*;
|
mod tengri_type; pub use self::tengri_type::*;
|
||||||
mod tengri_trait; pub use self::tengri_trait::*;
|
mod tengri_trait; pub use self::tengri_trait::*;
|
||||||
mod tengri_struct; pub use self::tengri_struct::*;
|
mod tengri_struct; pub use self::tengri_struct::*;
|
||||||
|
//pub(crate) use quanta::Clock;
|
||||||
#[macro_export] pub extern crate dizzle;
|
pub extern crate atomic_float; pub(crate) use atomic_float::AtomicF64;
|
||||||
pub use dizzle::*;
|
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;
|
||||||
use std::{time::Duration, thread::{spawn, JoinHandle}, io::Write};
|
use std::{time::Duration, thread::{spawn, JoinHandle}, io::Write};
|
||||||
pub(crate) use ::std::{
|
use unicode_width::*;
|
||||||
|
pub(crate) use ::{
|
||||||
|
std::{
|
||||||
io::{stdout, Stdout},
|
io::{stdout, Stdout},
|
||||||
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
|
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
ops::{Add, Sub, Mul, Div},
|
ops::{Add, Sub, Mul, Div},
|
||||||
marker::PhantomData,
|
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},
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define macros first, so that private macros are available in private modules:
|
// Define macros first, so that private macros are available in private modules:
|
||||||
|
|
@ -80,10 +71,10 @@ pub(crate) use ::std::{
|
||||||
/// FIXME: This is generic: should be called `eval` and be part of [dizzle].
|
/// FIXME: This is generic: should be called `eval` and be part of [dizzle].
|
||||||
#[cfg(feature = "dsl")] #[macro_export] macro_rules! view {
|
#[cfg(feature = "dsl")] #[macro_export] macro_rules! view {
|
||||||
($State:ident: $Output:ident: $namespaces:expr) => {
|
($State:ident: $Output:ident: $namespaces:expr) => {
|
||||||
impl Understand<$Output, ()> for $State {
|
impl View<$Output, ()> for $State {
|
||||||
fn understand_expr <'a> (&'a self, to: &mut $Output, expr: &'a impl Expression) -> Usually<()> {
|
fn view_expr <'a> (&'a self, to: &mut $Output, expr: &'a impl Expression) -> Usually<()> {
|
||||||
for namespace in $namespaces { if namespace(self, to, expr)? { return Ok(()) } }
|
for namespace in $namespaces { if namespace(self, to, expr)? { return Ok(()) } }
|
||||||
Err(format!("{}::<{}, ()>::understand_expr: unexpected: {expr:?}",
|
Err(format!("{}::<{}, ()>::view_expr: unexpected: {expr:?}",
|
||||||
stringify! { $State },
|
stringify! { $State },
|
||||||
stringify! { $Output }).into())
|
stringify! { $Output }).into())
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +98,7 @@ pub(crate) use ::std::{
|
||||||
#[cfg(feature = "dsl")] pub fn evaluate_output_expression <'a, O: Out + 'a, S> (
|
#[cfg(feature = "dsl")] pub fn evaluate_output_expression <'a, O: Out + 'a, S> (
|
||||||
state: &S, output: &mut O, expr: &'a impl Expression
|
state: &S, output: &mut O, expr: &'a impl Expression
|
||||||
) -> Usually<bool> where
|
) -> Usually<bool> where
|
||||||
S: Understand<O, ()>
|
S: View<O, ()>
|
||||||
+ for<'b>Namespace<'b, bool>
|
+ for<'b>Namespace<'b, bool>
|
||||||
+ for<'b>Namespace<'b, O::Unit>
|
+ for<'b>Namespace<'b, O::Unit>
|
||||||
{
|
{
|
||||||
|
|
@ -129,18 +120,18 @@ pub(crate) use ::std::{
|
||||||
|
|
||||||
Some("when") => output.place(&When::new(
|
Some("when") => output.place(&When::new(
|
||||||
state.namespace(arg0?)?.unwrap(),
|
state.namespace(arg0?)?.unwrap(),
|
||||||
Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap())
|
Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap())
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Some("either") => output.place(&Either::new(
|
Some("either") => output.place(&Either::new(
|
||||||
state.namespace(arg0?)?.unwrap(),
|
state.namespace(arg0?)?.unwrap(),
|
||||||
Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()),
|
Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap()),
|
||||||
Thunk::new(move|output: &mut O|state.understand(output, &arg2).unwrap())
|
Thunk::new(move|output: &mut O|state.view(output, &arg2).unwrap())
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Some("bsp") => output.place(&{
|
Some("bsp") => output.place(&{
|
||||||
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
|
let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap());
|
||||||
let b = Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap());
|
let b = Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap());
|
||||||
match frags.next() {
|
match frags.next() {
|
||||||
Some("n") => Bsp::n(a, b),
|
Some("n") => Bsp::n(a, b),
|
||||||
Some("s") => Bsp::s(a, b),
|
Some("s") => Bsp::s(a, b),
|
||||||
|
|
@ -153,7 +144,7 @@ pub(crate) use ::std::{
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Some("align") => output.place(&{
|
Some("align") => output.place(&{
|
||||||
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
|
let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap());
|
||||||
match frags.next() {
|
match frags.next() {
|
||||||
Some("n") => Align::n(a),
|
Some("n") => Align::n(a),
|
||||||
Some("s") => Align::s(a),
|
Some("s") => Align::s(a),
|
||||||
|
|
@ -167,7 +158,7 @@ pub(crate) use ::std::{
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Some("fill") => output.place(&{
|
Some("fill") => output.place(&{
|
||||||
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
|
let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap());
|
||||||
match frags.next() {
|
match frags.next() {
|
||||||
Some("xy") | None => Fill::XY(a),
|
Some("xy") | None => Fill::XY(a),
|
||||||
Some("x") => Fill::X(a),
|
Some("x") => Fill::X(a),
|
||||||
|
|
@ -179,7 +170,7 @@ pub(crate) use ::std::{
|
||||||
Some("fixed") => output.place(&{
|
Some("fixed") => output.place(&{
|
||||||
let axis = frags.next();
|
let axis = frags.next();
|
||||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
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());
|
let cb = Thunk::new(move|output: &mut O|state.view(output, &arg).unwrap());
|
||||||
match axis {
|
match axis {
|
||||||
Some("xy") | None => Fixed::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
Some("xy") | None => Fixed::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||||
Some("x") => Fixed::X(state.namespace(arg0?)?.unwrap(), cb),
|
Some("x") => Fixed::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
|
@ -192,7 +183,7 @@ pub(crate) use ::std::{
|
||||||
Some("min") => output.place(&{
|
Some("min") => output.place(&{
|
||||||
let axis = frags.next();
|
let axis = frags.next();
|
||||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
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());
|
let cb = Thunk::new(move|output: &mut O|state.view(output, &arg).unwrap());
|
||||||
match axis {
|
match axis {
|
||||||
Some("xy") | None => Min::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
Some("xy") | None => Min::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||||
Some("x") => Min::X(state.namespace(arg0?)?.unwrap(), cb),
|
Some("x") => Min::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
|
@ -204,7 +195,7 @@ pub(crate) use ::std::{
|
||||||
Some("max") => output.place(&{
|
Some("max") => output.place(&{
|
||||||
let axis = frags.next();
|
let axis = frags.next();
|
||||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
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());
|
let cb = Thunk::new(move|output: &mut O|state.view(output, &arg).unwrap());
|
||||||
match axis {
|
match axis {
|
||||||
Some("xy") | None => Max::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
Some("xy") | None => Max::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||||
Some("x") => Max::X(state.namespace(arg0?)?.unwrap(), cb),
|
Some("x") => Max::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
|
@ -216,7 +207,7 @@ pub(crate) use ::std::{
|
||||||
Some("push") => output.place(&{
|
Some("push") => output.place(&{
|
||||||
let axis = frags.next();
|
let axis = frags.next();
|
||||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
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());
|
let cb = Thunk::new(move|output: &mut O|state.view(output, &arg).unwrap());
|
||||||
match axis {
|
match axis {
|
||||||
Some("xy") | None => Push::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
Some("xy") | None => Push::XY(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
|
||||||
Some("x") => Push::X(state.namespace(arg0?)?.unwrap(), cb),
|
Some("x") => Push::X(state.namespace(arg0?)?.unwrap(), cb),
|
||||||
|
|
@ -282,7 +273,7 @@ pub(crate) use ::std::{
|
||||||
#[macro_export] macro_rules! tui_main {
|
#[macro_export] macro_rules! tui_main {
|
||||||
($expr:expr) => {
|
($expr:expr) => {
|
||||||
fn main () -> Usually<()> {
|
fn main () -> Usually<()> {
|
||||||
tengri::Tui::new(stdout()).run(true, $expr)?;
|
tengri::Tui::run(true, $expr)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -323,6 +314,37 @@ macro_rules! border {
|
||||||
)+}
|
)+}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run an app in the main loop.
|
||||||
|
pub fn tui_run <T: Send + Sync + Draw<TuiOut> + Handle<TuiIn> + 'static> (
|
||||||
|
join: bool,
|
||||||
|
state: &Arc<RwLock<T>>
|
||||||
|
) -> Usually<Arc<RwLock<Tui>>> {
|
||||||
|
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 <W: Write> (
|
pub fn tui_setup <W: Write> (
|
||||||
backend: &mut CrosstermBackend<W>
|
backend: &mut CrosstermBackend<W>
|
||||||
|
|
@ -453,26 +475,10 @@ pub fn tui_input <T: Handle<TuiIn> + 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<TuiOut, ()> 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> (
|
#[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> (
|
||||||
state: &S, output: &mut TuiOut, expr: impl Expression + 'a
|
state: &S, output: &mut TuiOut, expr: impl Expression + 'a
|
||||||
) -> Usually<bool> where
|
) -> Usually<bool> where
|
||||||
S: Understand<TuiOut, ()>
|
S: View<TuiOut, ()>
|
||||||
+ for<'b>Namespace<'b, bool>
|
+ for<'b>Namespace<'b, bool>
|
||||||
+ for<'b>Namespace<'b, u16>
|
+ for<'b>Namespace<'b, u16>
|
||||||
+ for<'b>Namespace<'b, Color>
|
+ for<'b>Namespace<'b, Color>
|
||||||
|
|
@ -488,22 +494,22 @@ pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> (
|
||||||
let _arg2 = tail1.head();
|
let _arg2 = tail1.head();
|
||||||
match frags.next() {
|
match frags.next() {
|
||||||
|
|
||||||
Some("text") => {
|
Some("text") => if let Some(src) = args?.src()? { output.place(&src) },
|
||||||
if let Some(src) = args?.src()? { output.place(&src) }
|
|
||||||
},
|
|
||||||
|
|
||||||
Some("fg") => {
|
Some("fg") => {
|
||||||
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
|
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
|
||||||
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color"));
|
output.place(&Tui::fg(
|
||||||
let thunk = Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap());
|
Namespace::<Color>::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")),
|
||||||
output.place(&Tui::fg(color, thunk))
|
Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()),
|
||||||
|
))
|
||||||
},
|
},
|
||||||
|
|
||||||
Some("bg") => {
|
Some("bg") => {
|
||||||
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
||||||
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color"));
|
output.place(&Tui::bg(
|
||||||
let thunk = Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap());
|
Namespace::<Color>::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")),
|
||||||
output.place(&Tui::bg(color, thunk))
|
Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()),
|
||||||
|
))
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => return Ok(false)
|
_ => return Ok(false)
|
||||||
|
|
@ -894,8 +900,7 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut output = String::new();
|
let engine = Tui::run(false, TestComponent("hello world".into()))?;
|
||||||
let engine = Tui::new(&mut output).run(false, TestComponent("hello world".into()))?;
|
|
||||||
engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
|
engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
//engine.run(&state)?;
|
//engine.run(&state)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -983,38 +983,11 @@ impl PerfModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tui {
|
impl Tui {
|
||||||
pub fn new (output: Stdout) -> Usually<Self> {
|
|
||||||
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.
|
/// Create and launch a terminal user interface.
|
||||||
pub fn run <T> (self, join: bool, state: &Arc<RwLock<T>>) -> Usually<Arc<RwLock<Self>>> where
|
pub fn run <T> (join: bool, state: T) -> Usually<Arc<RwLock<Self>>> where
|
||||||
T: Handle<TuiIn> + Draw<TuiOut> + Send + Sync + 'static
|
T: Handle<TuiIn> + Draw<TuiOut> + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
let tui = Arc::new(RwLock::new(self));
|
tui_run(join, &Arc::new(RwLock::new(state)))
|
||||||
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
|
/// True if done
|
||||||
pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
|
pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
|
||||||
|
|
@ -1048,11 +1021,6 @@ impl TuiEvent {
|
||||||
Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self))
|
Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<char> for TuiEvent {
|
|
||||||
fn from (c: char) -> Self {
|
|
||||||
Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TuiKey {
|
impl TuiKey {
|
||||||
const SPLIT: char = '/';
|
const SPLIT: char = '/';
|
||||||
#[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Usually<Self> {
|
#[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Usually<Self> {
|
||||||
|
|
@ -14,7 +14,6 @@ pub struct Tui {
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)] pub struct TuiIn {
|
#[derive(Debug, Clone)] pub struct TuiIn {
|
||||||
/// Input event
|
/// Input event
|
||||||
pub event: TuiEvent,
|
pub event: TuiEvent,
|
||||||
|
|
@ -112,6 +112,26 @@ pub trait Draw<O: Out> {
|
||||||
fn draw (&self, to: &mut O);
|
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<O, U> {
|
||||||
|
fn view_expr <'a> (&'a self, _output: &mut O, expr: &'a impl Expression) -> Usually<U> {
|
||||||
|
Err(format!("View::view_expr: no exprs defined: {expr:?}").into())
|
||||||
|
}
|
||||||
|
fn view_word <'a> (&'a self, _output: &mut O, word: &'a impl Symbol) -> Usually<U> {
|
||||||
|
Err(format!("View::view_word: no words defined: {word:?}").into())
|
||||||
|
}
|
||||||
|
fn view <'a> (&'a self, output: &mut O, dsl: &'a impl Language) -> Usually<U> {
|
||||||
|
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.
|
/// Outputs combinator.
|
||||||
pub trait Lay<O: Out>: Sized {}
|
pub trait Lay<O: Out>: Sized {}
|
||||||
|
|
||||||
20
tui/Cargo.toml
Normal file
20
tui/Cargo.toml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
[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 }
|
||||||
4
tui/README.md
Normal file
4
tui/README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
***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).
|
||||||
0
tui/src/.scratch.rs
Normal file
0
tui/src/.scratch.rs
Normal file
0
tui/src/lib.rs
Normal file
0
tui/src/lib.rs
Normal file
16
tui/src/tui_test.rs
Normal file
16
tui/src/tui_test.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
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 ));
|
||||||
|
//}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue