Compare commits

..

4 commits

Author SHA1 Message Date
e3e3c163da input: refactor, move dsl stuff to tek config
Some checks are pending
/ build (push) Waiting to run
2025-08-23 13:47:45 +03:00
0621793930 dsl: update docs 2025-08-23 13:10:59 +03:00
3e2b07158c output: update README 2025-08-23 12:50:31 +03:00
d92e5efdd0 dsl: add dsl_words, dsl_exprs, from_literal 2025-08-23 12:19:02 +03:00
20 changed files with 227 additions and 383 deletions

View file

@ -18,10 +18,18 @@ members = [
]
[workspace.package]
version = "0.13.0"
version = "0.14.0"
edition = "2024"
[workspace.dependencies]
tengri = { path = "./tengri" }
tengri_core = { path = "./core" }
tengri_input = { path = "./input" }
tengri_output = { path = "./output" }
tengri_tui = { path = "./tui" }
tengri_dsl = { path = "./dsl" }
tengri_proc = { path = "./proc" }
anyhow = { version = "1.0" }
atomic_float = { version = "1" }
better-panic = { version = "0.3.0" }

View file

@ -1,14 +1,25 @@
# `ket`
[***dizzle***](https://codeberg.org/unspeaker/tengri/src/branch/main/dsl)
is a means of adding a tiny interpreted domain-specific language to your programs.
**ket** is the configuration language of **tek**.
it's based on [edn](https://en.wikipedia.org/wiki/Clojure#Extensible_Data_Notation)
but without all the features.
dizzle currently provides an s-expression based syntax.
## usage
dizzle parses source code by means of the `Dsl`, `DslExpr` and `DslWord` traits.
those are implemented for basic stringy types and their `Option` and `Result` wrapped analogs.
to customize parsing, define and use your own traits on top of the provided ones.
### with `tengri_output`
dizzle evaluates the parsed source code by means of the `DslNs` trait. the methods of
this trait match literals, words, and expressions, against pre-defined lists. the
`dsl_words` and `dsl_exprs` macros let you define those lists slightly less verbosely.
this is a `tengri_output` view layout defined using ket:
## goals
* [x] const parse
* [ ] live reload
* [ ] serialize modified code back to original indentation
## examples
### in [`tengri_output`](../output)
```edn
(bsp/s (fixed/y 2 :toolbar)
@ -16,9 +27,7 @@ this is a `tengri_output` view layout defined using ket:
(bsp/s :outputs (bsp/s :inputs (bsp/s :tracks :scenes)))))))
```
### with `tengri_input`
this is a `tengri_input` keymap defined using ket:
### in [`tengri_input`](../input)
```edn
(@u undo 1)
@ -29,70 +38,6 @@ this is a `tengri_input` keymap defined using ket:
(@tab pool toggle)
```
## tokens
ket has 4 "types", represented by variants of the `Value` enum:
* `Num` - numeric literal
* `Sym` - textual symbol
* `Key` - textual key
* `Exp` - parenthesized group of tokens
### numeric literal
numbers are passed through as is. only non-negative integers are supported.
```edn
0
123456
```
### keys
keys are the names of available operations. they look like this:
```edn
simple-key
multi-part/key
```
keys are implemented by the underlying subsystem:
* in `tengri_output`, keys are names of layout primitives
* in `tengri_input`, keys are names of commands
### symbols
symbols that start with `:` represent the names of variables
provided by the `Context` trait - such as command parameters,
or entire layout components:
```edn
:symbol-name
```
symbols that start with `@` represent keybindings.
they are parsed in `tengri_tui` and look like this:
```edn
@ctrl-alt-shift-space
```
### parenthesized groups
parenthesized groups represent things like expressions
or configuration statements, and look like this:
```edn
(some-key :symbol (some/other-key @another-symbol 123) 456)
```
## goals
* [ ] const parse
* [ ] live reload
* [ ] serialize modified code back to original indentation
## implementation notes
### `DslExpr` trait behavior
@ -114,9 +59,7 @@ this is the trait which differentiates "a thing" from
* e2: Unexpected 'b'
* e3: Unexpected 'd'
## todo
### operators
### possible design for operator-based syntax
* replace: `(:= :name :value1 :valueN)`
* append: `(:+ :name :value2 :valueN)`

View file

@ -289,22 +289,16 @@ fn ok_flat <T> (x: Option<DslPerhaps<T>>) -> DslPerhaps<T> {
// Special form for numeric types
(num |$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => {
impl<'t> DslNs<'t, $Type> for $State {
const WORDS: DslNsMap<'t, fn (&'t $State)->Perhaps<$Type>> =
const WORDS: DslNsMap<'t, fn (&$State)->Perhaps<$Type>> =
DslNsMap::new(&[$(dsl_ns!{@word ($state: $State) -> $Type { $pat => $body }}),*]);
const EXPRS: DslNsMap<'t, fn (&'t $State, &str)->Perhaps<$Type>> =
const EXPRS: DslNsMap<'t, fn (&$State, &str)->Perhaps<$Type>> =
DslNsMap::new(&[$(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]);
fn from <D: Dsl> (&'t self, dsl: D) -> Perhaps<$Type> {
if let Ok(Some(src)) = dsl.src() {
if let Ok(num) = to_number(src) {
Ok(Some(num as $Type))
} else if let Ok(Some(src)) = src.word() {
self.from_word(src)
} else {
self.from_expr(src)
}
fn from_literal <D: Dsl> (&self, dsl: &D) -> Perhaps<$Type> {
Ok(if let Some(src) = dsl.src()? {
Some(to_number(src)? as $Type)
} else {
Ok(None)
}
None
})
}
}
};
@ -315,9 +309,9 @@ fn ok_flat <T> (x: Option<DslPerhaps<T>>) -> DslPerhaps<T> {
// Regular form for single type
(|$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => {
impl<'t> DslNs<'t, $Type> for $State {
const WORDS: DslNsMap<'t, fn (&'t $State)->Perhaps<$Type>> =
const WORDS: DslNsMap<'t, fn (&$State)->Perhaps<$Type>> =
DslNsMap::new(&[$(dsl_ns!{@word ($state: $State) -> $Type { $pat => $body }}),*]);
const EXPRS: DslNsMap<'t, fn (&'t $State, &str)->Perhaps<$Type>> =
const EXPRS: DslNsMap<'t, fn (&$State, &str)->Perhaps<$Type>> =
DslNsMap::new(&[$(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]);
}
};
@ -358,34 +352,36 @@ fn ok_flat <T> (x: Option<DslPerhaps<T>>) -> DslPerhaps<T> {
pub trait DslNs<'t, T: 't>: 't {
/// Known symbols.
const WORDS: DslNsMap<'t, fn (&'t Self)->Perhaps<T>> = DslNsMap::new(&[]);
const WORDS: DslNsMap<'t, fn (&Self)->Perhaps<T>> = DslNsMap::new(&[]);
/// Known expressions.
const EXPRS: DslNsMap<'t, fn (&'t Self, &str)->Perhaps<T>> = DslNsMap::new(&[]);
const EXPRS: DslNsMap<'t, fn (&Self, &str)->Perhaps<T>> = DslNsMap::new(&[]);
/// Resolve an expression or symbol.
fn from <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
if let Ok(Some(src)) = dsl.src() {
if let Ok(Some(src)) = src.word() {
self.from_word(src)
} else {
self.from_expr(src)
}
fn from <D: Dsl> (&self, dsl: &D) -> Perhaps<T> {
if let Ok(Some(literal)) = self.from_literal(dsl) {
Ok(Some(literal))
} else if let Ok(Some(meaning)) = self.from_word(dsl) {
Ok(Some(meaning))
} else {
Ok(None)
self.from_expr(dsl)
}
}
/// Resolve as literal if valid.
fn from_literal <D: Dsl> (&self, _: &D) -> Perhaps<T> {
Ok(None)
}
/// Resolve a symbol if known.
fn from_word <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
fn from_word <D: Dsl> (&self, dsl: &D) -> Perhaps<T> {
if let Some(dsl) = dsl.word()? {
for (word, get) in Self::WORDS.0 { if dsl == *word { return get(self) } }
for (key, get) in Self::WORDS.0 { if dsl == *key { return get(self) } }
}
return Ok(None)
}
/// Resolve an expression if known.
fn from_expr <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
if let Some(head) = dsl.expr().head()? {
for (key, value) in Self::EXPRS.0.iter() {
if head == *key {
return value(self, dsl.expr().tail()?.unwrap_or(""))
fn from_expr <D: Dsl> (&self, dsl: &D) -> Perhaps<T> {
if let Some(expr) = dsl.expr()? {
for (key, get) in Self::EXPRS.0.iter() {
if Some(*key) == expr.head()? {
return get(self, &expr.tail()?.unwrap_or(""))
}
}
}
@ -400,3 +396,35 @@ impl<'t, T: Debug + 't> DslNsMap<'t, T> {
/// Populate a namespace with pre-existing values.
pub const fn new (data: &'t [(&'t str, T)]) -> Self { Self(data) /* TODO a search trie */ }
}
#[macro_export] macro_rules!dsl_words((|$state:ident|->$Type:ty$({
$($word:literal => $body:expr),* $(,)?
})?)=>{
const WORDS: DslNsMap<'t, fn (&Self)->Perhaps<$Type>> = DslNsMap::new(&[$(
$(($word, |$state: &Self|{Ok(Some($body))})),*
)? ]);
});
#[macro_export] macro_rules!dsl_exprs((|$state:ident|->$Type:ty$({
$($name:literal ($($arg:ident:$ty:ty),* $(,)?) => $body:expr),* $(,)?
})?)=>{
const EXPRS: DslNsMap<'t, fn (&Self, &str)->Perhaps<$Type>> = DslNsMap::new(&[$(
$(($name, |$state: &Self, tail: &str|{
$(
let head = tail.head()?.unwrap_or_default();
let tail = tail.tail()?.unwrap_or_default();
let $arg: $ty = if let Some(arg) = $state.from(&head)? {
arg
} else {
return Err(format!("{}: arg \"{}\" ({}) got: {head} {tail}",
$name,
stringify!($arg),
stringify!($ty),
).into())
};
)*
Ok(Some($body))
})),*
)? ]);
});

View file

@ -4,12 +4,11 @@ description = "UI metaframework, input layer."
version = { workspace = true }
edition = { workspace = true }
[features]
dsl = [ "tengri_dsl" ]
[lib]
path = "input.rs"
[dependencies]
tengri_core = { path = "../core" }
tengri_dsl = { optional = true, path = "../dsl" }
[dev-dependencies]
tengri_tui = { path = "../tui" }

View file

@ -1,16 +1,6 @@
# `tengri_engine`
***tengri_input*** is where tengri's input handling is defined.
## rendering
## input handling
the **input thread** polls for keyboard events
and passes them onto the application's `Handle::handle` method.
thus, for a type to be a valid application for engine `E`,
it must implement the trait `Handle<E>`, which allows it
to respond to user input.
this thread has write access to the application state,
and is responsible for mutating it in response to
user activity.
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

77
input/input.rs Normal file
View file

@ -0,0 +1,77 @@
#![feature(associated_type_defaults)]
#![feature(if_let_guard)]
pub(crate) use tengri_core::*;
#[cfg(test)] mod input_test;
/// Event source
pub trait Input: Sized {
/// Type of input event
type Event;
/// Result of handling input
type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo
/// Currently handled event
fn event (&self) -> &Self::Event;
/// Whether component should exit
fn is_done (&self) -> bool;
/// Mark component as done
fn done (&self);
}
flex_trait_mut!(Handle <E: Input> {
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
Ok(None)
}
});
pub trait Command<S>: Send + Sync + Sized {
fn execute (self, state: &mut S) -> Perhaps<Self>;
fn delegate <T> (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
where Self: Sized
{
Ok(self.execute(state)?.map(wrap))
}
}
impl<S, T: Command<S>> Command<S> for Option<T> {
fn execute (self, _: &mut S) -> Perhaps<Self> {
Ok(None)
}
fn delegate <U> (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
where Self: Sized
{
Ok(None)
}
}
/// 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::input::Command<$State> for $Command {
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
Ok($handler)
}
}
};
}
/// Implement [Handle] for given `State` and `handler`.
#[macro_export] macro_rules! handle {
(|$self:ident:$State:ty,$input:ident|$handler:expr) => {
impl<E: Engine> ::tengri::input::Handle<E> for $State {
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
$handler
}
}
};
($E:ty: |$self:ident:$State:ty,$input:ident|$handler:expr) => {
impl ::tengri::input::Handle<$E> for $State {
fn handle (&mut $self, $input: &$E) ->
Perhaps<<$E as ::tengri::input::Input>::Handled>
{
$handler
}
}
}
}

View file

@ -1,37 +0,0 @@
use crate::*;
/// Event source
pub trait Input: Sized {
/// Type of input event
type Event;
/// Result of handling input
type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo
/// Currently handled event
fn event (&self) -> &Self::Event;
/// Whether component should exit
fn is_done (&self) -> bool;
/// Mark component as done
fn done (&self);
}
flex_trait_mut!(Handle <E: Input> {
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
Ok(None)
}
});
pub trait Command<S>: Send + Sync + Sized {
fn execute (self, state: &mut S) -> Perhaps<Self>;
fn delegate <T> (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
where Self: Sized
{
Ok(self.execute(state)?.map(wrap))
}
}
impl<S, T: Command<S>> Command<S> for Option<T> {
fn execute (self, _: &mut S) -> Perhaps<Self> {
Ok(None)
}
fn delegate <U> (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
where Self: Sized
{
Ok(None)
}
}

View file

@ -1,76 +0,0 @@
use crate::*;
use std::{sync::Arc, collections::BTreeMap, path::PathBuf};
/// A collection of input bindings.
///
/// Each contained layer defines a mapping from input event to command invocation
/// over a given state. Furthermore, each layer may have an associated cond,
/// so that only certain layers are active at a given time depending on state.
///
/// When a key is pressed, the bindings for it are checked in sequence.
/// When the first non-conditional or true conditional binding is executed,
/// that .event()binding's value is returned.
#[derive(Debug)]
pub struct EventMap<E, C>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
pub BTreeMap<E, Vec<Binding<C>>>
);
/// An input binding.
#[derive(Debug, Clone)]
pub struct Binding<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
impl<C> Binding<C> {
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
let command: Option<C> = None;
let condition: Option<Condition> = None;
let description: Option<Arc<str>> = None;
let source: Option<Arc<PathBuf>> = None;
if let Some(command) = command {
Ok(Self { commands: [command].into(), condition, description, source })
} else {
Err(format!("no command in {dsl:?}").into())
}
}
}
/// Input bindings are only returned if this evaluates to true
#[derive(Clone)]
pub struct Condition(Arc<Box<dyn Fn()->bool + Send + Sync>>);
impl_debug!(Condition |self, w| { write!(w, "*") });
/// Default is always empty map regardless if `E` and `C` implement [Default].
impl<E, C> Default for EventMap<E, C> {
fn default () -> Self { Self(Default::default()) }
}
impl<E: Clone + Ord, C> EventMap<E, C> {
/// Create a new event map
pub fn new () -> Self {
Default::default()
}
/// Add a binding to an owned event map.
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
self.add(event, binding);
self
}
/// Add a binding to an event map.
pub fn add (&mut self, event: E, binding: Binding<C>) -> &mut Self {
if !self.0.contains_key(&event) {
self.0.insert(event.clone(), Default::default());
}
self.0.get_mut(&event).unwrap().push(binding);
self
}
/// Return the binding(s) that correspond to an event.
pub fn query (&self, event: &E) -> Option<&[Binding<C>]> {
self.0.get(event).map(|x|x.as_slice())
}
/// Return the first binding that corresponds to an event, considering conditions.
pub fn dispatch (&self, event: &E) -> Option<&Binding<C>> {
self.query(event)
.map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next())
.flatten()
}
}

View file

@ -1,30 +0,0 @@
/// 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::input::Command<$State> for $Command {
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
Ok($handler)
}
}
};
}
/// Implement [Handle] for given `State` and `handler`.
#[macro_export] macro_rules! handle {
(|$self:ident:$State:ty,$input:ident|$handler:expr) => {
impl<E: Engine> ::tengri::input::Handle<E> for $State {
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
$handler
}
}
};
($E:ty: |$self:ident:$State:ty,$input:ident|$handler:expr) => {
impl ::tengri::input::Handle<$E> for $State {
fn handle (&mut $self, $input: &$E) ->
Perhaps<<$E as ::tengri::input::Input>::Handled>
{
$handler
}
}
}
}

View file

@ -1,9 +0,0 @@
#![feature(associated_type_defaults)]
#![feature(if_let_guard)]
pub(crate) use tengri_core::*;
mod input_macros;
mod input; pub use self::input::*;
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
#[cfg(feature = "dsl")] mod input_dsl;
#[cfg(feature = "dsl")] pub use self::input_dsl::*;
#[cfg(test)] mod input_test;

View file

@ -1,75 +1,20 @@
# `tengri_output`
***tengri_output*** is an abstract interface layout framework.
## free floating layout primitives
it expresses the following notions:
this crate exposes several layout operators
which work entirely in unsigned coordinates
and are generic over the trait `Content`.
most importantly, they are not dependent on rendering framework.
* [**space:**](./src/space.rs) `Direction`, `Coordinate`, `Area`, `Size`, `Measure`
|operator|description|
|-|-|
|**`When(x, a)`**|render `a` only when `x == true`|
|**`Either(x, a, b)`**|render `a` when `x == true`, otherwise render `b`|
|**`Map(get_iterator, callback)`**|transform items in uniform way|
|**`Bsp`**|concatenative layout|
|...|...|
|**`Align`**|pin content along axis|
|...|...|
|**`Fill`**|**make content's dimension equal to container's:**|
|`Fill::x(a)`|use container's width for content|
|`Fill::y(a)`|use container's height for content|
|`Fill::xy(a)`|use container's width and height for content|
|**`Fixed`**|**assign fixed dimension to content:**|
|`Fixed::x(w, a)`|use width `w` for content|
|`Fixed::y(w, a)`|use height `w` for content|
|`Fixed::xy(w, h, a)`|use width `w` and height `h` for content|
|**`Expand`/`Shrink`**|**change dimension of content:**|
|`Expand::x(n, a)`/`Shrink::x(n, a)`|increment/decrement width of content area by `n`|
|`Expand::y(n, a)`/`Shrink::y(n, a)`|increment/decrement height of content area by `m`|
|`Expand::xy(n, m, a)`/`Shrink::xy(n, m, a)`|increment/decrement width of content area by `n`, height by `m`|
|**`Min`/`Max`**|**constrain dimension of content:**|
|`Min::x(w, a)`/`Max::x(w, a)`|enforce minimum/maximum width `w` for content|
|`Min::y(h, a)`/`Max::y(h, a)`|enforce minimum/maximum height `h` for content|
|`Min::xy(w, h, a)`/`Max::xy(w, h, a)`|enforce minimum/maximum width `w` and height `h` for content|
|**`Push`/`Pull`**|**move content along axis:**|
|`Push::x(n, a)`/`Pull::x(n, a)`|increment/decrement `x` of content area|
|`Push::y(n, a)`/`Pull::y(n, a)`|increment/decrement `y` of content area|
|`Push::xy(n, m, a)`/`Pull::xy(n, m, a)`|increment/decrement `x` and `y` of content area|
* [**output:**](./src/output.rs) `Output`, `Render`, `Content`
* the layout operators are generic over `Render` and/or `Content`
* the traits `Render` and `Content` are generic over `Output`
* implement `Output` to bring a layout to a new backend:
[see `TuiOut` in `tengri_tui`](../tui/src/tui_engine/tui_output.rs)
**todo:**
* sensible `Margin`/`Padding`
* `Reduce`
## example rendering loop
the **render thread** continually invokes the
`Content::render` method of the application
to redraw the display. it does this efficiently
by using ratatui's double buffering.
thus, for a type to be a valid application for engine `E`,
it must implement the trait `Content<E>`, which allows
it to display content to the engine's output.
the most important thing about the `Content` trait is that
it composes:
* you can implement `Content::content` to build
`Content`s out of other `Content`s
* and/or `Content::area` for custom positioning and sizing,
* and/or `Content::render` for custom rendering
within the given `Content`'s area.
the manner of output is determined by the
`Engine::Output` type, a mutable pointer to which
is passed to the render method, e.g. in the case of
the `Tui` engine: `fn render(&self, output: &mut TuiOut)`
you can use `TuiOut::blit` and `TuiOut::place`
to draw at specified coordinates of the display, and/or
directly modify the underlying `ratatui::Buffer` at
`output.buffer`
rendering is intended to work with read-only access
to the application state. if you really need to update
values during rendering, use interior mutability.
* [**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)

View file

@ -10,7 +10,7 @@ pub(crate) use tengri_core::*;
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
mod space; pub use self::space::*;
mod ops; pub use self::ops::*;
mod layout; pub use self::layout::*;
mod output; pub use self::output::*;
#[cfg(test)] mod test;

View file

@ -47,7 +47,7 @@ impl<E: Output, C: Content<E>> Render<E> for C {
/// Opaque pointer to a renderable living on the heap.
///
/// Return this from [Content::content] to use dynamic dispatch.
pub type RenderBox<E> = Box<RenderDyn<E>>;
pub type RenderBox<E> = Box<dyn Render<E>>;
/// You can render from a box.
impl<E: Output> Content<E> for RenderBox<E> {
@ -55,11 +55,8 @@ impl<E: Output> Content<E> for RenderBox<E> {
//fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self }
}
/// Opaque pointer to a renderable.
pub type RenderDyn<E> = dyn Render<E>;
/// You can render from an opaque pointer.
impl<E: Output> Content<E> for &RenderDyn<E> where Self: Sized {
impl<E: Output> Content<E> for &dyn Render<E> where Self: Sized {
fn content (&self) -> impl Render<E> + '_ {
#[allow(suspicious_double_ref_op)]
self.deref()

View file

@ -9,10 +9,10 @@ default = [ "input", "output", "tui" ]
input = [ "tengri_input" ]
output = [ "tengri_output" ]
tui = [ "tengri_tui" ]
dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl", "tengri_tui/dsl" ]
dsl = [ "tengri_dsl", "tengri_output/dsl", "tengri_tui/dsl" ]
[dependencies]
tengri_core = { path = "../core" }
tengri_core = { workspace = true }
tengri_dsl = { optional = true, path = "../dsl" }
tengri_input = { optional = true, path = "../input" }
tengri_output = { optional = true, path = "../output" }

View file

@ -1,3 +1,17 @@
# tengri
***tengri*** is a metaframework for building interactive applications with rust. (aren't we all?)
an interface metaframework. currently supports ratatui.
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).

View file

@ -5,13 +5,13 @@ version = { workspace = true }
edition = { workspace = true }
[features]
dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl" ]
dsl = [ "tengri_dsl", "tengri_output/dsl" ]
[dependencies]
tengri_core = { path = "../core" }
tengri_input = { path = "../input" }
tengri_output = { path = "../output" }
tengri_dsl = { optional = true, path = "../dsl" }
tengri_core = { workspace = true }
tengri_input = { workspace = true }
tengri_output = { workspace = true }
tengri_dsl = { workspace = true, optional = true }
palette = { workspace = true }
rand = { workspace = true }
@ -24,6 +24,6 @@ quanta = { workspace = true }
unicode-width = { workspace = true }
[dev-dependencies]
tengri = { path = "../tengri", features = [ "dsl" ] }
tengri_dsl = { path = "../dsl" }
tengri_proc = { path = "../proc" }
tengri = { workspace = true, features = [ "dsl" ] }
tengri_dsl = { workspace = true }
tengri_proc = { workspace = true }

View file

@ -1,15 +1,4 @@
# `tengri_tui`
***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).
the `Tui` struct (the *engine*) implements the
`tengri_input::Input` and `tengri_output::Output` traits.
at launch, the `Tui` engine spawns two threads,
a **render thread** and an **input thread**. (the
application may spawn further threads, such as a
**jack thread**.)
all threads communicate using shared ownership,
`Arc<RwLock>` and `Arc<Atomic>`. the engine and
application instances are expected to be wrapped
in `Arc<RwLock>`; internally, those synchronization
mechanisms may be used liberally.
tengri is published under [**AGPL3**](../LICENSE).

View file

@ -7,6 +7,12 @@ mod tui_event; pub use self::tui_event::*;
mod tui_output; pub use self::tui_output::*;
mod tui_perf; pub use self::tui_perf::*;
// The `Tui` struct (the *engine*) implements the
// `tengri_input::Input` and `tengri_output::Output` traits.
// At launch, the `Tui` engine spawns two threads, the render thread and the input thread.
// the application may further spawn other threads. All threads communicate using shared ownership:
// `Arc<RwLock<T>>` and `Arc<AtomicT>`. Thus, at launch the engine and application instances are expected to be wrapped in `Arc<RwLock>`.
pub struct Tui {
pub exited: Arc<AtomicBool>,
pub backend: CrosstermBackend<Stdout>,