edn -> dsl

This commit is contained in:
🪞👃🪞 2025-03-14 23:44:13 +02:00
parent d30eda33d1
commit 877b344765
35 changed files with 197 additions and 225 deletions

View file

@ -9,7 +9,7 @@ jobs:
- run: whoami && pwd && ls -al - run: whoami && pwd && ls -al
- run: nix-shell --cores 4 --command 'cloc src/ && cloc .' .forgejo/workflows/build.nix - run: nix-shell --cores 4 --command 'cloc src/ && cloc .' .forgejo/workflows/build.nix
- run: nix-shell --cores 4 --command 'rustup install nightly && cargo version -vv' .forgejo/workflows/build.nix - run: nix-shell --cores 4 --command 'rustup install nightly && cargo version -vv' .forgejo/workflows/build.nix
- run: nix-shell --cores 4 --command 'just cov-md' .forgejo/workflows/build.nix - run: nix-shell --cores 4 --command 'just cov-md-ci' .forgejo/workflows/build.nix
- run: nix-shell --cores 4 --command 'just doc' .forgejo/workflows/build.nix - run: nix-shell --cores 4 --command 'just doc' .forgejo/workflows/build.nix
#- run: nix-shell --cores 4 --command 'just build-release' .forgejo/workflows/build.nix #- run: nix-shell --cores 4 --command 'just build-release' .forgejo/workflows/build.nix
#- run: nix-shell -p docker --command "docker run --security-opt seccomp=unconfined -v $PWD:/volume xd009642/tarpaulin cargo tarpaulin --out Html --all-features" #- run: nix-shell -p docker --command "docker run --security-opt seccomp=unconfined -v $PWD:/volume xd009642/tarpaulin cargo tarpaulin --out Html --all-features"

103
Cargo.lock generated
View file

@ -900,52 +900,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "tek_edn"
version = "0.1.0"
dependencies = [
"itertools 0.14.0",
"konst",
"proptest",
"tek_tui",
"thiserror",
]
[[package]]
name = "tek_input"
version = "0.2.0"
dependencies = [
"tek_edn",
"tek_tui",
]
[[package]]
name = "tek_output"
version = "0.2.0"
dependencies = [
"proptest",
"proptest-derive",
"tek_edn",
"tek_tui",
]
[[package]]
name = "tek_tui"
version = "0.2.0"
dependencies = [
"atomic_float",
"better-panic",
"crossterm",
"konst",
"palette",
"quanta",
"rand",
"ratatui",
"tek_edn",
"tek_input",
"tek_output",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.17.1" version = "3.17.1"
@ -960,6 +914,63 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "tengri"
version = "0.1.0"
dependencies = [
"tengri_dsl",
"tengri_input",
"tengri_output",
"tengri_tui",
]
[[package]]
name = "tengri_dsl"
version = "0.1.0"
dependencies = [
"itertools 0.14.0",
"konst",
"proptest",
"tengri_tui",
"thiserror",
]
[[package]]
name = "tengri_input"
version = "0.1.0"
dependencies = [
"tengri_dsl",
"tengri_tui",
]
[[package]]
name = "tengri_output"
version = "0.1.0"
dependencies = [
"proptest",
"proptest-derive",
"tengri_dsl",
"tengri_tui",
]
[[package]]
name = "tengri_tui"
version = "0.1.0"
dependencies = [
"atomic_float",
"better-panic",
"crossterm",
"konst",
"palette",
"quanta",
"rand",
"ratatui",
"tengri",
"tengri_dsl",
"tengri_input",
"tengri_output",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.12" version = "2.0.12"

View file

@ -5,7 +5,7 @@ members = [
"./input", "./input",
"./output", "./output",
"./tui", "./tui",
"./edn" "./dsl"
] ]
[profile.release] [profile.release]

View file

@ -2,11 +2,14 @@ covfig := "CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' RUSTDOCFLAGS='-
grcov-binary := "--binary-path ./target/coverage/deps/" grcov-binary := "--binary-path ./target/coverage/deps/"
grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'" grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'"
cov: cov:
{{covfig}} time cargo test -j4 --workspace --exclude jack --profile coverage {{covfig}} time cargo test -j4 --workspace --profile coverage
rm -rf target/coverage/html || true rm -rf target/coverage/html || true
{{covfig}} time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t html -o target/coverage/html {{covfig}} time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t html -o target/coverage/html
cov-md: cov-md:
{{covfig}} time cargo test -j4 --workspace --exclude jack --profile coverage {{covfig}} time cargo test -j4 --workspace --profile coverage
{{covfig}} time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t markdown | sort
cov-md-ci:
{{covfig}} time cargo test -j4 --workspace --profile coverage -- --skip test_tui_engine
{{covfig}} time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t markdown | sort {{covfig}} time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t markdown | sort
doc: doc:
cargo doc cargo doc

View file

@ -764,7 +764,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tengri_edn" name = "tengri_dsl"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clojure-reader", "clojure-reader",
@ -781,7 +781,7 @@ version = "0.2.0"
name = "tengri_output" name = "tengri_output"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"tengri_edn", "tengri_dsl",
] ]
[[package]] [[package]]
@ -793,7 +793,7 @@ dependencies = [
"palette", "palette",
"rand", "rand",
"ratatui", "ratatui",
"tengri_edn", "tengri_dsl",
"tengri_input", "tengri_input",
"tengri_output", "tengri_output",
] ]

View file

@ -5,7 +5,7 @@
//! //!
//! ``` //! ```
//! let src = include_str!("../test.edn"); //! let src = include_str!("../test.edn");
//! let mut view = tengri_edn::TokenIter::new(src); //! let mut view = tengri_dsl::TokenIter::new(src);
//! assert_eq!(view.0.0, src); //! assert_eq!(view.0.0, src);
//! assert_eq!(view.peek(), view.0.peek()) //! assert_eq!(view.peek(), view.0.peek())
//! ``` //! ```

View file

@ -10,7 +10,7 @@
//! with slightly different parsing rules. //! with slightly different parsing rules.
//! * [Value::Num] is an unsigned integer literal. //! * [Value::Num] is an unsigned integer literal.
//!``` //!```
//! use tengri_edn::{*, Value::*}; //! use tengri_dsl::{*, Value::*};
//! let source = include_str!("../test.edn"); //! let source = include_str!("../test.edn");
//! let mut view = TokenIter::new(source); //! let mut view = TokenIter::new(source);
//! assert_eq!(view.peek(), Some(Token { //! assert_eq!(view.peek(), Some(Token {

View file

@ -5,10 +5,11 @@ version = "0.1.0"
description = "UI metaframework, input layer." description = "UI metaframework, input layer."
[dependencies] [dependencies]
tengri_edn = { optional = true, path = "../edn" } tengri_dsl = { optional = true, path = "../dsl" }
[features] [features]
edn = [ "tengri_edn" ] dsl = [ "tengri_dsl" ]
[dev-dependencies] [dev-dependencies]
tengri_tui = { path = "../tui" } tengri_tui = { path = "../tui" }
tengri_dsl = { path = "../dsl" }

View file

@ -42,6 +42,7 @@ impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> {
input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?); input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?);
}; };
} }
#[macro_export] macro_rules! input_to_command { #[macro_export] macro_rules! input_to_command {
(<$($l:lifetime),+> $Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => { (<$($l:lifetime),+> $Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => {
impl<$($l),+> InputToCommand<$Input, $State> for $Command { impl<$($l),+> InputToCommand<$Input, $State> for $Command {
@ -70,4 +71,3 @@ pub trait InputToCommand<I, S>: Command<S> + Sized {
}) })
} }
} }

View file

@ -46,7 +46,7 @@ impl<E: Input, H: Handle<E>> Handle<E> for &mut H {
} }
impl<E: Input, H: Handle<E>> Handle<E> for Option<H> { impl<E: Input, H: Handle<E>> Handle<E> for Option<H> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> { fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
if let Some(ref mut handle) = self { if let Some(handle) = self {
handle.handle(context) handle.handle(context)
} else { } else {
Ok(None) Ok(None)

View file

@ -1,13 +1,18 @@
use crate::*; use crate::*;
/// [Input] state that can be matched against a [Value]. /// [Input] state that can be matched against a [Value].
pub trait AtomInput: Input { pub trait AtomInput: Input {
fn matches_atom (&self, token: &str) -> bool; fn matches_atom (&self, token: &str) -> bool;
} }
#[cfg(feature = "dsl")]
pub trait KeyMap<'a> { pub trait KeyMap<'a> {
/// Try to find a command that matches the current input event. /// Try to find a command that matches the current input event.
fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I) fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
-> Option<C>; -> Option<C>;
} }
#[cfg(feature = "dsl")]
impl<'a> KeyMap<'a> for SourceIter<'a> { impl<'a> KeyMap<'a> for SourceIter<'a> {
fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I) fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
-> Option<C> -> Option<C>
@ -35,6 +40,8 @@ impl<'a> KeyMap<'a> for SourceIter<'a> {
None None
} }
} }
#[cfg(feature = "dsl")]
impl<'a> KeyMap<'a> for TokenIter<'a> { impl<'a> KeyMap<'a> for TokenIter<'a> {
fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I) fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
-> Option<C> -> Option<C>
@ -61,10 +68,16 @@ impl<'a> KeyMap<'a> for TokenIter<'a> {
None None
} }
} }
/// A [Command] that can be constructed from a [Token]. /// A [Command] that can be constructed from a [Token].
#[cfg(feature = "dsl")]
pub trait AtomCommand<'a, C>: TryFromAtom<'a, C> + Command<C> {} pub trait AtomCommand<'a, C>: TryFromAtom<'a, C> + Command<C> {}
#[cfg(feature = "dsl")]
impl<'a, C, T: TryFromAtom<'a, C> + Command<C>> AtomCommand<'a, C> for T {} impl<'a, C, T: TryFromAtom<'a, C> + Command<C>> AtomCommand<'a, C> for T {}
/** Implement `AtomCommand` for given `State` and `Command` */ /** Implement `AtomCommand` for given `State` and `Command` */
#[cfg(feature = "dsl")]
#[macro_export] macro_rules! atom_command { #[macro_export] macro_rules! atom_command {
($Command:ty : |$state:ident:<$State:ident: $Trait:path>| { $(( ($Command:ty : |$state:ident:<$State:ident: $Trait:path>| { $((
// identifier // identifier
@ -156,72 +169,9 @@ impl<'a, C, T: TryFromAtom<'a, C> + Command<C>> AtomCommand<'a, C> for T {}
let $arg: $type = Context::<$type>::get_or_fail($state, $arg); let $arg: $type = Context::<$type>::get_or_fail($state, $arg);
}; };
} }
//pub struct SourceKeyMap<'a>(&'a str);
//impl<'a> KeyMap for SourceKeyMap<'a> { #[cfg(all(test, feature = "dsl"))]
//fn command <S, C: AtomCommand<S>> (&self, state: &S, input: &AtomInput) -> Option<C> { #[test] fn test_atom_keymap () -> Usually<()> {
//todo!();
//None
//}
//}
//pub struct ParsedKeyMap<'a>(TokensIterator<'a>);
//impl<'a> KeyMap for ParsedKeyMap<'a> {
//fn command <S, C: AtomCommand<S>> (&self, state: &S, input: &AtomInput) -> Option<C> {
//todo!();
//None
//}
//}
//pub struct RefKeyMap<'a>(TokensIterator<'a>);
//impl<'a> KeyMap for RefKeyMap<'a> {
//fn command <S, C: AtomCommand<S>> (&self, state: &S, input: &AtomInput) -> Option<C> {
//todo!();
////for token in self.0 {
////match token?.kind() {
////TokenKind::Exp => match atoms.as_slice() {
////[key, command, args @ ..] => match (key.kind(), key.text()) {
////(TokenKind::Sym, key) => {
////if input.matches_atom(key) {
////let command = C::from_atom(state, command, args);
////if command.is_some() {
////return command
////}
////}
////},
////_ => panic!("invalid config: {item}")
////},
////_ => panic!("invalid config: {item}")
////}
////_ => panic!("invalid config: {item}")
////}
////}
//None
//}
//}
//pub struct ArcKeyMap(Vec<ArcAtom>);
//impl KeyMap for ArcKeyMap {
//fn command <S, C: AtomCommand<S>> (&self, state: &S, input: &AtomInput) -> Option<C> {
//for atom in self.0.iter() {
//match atom {
//ArcAtom::Exp(atoms) => match atoms.as_slice() {
//[key, command, args @ ..] => match (key.kind(), key.text()) {
//(TokenKind::Sym, key) => {
//if input.matches_atom(key) {
//let command = C::from_atom(state, command, args);
//if command.is_some() {
//return command
//}
//}
//},
//_ => panic!("invalid config: {atom}")
//},
//_ => panic!("invalid config: {atom}")
//}
//_ => panic!("invalid config: {atom}")
//}
//}
//None
//}
//}
#[cfg(test)] #[test] fn test_atom_keymap () -> Usually<()> {
let keymap = SourceIter::new(""); let keymap = SourceIter::new("");
Ok(()) Ok(())
} }

View file

@ -1,16 +1,22 @@
#![feature(associated_type_defaults)] #![feature(associated_type_defaults)]
mod input; pub use self::input::*;
mod command; pub use self::command::*; mod command; pub use self::command::*;
mod handle; pub use self::handle::*;
mod keymap; pub use self::keymap::*; mod keymap; pub use self::keymap::*;
//mod event_map; pub use self::event_map::*;
pub(crate) use ::tek_edn::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
/// Standard error trait. /// Standard error trait.
pub(crate) use std::error::Error; pub(crate) use std::error::Error;
/// Standard result type.
#[cfg(test)] pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
/// Standard optional result type. /// Standard optional result type.
pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn Error>>; pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
#[cfg(test)] #[test] fn test_stub_input () -> Usually<()> {
/// Standard result type.
#[cfg(test)]
pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
#[cfg(test)]
#[test] fn test_stub_input () -> Usually<()> {
use crate::*; use crate::*;
struct TestInput(bool); struct TestInput(bool);
enum TestEvent { Test1 } enum TestEvent { Test1 }

View file

@ -5,12 +5,14 @@ version = "0.1.0"
description = "UI metaframework, output layer." description = "UI metaframework, output layer."
[dependencies] [dependencies]
tengri_edn = { optional = true, path = "../edn" } tengri_dsl = { optional = true, path = "../dsl" }
[features] [features]
edn = [ "tengri_edn" ] dsl = [ "tengri_dsl" ]
[dev-dependencies] [dev-dependencies]
tengri = { path = "../tengri", features = [ "dsl", "tui" ] }
tengri_tui = { path = "../tui" } tengri_tui = { path = "../tui" }
tengri_dsl = { path = "../dsl" }
proptest = "^1" proptest = "^1"
proptest-derive = "^0.5.1" proptest-derive = "^0.5.1"

View file

@ -1,12 +1,11 @@
# `tek_output` # `tengri_output`
## free floating layout primitives ## free floating layout primitives
this crate exposes several layout operators this crate exposes several layout operators
which work entirely in unsigned coordinates which work entirely in unsigned coordinates
and are generic over `tek_engine::Engine` and are generic over the trait `Content`.
and `tek_engine::Content`. chiefly, they most importantly, they are not dependent on rendering framework.
are not dependent on rendering framework.
|operator|description| |operator|description|
|-|-| |-|-|

View file

@ -1,4 +1,3 @@
//#![feature(lazy_type_alias)]
#![feature(step_trait)] #![feature(step_trait)]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_assoc_type)] #![feature(impl_trait_in_assoc_type)]
@ -18,7 +17,9 @@ mod view; pub use self::view::*;
pub(crate) use std::marker::PhantomData; pub(crate) use std::marker::PhantomData;
pub(crate) use std::error::Error; pub(crate) use std::error::Error;
pub(crate) use ::tek_edn::*;
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
/// Standard result type. /// Standard result type.
pub type Usually<T> = Result<T, Box<dyn Error>>; pub type Usually<T> = Result<T, Box<dyn Error>>;
/// Standard optional result type. /// Standard optional result type.

View file

@ -1,10 +1,10 @@
use crate::*; use crate::*;
use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}}; use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}};
//use ratatui::prelude::{Style, Color};
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
pub trait HasSize<E: Output> { pub trait HasSize<E: Output> {
fn size (&self) -> &Measure<E>; fn size (&self) -> &Measure<E>;
} }
#[macro_export] macro_rules! has_size { #[macro_export] macro_rules! has_size {
(<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? {
@ -12,6 +12,7 @@ pub trait HasSize<E: Output> {
} }
} }
} }
/// A widget that tracks its render width and height /// A widget that tracks its render width and height
#[derive(Default)] #[derive(Default)]
pub struct Measure<E: Output> { pub struct Measure<E: Output> {
@ -19,12 +20,15 @@ pub struct Measure<E: Output> {
pub x: Arc<AtomicUsize>, pub x: Arc<AtomicUsize>,
pub y: Arc<AtomicUsize>, pub y: Arc<AtomicUsize>,
} }
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
impl<E: Output> Content<E> for Measure<E> { impl<E: Output> Content<E> for Measure<E> {
fn render (&self, to: &mut E) { fn render (&self, to: &mut E) {
self.x.store(to.area().w().into(), Relaxed); self.x.store(to.area().w().into(), Relaxed);
self.y.store(to.area().h().into(), Relaxed); self.y.store(to.area().h().into(), Relaxed);
} }
} }
impl<E: Output> Clone for Measure<E> { impl<E: Output> Clone for Measure<E> {
fn clone (&self) -> Self { fn clone (&self) -> Self {
Self { Self {
@ -34,6 +38,7 @@ impl<E: Output> Clone for Measure<E> {
} }
} }
} }
impl<E: Output> std::fmt::Debug for Measure<E> { impl<E: Output> std::fmt::Debug for Measure<E> {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("Measure") f.debug_struct("Measure")
@ -42,6 +47,7 @@ impl<E: Output> std::fmt::Debug for Measure<E> {
.finish() .finish()
} }
} }
impl<E: Output> Measure<E> { impl<E: Output> Measure<E> {
pub fn new () -> Self { pub fn new () -> Self {
Self { Self {
@ -79,66 +85,3 @@ impl<E: Output> Measure<E> {
Bsp::b(Fill::xy(self), item) Bsp::b(Fill::xy(self), item)
} }
} }
//#[cfg(test)] #[test] fn test_measure () {
//use tek_tui::*;
//let size: Measure<TuiOut> = Measure::default().set_w(1usize).set_h(1usize).clone();
//let size: Measure<TuiOut> = (&Measure::new().set_wh(2usize, 1usize)).clone();
//let _ = format!("{:?}", &size);
//let _ = size.wh();
//let _ = size.format();
//let _ = size.of(());
//}
///// A scrollable area.
//pub struct Scroll<E, F>(pub F, pub Direction, pub u64, PhantomData<E>)
//where
//E: Output,
//F: Send + Sync + Fn(&mut dyn FnMut(&dyn Content<E>)->Usually<()>)->Usually<()>;
//pub trait ContentDebug<E: Output> {
//fn debug <W: Content<E>> (other: W) -> DebugOverlay<E, W> {
//DebugOverlay(Default::default(), other)
//}
//}
//impl<E: Output> ContentDebug<E> for E {}
//impl Render<TuiOut> for Measure<TuiOut> {
//fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
//Ok(Some([0u16.into(), 0u16.into()].into()))
//}
//fn render (&self, to: &mut TuiOut) -> Usually<()> {
//self.set_w(to.area().w());
//self.set_h(to.area().h());
//Ok(())
//}
//}
//impl Measure<TuiOut> {
//pub fn debug (&self) -> ShowMeasure {
//ShowMeasure(&self)
//}
//}
//render!(Tui: |self: ShowMeasure<'a>|render(|to: &mut TuiOut|Ok({
//let w = self.0.w();
//let h = self.0.h();
//to.blit(&format!(" {w} x {h} "), to.area.x(), to.area.y(), Some(
//Style::default().bold().italic().bg(Color::Rgb(255, 0, 255)).fg(Color::Rgb(0,0,0))
//))
//})));
//pub struct ShowMeasure<'a>(&'a Measure<TuiOut>);
//pub struct DebugOverlay<E: Output, W: Render<E>>(PhantomData<E>, pub W);
//impl<T: Render<TuiOut>> Render<TuiOut> for DebugOverlay<Tui, T> {
//fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
//self.1.min_size(to)
//}
//fn render (&self, to: &mut TuiOut) -> Usually<()> {
//let [x, y, w, h] = to.area();
//self.1.render(to)?;
//Ok(to.blit(&format!("{w}x{h}+{x}+{y}"), x, y, Some(Style::default().green())))
//}
//}

View file

@ -1,6 +1,6 @@
//! Aligns things to the container. Comes with caveats. //! Aligns things to the container. Comes with caveats.
//! ``` //! ```
//! use ::tek_tui::{*, tek_output::*}; //! use ::tengri::{output::*, tui::*};
//! let area: [u16;4] = [10, 10, 20, 20]; //! let area: [u16;4] = [10, 10, 20, 20];
//! fn test (area: [u16;4], item: &impl Content<TuiOut>, expected: [u16;4]) { //! fn test (area: [u16;4], item: &impl Content<TuiOut>, expected: [u16;4]) {
//! assert_eq!(Content::layout(item, area), expected); //! assert_eq!(Content::layout(item, area), expected);
@ -28,9 +28,12 @@
//! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]); //! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
//! ``` //! ```
use crate::*; use crate::*;
#[derive(Debug, Copy, Clone, Default)] pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } #[derive(Debug, Copy, Clone, Default)] pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W }
pub struct Align<A>(Alignment, A); pub struct Align<A>(Alignment, A);
try_from_expr!(<'a, E>: Align<RenderBox<'a, E>>: |state, iter|
#[cfg(feature = "dsl")]
try_from_expr!(<'a, E>: Align<RenderBox<'a, E>>: |state, iter|{
if let Some(Token { value: Value::Key(key), .. }) = iter.peek() { if let Some(Token { value: Value::Key(key), .. }) = iter.peek() {
match key { match key {
"align/c"|"align/x"|"align/y"| "align/c"|"align/x"|"align/y"|
@ -56,7 +59,9 @@ try_from_expr!(<'a, E>: Align<RenderBox<'a, E>>: |state, iter|
}, },
_ => return None _ => return None
} }
}); }
});
impl<A> Align<A> { impl<A> Align<A> {
#[inline] pub fn c (a: A) -> Self { Self(Alignment::Center, a) } #[inline] pub fn c (a: A) -> Self { Self(Alignment::Center, a) }
#[inline] pub fn x (a: A) -> Self { Self(Alignment::X, a) } #[inline] pub fn x (a: A) -> Self { Self(Alignment::X, a) }

View file

@ -16,6 +16,7 @@ impl<E: Output, A: Content<E>, B: Content<E>> Content<E> for Bsp<A, B> {
} }
} }
} }
#[cfg(feature = "dsl")]
try_from_expr!(<'a, E>: Bsp<RenderBox<'a, E>, RenderBox<'a, E>>: |state, iter| { try_from_expr!(<'a, E>: Bsp<RenderBox<'a, E>, RenderBox<'a, E>>: |state, iter| {
if let Some(Token { value: Value::Key(key), .. }) = iter.peek() { if let Some(Token { value: Value::Key(key), .. }) = iter.peek() {
match key { match key {

View file

@ -1,10 +1,14 @@
use crate::*; use crate::*;
/// Show an item only when a condition is true. /// Show an item only when a condition is true.
pub struct When<A>(pub bool, pub A); pub struct When<A>(pub bool, pub A);
impl<A> When<A> { #[inline] pub fn new (c: bool, a: A) -> Self { Self(c, a) } } impl<A> When<A> { #[inline] pub fn new (c: bool, a: A) -> Self { Self(c, a) } }
/// Show one item if a condition is true and another if the condition is false /// Show one item if a condition is true and another if the condition is false
pub struct Either<A, B>(pub bool, pub A, pub B); pub struct Either<A, B>(pub bool, pub A, pub B);
impl<A, B> Either<A, B> { #[inline] pub fn new (c: bool, a: A, b: B) -> Self { Self(c, a, b) } } impl<A, B> Either<A, B> { #[inline] pub fn new (c: bool, a: A, b: B) -> Self { Self(c, a, b) } }
#[cfg(feature = "dsl")]
try_from_expr!(<'a, E>: When<RenderBox<'a, E>>: |state, iter| { try_from_expr!(<'a, E>: When<RenderBox<'a, E>>: |state, iter| {
if let Some(Token { value: Value::Key("when"), .. }) = iter.peek() { if let Some(Token { value: Value::Key("when"), .. }) = iter.peek() {
let _ = iter.next().unwrap(); let _ = iter.next().unwrap();
@ -15,6 +19,8 @@ try_from_expr!(<'a, E>: When<RenderBox<'a, E>>: |state, iter| {
return Some(Self(condition, content)) return Some(Self(condition, content))
} }
}); });
#[cfg(feature = "dsl")]
try_from_expr!(<'a, E>: Either<RenderBox<'a, E>, RenderBox<'a, E>>: |state, iter| { try_from_expr!(<'a, E>: Either<RenderBox<'a, E>, RenderBox<'a, E>>: |state, iter| {
if let Some(Token { value: Value::Key("either"), .. }) = iter.peek() { if let Some(Token { value: Value::Key("either"), .. }) = iter.peek() {
let _ = iter.next().unwrap(); let _ = iter.next().unwrap();
@ -27,6 +33,7 @@ try_from_expr!(<'a, E>: Either<RenderBox<'a, E>, RenderBox<'a, E>>: |state, iter
return Some(Self(condition, content, alternate)) return Some(Self(condition, content, alternate))
} }
}); });
impl<E: Output, A: Render<E>> Content<E> for When<A> { impl<E: Output, A: Render<E>> Content<E> for When<A> {
fn layout (&self, to: E::Area) -> E::Area { fn layout (&self, to: E::Area) -> E::Area {
let Self(cond, item) = self; let Self(cond, item) = self;
@ -45,6 +52,7 @@ impl<E: Output, A: Render<E>> Content<E> for When<A> {
if *cond { item.render(to) } if *cond { item.render(to) }
} }
} }
impl<E: Output, A: Render<E>, B: Render<E>> Content<E> for Either<A, B> { impl<E: Output, A: Render<E>, B: Render<E>> Content<E> for Either<A, B> {
fn layout (&self, to: E::Area) -> E::Area { fn layout (&self, to: E::Area) -> E::Area {
let Self(cond, a, b) = self; let Self(cond, a, b) = self;

View file

@ -3,7 +3,7 @@
//! //!
//! Transform may also react to the [Area] provided. //! Transform may also react to the [Area] provided.
//! ``` //! ```
//! use ::tek_tui::{*, tek_output::*}; //! use ::tengri::{output::*, tui::*};
//! let area: [u16;4] = [10, 10, 20, 20]; //! let area: [u16;4] = [10, 10, 20, 20];
//! fn test (area: [u16;4], item: &impl Content<TuiOut>, expected: [u16;4]) { //! fn test (area: [u16;4], item: &impl Content<TuiOut>, expected: [u16;4]) {
//! assert_eq!(Content::layout(item, area), expected); //! assert_eq!(Content::layout(item, area), expected);
@ -30,6 +30,7 @@ macro_rules! transform_xy {
#[inline] pub fn y (item: T) -> Self { Self::Y(item) } #[inline] pub fn y (item: T) -> Self { Self::Y(item) }
#[inline] pub fn xy (item: T) -> Self { Self::XY(item) } #[inline] pub fn xy (item: T) -> Self { Self::XY(item) }
} }
#[cfg(feature = "dsl")]
impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T> impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T>
for $Enum<RenderBox<'a, E>> { for $Enum<RenderBox<'a, E>> {
fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option<Self> { fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option<Self> {
@ -76,6 +77,7 @@ macro_rules! transform_xy_unit {
#[inline] pub fn y (y: U, item: T) -> Self { Self::Y(y, item) } #[inline] pub fn y (y: U, item: T) -> Self { Self::Y(y, item) }
#[inline] pub fn xy (x: U, y: U, item: T) -> Self { Self::XY(x, y, item) } #[inline] pub fn xy (x: U, y: U, item: T) -> Self { Self::XY(x, y, item) }
} }
#[cfg(feature = "dsl")]
impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T> impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T>
for $Enum<E::Unit, RenderBox<'a, E>> { for $Enum<E::Unit, RenderBox<'a, E>> {
fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option<Self> { fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option<Self> {

View file

@ -20,6 +20,7 @@ pub trait Output: Send + Sync + Sized {
#[inline] fn h (&self) -> Self::Unit { self.area().h() } #[inline] fn h (&self) -> Self::Unit { self.area().h() }
#[inline] fn wh (&self) -> Self::Size { self.area().wh().into() } #[inline] fn wh (&self) -> Self::Size { self.area().wh().into() }
} }
/// Renderable with dynamic dispatch. /// Renderable with dynamic dispatch.
pub trait Render<E: Output> { pub trait Render<E: Output> {
/// Compute layout. /// Compute layout.
@ -31,6 +32,7 @@ pub trait Render<E: Output> {
Box::new(self) as RenderBox<'a, E> Box::new(self) as RenderBox<'a, E>
} }
} }
/// Most importantly, every [Content] is also a [Render]. /// Most importantly, every [Content] is also a [Render].
/// ///
/// However, the converse does not hold true. /// However, the converse does not hold true.
@ -40,17 +42,21 @@ impl<E: Output, C: Content<E>> Render<E> for C {
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) } fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
fn render (&self, output: &mut E) { Content::render(self, output) } fn render (&self, output: &mut E) { Content::render(self, output) }
} }
/// Opaque pointer to a renderable living on the heap. /// Opaque pointer to a renderable living on the heap.
/// ///
/// Return this from [Content::content] to use dynamic dispatch. /// Return this from [Content::content] to use dynamic dispatch.
pub type RenderBox<'a, E> = Box<RenderDyn<'a, E>>; pub type RenderBox<'a, E> = Box<RenderDyn<'a, E>>;
/// You can render from a box. /// You can render from a box.
impl<'a, E: Output> Content<E> for RenderBox<'a, E> { impl<'a, E: Output> Content<E> for RenderBox<'a, E> {
fn content (&self) -> impl Render<E> { self.deref() } fn content (&self) -> impl Render<E> { self.deref() }
//fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self } //fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self }
} }
/// Opaque pointer to a renderable. /// Opaque pointer to a renderable.
pub type RenderDyn<'a, E> = dyn Render<E> + Send + Sync + 'a; pub type RenderDyn<'a, E> = dyn Render<E> + Send + Sync + 'a;
/// You can render from an opaque pointer. /// You can render from an opaque pointer.
impl<'a, E: Output> Content<E> for &RenderDyn<'a, E> where Self: Sized { impl<'a, E: Output> Content<E> for &RenderDyn<'a, E> where Self: Sized {
fn content (&self) -> impl Render<E> { fn content (&self) -> impl Render<E> {
@ -66,6 +72,7 @@ impl<'a, E: Output> Content<E> for &RenderDyn<'a, E> where Self: Sized {
Render::render(self.deref(), output) Render::render(self.deref(), output)
} }
} }
/// Composable renderable with static dispatch. /// Composable renderable with static dispatch.
pub trait Content<E: Output> { pub trait Content<E: Output> {
/// Return a [Render]able of a specific type. /// Return a [Render]able of a specific type.
@ -75,17 +82,20 @@ pub trait Content<E: Output> {
/// Draw to output. By default, delegates to [Self::content]. /// Draw to output. By default, delegates to [Self::content].
fn render (&self, output: &mut E) { self.content().render(output) } fn render (&self, output: &mut E) { self.content().render(output) }
} }
/// Every pointer to [Content] is a [Content]. /// Every pointer to [Content] is a [Content].
impl<E: Output, C: Content<E>> Content<E> for &C { impl<E: Output, C: Content<E>> Content<E> for &C {
fn content (&self) -> impl Render<E> { (*self).content() } fn content (&self) -> impl Render<E> { (*self).content() }
fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) } fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) }
fn render (&self, output: &mut E) { (*self).render(output) } fn render (&self, output: &mut E) { (*self).render(output) }
} }
/// The platonic ideal unit of [Content]: total emptiness at dead center (e=1vg^sqrt(-1)) /// The platonic ideal unit of [Content]: total emptiness at dead center (e=1vg^sqrt(-1))
impl<E: Output> Content<E> for () { impl<E: Output> Content<E> for () {
fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() } fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() }
fn render (&self, _: &mut E) {} fn render (&self, _: &mut E) {}
} }
impl<E: Output, T: Content<E>> Content<E> for Option<T> { impl<E: Output, T: Content<E>> Content<E> for Option<T> {
fn content (&self) -> impl Render<E> { fn content (&self) -> impl Render<E> {
self.as_ref() self.as_ref()
@ -100,6 +110,7 @@ impl<E: Output, T: Content<E>> Content<E> for Option<T> {
.map(|content|content.render(output)); .map(|content|content.render(output));
} }
} }
/// Implement [Content] with composable content for a struct. /// Implement [Content] with composable content for a struct.
#[macro_export] macro_rules! content { #[macro_export] macro_rules! content {
// Implement for all [Output]s. // Implement for all [Output]s.
@ -119,6 +130,7 @@ impl<E: Output, T: Content<E>> Content<E> for Option<T> {
} }
}; };
} }
/// Implement [Content] with custom rendering for a struct. /// Implement [Content] with custom rendering for a struct.
#[macro_export] macro_rules! render { #[macro_export] macro_rules! render {
(|$self:ident:$Struct:ident $(< (|$self:ident:$Struct:ident $(<

View file

@ -1,4 +1,5 @@
use crate::*; use crate::*;
#[macro_export] macro_rules! view { #[macro_export] macro_rules! view {
($Output:ty: |$self:ident: $State:ty| $expr:expr; { ($Output:ty: |$self:ident: $State:ty| $expr:expr; {
$($sym:literal => $body:expr),* $(,)? $($sym:literal => $body:expr),* $(,)?
@ -23,7 +24,10 @@ use crate::*;
// An ephemeral wrapper around view state and view description, // An ephemeral wrapper around view state and view description,
// that is meant to be constructed and returned from [Content::content]. // that is meant to be constructed and returned from [Content::content].
#[cfg(feature = "dsl")]
pub struct View<'a, T>(pub &'a T, pub SourceIter<'a>); pub struct View<'a, T>(pub &'a T, pub SourceIter<'a>);
#[cfg(feature = "dsl")]
impl<'a, O: Output + 'a, T: ViewContext<'a, O>> Content<O> for View<'a, T> { impl<'a, O: Output + 'a, T: ViewContext<'a, O>> Content<O> for View<'a, T> {
fn content (&self) -> impl Render<O> { fn content (&self) -> impl Render<O> {
let iter = self.1.clone(); let iter = self.1.clone();
@ -35,7 +39,9 @@ impl<'a, O: Output + 'a, T: ViewContext<'a, O>> Content<O> for View<'a, T> {
return None return None
} }
} }
// Provides components to the view. // Provides components to the view.
#[cfg(feature = "dsl")]
pub trait ViewContext<'a, E: Output + 'a>: Send + Sync pub trait ViewContext<'a, E: Output + 'a>: Send + Sync
+ Context<bool> + Context<bool>
+ Context<usize> + Context<usize>
@ -67,6 +73,8 @@ pub trait ViewContext<'a, E: Output + 'a>: Send + Sync
None None
} }
} }
#[cfg(feature = "dsl")]
#[macro_export] macro_rules! try_delegate { #[macro_export] macro_rules! try_delegate {
($s:ident, $atom:expr, $T:ty) => { ($s:ident, $atom:expr, $T:ty) => {
if let Some(value) = <$T>::try_from_atom($s, $atom) { if let Some(value) = <$T>::try_from_atom($s, $atom) {
@ -74,6 +82,8 @@ pub trait ViewContext<'a, E: Output + 'a>: Send + Sync
} }
} }
} }
#[cfg(feature = "dsl")]
#[macro_export] macro_rules! try_from_expr { #[macro_export] macro_rules! try_from_expr {
(<$l:lifetime, $E:ident>: $Struct:ty: |$state:ident, $iter:ident|$body:expr) => { (<$l:lifetime, $E:ident>: $Struct:ty: |$state:ident, $iter:ident|$body:expr) => {
impl<$l, $E: Output + $l, T: ViewContext<$l, $E>> TryFromAtom<$l, T> for $Struct { impl<$l, $E: Output + $l, T: ViewContext<$l, $E>> TryFromAtom<$l, T> for $Struct {

View file

@ -5,7 +5,7 @@ version = "0.1.0"
description = "UI metaframework." description = "UI metaframework."
[dependencies] [dependencies]
tengri_edn = { optional = true, path = "../edn" } tengri_dsl = { optional = true, path = "../dsl" }
tengri_input = { optional = true, path = "../input" } tengri_input = { optional = true, path = "../input" }
tengri_output = { optional = true, path = "../output" } tengri_output = { optional = true, path = "../output" }
tengri_tui = { optional = true, path = "../tui" } tengri_tui = { optional = true, path = "../tui" }
@ -15,4 +15,4 @@ default = [ "input", "output", "tui" ]
input = [ "tengri_input" ] input = [ "tengri_input" ]
output = [ "tengri_output" ] output = [ "tengri_output" ]
tui = [ "tengri_tui" ] tui = [ "tengri_tui" ]
edn = [ "tengri_edn", "tengri_input/edn", "tengri_output/edn", "tengri_tui/edn" ] dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl", "tengri_tui/dsl" ]

View file

@ -1,4 +1,4 @@
#[cfg(feature="output")] pub use ::tengri_output as output; #[cfg(feature="output")] pub use ::tengri_output as output;
#[cfg(feature="input")] pub use ::tengri_input as input; #[cfg(feature="input")] pub use ::tengri_input as input;
#[cfg(feature="edn")] pub use ::tengri_edn as edn; #[cfg(feature="dsl")] pub use ::tengri_dsl as dsl;
#[cfg(feature="tui")] pub use ::tengri_tui as tui; #[cfg(feature="tui")] pub use ::tengri_tui as tui;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "tengri_tui" name = "tengri_tui"
edition = "2021" edition = "2024"
version = "0.1.0" version = "0.1.0"
description = "UI metaframework, Ratatui backend." description = "UI metaframework, Ratatui backend."
@ -16,7 +16,11 @@ quanta = "0.12.3"
tengri_input = { path = "../input" } tengri_input = { path = "../input" }
tengri_output = { path = "../output" } tengri_output = { path = "../output" }
tengri_edn = { optional = true, path = "../edn" } tengri_dsl = { optional = true, path = "../dsl" }
[dev-dependencies]
tengri = { path = "../tengri", features = [ "dsl" ] }
tengri_dsl = { path = "../dsl" }
[features] [features]
edn = [ "tengri_edn", "tengri_input/edn", "tengri_output/edn" ] dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl" ]

View file

@ -1,5 +1,4 @@
use tengri_tui::{*, tengri_input::*, tengri_output::*}; use tengri::{self, input::*, output::*, tui::*, dsl::*};
use tengri_edn::*;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use crossterm::event::{*, KeyCode::*}; use crossterm::event::{*, KeyCode::*};
use crate::ratatui::style::Color; use crate::ratatui::style::Color;

View file

@ -6,29 +6,43 @@ mod tui_file; pub use self::tui_file::*;
mod tui_input; pub use self::tui_input::*; mod tui_input; pub use self::tui_input::*;
mod tui_output; pub use self::tui_output::*; mod tui_output; pub use self::tui_output::*;
mod tui_perf; pub use self::tui_perf::*; mod tui_perf; pub use self::tui_perf::*;
pub use ::tek_edn;// pub(crate) use ::tek_edn::*;
//pub use ::tek_time; pub(crate) use ::tek_time::*; pub use ::tengri_input as input;
pub use ::tek_input; pub(crate) use tek_input::*; pub(crate) use ::tengri_input::*;
pub use ::tek_output; pub(crate) use tek_output::*;
pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity}; pub use ::tengri_output as output;
pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*}; pub(crate) use ::tengri_output::*;
pub use ::crossterm; pub(crate) use crossterm::{
#[cfg(feature = "dsl")]
pub use ::tengri_dsl;
#[cfg(feature = "dsl")]
pub(crate) use ::tengri_dsl::*;
pub(crate) use atomic_float::AtomicF64;
pub use ::better_panic; pub(crate) use ::better_panic::{Settings, Verbosity};
pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*};
pub use ::crossterm; pub(crate) use ::crossterm::{
ExecutableCommand, ExecutableCommand,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
}; };
pub use ::ratatui; pub(crate) use ratatui::{
pub use ::ratatui; pub(crate) use ratatui::{
prelude::{Color, Style, Buffer}, prelude::{Color, Style, Buffer},
style::Modifier, style::Modifier,
backend::{Backend, CrosstermBackend, ClearType}, backend::{Backend, CrosstermBackend, ClearType},
layout::{Size, Rect}, layout::{Size, Rect},
buffer::Cell buffer::Cell
}; };
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
pub(crate) use std::io::{stdout, Stdout}; pub(crate) use std::io::{stdout, Stdout};
pub(crate) use std::path::PathBuf; pub(crate) use std::path::PathBuf;
pub(crate) use std::ffi::OsString; pub(crate) use std::ffi::OsString;
pub(crate) use atomic_float::AtomicF64;
#[macro_export] macro_rules! from { #[macro_export] macro_rules! from {
($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => { ($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => {
impl $(<$($lt),+>)? From<$Source> for $Target { impl $(<$($lt),+>)? From<$Source> for $Target {
@ -36,6 +50,7 @@ pub(crate) use atomic_float::AtomicF64;
} }
}; };
} }
#[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> { #[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> {
use crate::*; use crate::*;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};