mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-03-13 12:10:44 +01:00
move the jack stuff from tek into tengri
This commit is contained in:
parent
5d0dc40fdc
commit
c1b727bafc
11 changed files with 3006 additions and 1494 deletions
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[submodule "dizzle"]
|
||||||
|
path = dizzle
|
||||||
|
url = ssh://git@codeberg.org/unspeaker/dizzle.git
|
||||||
|
[submodule "rust-jack"]
|
||||||
|
path = rust-jack
|
||||||
|
url = https://codeberg.org/unspeaker/rust-jack
|
||||||
1357
Cargo.lock
generated
1357
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
23
Cargo.toml
23
Cargo.toml
|
|
@ -5,31 +5,34 @@ version = "0.15.0"
|
||||||
description = "UI metaframework."
|
description = "UI metaframework."
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "input", "output", "tui" ]
|
default = ["tui", "jack", "dsl"]
|
||||||
bumpalo = ["dep:bumpalo"]
|
bumpalo = ["dep:bumpalo"]
|
||||||
input = [ ]
|
tui = ["dep:ratatui", "dep:crossterm"]
|
||||||
output = [ ]
|
gui = ["dep:winit"]
|
||||||
tui = [ ]
|
dsl = ["dep:dizzle"]
|
||||||
dsl = [ ]
|
jack = ["dep:jack"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0" }
|
anyhow = { version = "1.0" }
|
||||||
atomic_float = { version = "1" }
|
atomic_float = { version = "1" }
|
||||||
better-panic = { version = "0.3.0" }
|
better-panic = { version = "0.3.0" }
|
||||||
bumpalo = { version = "3.19.0", optional = true }
|
|
||||||
crossterm = { version = "0.29.0" }
|
|
||||||
palette = { version = "0.7.6", features = [ "random" ] }
|
palette = { version = "0.7.6", features = [ "random" ] }
|
||||||
quanta = { version = "0.12.3" }
|
quanta = { version = "0.12.3" }
|
||||||
rand = { version = "0.8.5" }
|
rand = { version = "0.8.5" }
|
||||||
ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
|
|
||||||
unicode-width = { version = "0.2" }
|
unicode-width = { version = "0.2" }
|
||||||
dizzle = { path = "../dizzle" }
|
|
||||||
|
dizzle = { optional = true, path = "./dizzle" }
|
||||||
|
jack = { optional = true, path = "./rust-jack" }
|
||||||
|
winit = { optional = true, version = "0.30.4", features = [ "x11" ]}
|
||||||
|
bumpalo = { optional = true, version = "3.19.0" }
|
||||||
|
crossterm = { optional = true, version = "0.29.0" }
|
||||||
|
ratatui = { optional = true, version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = { version = "^1" }
|
proptest = { version = "^1" }
|
||||||
proptest-derive = { version = "^0.5.1" }
|
proptest-derive = { version = "^0.5.1" }
|
||||||
tengri = { path = ".", features = [ "dsl" ] }
|
tengri = { path = ".", features = [ "dsl" ] }
|
||||||
tengri_proc = { path = "./proc" }
|
#tengri_proc = { path = "./proc" }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/tengri.rs"
|
path = "src/tengri.rs"
|
||||||
|
|
|
||||||
59
README.md
59
README.md
|
|
@ -1,56 +1,31 @@
|
||||||
|
# tengri
|
||||||
|
|
||||||
***tengri*** is a metaframework for building interactive applications with rust. (aren't we all?)
|
***tengri*** is a metaframework for building interactive applications with rust. (aren't we all?)
|
||||||
|
|
||||||
|
tengri is published under [**AGPL3**](../LICENSE).
|
||||||
|
|
||||||
|
## tengri and tek
|
||||||
|
|
||||||
tengri is developed as part of [***tek***](https://codeberg.org/unspeaker/tek),
|
tengri is developed as part of [***tek***](https://codeberg.org/unspeaker/tek),
|
||||||
a music program for terminals.
|
a music program for terminals.
|
||||||
|
|
||||||
tengri contains:
|
## tengri and dizzle
|
||||||
* [***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:
|
tengri integrates with [***dizzle***](./dizzle),
|
||||||
* [***core***](./core), the shared definitions ("utils") module
|
a framework for defining domain-specific languages.
|
||||||
* [***proc***](./proc), the space for procedural macros
|
|
||||||
* [***tengri***](./tengri), the top-level reexport crate
|
|
||||||
|
|
||||||
tengri is published under [**AGPL3**](./LICENSE).
|
## what's in the box
|
||||||
|
|
||||||
## Input
|
excluding the reexports, tengri is a flat namespace containing the following kinds of things:
|
||||||
|
|
||||||
***tengri_input*** is where tengri's input handling is defined.
|
### logical
|
||||||
|
|
||||||
the following items are provided:
|
### temporal
|
||||||
* `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
|
### aural
|
||||||
|
|
||||||
***tengri_output*** is an abstract interface layout framework.
|
### spatial
|
||||||
|
|
||||||
it expresses the following notions:
|
### textual
|
||||||
|
|
||||||
* [**space:**](./src/space.rs) `Direction`, `Coordinate`, `Area`, `Size`, `Measure`
|
checkout the repo and generate docs with `cargo doc` to explore!
|
||||||
|
|
||||||
* [**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
dizzle
Submodule
1
dizzle
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 909b94cbd4beffb49be314724dc79db9374bcc99
|
||||||
1
rust-jack
Submodule
1
rust-jack
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 764a38a880ab4749ea60aa7e53cd814b858e606c
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
|
//impl<O: Out, T: HasContent<O>> Draw<O> for T {
|
||||||
|
//fn draw (&self, to: &mut O) {
|
||||||
|
//let area = to.area();
|
||||||
|
//*to.area_mut() = self.0;
|
||||||
|
//self.content().draw(to);
|
||||||
|
//*to.area_mut() = area;
|
||||||
|
//}
|
||||||
|
//}
|
||||||
///// The syntagm `(when :condition :content)` corresponds to a [When] layout element.
|
///// The syntagm `(when :condition :content)` corresponds to a [When] layout element.
|
||||||
//impl<S, A> FromDsl<S> for When<A> where bool: FromDsl<S>, A: FromDsl<S> {
|
//impl<S, A> FromDsl<S> for When<A> where bool: FromDsl<S>, A: FromDsl<S> {
|
||||||
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
|
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
|
||||||
|
|
|
||||||
450
src/tengri.rs
450
src/tengri.rs
|
|
@ -9,23 +9,27 @@
|
||||||
#![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)]
|
||||||
mod tengri_impl;
|
|
||||||
mod tengri_trait; pub use self::tengri_trait::*;
|
|
||||||
mod tengri_struct; pub use self::tengri_struct::*;
|
mod tengri_struct; pub use self::tengri_struct::*;
|
||||||
|
mod tengri_trait; pub use self::tengri_trait::*;
|
||||||
|
mod tengri_impl; pub use self::tengri_impl::*;
|
||||||
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
|
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
|
||||||
pub extern crate dizzle; pub use dizzle::*;
|
pub extern crate dizzle; pub use dizzle::*;
|
||||||
pub extern crate atomic_float; pub(crate) use atomic_float::AtomicF64;
|
pub extern crate atomic_float; pub(crate) use atomic_float::AtomicF64;
|
||||||
pub extern crate palette; pub(crate) use ::palette::{*, convert::*, okhsl::*};
|
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 better_panic; pub(crate) use better_panic::{Settings, Verbosity};
|
||||||
pub extern crate unicode_width; pub(crate) use unicode_width::*;
|
pub extern crate unicode_width; pub(crate) use unicode_width::*;
|
||||||
pub extern crate ratatui; pub(crate) use ::ratatui::{
|
#[cfg(feature = "jack")] pub extern crate jack;
|
||||||
|
#[cfg(feature = "jack")] pub use jack::{*, contrib::{*, ClosureProcessHandler}};
|
||||||
|
#[cfg(feature = "tui")] pub extern crate ratatui;
|
||||||
|
#[cfg(feature = "tui")] pub(crate) use ::ratatui::{
|
||||||
prelude::{Color, Style, Buffer, Position},
|
prelude::{Color, Style, Buffer, Position},
|
||||||
style::{Stylize, Modifier, Color::*},
|
style::{Stylize, Modifier, Color::*},
|
||||||
backend::{Backend, CrosstermBackend, ClearType},
|
backend::{Backend, CrosstermBackend, ClearType},
|
||||||
layout::{Size, Rect},
|
layout::{Size, Rect},
|
||||||
buffer::Cell
|
buffer::Cell
|
||||||
};
|
};
|
||||||
pub extern crate crossterm; pub(crate) use ::crossterm::{
|
#[cfg(feature = "tui")] pub extern crate crossterm;
|
||||||
|
#[cfg(feature = "tui")] pub(crate) use ::crossterm::{
|
||||||
ExecutableCommand,
|
ExecutableCommand,
|
||||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
||||||
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
||||||
|
|
@ -75,43 +79,35 @@ pub(crate) use ::std::{
|
||||||
|
|
||||||
/// Stack things on top of each other,
|
/// Stack things on top of each other,
|
||||||
#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => {{
|
#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => {{
|
||||||
let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp
|
let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)* bsp
|
||||||
}});
|
}});
|
||||||
|
|
||||||
/// Stack southward.
|
/// Stack southward.
|
||||||
#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => {{
|
#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => {{
|
||||||
let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp
|
let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)* bsp
|
||||||
}});
|
}});
|
||||||
|
|
||||||
/// Stack northward.
|
/// Stack northward.
|
||||||
#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => {{
|
#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => {{
|
||||||
let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp
|
let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)* bsp
|
||||||
}});
|
}});
|
||||||
|
|
||||||
/// Stack eastward.
|
/// Stack eastward.
|
||||||
#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{
|
#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => {{
|
||||||
let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp
|
let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)* bsp
|
||||||
}});
|
}});
|
||||||
|
|
||||||
/// Implement [Command] for given `State` and `handler`
|
/// Define an enum containing commands, and implement [Command] trait for over given `State`.
|
||||||
#[macro_export] macro_rules! command {
|
#[macro_export] macro_rules! def_command (
|
||||||
($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
|
($Command:ident: |$state:ident: $State:ty| {
|
||||||
impl$(<$($l),+>)? ::tengri::Command<$State> for $Command {
|
// FIXME: support attrs (docstrings)
|
||||||
fn execute (&$self, $state: &mut $State) -> Perhaps<Self> {
|
|
||||||
Ok($handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! def_command (($Command:ident: |$state:ident: $State:ty| {
|
|
||||||
$($Variant:ident$({$($arg:ident:$Arg:ty),+ $(,)?})?=>$body:expr),* $(,)?
|
$($Variant:ident$({$($arg:ident:$Arg:ty),+ $(,)?})?=>$body:expr),* $(,)?
|
||||||
})=>{
|
})=>{
|
||||||
#[derive(Debug)]
|
#[derive(Debug)] pub enum $Command {
|
||||||
pub enum $Command {
|
// FIXME: support attrs (docstrings)
|
||||||
$($Variant $({ $($arg: $Arg),* })?),*
|
$($Variant $({ $($arg: $Arg),* })?),*
|
||||||
}
|
}
|
||||||
impl Command<$State> for $Command {
|
impl ::tengri::Command<$State> for $Command {
|
||||||
fn execute (&self, $state: &mut $State) -> Perhaps<Self> {
|
fn execute (&self, $state: &mut $State) -> Perhaps<Self> {
|
||||||
match self {
|
match self {
|
||||||
$(Self::$Variant $({ $($arg),* })? => $body,)*
|
$(Self::$Variant $({ $($arg),* })? => $body,)*
|
||||||
|
|
@ -160,163 +156,8 @@ pub(crate) use ::std::{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! border {
|
|
||||||
($($T:ident {
|
|
||||||
$nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal
|
|
||||||
$($x:tt)*
|
|
||||||
}),+) => {$(
|
|
||||||
impl BorderStyle for $T {
|
|
||||||
const NW: &'static str = $nw;
|
|
||||||
const N: &'static str = $n;
|
|
||||||
const NE: &'static str = $ne;
|
|
||||||
const W: &'static str = $w;
|
|
||||||
const E: &'static str = $e;
|
|
||||||
const SW: &'static str = $sw;
|
|
||||||
const S: &'static str = $s;
|
|
||||||
const SE: &'static str = $se;
|
|
||||||
$($x)*
|
|
||||||
fn enabled (&self) -> bool { self.0 }
|
|
||||||
}
|
|
||||||
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
|
|
||||||
impl Layout<TuiOut> for $T {}
|
|
||||||
impl Draw<TuiOut> for $T {
|
|
||||||
fn draw (&self, to: &mut TuiOut) {
|
|
||||||
if self.enabled() { let _ = BorderStyle::draw(self, to); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn tui_setup <W: Write> (
|
|
||||||
backend: &mut CrosstermBackend<W>
|
|
||||||
) -> Usually<()> {
|
|
||||||
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
|
||||||
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
|
|
||||||
stdout().execute(LeaveAlternateScreen).unwrap();
|
|
||||||
CrosstermBackend::new(stdout()).show_cursor().unwrap();
|
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
better_panic_handler(info);
|
|
||||||
}));
|
|
||||||
stdout().execute(EnterAlternateScreen)?;
|
|
||||||
backend.hide_cursor()?;
|
|
||||||
enable_raw_mode().map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
|
||||||
stdout().execute(LeaveAlternateScreen)?;
|
|
||||||
backend.show_cursor()?;
|
|
||||||
disable_raw_mode().map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tui_resized <W: Write> (
|
|
||||||
backend: &mut CrosstermBackend<W>,
|
|
||||||
buffer: &mut Buffer,
|
|
||||||
size: ratatui::prelude::Rect
|
|
||||||
) {
|
|
||||||
if buffer.area != size {
|
|
||||||
backend.clear_region(ClearType::All).unwrap();
|
|
||||||
buffer.resize(size);
|
|
||||||
buffer.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tui_redrawn <'b, W: Write> (
|
|
||||||
backend: &mut CrosstermBackend<W>,
|
|
||||||
mut prev_buffer: &'b mut Buffer,
|
|
||||||
mut next_buffer: &'b mut Buffer
|
|
||||||
) {
|
|
||||||
let updates = prev_buffer.diff(&next_buffer);
|
|
||||||
backend.draw(updates.into_iter()).expect("failed to render");
|
|
||||||
Backend::flush(backend).expect("failed to flush output new_buffer");
|
|
||||||
std::mem::swap(&mut prev_buffer, &mut next_buffer);
|
|
||||||
next_buffer.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tui_update (
|
|
||||||
buf: &mut Buffer, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
|
|
||||||
) {
|
|
||||||
for row in 0..area.h() {
|
|
||||||
let y = area.y() + row;
|
|
||||||
for col in 0..area.w() {
|
|
||||||
let x = area.x() + col;
|
|
||||||
if x < buf.area.width && y < buf.area.height {
|
|
||||||
if let Some(cell) = buf.cell_mut(ratatui::prelude::Position { x, y }) {
|
|
||||||
callback(cell, col, row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawn the output thread.
|
|
||||||
pub fn tui_output <T: Draw<TuiOut> + Send + Sync + 'static> (
|
|
||||||
engine: Arc<RwLock<Tui>>, state: &Arc<RwLock<T>>, timer: Duration
|
|
||||||
) -> Result<JoinHandle<()>, std::io::Error> {
|
|
||||||
let exited = engine.read().unwrap().exited.clone();
|
|
||||||
let engine = engine.clone();
|
|
||||||
let state = state.clone();
|
|
||||||
let Size { width, height } = engine.read().unwrap().backend.size().expect("get size failed");
|
|
||||||
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
|
||||||
std::thread::Builder::new()
|
|
||||||
.name("tui output thread".into())
|
|
||||||
.spawn(move || loop {
|
|
||||||
if exited.fetch_and(true, Relaxed) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let t0 = engine.read().unwrap().perf.get_t0();
|
|
||||||
let Size { width, height } = engine.read().unwrap().backend.size()
|
|
||||||
.expect("get size failed");
|
|
||||||
if let Ok(state) = state.try_read() {
|
|
||||||
let size = Rect { x: 0, y: 0, width, height };
|
|
||||||
if buffer.area != size {
|
|
||||||
engine.write().unwrap().backend.clear_region(ClearType::All).expect("clear failed");
|
|
||||||
buffer.resize(size);
|
|
||||||
buffer.reset();
|
|
||||||
}
|
|
||||||
let mut output = TuiOut { buffer, area: XYWH(0, 0, width, height) };
|
|
||||||
state.draw(&mut output);
|
|
||||||
buffer = engine.write().unwrap().flip(output.buffer, size);
|
|
||||||
}
|
|
||||||
let t1 = (*engine.read().unwrap()).perf.get_t1(t0).unwrap();
|
|
||||||
buffer.set_string(0, 0, &format!("{:>3}.{:>3}ms", t1.as_millis(), t1.as_micros() % 1000), Style::default());
|
|
||||||
std::thread::sleep(timer);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawn the input thread.
|
|
||||||
pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> (
|
|
||||||
engine: Arc<RwLock<Tui>>, state: &Arc<RwLock<T>>, timer: Duration
|
|
||||||
) -> JoinHandle<()> {
|
|
||||||
let exited = engine.read().unwrap().exited.clone();
|
|
||||||
let state = state.clone();
|
|
||||||
spawn(move || loop {
|
|
||||||
if exited.fetch_and(true, Relaxed) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if poll(timer).is_ok() {
|
|
||||||
let event = read().unwrap();
|
|
||||||
match event {
|
|
||||||
Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Char('c'),
|
|
||||||
modifiers: KeyModifiers::CONTROL,
|
|
||||||
kind: KeyEventKind::Press,
|
|
||||||
state: KeyEventState::NONE
|
|
||||||
}) => {
|
|
||||||
exited.store(true, Relaxed);
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
let exited = exited.clone();
|
|
||||||
let event = TuiEvent::from_crossterm(event);
|
|
||||||
if let Err(e) = state.write().unwrap().handle(&TuiIn { exited, event }) {
|
|
||||||
panic!("{e}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define layout operation.
|
/// Define layout operation.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -470,259 +311,6 @@ pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> (
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should be impl something or other...
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tengri::{Namespace, Understand, TuiOut, ratatui::prelude::Color};
|
|
||||||
///
|
|
||||||
/// struct State;
|
|
||||||
/// impl<'b> Namespace<'b, bool> for State {}
|
|
||||||
/// impl<'b> Namespace<'b, u16> for State {}
|
|
||||||
/// impl<'b> Namespace<'b, Color> for State {}
|
|
||||||
/// impl Understand<TuiOut, ()> for State {}
|
|
||||||
/// # fn main () -> tengri::Usually<()> {
|
|
||||||
/// let state = State;
|
|
||||||
/// let mut out = TuiOut::default();
|
|
||||||
/// tengri::evaluate_output_expression_tui(&state, &mut out, "")?;
|
|
||||||
/// tengri::evaluate_output_expression_tui(&state, &mut out, "text Hello world!")?;
|
|
||||||
/// tengri::evaluate_output_expression_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?;
|
|
||||||
/// tengri::evaluate_output_expression_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?;
|
|
||||||
/// tengri::evaluate_output_expression_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
|
|
||||||
/// # Ok(()) }
|
|
||||||
/// ```
|
|
||||||
#[cfg(feature = "dsl")] pub fn evaluate_output_expression_tui <'a, S> (
|
|
||||||
state: &S, output: &mut TuiOut, expr: impl Expression + 'a
|
|
||||||
) -> Usually<bool> where
|
|
||||||
S: Understand<TuiOut, ()>
|
|
||||||
+ for<'b>Namespace<'b, bool>
|
|
||||||
+ for<'b>Namespace<'b, u16>
|
|
||||||
+ for<'b>Namespace<'b, Color>
|
|
||||||
{
|
|
||||||
// See `tengri::evaluate_output_expression`
|
|
||||||
let head = expr.head()?;
|
|
||||||
let mut frags = head.src()?.unwrap_or_default().split("/");
|
|
||||||
let args = expr.tail();
|
|
||||||
let arg0 = args.head();
|
|
||||||
let tail0 = args.tail();
|
|
||||||
let arg1 = tail0.head();
|
|
||||||
let tail1 = tail0.tail();
|
|
||||||
let arg2 = tail1.head();
|
|
||||||
match frags.next() {
|
|
||||||
|
|
||||||
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))
|
|
||||||
},
|
|
||||||
|
|
||||||
Some("bg") => {
|
|
||||||
//panic!("expr: {expr:?}\nhead: {head:?}\nfrags: {frags:?}\nargs: {args:?}\narg0: {arg0:?}\ntail0: {tail0:?}\narg1: {arg1:?}\ntail1: {tail1:?}\narg2: {arg2:?}");
|
|
||||||
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");;
|
|
||||||
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");
|
|
||||||
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
|
||||||
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color"));
|
|
||||||
let thunk = Thunk::new(move|output: &mut TuiOut|state.understand(output, &arg1).unwrap());
|
|
||||||
output.place(&Tui::bg(color, thunk))
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => return Ok(false)
|
|
||||||
|
|
||||||
};
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn named_key (token: &str) -> Option<KeyCode> {
|
|
||||||
use KeyCode::*;
|
|
||||||
Some(match token {
|
|
||||||
"up" => Up,
|
|
||||||
"down" => Down,
|
|
||||||
"left" => Left,
|
|
||||||
"right" => Right,
|
|
||||||
"esc" | "escape" => Esc,
|
|
||||||
"enter" | "return" => Enter,
|
|
||||||
"delete" | "del" => Delete,
|
|
||||||
"backspace" => Backspace,
|
|
||||||
"tab" => Tab,
|
|
||||||
"space" => Char(' '),
|
|
||||||
"comma" => Char(','),
|
|
||||||
"period" => Char('.'),
|
|
||||||
"plus" => Char('+'),
|
|
||||||
"minus" | "dash" => Char('-'),
|
|
||||||
"equal" | "equals" => Char('='),
|
|
||||||
"underscore" => Char('_'),
|
|
||||||
"backtick" => Char('`'),
|
|
||||||
"lt" => Char('<'),
|
|
||||||
"gt" => Char('>'),
|
|
||||||
"cbopen" | "openbrace" => Char('{'),
|
|
||||||
"cbclose" | "closebrace" => Char('}'),
|
|
||||||
"bropen" | "openbracket" => Char('['),
|
|
||||||
"brclose" | "closebracket" => Char(']'),
|
|
||||||
"pgup" | "pageup" => PageUp,
|
|
||||||
"pgdn" | "pagedown" => PageDown,
|
|
||||||
"f1" => F(1),
|
|
||||||
"f2" => F(2),
|
|
||||||
"f3" => F(3),
|
|
||||||
"f4" => F(4),
|
|
||||||
"f5" => F(5),
|
|
||||||
"f6" => F(6),
|
|
||||||
"f7" => F(7),
|
|
||||||
"f8" => F(8),
|
|
||||||
"f9" => F(9),
|
|
||||||
"f10" => F(10),
|
|
||||||
"f11" => F(11),
|
|
||||||
"f12" => F(12),
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let _ = tengri::button_2("", "", true);
|
|
||||||
/// let _ = tengri::button_2("", "", false);
|
|
||||||
/// ```
|
|
||||||
pub fn button_2 <'a> (key: impl Content<TuiOut>, label: impl Content<TuiOut>, editing: bool) -> impl Content<TuiOut> {
|
|
||||||
Tui::bold(true, Bsp::e(
|
|
||||||
Tui::fg_bg(Tui::orange(), Tui::g(0), Bsp::e(Tui::fg(Tui::g(0), &"▐"), Bsp::e(key, Tui::fg(Tui::g(96), &"▐")))),
|
|
||||||
When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label))))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let _ = tengri::button_3("", "", "", true);
|
|
||||||
/// let _ = tengri::button_3("", "", "", false);
|
|
||||||
/// ```
|
|
||||||
pub fn button_3 <'a> (
|
|
||||||
key: impl Content<TuiOut>, label: impl Content<TuiOut>, value: impl Content<TuiOut>, editing: bool,
|
|
||||||
) -> impl Content<TuiOut> {
|
|
||||||
Tui::bold(true, Bsp::e(
|
|
||||||
Tui::fg_bg(Tui::orange(), Tui::g(0),
|
|
||||||
Bsp::e(Tui::fg(Tui::g(0), &"▐"), Bsp::e(key, Tui::fg(if editing { Tui::g(128) } else { Tui::g(96) }, "▐")))),
|
|
||||||
Bsp::e(
|
|
||||||
When::new(!editing, Bsp::e(Tui::fg_bg(Tui::g(255), Tui::g(96), label), Tui::fg_bg(Tui::g(128), Tui::g(96), &"▐"),)),
|
|
||||||
Bsp::e(Tui::fg_bg(Tui::g(224), Tui::g(128), value), Tui::fg_bg(Tui::g(128), Reset, &"▌"), ))))
|
|
||||||
}
|
|
||||||
|
|
||||||
border! {
|
|
||||||
Square {
|
|
||||||
"┌" "─" "┐"
|
|
||||||
"│" "│"
|
|
||||||
"└" "─" "┘" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
SquareBold {
|
|
||||||
"┏" "━" "┓"
|
|
||||||
"┃" "┃"
|
|
||||||
"┗" "━" "┛" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
TabLike {
|
|
||||||
"╭" "─" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"│" " " "│" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Lozenge {
|
|
||||||
"╭" "─" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"╰" "─" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Brace {
|
|
||||||
"╭" "" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"╰" "" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
LozengeDotted {
|
|
||||||
"╭" "┅" "╮"
|
|
||||||
"┇" "┇"
|
|
||||||
"╰" "┅" "╯" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Quarter {
|
|
||||||
"▎" "▔" "🮇"
|
|
||||||
"▎" "🮇"
|
|
||||||
"▎" "▁" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
QuarterV {
|
|
||||||
"▎" "" "🮇"
|
|
||||||
"▎" "🮇"
|
|
||||||
"▎" "" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Chamfer {
|
|
||||||
"🭂" "▔" "🭍"
|
|
||||||
"▎" "🮇"
|
|
||||||
"🭓" "▁" "🭞" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Corners {
|
|
||||||
"🬆" "" "🬊" // 🬴 🬸
|
|
||||||
"" ""
|
|
||||||
"🬱" "" "🬵" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
CornersTall {
|
|
||||||
"🭽" "" "🭾"
|
|
||||||
"" ""
|
|
||||||
"🭼" "" "🭿" fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Outer {
|
|
||||||
"🭽" "▔" "🭾"
|
|
||||||
"▏" "▕"
|
|
||||||
"🭼" "▁" "🭿"
|
|
||||||
const W0: &'static str = "[";
|
|
||||||
const E0: &'static str = "]";
|
|
||||||
const N0: &'static str = "⎴";
|
|
||||||
const S0: &'static str = "⎵";
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Thick {
|
|
||||||
"▄" "▄" "▄"
|
|
||||||
"█" "█"
|
|
||||||
"▀" "▀" "▀"
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Rugged {
|
|
||||||
"▄" "▂" "▄"
|
|
||||||
"▐" "▌"
|
|
||||||
"▀" "🮂" "▀"
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Skinny {
|
|
||||||
"▗" "▄" "▖"
|
|
||||||
"▐" "▌"
|
|
||||||
"▝" "▀" "▘"
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Brackets {
|
|
||||||
"⎡" "" "⎤"
|
|
||||||
"" ""
|
|
||||||
"⎣" "" "⎦"
|
|
||||||
const W0: &'static str = "[";
|
|
||||||
const E0: &'static str = "]";
|
|
||||||
const N0: &'static str = "⎴";
|
|
||||||
const S0: &'static str = "⎵";
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
},
|
|
||||||
Reticle {
|
|
||||||
"⎡" "" "⎤"
|
|
||||||
"" ""
|
|
||||||
"⎣" "" "⎦"
|
|
||||||
const W0: &'static str = "╟";
|
|
||||||
const E0: &'static str = "╢";
|
|
||||||
const N0: &'static str = "┯";
|
|
||||||
const S0: &'static str = "┷";
|
|
||||||
fn style (&self) -> Option<Style> { Some(self.1) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn okhsl_to_rgb (color: Okhsl<f32>) -> Color {
|
|
||||||
let Srgb { red, green, blue, .. }: Srgb<f32> = Srgb::from_color_unclamped(color);
|
|
||||||
Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgb_to_okhsl (color: Color) -> Okhsl<f32> {
|
|
||||||
if let Color::Rgb(r, g, b) = color {
|
|
||||||
Okhsl::from_color(Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0))
|
|
||||||
} else {
|
|
||||||
unreachable!("only Color::Rgb is supported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trim string with [unicode_width].
|
/// Trim string with [unicode_width].
|
||||||
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
|
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
|
||||||
|
|
|
||||||
1738
src/tengri_impl.rs
1738
src/tengri_impl.rs
File diff suppressed because it is too large
Load diff
|
|
@ -181,14 +181,14 @@ pub use self::spatial::*; mod spatial {
|
||||||
/// Set the maximum width and/or height of the content.
|
/// Set the maximum width and/or height of the content.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// let maximum = tengri::Min::XY(3, 5, "Hello"); // 3x1
|
/// let maximum = tengri::Max::XY(3, 5, "Hello"); // 3x1
|
||||||
/// ```
|
/// ```
|
||||||
pub enum Max<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
pub enum Max<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
||||||
|
|
||||||
/// Set the minimum width and/or height of the content.
|
/// Set the minimum width and/or height of the content.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// let minimam = tengri::Min::XY(3, 5, "Hello"); // 5x5
|
/// let minimum = tengri::Min::XY(3, 5, "Hello"); // 5x5
|
||||||
/// ```
|
/// ```
|
||||||
pub enum Min<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
pub enum Min<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
||||||
|
|
||||||
|
|
@ -269,11 +269,10 @@ pub use self::spatial::*; mod spatial {
|
||||||
|
|
||||||
pub __: PhantomData<(O, B)>,
|
pub __: PhantomData<(O, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A color in OKHSL and RGB representations.
|
/// A color in OKHSL and RGB representations.
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
|
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
|
||||||
pub okhsl: Okhsl<f32>,
|
pub okhsl: Okhsl<f32>,
|
||||||
pub rgb: Color,
|
#[cfg(feature = "tui")] pub rgb: Color,
|
||||||
}
|
}
|
||||||
/// A color in OKHSL and RGB with lighter and darker variants.
|
/// A color in OKHSL and RGB with lighter and darker variants.
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemTheme {
|
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemTheme {
|
||||||
|
|
@ -285,9 +284,26 @@ pub use self::spatial::*; mod spatial {
|
||||||
pub darker: ItemColor,
|
pub darker: ItemColor,
|
||||||
pub darkest: ItemColor,
|
pub darkest: ItemColor,
|
||||||
}
|
}
|
||||||
|
// TODO DOCUMENTME
|
||||||
|
pub struct FieldH<Theme, Label, Value>(pub Theme, pub Label, pub Value);
|
||||||
|
// TODO DOCUMENTME
|
||||||
|
pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
|
||||||
|
// TODO:
|
||||||
|
pub struct Field<C, T, U> {
|
||||||
|
pub direction: Direction,
|
||||||
|
pub label: Option<T>,
|
||||||
|
pub label_fg: Option<C>,
|
||||||
|
pub label_bg: Option<C>,
|
||||||
|
pub label_align: Option<Direction>,
|
||||||
|
pub value: Option<U>,
|
||||||
|
pub value_fg: Option<C>,
|
||||||
|
pub value_bg: Option<C>,
|
||||||
|
pub value_align: Option<Direction>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::terminal::*; mod terminal {
|
#[cfg(feature = "tui")] pub use self::terminal::*;
|
||||||
|
#[cfg(feature = "tui")] mod terminal {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// The TUI engine.
|
/// The TUI engine.
|
||||||
|
|
@ -354,16 +370,6 @@ pub use self::terminal::*; mod terminal {
|
||||||
|
|
||||||
pub struct Styled<T>(pub Option<Style>, pub T);
|
pub struct Styled<T>(pub Option<Style>, pub T);
|
||||||
|
|
||||||
/// Displays an owned [str]-like with fixed maximum width.
|
|
||||||
///
|
|
||||||
/// Width is computed using [unicode_width].
|
|
||||||
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
|
|
||||||
|
|
||||||
/// Displays a borrowed [str]-like with fixed maximum width
|
|
||||||
///
|
|
||||||
/// Width is computed using [unicode_width].
|
|
||||||
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
|
||||||
|
|
||||||
/// A cell that takes up 3 rows on its own,
|
/// A cell that takes up 3 rows on its own,
|
||||||
/// but stacks, giving (N+1)*2 rows per N cells.
|
/// but stacks, giving (N+1)*2 rows per N cells.
|
||||||
pub struct Phat<T> {
|
pub struct Phat<T> {
|
||||||
|
|
@ -382,25 +388,6 @@ pub use self::terminal::*; mod terminal {
|
||||||
pub right: (u16, C),
|
pub right: (u16, C),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:
|
|
||||||
pub struct Field<C, T, U> {
|
|
||||||
pub direction: Direction,
|
|
||||||
pub label: Option<T>,
|
|
||||||
pub label_fg: Option<C>,
|
|
||||||
pub label_bg: Option<C>,
|
|
||||||
pub label_align: Option<Direction>,
|
|
||||||
pub value: Option<U>,
|
|
||||||
pub value_fg: Option<C>,
|
|
||||||
pub value_bg: Option<C>,
|
|
||||||
pub value_align: Option<Direction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO DOCUMENTME
|
|
||||||
pub struct FieldH<Theme, Label, Value>(pub Theme, pub Label, pub Value);
|
|
||||||
|
|
||||||
// TODO DOCUMENTME
|
|
||||||
pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
|
|
||||||
|
|
||||||
/// Repeat a string, e.g. for background
|
/// Repeat a string, e.g. for background
|
||||||
pub enum Repeat<'a> {
|
pub enum Repeat<'a> {
|
||||||
X(&'a str),
|
X(&'a str),
|
||||||
|
|
@ -416,3 +403,99 @@ pub use self::terminal::*; mod terminal {
|
||||||
Y { offset: usize, length: usize, total: usize, }
|
Y { offset: usize, length: usize, total: usize, }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use self::textual::*;
|
||||||
|
mod textual {
|
||||||
|
/// Displays an owned [str]-like with fixed maximum width.
|
||||||
|
///
|
||||||
|
/// Width is computed using [unicode_width].
|
||||||
|
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
|
||||||
|
/// Displays a borrowed [str]-like with fixed maximum width
|
||||||
|
///
|
||||||
|
/// Width is computed using [unicode_width].
|
||||||
|
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "jack")] pub use self::aural::*;
|
||||||
|
#[cfg(feature = "jack")] mod aural {
|
||||||
|
use crate::*;
|
||||||
|
use ::jack::*;
|
||||||
|
|
||||||
|
/// Wraps [JackState], and through it [jack::Client] when connected.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let jack = tengri::Jack::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug, Default)] pub struct Jack<'j> (
|
||||||
|
pub(crate) Arc<RwLock<JackState<'j>>>
|
||||||
|
);
|
||||||
|
|
||||||
|
/// This is a connection which may be [Inactive], [Activating], or [Active].
|
||||||
|
/// In the [Active] and [Inactive] states, [JackState::client] returns a
|
||||||
|
/// [jack::Client], which you can use to talk to the JACK API.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let state = tengri::JackState::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub enum JackState<'j> {
|
||||||
|
/// Unused
|
||||||
|
#[default] Inert,
|
||||||
|
/// Before activation.
|
||||||
|
Inactive(Client),
|
||||||
|
/// During activation.
|
||||||
|
Activating,
|
||||||
|
/// After activation. Must not be dropped for JACK thread to persist.
|
||||||
|
Active(DynamicAsyncClient<'j>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event enum for JACK events.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let event = tengri::JackEvent::XRun;
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq)] pub enum JackEvent {
|
||||||
|
ThreadInit,
|
||||||
|
Shutdown(ClientStatus, Arc<str>),
|
||||||
|
Freewheel(bool),
|
||||||
|
SampleRate(Frames),
|
||||||
|
ClientRegistration(Arc<str>, bool),
|
||||||
|
PortRegistration(PortId, bool),
|
||||||
|
PortRename(PortId, Arc<str>, Arc<str>),
|
||||||
|
PortsConnected(PortId, PortId, bool),
|
||||||
|
GraphReorder,
|
||||||
|
XRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic notification handler that emits [JackEvent]
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let notify = tengri::JackNotify(|_|{});
|
||||||
|
/// ```
|
||||||
|
pub struct JackNotify<T: Fn(JackEvent) + Send>(pub T);
|
||||||
|
|
||||||
|
/// Running JACK [AsyncClient] with maximum type erasure.
|
||||||
|
///
|
||||||
|
/// One [Box] contains function that handles [JackEvent]s.
|
||||||
|
///
|
||||||
|
/// Another [Box] containing a function that handles realtime IO.
|
||||||
|
///
|
||||||
|
/// That's all it knows about them.
|
||||||
|
pub type DynamicAsyncClient<'j>
|
||||||
|
= AsyncClient<DynamicNotifications<'j>, DynamicAudioHandler<'j>>;
|
||||||
|
|
||||||
|
/// Notification handler wrapper for [BoxedAudioHandler].
|
||||||
|
pub type DynamicAudioHandler<'j> =
|
||||||
|
::jack::contrib::ClosureProcessHandler<(), BoxedAudioHandler<'j>>;
|
||||||
|
|
||||||
|
/// Boxed realtime callback.
|
||||||
|
pub type BoxedAudioHandler<'j> =
|
||||||
|
Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send + Sync + 'j>;
|
||||||
|
|
||||||
|
/// Notification handler wrapper for [BoxedJackEventHandler].
|
||||||
|
pub type DynamicNotifications<'j> =
|
||||||
|
JackNotify<BoxedJackEventHandler<'j>>;
|
||||||
|
|
||||||
|
/// Boxed [JackEvent] callback.
|
||||||
|
pub type BoxedJackEventHandler<'j> =
|
||||||
|
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
use crate::*;
|
pub use self::input::*; mod input {
|
||||||
|
|
||||||
pub use self::input::*;
|
|
||||||
mod input {
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Something that will exit and not resume, e.g. the main input loop.
|
/// Something that will exit and not resume, e.g. the main input loop.
|
||||||
|
|
@ -110,11 +107,9 @@ mod input {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::output::*;
|
pub use self::output::*; mod output {
|
||||||
mod output {
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
/// Output target.
|
||||||
/// Drawing target.
|
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use tengri::*;
|
/// use tengri::*;
|
||||||
|
|
@ -151,7 +146,6 @@ mod output {
|
||||||
self.place_at(content.layout(self.area()), content)
|
self.place_at(content.layout(self.area()), content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A numeric type that can be used as coordinate.
|
/// A numeric type that can be used as coordinate.
|
||||||
///
|
///
|
||||||
/// FIXME: Replace this ad-hoc trait with `num` crate.
|
/// FIXME: Replace this ad-hoc trait with `num` crate.
|
||||||
|
|
@ -177,15 +171,10 @@ mod output {
|
||||||
0.into()
|
0.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drawable with dynamic dispatch.
|
/// Drawable with dynamic dispatch.
|
||||||
pub trait Draw<O: Out> {
|
pub trait Draw<O: Out> { fn draw (&self, to: &mut O); }
|
||||||
fn draw (&self, to: &mut O);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Outputs combinator.
|
/// Outputs combinator.
|
||||||
pub trait Lay<O: Out>: Sized {}
|
pub trait Lay<O: Out>: Sized {}
|
||||||
|
|
||||||
/// Drawable area of display.
|
/// Drawable area of display.
|
||||||
pub trait Layout<O: Out> {
|
pub trait Layout<O: Out> {
|
||||||
fn layout_x (&self, to: XYWH<O::Unit>) -> O::Unit { to.x() }
|
fn layout_x (&self, to: XYWH<O::Unit>) -> O::Unit { to.x() }
|
||||||
|
|
@ -200,28 +189,22 @@ mod output {
|
||||||
XYWH(self.layout_x(to), self.layout_y(to), self.layout_w(to), self.layout_h(to))
|
XYWH(self.layout_x(to), self.layout_y(to), self.layout_w(to), self.layout_h(to))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HasContent<O: Out> {
|
|
||||||
fn content (&self) -> impl Content<O>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO DOCUMENTME
|
// TODO DOCUMENTME
|
||||||
pub trait Content<O: Out>: Draw<O> + Layout<O> {}
|
pub trait Content<O: Out>: Draw<O> + Layout<O> {}
|
||||||
|
// TODO DOCUMENTME
|
||||||
|
pub trait HasContent<O: Out> { fn content (&self) -> impl Content<O>; }
|
||||||
// Something that has an origin point (X, Y).
|
// Something that has an origin point (X, Y).
|
||||||
pub trait HasXY<N: Coord> {
|
pub trait HasXY<N: Coord> {
|
||||||
fn x (&self) -> N;
|
fn x (&self) -> N;
|
||||||
fn y (&self) -> N;
|
fn y (&self) -> N;
|
||||||
fn xy (&self) -> XY<N> { XY(self.x(), self.y()) }
|
fn xy (&self) -> XY<N> { XY(self.x(), self.y()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Something that has a size (W, H).
|
// Something that has a size (W, H).
|
||||||
pub trait HasWH<N: Coord> {
|
pub trait HasWH<N: Coord> {
|
||||||
fn w (&self) -> N;
|
fn w (&self) -> N;
|
||||||
fn h (&self) -> N;
|
fn h (&self) -> N;
|
||||||
fn wh (&self) -> WH<N> { WH(self.w(), self.h()) }
|
fn wh (&self) -> WH<N> { WH(self.w(), self.h()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Something that has a 2D bounding box (X, Y, W, H).
|
// Something that has a 2D bounding box (X, Y, W, H).
|
||||||
//
|
//
|
||||||
// FIXME: The other way around?
|
// FIXME: The other way around?
|
||||||
|
|
@ -237,7 +220,8 @@ mod output {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub trait HasPerf { fn perf (&self) -> &PerfModel; }
|
||||||
|
pub trait HasColor { fn color (&self) -> ItemColor; }
|
||||||
// Something that has a [Measure] of its rendered size.
|
// Something that has a [Measure] of its rendered size.
|
||||||
pub trait Measured<O: Out> {
|
pub trait Measured<O: Out> {
|
||||||
fn measure (&self) -> &Measure<O>;
|
fn measure (&self) -> &Measure<O>;
|
||||||
|
|
@ -245,12 +229,17 @@ mod output {
|
||||||
fn measure_height (&self) -> O::Unit { self.measure().h() }
|
fn measure_height (&self) -> O::Unit { self.measure().h() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HasPerf {
|
|
||||||
fn perf (&self) -> &PerfModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HasColor { fn color (&self) -> ItemColor; }
|
#[cfg(feature = "tui")] pub use self::tui::*;
|
||||||
|
#[cfg(feature = "tui")] mod tui {
|
||||||
|
use crate::*;
|
||||||
|
use ratatui::prelude::*;
|
||||||
|
pub trait TuiDraw = Draw<TuiOut>;
|
||||||
|
pub trait TuiLayout = crate::Layout<TuiOut>;
|
||||||
|
pub trait TuiContent = Content<TuiOut>;
|
||||||
|
pub trait TuiHandle = Handle<TuiIn>;
|
||||||
|
pub trait TuiWidget = TuiDraw + TuiHandle;
|
||||||
pub trait BorderStyle: Content<TuiOut> + Copy {
|
pub trait BorderStyle: Content<TuiOut> + Copy {
|
||||||
fn enabled (&self) -> bool;
|
fn enabled (&self) -> bool;
|
||||||
fn enclose (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
fn enclose (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||||
|
|
@ -346,12 +335,121 @@ mod output {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::tui::*;
|
#[cfg(feature = "jack")] pub use self::jack::*;
|
||||||
mod tui {
|
#[cfg(feature = "jack")] mod jack {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
pub trait TuiDraw = Draw<TuiOut>;
|
use ::jack::{*, contrib::{*, Position}};
|
||||||
pub trait TuiLayout = Layout<TuiOut>;
|
|
||||||
pub trait TuiContent = Content<TuiOut>;
|
/// Things that can provide a [jack::Client] reference.
|
||||||
pub trait TuiHandle = Handle<TuiIn>;
|
///
|
||||||
pub trait TuiWidget = TuiDraw + TuiHandle;
|
/// ```
|
||||||
|
/// use tengri::{Jack, HasJack};
|
||||||
|
///
|
||||||
|
/// let jack: &Jack = Jacked::default().jack();
|
||||||
|
///
|
||||||
|
/// #[derive(Default)] struct Jacked<'j>(Jack<'j>);
|
||||||
|
///
|
||||||
|
/// impl<'j> HasJack<'j> for Jacked<'j> {
|
||||||
|
/// fn jack (&self) -> &Jack<'j> { &self.0 }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait HasJack<'j>: Send + Sync {
|
||||||
|
|
||||||
|
/// Return the internal [jack::Client] handle
|
||||||
|
/// that lets you call the JACK API.
|
||||||
|
fn jack (&self) -> &Jack<'j>;
|
||||||
|
|
||||||
|
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
||||||
|
self.jack().with_client(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
|
||||||
|
self.with_client(|client|client.port_by_name(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
||||||
|
self.with_client(|c|c.port_by_id(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
|
||||||
|
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> {
|
||||||
|
if enable {
|
||||||
|
self.with_client(|client|match client.register_timebase_callback(false, callback) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_follow (&self, _enable: bool) -> Usually<()> {
|
||||||
|
// TODO: sync follow
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for thing that has a JACK process callback.
|
||||||
|
pub trait Audio {
|
||||||
|
|
||||||
|
/// Handle a JACK event.
|
||||||
|
fn handle (&mut self, _event: JackEvent) {}
|
||||||
|
|
||||||
|
/// Projecss a JACK chunk.
|
||||||
|
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The JACK process callback function passed to the server.
|
||||||
|
fn callback (
|
||||||
|
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
|
||||||
|
) -> Control where Self: Sized {
|
||||||
|
if let Ok(mut state) = state.write() {
|
||||||
|
state.process(client, scope)
|
||||||
|
} else {
|
||||||
|
Control::Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement [Audio]: provide JACK callbacks.
|
||||||
|
#[macro_export] macro_rules! impl_audio {
|
||||||
|
|
||||||
|
(|
|
||||||
|
$self1:ident:
|
||||||
|
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|
||||||
|
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
|
||||||
|
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
|
||||||
|
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($Struct:ident: $process:ident, $handle:ident) => {
|
||||||
|
impl Audio for $Struct {
|
||||||
|
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
||||||
|
$process(self, c, s)
|
||||||
|
}
|
||||||
|
#[inline] fn handle (&mut self, e: JackEvent) {
|
||||||
|
$handle(self, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($Struct:ident: $process:ident) => {
|
||||||
|
impl Audio for $Struct {
|
||||||
|
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
||||||
|
$process(self, c, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait JackPerfModel {
|
||||||
|
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue