diff --git a/Cargo.toml b/Cargo.toml index 934b268..e491b53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,18 +18,10 @@ members = [ ] [workspace.package] -version = "0.14.0" +version = "0.13.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" } diff --git a/dsl/README.md b/dsl/README.md index 0ef496d..ce24bc1 100644 --- a/dsl/README.md +++ b/dsl/README.md @@ -1,25 +1,14 @@ -[***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` -dizzle currently provides an s-expression based syntax. +**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 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. +## usage -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. +### with `tengri_output` -## goals - -* [x] const parse -* [ ] live reload -* [ ] serialize modified code back to original indentation - -## examples - -### in [`tengri_output`](../output) +this is a `tengri_output` view layout defined using ket: ```edn (bsp/s (fixed/y 2 :toolbar) @@ -27,7 +16,9 @@ this trait match literals, words, and expressions, against pre-defined lists. th (bsp/s :outputs (bsp/s :inputs (bsp/s :tracks :scenes))))))) ``` -### in [`tengri_input`](../input) +### with `tengri_input` + +this is a `tengri_input` keymap defined using ket: ```edn (@u undo 1) @@ -38,6 +29,70 @@ this trait match literals, words, and expressions, against pre-defined lists. th (@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 @@ -59,7 +114,9 @@ this is the trait which differentiates "a thing" from * e2: Unexpected 'b' * e3: Unexpected 'd' -### possible design for operator-based syntax +## todo + +### operators * replace: `(:= :name :value1 :valueN)` * append: `(:+ :name :value2 :valueN)` diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index a522258..de6a978 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -289,16 +289,22 @@ fn ok_flat (x: Option>) -> DslPerhaps { // 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 (&$State)->Perhaps<$Type>> = + const WORDS: DslNsMap<'t, fn (&'t $State)->Perhaps<$Type>> = DslNsMap::new(&[$(dsl_ns!{@word ($state: $State) -> $Type { $pat => $body }}),*]); - const EXPRS: DslNsMap<'t, fn (&$State, &str)->Perhaps<$Type>> = + const EXPRS: DslNsMap<'t, fn (&'t $State, &str)->Perhaps<$Type>> = DslNsMap::new(&[$(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]); - fn from_literal (&self, dsl: &D) -> Perhaps<$Type> { - Ok(if let Some(src) = dsl.src()? { - Some(to_number(src)? as $Type) + fn from (&'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) + } } else { - None - }) + Ok(None) + } } } }; @@ -309,9 +315,9 @@ fn ok_flat (x: Option>) -> DslPerhaps { // 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 (&$State)->Perhaps<$Type>> = + const WORDS: DslNsMap<'t, fn (&'t $State)->Perhaps<$Type>> = DslNsMap::new(&[$(dsl_ns!{@word ($state: $State) -> $Type { $pat => $body }}),*]); - const EXPRS: DslNsMap<'t, fn (&$State, &str)->Perhaps<$Type>> = + const EXPRS: DslNsMap<'t, fn (&'t $State, &str)->Perhaps<$Type>> = DslNsMap::new(&[$(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]); } }; @@ -352,36 +358,34 @@ fn ok_flat (x: Option>) -> DslPerhaps { pub trait DslNs<'t, T: 't>: 't { /// Known symbols. - const WORDS: DslNsMap<'t, fn (&Self)->Perhaps> = DslNsMap::new(&[]); + const WORDS: DslNsMap<'t, fn (&'t Self)->Perhaps> = DslNsMap::new(&[]); /// Known expressions. - const EXPRS: DslNsMap<'t, fn (&Self, &str)->Perhaps> = DslNsMap::new(&[]); + const EXPRS: DslNsMap<'t, fn (&'t Self, &str)->Perhaps> = DslNsMap::new(&[]); /// Resolve an expression or symbol. - fn from (&self, dsl: &D) -> Perhaps { - 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)) + fn from (&'t self, dsl: D) -> Perhaps { + if let Ok(Some(src)) = dsl.src() { + if let Ok(Some(src)) = src.word() { + self.from_word(src) + } else { + self.from_expr(src) + } } else { - self.from_expr(dsl) + Ok(None) } } - /// Resolve as literal if valid. - fn from_literal (&self, _: &D) -> Perhaps { - Ok(None) - } /// Resolve a symbol if known. - fn from_word (&self, dsl: &D) -> Perhaps { + fn from_word (&'t self, dsl: D) -> Perhaps { if let Some(dsl) = dsl.word()? { - for (key, get) in Self::WORDS.0 { if dsl == *key { return get(self) } } + for (word, get) in Self::WORDS.0 { if dsl == *word { return get(self) } } } return Ok(None) } /// Resolve an expression if known. - fn from_expr (&self, dsl: &D) -> Perhaps { - 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("")) + fn from_expr (&'t self, dsl: D) -> Perhaps { + 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("")) } } } @@ -396,35 +400,3 @@ 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)) - })),* - )? ]); -}); diff --git a/input/Cargo.toml b/input/Cargo.toml index 0c9218d..d3bf1ec 100644 --- a/input/Cargo.toml +++ b/input/Cargo.toml @@ -4,11 +4,12 @@ description = "UI metaframework, input layer." version = { workspace = true } edition = { workspace = true } -[lib] -path = "input.rs" +[features] +dsl = [ "tengri_dsl" ] [dependencies] tengri_core = { path = "../core" } +tengri_dsl = { optional = true, path = "../dsl" } [dev-dependencies] tengri_tui = { path = "../tui" } diff --git a/input/README.md b/input/README.md index 2fa3ef8..4261a22 100644 --- a/input/README.md +++ b/input/README.md @@ -1,6 +1,16 @@ -***tengri_input*** is where tengri's input handling is defined. +# `tengri_engine` -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 +## 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`, 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. diff --git a/input/input.rs b/input/input.rs deleted file mode 100644 index 2695a47..0000000 --- a/input/input.rs +++ /dev/null @@ -1,77 +0,0 @@ -#![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> 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 { - fn handle (&mut self, _input: &E) -> Perhaps { - Ok(None) - } -}); - -pub trait Command: Send + Sync + Sized { - fn execute (self, state: &mut S) -> Perhaps; - fn delegate (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps - where Self: Sized - { - Ok(self.execute(state)?.map(wrap)) - } -} - -impl> Command for Option { - fn execute (self, _: &mut S) -> Perhaps { - Ok(None) - } - fn delegate (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps - 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 { - Ok($handler) - } - } - }; -} - -/// Implement [Handle] for given `State` and `handler`. -#[macro_export] macro_rules! handle { - (|$self:ident:$State:ty,$input:ident|$handler:expr) => { - impl ::tengri::input::Handle for $State { - fn handle (&mut $self, $input: &E) -> Perhaps { - $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 - } - } - } -} diff --git a/input/src/input.rs b/input/src/input.rs new file mode 100644 index 0000000..48e692f --- /dev/null +++ b/input/src/input.rs @@ -0,0 +1,37 @@ +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> 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 { + fn handle (&mut self, _input: &E) -> Perhaps { + Ok(None) + } +}); +pub trait Command: Send + Sync + Sized { + fn execute (self, state: &mut S) -> Perhaps; + fn delegate (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps + where Self: Sized + { + Ok(self.execute(state)?.map(wrap)) + } +} +impl> Command for Option { + fn execute (self, _: &mut S) -> Perhaps { + Ok(None) + } + fn delegate (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps + where Self: Sized + { + Ok(None) + } +} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs new file mode 100644 index 0000000..77c6c94 --- /dev/null +++ b/input/src/input_dsl.rs @@ -0,0 +1,76 @@ +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( + /// Map of each event (e.g. key combination) to + /// all command expressions bound to it by + /// all loaded input layers. + pub BTreeMap>> +); +/// An input binding. +#[derive(Debug, Clone)] +pub struct Binding { + pub commands: Arc<[C]>, + pub condition: Option, + pub description: Option>, + pub source: Option>, +} +impl Binding { + pub fn from_dsl (dsl: impl Dsl) -> Usually { + let command: Option = None; + let condition: Option = None; + let description: Option> = None; + let source: Option> = 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(Arcbool + Send + Sync>>); +impl_debug!(Condition |self, w| { write!(w, "*") }); +/// Default is always empty map regardless if `E` and `C` implement [Default]. +impl Default for EventMap { + fn default () -> Self { Self(Default::default()) } +} +impl EventMap { + /// 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) -> Self { + self.add(event, binding); + self + } + /// Add a binding to an event map. + pub fn add (&mut self, event: E, binding: Binding) -> &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]> { + 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> { + self.query(event) + .map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next()) + .flatten() + } +} diff --git a/input/src/input_macros.rs b/input/src/input_macros.rs new file mode 100644 index 0000000..e47b902 --- /dev/null +++ b/input/src/input_macros.rs @@ -0,0 +1,30 @@ +/// 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 { + Ok($handler) + } + } + }; +} + +/// Implement [Handle] for given `State` and `handler`. +#[macro_export] macro_rules! handle { + (|$self:ident:$State:ty,$input:ident|$handler:expr) => { + impl ::tengri::input::Handle for $State { + fn handle (&mut $self, $input: &E) -> Perhaps { + $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 + } + } + } +} diff --git a/input/input_test.rs b/input/src/input_test.rs similarity index 100% rename from input/input_test.rs rename to input/src/input_test.rs diff --git a/input/src/lib.rs b/input/src/lib.rs new file mode 100644 index 0000000..800ee2f --- /dev/null +++ b/input/src/lib.rs @@ -0,0 +1,9 @@ +#![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; diff --git a/output/README.md b/output/README.md index b827f46..dc19511 100644 --- a/output/README.md +++ b/output/README.md @@ -1,20 +1,75 @@ -***tengri_output*** is an abstract interface layout framework. +# `tengri_output` -it expresses the following notions: +## free floating layout primitives -* [**space:**](./src/space.rs) `Direction`, `Coordinate`, `Area`, `Size`, `Measure` +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. -* [**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) +|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| -* [**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) +**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`, 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. diff --git a/output/src/lib.rs b/output/src/lib.rs index 8576d23..334c418 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -10,7 +10,7 @@ pub(crate) use tengri_core::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; mod space; pub use self::space::*; -mod layout; pub use self::layout::*; +mod ops; pub use self::ops::*; mod output; pub use self::output::*; #[cfg(test)] mod test; diff --git a/output/src/layout.rs b/output/src/ops.rs similarity index 100% rename from output/src/layout.rs rename to output/src/ops.rs diff --git a/output/src/output.rs b/output/src/output.rs index 324d7a5..77a48b8 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -47,7 +47,7 @@ impl> Render for C { /// Opaque pointer to a renderable living on the heap. /// /// Return this from [Content::content] to use dynamic dispatch. -pub type RenderBox = Box>; +pub type RenderBox = Box>; /// You can render from a box. impl Content for RenderBox { @@ -55,8 +55,11 @@ impl Content for RenderBox { //fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self } } +/// Opaque pointer to a renderable. +pub type RenderDyn = dyn Render; + /// You can render from an opaque pointer. -impl Content for &dyn Render where Self: Sized { +impl Content for &RenderDyn where Self: Sized { fn content (&self) -> impl Render + '_ { #[allow(suspicious_double_ref_op)] self.deref() diff --git a/tengri/Cargo.toml b/tengri/Cargo.toml index c14cdf8..cd87dfc 100644 --- a/tengri/Cargo.toml +++ b/tengri/Cargo.toml @@ -9,10 +9,10 @@ default = [ "input", "output", "tui" ] input = [ "tengri_input" ] output = [ "tengri_output" ] tui = [ "tengri_tui" ] -dsl = [ "tengri_dsl", "tengri_output/dsl", "tengri_tui/dsl" ] +dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl", "tengri_tui/dsl" ] [dependencies] -tengri_core = { workspace = true } +tengri_core = { path = "../core" } tengri_dsl = { optional = true, path = "../dsl" } tengri_input = { optional = true, path = "../input" } tengri_output = { optional = true, path = "../output" } diff --git a/tengri/README.md b/tengri/README.md index 962f196..4500aef 100644 --- a/tengri/README.md +++ b/tengri/README.md @@ -1,17 +1,3 @@ -***tengri*** is a metaframework for building interactive applications with rust. (aren't we all?) +# tengri -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). +an interface metaframework. currently supports ratatui. diff --git a/tui/Cargo.toml b/tui/Cargo.toml index 284ad75..5fe3f34 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -5,13 +5,13 @@ version = { workspace = true } edition = { workspace = true } [features] -dsl = [ "tengri_dsl", "tengri_output/dsl" ] +dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl" ] [dependencies] -tengri_core = { workspace = true } -tengri_input = { workspace = true } -tengri_output = { workspace = true } -tengri_dsl = { workspace = true, optional = true } +tengri_core = { path = "../core" } +tengri_input = { path = "../input" } +tengri_output = { path = "../output" } +tengri_dsl = { optional = true, path = "../dsl" } palette = { workspace = true } rand = { workspace = true } @@ -24,6 +24,6 @@ quanta = { workspace = true } unicode-width = { workspace = true } [dev-dependencies] -tengri = { workspace = true, features = [ "dsl" ] } -tengri_dsl = { workspace = true } -tengri_proc = { workspace = true } +tengri = { path = "../tengri", features = [ "dsl" ] } +tengri_dsl = { path = "../dsl" } +tengri_proc = { path = "../proc" } diff --git a/tui/README.md b/tui/README.md index 8e227d8..d8c2f29 100644 --- a/tui/README.md +++ b/tui/README.md @@ -1,4 +1,15 @@ -***tengri_tui*** is an implementation of [tengri_output](../output) and [tengri_input](../input) -on top of [ratatui](https://ratatui.rs/) and [crossterm](https://github.com/crossterm-rs/crossterm). +# `tengri_tui` -tengri is published under [**AGPL3**](../LICENSE). +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` and `Arc`. the engine and +application instances are expected to be wrapped +in `Arc`; internally, those synchronization +mechanisms may be used liberally. diff --git a/tui/src/tui_engine.rs b/tui/src/tui_engine.rs index ff93db3..4700555 100644 --- a/tui/src/tui_engine.rs +++ b/tui/src/tui_engine.rs @@ -7,12 +7,6 @@ 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>` and `Arc`. Thus, at launch the engine and application instances are expected to be wrapped in `Arc`. pub struct Tui { pub exited: Arc, pub backend: CrosstermBackend,