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
25
Cargo.toml
25
Cargo.toml
|
|
@ -5,31 +5,34 @@ version = "0.15.0"
|
|||
description = "UI metaframework."
|
||||
|
||||
[features]
|
||||
default = [ "input", "output", "tui" ]
|
||||
bumpalo = [ "dep:bumpalo" ]
|
||||
input = [ ]
|
||||
output = [ ]
|
||||
tui = [ ]
|
||||
dsl = [ ]
|
||||
default = ["tui", "jack", "dsl"]
|
||||
bumpalo = ["dep:bumpalo"]
|
||||
tui = ["dep:ratatui", "dep:crossterm"]
|
||||
gui = ["dep:winit"]
|
||||
dsl = ["dep:dizzle"]
|
||||
jack = ["dep:jack"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0" }
|
||||
atomic_float = { version = "1" }
|
||||
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" ] }
|
||||
quanta = { version = "0.12.3" }
|
||||
rand = { version = "0.8.5" }
|
||||
ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||
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]
|
||||
proptest = { version = "^1" }
|
||||
proptest-derive = { version = "^0.5.1" }
|
||||
tengri = { path = ".", features = [ "dsl" ] }
|
||||
tengri_proc = { path = "./proc" }
|
||||
#tengri_proc = { path = "./proc" }
|
||||
|
||||
[lib]
|
||||
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 published under [**AGPL3**](../LICENSE).
|
||||
|
||||
## tengri and tek
|
||||
|
||||
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/).
|
||||
## tengri and dizzle
|
||||
|
||||
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 integrates with [***dizzle***](./dizzle),
|
||||
a framework for defining domain-specific languages.
|
||||
|
||||
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:
|
||||
* `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
|
||||
### temporal
|
||||
|
||||
## 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`
|
||||
|
||||
* [**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).
|
||||
checkout the repo and generate docs with `cargo doc` to explore!
|
||||
|
|
|
|||
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.
|
||||
//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> {
|
||||
|
|
|
|||
454
src/tengri.rs
454
src/tengri.rs
|
|
@ -9,23 +9,27 @@
|
|||
#![feature(trait_alias)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![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_trait; pub use self::tengri_trait::*;
|
||||
mod tengri_impl; pub use self::tengri_impl::*;
|
||||
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
|
||||
pub extern crate dizzle; pub use dizzle::*;
|
||||
pub extern crate atomic_float; pub(crate) use atomic_float::AtomicF64;
|
||||
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::*;
|
||||
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},
|
||||
style::{Stylize, Modifier, Color::*},
|
||||
backend::{Backend, CrosstermBackend, ClearType},
|
||||
layout::{Size, Rect},
|
||||
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,
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
||||
event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
||||
|
|
@ -75,43 +79,35 @@ pub(crate) use ::std::{
|
|||
|
||||
/// Stack things on top of each other,
|
||||
#[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.
|
||||
#[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.
|
||||
#[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.
|
||||
#[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`
|
||||
#[macro_export] macro_rules! command {
|
||||
($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
|
||||
impl$(<$($l),+>)? ::tengri::Command<$State> for $Command {
|
||||
fn execute (&$self, $state: &mut $State) -> Perhaps<Self> {
|
||||
Ok($handler)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! def_command (($Command:ident: |$state:ident: $State:ty| {
|
||||
/// Define an enum containing commands, and implement [Command] trait for over given `State`.
|
||||
#[macro_export] macro_rules! def_command (
|
||||
($Command:ident: |$state:ident: $State:ty| {
|
||||
// FIXME: support attrs (docstrings)
|
||||
$($Variant:ident$({$($arg:ident:$Arg:ty),+ $(,)?})?=>$body:expr),* $(,)?
|
||||
})=>{
|
||||
#[derive(Debug)]
|
||||
pub enum $Command {
|
||||
})=>{
|
||||
#[derive(Debug)] pub enum $Command {
|
||||
// FIXME: support attrs (docstrings)
|
||||
$($Variant $({ $($arg: $Arg),* })?),*
|
||||
}
|
||||
impl Command<$State> for $Command {
|
||||
impl ::tengri::Command<$State> for $Command {
|
||||
fn execute (&self, $state: &mut $State) -> Perhaps<Self> {
|
||||
match self {
|
||||
$(Self::$Variant $({ $($arg),* })? => $body,)*
|
||||
|
|
@ -119,7 +115,7 @@ pub(crate) use ::std::{
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/// Implement [Handle] for given `State` and `handler`.
|
||||
#[macro_export] macro_rules! handle {
|
||||
|
|
@ -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.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -470,259 +311,6 @@ pub fn tui_input <T: Handle<TuiIn> + Send + Sync + 'static> (
|
|||
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].
|
||||
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
|
||||
|
|
|
|||
1802
src/tengri_impl.rs
1802
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.
|
||||
///
|
||||
/// ```
|
||||
/// 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), }
|
||||
|
||||
/// 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), }
|
||||
|
||||
|
|
@ -269,11 +269,10 @@ pub use self::spatial::*; mod spatial {
|
|||
|
||||
pub __: PhantomData<(O, B)>,
|
||||
}
|
||||
|
||||
/// A color in OKHSL and RGB representations.
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
|
||||
pub okhsl: Okhsl<f32>,
|
||||
pub rgb: Color,
|
||||
#[cfg(feature = "tui")] pub rgb: Color,
|
||||
}
|
||||
/// A color in OKHSL and RGB with lighter and darker variants.
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemTheme {
|
||||
|
|
@ -285,9 +284,26 @@ pub use self::spatial::*; mod spatial {
|
|||
pub darker: 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::*;
|
||||
|
||||
/// The TUI engine.
|
||||
|
|
@ -354,16 +370,6 @@ pub use self::terminal::*; mod terminal {
|
|||
|
||||
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,
|
||||
/// but stacks, giving (N+1)*2 rows per N cells.
|
||||
pub struct Phat<T> {
|
||||
|
|
@ -382,25 +388,6 @@ pub use self::terminal::*; mod terminal {
|
|||
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
|
||||
pub enum Repeat<'a> {
|
||||
X(&'a str),
|
||||
|
|
@ -416,3 +403,99 @@ pub use self::terminal::*; mod terminal {
|
|||
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::*;
|
||||
|
||||
/// Something that will exit and not resume, e.g. the main input loop.
|
||||
|
|
@ -110,11 +107,9 @@ mod input {
|
|||
});
|
||||
}
|
||||
|
||||
pub use self::output::*;
|
||||
mod output {
|
||||
pub use self::output::*; mod output {
|
||||
use crate::*;
|
||||
|
||||
/// Drawing target.
|
||||
/// Output target.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::*;
|
||||
|
|
@ -151,7 +146,6 @@ mod output {
|
|||
self.place_at(content.layout(self.area()), content)
|
||||
}
|
||||
}
|
||||
|
||||
/// A numeric type that can be used as coordinate.
|
||||
///
|
||||
/// FIXME: Replace this ad-hoc trait with `num` crate.
|
||||
|
|
@ -177,15 +171,10 @@ mod output {
|
|||
0.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Drawable with dynamic dispatch.
|
||||
pub trait Draw<O: Out> {
|
||||
fn draw (&self, to: &mut O);
|
||||
}
|
||||
|
||||
pub trait Draw<O: Out> { fn draw (&self, to: &mut O); }
|
||||
/// Outputs combinator.
|
||||
pub trait Lay<O: Out>: Sized {}
|
||||
|
||||
/// Drawable area of display.
|
||||
pub trait Layout<O: Out> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasContent<O: Out> {
|
||||
fn content (&self) -> impl Content<O>;
|
||||
}
|
||||
|
||||
// TODO DOCUMENTME
|
||||
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).
|
||||
pub trait HasXY<N: Coord> {
|
||||
fn x (&self) -> N;
|
||||
fn y (&self) -> N;
|
||||
fn xy (&self) -> XY<N> { XY(self.x(), self.y()) }
|
||||
}
|
||||
|
||||
// Something that has a size (W, H).
|
||||
pub trait HasWH<N: Coord> {
|
||||
fn w (&self) -> N;
|
||||
fn h (&self) -> N;
|
||||
fn wh (&self) -> WH<N> { WH(self.w(), self.h()) }
|
||||
}
|
||||
|
||||
// Something that has a 2D bounding box (X, Y, W, H).
|
||||
//
|
||||
// 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.
|
||||
pub trait Measured<O: Out> {
|
||||
fn measure (&self) -> &Measure<O>;
|
||||
|
|
@ -245,12 +229,17 @@ mod output {
|
|||
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 {
|
||||
fn enabled (&self) -> bool;
|
||||
fn enclose (self, w: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||
|
|
@ -346,12 +335,121 @@ mod output {
|
|||
}
|
||||
}
|
||||
|
||||
pub use self::tui::*;
|
||||
mod tui {
|
||||
#[cfg(feature = "jack")] pub use self::jack::*;
|
||||
#[cfg(feature = "jack")] mod jack {
|
||||
use crate::*;
|
||||
pub trait TuiDraw = Draw<TuiOut>;
|
||||
pub trait TuiLayout = Layout<TuiOut>;
|
||||
pub trait TuiContent = Content<TuiOut>;
|
||||
pub trait TuiHandle = Handle<TuiIn>;
|
||||
pub trait TuiWidget = TuiDraw + TuiHandle;
|
||||
use ::jack::{*, contrib::{*, Position}};
|
||||
|
||||
/// Things that can provide a [jack::Client] reference.
|
||||
///
|
||||
/// ```
|
||||
/// 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