mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-04-04 05:50:44 +02:00
Compare commits
4 commits
c1011ddb7f
...
f34ceb2e88
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f34ceb2e88 | ||
|
|
006cddcc16 | ||
|
|
b294f2e62b | ||
|
|
85ccb0737f |
18 changed files with 250 additions and 272 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
|
@ -1166,35 +1166,20 @@ 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",
|
||||||
"tengri",
|
"palette",
|
||||||
"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_tui",
|
"tengri_proc",
|
||||||
|
"unicode-width 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1208,26 +1193,6 @@ 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,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]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
||||||
|
|
@ -5,39 +45,5 @@ lto = true
|
||||||
inherits = "test"
|
inherits = "test"
|
||||||
lto = false
|
lto = false
|
||||||
|
|
||||||
[workspace]
|
[target.'cfg(target_os = "linux")']
|
||||||
resolver = "2"
|
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
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" }
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
tengri/README.md
|
|
||||||
56
README.md
Normal file
56
README.md
Normal file
|
|
@ -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).
|
||||||
|
|
@ -881,3 +881,18 @@ 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 ));
|
||||||
|
//}
|
||||||
|
|
@ -47,3 +47,10 @@ 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,43 +10,52 @@
|
||||||
#![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;
|
|
||||||
pub extern crate atomic_float; pub(crate) use atomic_float::AtomicF64;
|
#[macro_export] pub extern crate dizzle;
|
||||||
pub extern crate ratatui;
|
pub use dizzle::*;
|
||||||
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};
|
||||||
use unicode_width::*;
|
pub(crate) use ::std::{
|
||||||
pub(crate) use ::{
|
io::{stdout, Stdout},
|
||||||
std::{
|
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
|
||||||
io::{stdout, Stdout},
|
fmt::{Debug, Display},
|
||||||
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
|
ops::{Add, Sub, Mul, Div},
|
||||||
fmt::{Debug, Display},
|
marker::PhantomData,
|
||||||
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},
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define macros first, so that private macros are available in private modules:
|
// Define macros first, so that private macros are available in private modules:
|
||||||
|
|
@ -71,10 +80,10 @@ pub(crate) use ::{
|
||||||
/// 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 View<$Output, ()> for $State {
|
impl Understand<$Output, ()> for $State {
|
||||||
fn view_expr <'a> (&'a self, to: &mut $Output, expr: &'a impl Expression) -> Usually<()> {
|
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(()) } }
|
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! { $State },
|
||||||
stringify! { $Output }).into())
|
stringify! { $Output }).into())
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +107,7 @@ pub(crate) use ::{
|
||||||
#[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: View<O, ()>
|
S: Understand<O, ()>
|
||||||
+ for<'b>Namespace<'b, bool>
|
+ for<'b>Namespace<'b, bool>
|
||||||
+ for<'b>Namespace<'b, O::Unit>
|
+ for<'b>Namespace<'b, O::Unit>
|
||||||
{
|
{
|
||||||
|
|
@ -120,18 +129,18 @@ pub(crate) use ::{
|
||||||
|
|
||||||
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.view(output, &arg1).unwrap())
|
Thunk::new(move|output: &mut O|state.understand(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.view(output, &arg1).unwrap()),
|
Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()),
|
||||||
Thunk::new(move|output: &mut O|state.view(output, &arg2).unwrap())
|
Thunk::new(move|output: &mut O|state.understand(output, &arg2).unwrap())
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Some("bsp") => output.place(&{
|
Some("bsp") => 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());
|
||||||
let b = Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap());
|
let b = Thunk::new(move|output: &mut O|state.understand(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),
|
||||||
|
|
@ -144,7 +153,7 @@ pub(crate) use ::{
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Some("align") => output.place(&{
|
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() {
|
match frags.next() {
|
||||||
Some("n") => Align::n(a),
|
Some("n") => Align::n(a),
|
||||||
Some("s") => Align::s(a),
|
Some("s") => Align::s(a),
|
||||||
|
|
@ -158,7 +167,7 @@ pub(crate) use ::{
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Some("fill") => output.place(&{
|
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() {
|
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),
|
||||||
|
|
@ -170,7 +179,7 @@ pub(crate) use ::{
|
||||||
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.view(output, &arg).unwrap());
|
let cb = Thunk::new(move|output: &mut O|state.understand(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),
|
||||||
|
|
@ -183,7 +192,7 @@ pub(crate) use ::{
|
||||||
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.view(output, &arg).unwrap());
|
let cb = Thunk::new(move|output: &mut O|state.understand(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),
|
||||||
|
|
@ -195,7 +204,7 @@ pub(crate) use ::{
|
||||||
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.view(output, &arg).unwrap());
|
let cb = Thunk::new(move|output: &mut O|state.understand(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),
|
||||||
|
|
@ -207,7 +216,7 @@ pub(crate) use ::{
|
||||||
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.view(output, &arg).unwrap());
|
let cb = Thunk::new(move|output: &mut O|state.understand(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),
|
||||||
|
|
@ -273,7 +282,7 @@ pub(crate) use ::{
|
||||||
#[macro_export] macro_rules! tui_main {
|
#[macro_export] macro_rules! tui_main {
|
||||||
($expr:expr) => {
|
($expr:expr) => {
|
||||||
fn main () -> Usually<()> {
|
fn main () -> Usually<()> {
|
||||||
tengri::Tui::run(true, $expr)?;
|
tengri::Tui::new(stdout()).run(true, $expr)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -314,37 +323,6 @@ 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>
|
||||||
|
|
@ -475,10 +453,26 @@ 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: View<TuiOut, ()>
|
S: Understand<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>
|
||||||
|
|
@ -494,22 +488,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") => if let Some(src) = args?.src()? { output.place(&src) },
|
Some("text") => {
|
||||||
|
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)");
|
||||||
output.place(&Tui::fg(
|
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color"));
|
||||||
Namespace::<Color>::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());
|
||||||
Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()),
|
output.place(&Tui::fg(color, thunk))
|
||||||
))
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Some("bg") => {
|
Some("bg") => {
|
||||||
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
||||||
output.place(&Tui::bg(
|
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color"));
|
||||||
Namespace::<Color>::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());
|
||||||
Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()),
|
output.place(&Tui::bg(color, thunk))
|
||||||
))
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => return Ok(false)
|
_ => return Ok(false)
|
||||||
|
|
@ -900,7 +894,8 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
|
||||||
Ok(None)
|
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.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
//engine.run(&state)?;
|
//engine.run(&state)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -983,11 +983,38 @@ 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> (join: bool, state: T) -> Usually<Arc<RwLock<Self>>> where
|
pub fn run <T> (self, join: bool, state: &Arc<RwLock<T>>) -> Usually<Arc<RwLock<Self>>> where
|
||||||
T: Handle<TuiIn> + Draw<TuiOut> + Send + Sync + 'static
|
T: Handle<TuiIn> + Draw<TuiOut> + 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
|
/// 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) }
|
||||||
|
|
@ -1021,6 +1048,11 @@ 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,6 +14,7 @@ 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,26 +112,6 @@ 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 {}
|
||||||
|
|
||||||
|
|
@ -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"]
|
|
||||||
|
|
@ -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 }
|
|
||||||
|
|
@ -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).
|
|
||||||
|
|
@ -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 ));
|
|
||||||
//}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue