mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-04-03 21:40: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"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"atomic_float",
|
||||
"better-panic",
|
||||
"bumpalo",
|
||||
"crossterm 0.29.0",
|
||||
"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-derive",
|
||||
"quanta",
|
||||
"rand 0.8.5",
|
||||
"ratatui",
|
||||
"tengri",
|
||||
"tengri_proc",
|
||||
"unicode-width 0.2.0",
|
||||
"tengri_tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1193,6 +1208,26 @@ 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"
|
||||
|
|
|
|||
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]
|
||||
lto = true
|
||||
|
||||
|
|
@ -45,5 +5,39 @@ lto = true
|
|||
inherits = "test"
|
||||
lto = false
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
[workspace]
|
||||
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
|
||||
//}
|
||||
|
||||
//#[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`
|
||||
* 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).
|
||||
|
|
@ -10,52 +10,43 @@
|
|||
#![feature(trait_alias)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(type_changing_struct_update)]
|
||||
|
||||
//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;
|
||||
//#![feature(non_lifetime_binders)]
|
||||
mod tengri_impl;
|
||||
mod tengri_type; pub use self::tengri_type::*;
|
||||
mod tengri_trait; pub use self::tengri_trait::*;
|
||||
mod tengri_struct; pub use self::tengri_struct::*;
|
||||
|
||||
#[macro_export] pub extern crate dizzle;
|
||||
pub use dizzle::*;
|
||||
|
||||
//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;
|
||||
use std::{time::Duration, thread::{spawn, JoinHandle}, io::Write};
|
||||
pub(crate) use ::std::{
|
||||
io::{stdout, Stdout},
|
||||
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}},
|
||||
fmt::{Debug, Display},
|
||||
ops::{Add, Sub, Mul, Div},
|
||||
marker::PhantomData,
|
||||
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},
|
||||
}
|
||||
};
|
||||
|
||||
// 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].
|
||||
#[cfg(feature = "dsl")] #[macro_export] macro_rules! view {
|
||||
($State:ident: $Output:ident: $namespaces:expr) => {
|
||||
impl Understand<$Output, ()> for $State {
|
||||
fn understand_expr <'a> (&'a self, to: &mut $Output, expr: &'a impl Expression) -> Usually<()> {
|
||||
impl View<$Output, ()> for $State {
|
||||
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(()) } }
|
||||
Err(format!("{}::<{}, ()>::understand_expr: unexpected: {expr:?}",
|
||||
Err(format!("{}::<{}, ()>::view_expr: unexpected: {expr:?}",
|
||||
stringify! { $State },
|
||||
stringify! { $Output }).into())
|
||||
}
|
||||
|
|
@ -107,7 +98,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<bool> where
|
||||
S: Understand<O, ()>
|
||||
S: View<O, ()>
|
||||
+ for<'b>Namespace<'b, bool>
|
||||
+ for<'b>Namespace<'b, O::Unit>
|
||||
{
|
||||
|
|
@ -129,18 +120,18 @@ pub(crate) use ::std::{
|
|||
|
||||
Some("when") => output.place(&When::new(
|
||||
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(
|
||||
state.namespace(arg0?)?.unwrap(),
|
||||
Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap()),
|
||||
Thunk::new(move|output: &mut O|state.understand(output, &arg2).unwrap())
|
||||
Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap()),
|
||||
Thunk::new(move|output: &mut O|state.view(output, &arg2).unwrap())
|
||||
)),
|
||||
|
||||
Some("bsp") => output.place(&{
|
||||
let a = Thunk::new(move|output: &mut O|state.understand(output, &arg0).unwrap());
|
||||
let b = Thunk::new(move|output: &mut O|state.understand(output, &arg1).unwrap());
|
||||
let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap());
|
||||
let b = Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap());
|
||||
match frags.next() {
|
||||
Some("n") => Bsp::n(a, b),
|
||||
Some("s") => Bsp::s(a, b),
|
||||
|
|
@ -153,7 +144,7 @@ pub(crate) use ::std::{
|
|||
}),
|
||||
|
||||
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() {
|
||||
Some("n") => Align::n(a),
|
||||
Some("s") => Align::s(a),
|
||||
|
|
@ -167,7 +158,7 @@ pub(crate) use ::std::{
|
|||
}),
|
||||
|
||||
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() {
|
||||
Some("xy") | None => Fill::XY(a),
|
||||
Some("x") => Fill::X(a),
|
||||
|
|
@ -179,7 +170,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.understand(output, &arg).unwrap());
|
||||
let cb = Thunk::new(move|output: &mut O|state.view(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 +183,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.understand(output, &arg).unwrap());
|
||||
let cb = Thunk::new(move|output: &mut O|state.view(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 +195,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.understand(output, &arg).unwrap());
|
||||
let cb = Thunk::new(move|output: &mut O|state.view(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 +207,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.understand(output, &arg).unwrap());
|
||||
let cb = Thunk::new(move|output: &mut O|state.view(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),
|
||||
|
|
@ -282,7 +273,7 @@ pub(crate) use ::std::{
|
|||
#[macro_export] macro_rules! tui_main {
|
||||
($expr:expr) => {
|
||||
fn main () -> Usually<()> {
|
||||
tengri::Tui::new(stdout()).run(true, $expr)?;
|
||||
tengri::Tui::run(true, $expr)?;
|
||||
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> (
|
||||
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> (
|
||||
state: &S, output: &mut TuiOut, expr: impl Expression + 'a
|
||||
) -> Usually<bool> where
|
||||
S: Understand<TuiOut, ()>
|
||||
S: View<TuiOut, ()>
|
||||
+ for<'b>Namespace<'b, bool>
|
||||
+ for<'b>Namespace<'b, u16>
|
||||
+ for<'b>Namespace<'b, Color>
|
||||
|
|
@ -488,22 +494,22 @@ pub fn tui_input <T: Handle<TuiIn> + 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)");
|
||||
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))
|
||||
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
|
||||
output.place(&Tui::fg(
|
||||
Namespace::<Color>::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")),
|
||||
Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()),
|
||||
))
|
||||
},
|
||||
|
||||
Some("bg") => {
|
||||
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
||||
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))
|
||||
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
||||
output.place(&Tui::bg(
|
||||
Namespace::<Color>::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")),
|
||||
Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()),
|
||||
))
|
||||
},
|
||||
|
||||
_ => return Ok(false)
|
||||
|
|
@ -894,8 +900,7 @@ pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
let mut output = String::new();
|
||||
let engine = Tui::new(&mut output).run(false, TestComponent("hello world".into()))?;
|
||||
let engine = Tui::run(false, TestComponent("hello world".into()))?;
|
||||
engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
//engine.run(&state)?;
|
||||
Ok(())
|
||||
|
|
@ -983,38 +983,11 @@ impl PerfModel {
|
|||
}
|
||||
|
||||
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.
|
||||
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
|
||||
{
|
||||
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)
|
||||
tui_run(join, &Arc::new(RwLock::new(state)))
|
||||
}
|
||||
/// True if done
|
||||
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))
|
||||
}
|
||||
}
|
||||
impl From<char> 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<Self> {
|
||||
|
|
@ -14,7 +14,6 @@ pub struct Tui {
|
|||
pub perf: PerfModel,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)] pub struct TuiIn {
|
||||
/// Input event
|
||||
pub event: TuiEvent,
|
||||
|
|
@ -112,6 +112,26 @@ pub trait Draw<O: Out> {
|
|||
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.
|
||||
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