From d92e5efdd0e009ba0492a75b3aad0b3c142b9056 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 23 Aug 2025 12:19:02 +0300 Subject: [PATCH 1/4] dsl: add dsl_words, dsl_exprs, from_literal --- dsl/src/dsl.rs | 92 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 32 deletions(-) diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index de6a978..d6cc366 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -289,22 +289,16 @@ 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 (&'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 (&'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 (&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 (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 (&'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 (x: Option>) -> DslPerhaps { pub trait DslNs<'t, T: 't>: 't { /// Known symbols. - const WORDS: DslNsMap<'t, fn (&'t Self)->Perhaps> = DslNsMap::new(&[]); + const WORDS: DslNsMap<'t, fn (&Self)->Perhaps> = DslNsMap::new(&[]); /// Known expressions. - const EXPRS: DslNsMap<'t, fn (&'t Self, &str)->Perhaps> = DslNsMap::new(&[]); + const EXPRS: DslNsMap<'t, fn (&Self, &str)->Perhaps> = DslNsMap::new(&[]); /// Resolve an expression or symbol. - 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) - } + 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)) } else { - Ok(None) + self.from_expr(dsl) } } + /// Resolve as literal if valid. + fn from_literal (&self, dsl: &D) -> Perhaps { + Ok(None) + } /// Resolve a symbol if known. - fn from_word (&'t self, dsl: D) -> Perhaps { + fn from_word (&self, dsl: &D) -> Perhaps { 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 (&'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("")) + 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("")) } } } @@ -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)) + })),* + )? ]); +}); From 3e2b07158cb812fe864029e635f10c1e1ca84355 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 23 Aug 2025 12:42:51 +0300 Subject: [PATCH 2/4] output: update README --- output/README.md | 87 ++++++-------------------------- output/src/{ops.rs => layout.rs} | 0 output/src/lib.rs | 2 +- output/src/output.rs | 7 +-- tengri/README.md | 11 +++- 5 files changed, 28 insertions(+), 79 deletions(-) rename output/src/{ops.rs => layout.rs} (100%) diff --git a/output/README.md b/output/README.md index dc19511..b827f46 100644 --- a/output/README.md +++ b/output/README.md @@ -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`, 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) diff --git a/output/src/ops.rs b/output/src/layout.rs similarity index 100% rename from output/src/ops.rs rename to output/src/layout.rs diff --git a/output/src/lib.rs b/output/src/lib.rs index 334c418..8576d23 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 ops; pub use self::ops::*; +mod layout; pub use self::layout::*; mod output; pub use self::output::*; #[cfg(test)] mod test; diff --git a/output/src/output.rs b/output/src/output.rs index 77a48b8..324d7a5 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,11 +55,8 @@ 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 &RenderDyn where Self: Sized { +impl Content for &dyn Render where Self: Sized { fn content (&self) -> impl Render + '_ { #[allow(suspicious_double_ref_op)] self.deref() diff --git a/tengri/README.md b/tengri/README.md index 4500aef..9c5e04d 100644 --- a/tengri/README.md +++ b/tengri/README.md @@ -1,3 +1,10 @@ -# 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/). From 062179393037dc948a09aa24cb0000b6ef1dea8e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 23 Aug 2025 12:58:19 +0300 Subject: [PATCH 3/4] dsl: update docs --- dsl/README.md | 97 +++++++++---------------------------------- tengri/README.md | 13 ++++-- tui/README.md | 17 ++------ tui/src/tui_engine.rs | 6 +++ 4 files changed, 39 insertions(+), 94 deletions(-) diff --git a/dsl/README.md b/dsl/README.md index ce24bc1..0ef496d 100644 --- a/dsl/README.md +++ b/dsl/README.md @@ -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)` diff --git a/tengri/README.md b/tengri/README.md index 9c5e04d..962f196 100644 --- a/tengri/README.md +++ b/tengri/README.md @@ -4,7 +4,14 @@ 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. +* [***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). diff --git a/tui/README.md b/tui/README.md index d8c2f29..8e227d8 100644 --- a/tui/README.md +++ b/tui/README.md @@ -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` and `Arc`. the engine and -application instances are expected to be wrapped -in `Arc`; internally, those synchronization -mechanisms may be used liberally. +tengri is published under [**AGPL3**](../LICENSE). diff --git a/tui/src/tui_engine.rs b/tui/src/tui_engine.rs index 4700555..ff93db3 100644 --- a/tui/src/tui_engine.rs +++ b/tui/src/tui_engine.rs @@ -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>` 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, From e3e3c163da02165e77a259eb715749b7f0097498 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 23 Aug 2025 13:47:45 +0300 Subject: [PATCH 4/4] input: refactor, move dsl stuff to tek config --- Cargo.toml | 10 ++++- dsl/src/dsl.rs | 2 +- input/Cargo.toml | 5 +-- input/README.md | 20 +++------ input/input.rs | 77 +++++++++++++++++++++++++++++++++++ input/{src => }/input_test.rs | 0 input/src/input.rs | 37 ----------------- input/src/input_dsl.rs | 76 ---------------------------------- input/src/input_macros.rs | 30 -------------- input/src/lib.rs | 9 ---- tengri/Cargo.toml | 4 +- tui/Cargo.toml | 16 ++++---- 12 files changed, 104 insertions(+), 182 deletions(-) create mode 100644 input/input.rs rename input/{src => }/input_test.rs (100%) delete mode 100644 input/src/input.rs delete mode 100644 input/src/input_dsl.rs delete mode 100644 input/src/input_macros.rs delete mode 100644 input/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index e491b53..934b268 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index d6cc366..a522258 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -366,7 +366,7 @@ pub trait DslNs<'t, T: 't>: 't { } } /// Resolve as literal if valid. - fn from_literal (&self, dsl: &D) -> Perhaps { + fn from_literal (&self, _: &D) -> Perhaps { Ok(None) } /// Resolve a symbol if known. diff --git a/input/Cargo.toml b/input/Cargo.toml index d3bf1ec..0c9218d 100644 --- a/input/Cargo.toml +++ b/input/Cargo.toml @@ -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" } diff --git a/input/README.md b/input/README.md index 4261a22..2fa3ef8 100644 --- a/input/README.md +++ b/input/README.md @@ -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`, 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 diff --git a/input/input.rs b/input/input.rs new file mode 100644 index 0000000..2695a47 --- /dev/null +++ b/input/input.rs @@ -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> 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_test.rs b/input/input_test.rs similarity index 100% rename from input/src/input_test.rs rename to input/input_test.rs diff --git a/input/src/input.rs b/input/src/input.rs deleted file mode 100644 index 48e692f..0000000 --- a/input/src/input.rs +++ /dev/null @@ -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> 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 deleted file mode 100644 index 77c6c94..0000000 --- a/input/src/input_dsl.rs +++ /dev/null @@ -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( - /// 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 deleted file mode 100644 index e47b902..0000000 --- a/input/src/input_macros.rs +++ /dev/null @@ -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 { - 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/lib.rs b/input/src/lib.rs deleted file mode 100644 index 800ee2f..0000000 --- a/input/src/lib.rs +++ /dev/null @@ -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; diff --git a/tengri/Cargo.toml b/tengri/Cargo.toml index cd87dfc..c14cdf8 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_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" } diff --git a/tui/Cargo.toml b/tui/Cargo.toml index 5fe3f34..284ad75 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -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 }