From 238ac2e8882b584371d916cc5cba76a2fc6878d1 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 19 Jul 2025 08:42:08 +0300 Subject: [PATCH] simplify --- dsl/Cargo.toml | 3 + dsl/src/{lib.rs => dsl.rs} | 374 +++++++++++++++++++----------- dsl/src/dsl_test.rs | 0 dsl/src/test.rs | 104 --------- input/src/input_dsl.rs | 4 +- output/src/lib.rs | 3 + output/src/ops.rs | 387 ++++++++++++++++++++++++++++++-- output/src/ops/_collect.rs | 23 -- output/src/ops/_reduce.rs | 93 -------- output/src/ops/map.rs | 150 ------------- output/src/ops/memo.rs | 30 --- output/src/ops/stack.rs | 109 --------- output/src/ops/thunk.rs | 69 ------ output/src/space.rs | 279 ++++++++++++++++++++++- output/src/space/area.rs | 98 -------- output/src/space/coordinate.rs | 31 --- output/src/space/direction.rs | 22 -- output/src/space/measure.rs | 91 -------- output/src/space/size.rs | 40 ---- proc/src/proc_command.rs | 7 +- proc/src/proc_expose.rs | 4 +- proc/src/proc_view.rs | 8 +- tui/src/tui_engine.rs | 4 +- tui/src/tui_engine/tui_input.rs | 140 ++++++++++-- tui/src/tui_engine/tui_keys.rs | 92 -------- 25 files changed, 1018 insertions(+), 1147 deletions(-) rename dsl/src/{lib.rs => dsl.rs} (60%) create mode 100644 dsl/src/dsl_test.rs delete mode 100644 dsl/src/test.rs delete mode 100644 output/src/ops/_collect.rs delete mode 100644 output/src/ops/_reduce.rs delete mode 100644 output/src/ops/map.rs delete mode 100644 output/src/ops/memo.rs delete mode 100644 output/src/ops/stack.rs delete mode 100644 output/src/ops/thunk.rs delete mode 100644 output/src/space/area.rs delete mode 100644 output/src/space/coordinate.rs delete mode 100644 output/src/space/direction.rs delete mode 100644 output/src/space/measure.rs delete mode 100644 output/src/space/size.rs delete mode 100644 tui/src/tui_engine/tui_keys.rs diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml index 256b599..c61a057 100644 --- a/dsl/Cargo.toml +++ b/dsl/Cargo.toml @@ -4,6 +4,9 @@ description = "UI metaframework, tiny S-expression-based DSL." version = { workspace = true } edition = { workspace = true } +[lib] +path = "src/dsl.rs" + [dependencies] tengri_core = { path = "../core" } konst = { workspace = true } diff --git a/dsl/src/lib.rs b/dsl/src/dsl.rs similarity index 60% rename from dsl/src/lib.rs rename to dsl/src/dsl.rs index 703f8ba..2f1c150 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/dsl.rs @@ -5,6 +5,7 @@ extern crate const_panic; use const_panic::{concat_panic, PanicFmt}; pub(crate) use ::tengri_core::*; +use std::ops::Deref; pub(crate) use std::error::Error; pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; @@ -13,7 +14,80 @@ pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use thiserror::Error; pub(crate) use self::DslError::*; -#[cfg(test)] mod test; +#[cfg(test)] mod dsl_test; + +#[macro_export] macro_rules! dsl_read_advance (($exp:ident, $pat:pat => $val:expr)=>{{ + let (head, tail) = $exp.advance(); $exp = tail; match head { + Some($pat) => $val, _ => Err(format!("(e4) unexpected {head:?}").into()) }}}); + +/// Coerce to [Val] for predefined [Self::Str] and [Self::Exp]. +pub trait Dsl: Clone + Debug { + /// The string representation for a dizzle. + type Str: DslStr; + /// The string representation for a dizzle. + type Exp: DslExp; + /// Request the top-level DSL [Val]ue. + /// May perform cloning or parsing. + fn val (&self) -> Val; + fn err (&self) -> Option {self.val().err()} + fn nil (&self) -> bool {self.val().nil()} + fn num (&self) -> Option {self.val().num()} + fn sym (&self) -> Option {self.val().sym()} + fn key (&self) -> Option {self.val().key()} + fn str (&self) -> Option {self.val().str()} + fn exp (&self) -> Option {self.val().exp()} + fn exp_depth (&self) -> Option {self.val().exp_depth()} + fn exp_head (&self) -> Val {self.val().exp_head()} + fn exp_tail (&self) -> Self::Exp {self.val().exp_tail()} + fn exp_each (&self, f: impl Fn(&Self) -> Usually<()>) -> Usually<()> { + todo!() + } + fn advance (&self) -> (Val, Self::Exp) { + (self.exp_head(), self.exp_tail()) + } +} + +impl<'s> Dsl for &'s str { + type Str = &'s str; + type Exp = &'s str; + fn val (&self) -> Val { Val::Exp(0, self) } +} + +impl<'s> Dsl for Cst<'s> { + type Str = &'s str; + type Exp = Cst<'s>; + fn val (&self) -> Val { Val::Exp(0, *self) } +} + +impl Dsl for Ast { + type Str = Arc; + type Exp = Ast; + fn val (&self) -> Val { Val::Exp(0, self.clone()) } +} + +impl Dsl for Token { + type Str = Str; + type Exp = Exp; + fn val (&self) -> Val { self.value.clone() } +} + +impl Dsl for Val { + type Str = Str; + type Exp = Exp; + fn val (&self) -> Val { self.clone() } +} + +/// The expression representation for a [Dsl] implementation. +/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. +pub trait DslExp: PartialEq + Clone + Debug + Dsl {} +impl DslExp for T {} +/// The string representation for a [Dsl] implementation. +/// [Cst] uses `&'s str`. [Ast] uses `Arc`. +pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + Deref { + fn as_str (&self) -> &str { self.as_ref() } + fn as_arc (&self) -> Arc { self.as_ref().into() } +} +impl + Deref> DslStr for Str {} /// Enumeration of values that may figure in an expression. /// Generic over string and expression storage. #[derive(Clone, Debug, PartialEq, Default)] @@ -39,31 +113,20 @@ pub enum Val { Error(DslError), } impl Copy for Val {} -impl Val { - pub fn convert (&self) -> Val where - T::Str: for<'a> From<&'a Str>, - T::Exp: for<'a> From<&'a Exp> - { +impl Val { + pub fn convert_to (&self, to_str: impl Fn(&S1)->S2, to_exp: impl Fn(&E1)->E2) -> Val { match self { Val::Nil => Val::Nil, Val::Num(u) => Val::Num(*u), - Val::Sym(s) => Val::Sym(s.into()), - Val::Key(s) => Val::Key(s.into()), - Val::Str(s) => Val::Str(s.into()), - Val::Exp(d, x) => Val::Exp(*d, x.into()), + Val::Sym(s) => Val::Sym(to_str(s)), + Val::Key(s) => Val::Key(to_str(s)), + Val::Str(s) => Val::Str(to_str(s)), + Val::Exp(d, x) => Val::Exp(*d, to_exp(x)), Val::Error(e) => Val::Error(*e) } } } -/// The expression representation for a [Dsl] implementation. -/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. -pub trait DslExp: PartialEq + Clone + Default + Debug + Dsl {} -impl DslExp for T {} -/// The string representation for a [Dsl] implementation. -/// [Cst] uses `&'s str`. [Ast] uses `Arc`. -pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + std::ops::Deref {} -impl + std::ops::Deref> DslStr for T {} -impl> Val { +impl> Val { pub const fn err (&self) -> Option {match self{Val::Error(e)=>Some(*e), _=>None}} pub const fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}} pub const fn num (&self) -> Option {match self{Val::Num(n)=>Some(*n), _=>None}} @@ -85,7 +148,9 @@ pub struct Token { /// Length of span. pub length: usize, } +/// Tokens are copiable where possible. impl Copy for Token {} +/// Token methods. impl Token { pub const fn end (&self) -> usize { self.start.saturating_add(self.length) } @@ -99,83 +164,85 @@ impl Token { Self { value: self.value, ..*self } } } -/// To the [Dsl], a token is equivalent to its `value` field. -impl Dsl for Token { - type Str = Str; type Exp = Exp; - fn dsl (&self) -> Val { self.value.clone() } -} -/// Coerce to [Val] for predefined [Self::Str] and [Self::Exp]. -pub trait Dsl: Clone + Debug { - /// The string representation for a dizzle. - type Str: DslStr; - /// The expression representation for a dizzle. - type Exp: DslExp; - /// Request the top-level DSL [Val]ue. - /// May perform cloning or parsing. - fn dsl (&self) -> Val; - fn err (&self) -> Option {self.dsl().err()} - fn nil (&self) -> bool {self.dsl().nil()} - fn num (&self) -> Option {self.dsl().num()} - fn sym (&self) -> Option {self.dsl().sym()} - fn key (&self) -> Option {self.dsl().key()} - fn str (&self) -> Option {self.dsl().str()} - fn exp (&self) -> Option {self.dsl().exp()} - fn exp_depth (&self) -> Option {self.dsl().exp_depth()} - fn exp_head (&self) -> Val {self.dsl().exp_head()} - fn exp_tail (&self) -> Self::Exp {self.dsl().exp_tail()} - fn exp_each (&self, f: impl Fn(&Self) -> Usually<()>) -> Usually<()> { todo!() } -} -/// The most basic implementor of the [Dsl] trait. -impl Dsl for Val { - type Str = Str; type Exp = Exp; - fn dsl (&self) -> Val { self.clone() } -} -/// The abstract syntax tree (AST) can be produced from the CST -/// by cloning source slices into owned ([Arc]) string slices. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Ast(Arc, Ast>>>>); -pub type AstVal = Val, Ast>; -pub type AstToken = Token, Ast>; -impl Dsl for Ast { - type Str = Arc; type Exp = Ast; - fn dsl (&self) -> Val, Ast> { Val::Exp(0, Ast(self.0.clone())) } -} -impl<'s> From<&'s str> for Ast { - fn from (source: &'s str) -> Self { - let source: Arc = source.into(); - Self(CstIter(CstConstIter(source.as_ref())) - .map(|token|Arc::new(Token { - source: source.clone(), - start: token.start, - length: token.length, - value: match token.value { - Val::Nil => Val::Nil, - Val::Num(u) => Val::Num(u), - Val::Sym(s) => Val::Sym(s.into()), - Val::Key(s) => Val::Key(s.into()), - Val::Str(s) => Val::Str(s.into()), - Val::Exp(d, x) => Val::Exp(d, x.into()), - Val::Error(e) => Val::Error(e.into()) - }, - })) - .collect::>() - .into()) - } -} -impl<'s> From> for Ast { - fn from (cst: Cst<'s>) -> Self { - let mut tokens: VecDeque<_> = Default::default(); - Self(tokens.into()) - } -} /// The concrete syntax tree (CST) implements zero-copy /// parsing of the DSL from a string reference. CST items /// preserve info about their location in the source. /// CST stores strings as source references and expressions as [CstIter] instances. -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct Cst<'s>(pub CstIter<'s>); -pub type CstVal<'s> = Val<&'s str, Cst<'s>>; -pub type CstToken<'s> = Token<&'s str, Cst<'s>>; +#[derive(Debug, Copy, Clone, PartialEq, Default)] +pub enum Cst<'s> { + #[default] __, + Source(&'s str), + Token(CstToken<'s>), + Val(CstVal<'s>), + Iter(CstIter<'s>), + ConstIter(CstConstIter<'s>), +} +impl<'s> From<&'s str> for Cst<'s> { + fn from (src: &'s str) -> Self { + Cst::Source(src) + } +} +impl<'s> From> for Cst<'s> { + fn from (token: CstToken<'s>) -> Self { + Cst::Token(token) + } +} +impl<'s> From> for Cst<'s> { + fn from (val: CstVal<'s>) -> Self { + Cst::Val(val) + } +} +impl<'s> From> for Cst<'s> { + fn from (iter: CstIter<'s>) -> Self { + Cst::Iter(iter) + } +} +impl<'s> From> for Cst<'s> { + fn from (iter: CstConstIter<'s>) -> Self { + Cst::ConstIter(iter) + } +} +impl<'s> From> for Ast { + fn from (cst: Cst<'s>) -> Self { + match cst { + Cst::Source(source) | Cst::Iter(CstIter(CstConstIter(source))) | + Cst::ConstIter(CstConstIter(source)) => + Ast::Source(source.into()), + Cst::Val(value) => + Ast::Val(value.convert_to(|s|(*s).into(), |e|Box::new((*e).into()))), + Cst::Token(Token { source, start, length, value }) => { + let source = AsRef::::as_ref(source).into(); + let value = value.convert_to(|s|(*s).into(), |e|Box::new((*e).into())); + Ast::Token(Token { source, start, length, value }) + }, + _ => unreachable!() + } + } +} +/// The abstract syntax tree (AST) can be produced from the CST +/// by cloning source slices into owned ([Arc]) string slices. +#[derive(Debug, Clone, PartialEq, Default)] +pub enum Ast { + #[default] __, + Source(Arc), + Token(Token, Box>), + Val(Val, Box>), + Exp(Arc, Ast>>>>), +} +impl<'s> From<&'s str> for Ast { + fn from (src: &'s str) -> Self { + Self::Source(src.into()) + } +} +impl<'s, Str: DslStr, Exp: DslExp + Into + 's> From> for Ast { + fn from (Token { source, start, length, value }: Token) -> Self { + let source: Arc = source.as_ref().into(); + let value = value.convert_to(|s|s.as_ref().into(), |e|Box::new(e.clone().into())); + Self::Token(Token { source, start, length, value }) + } +} +pub type CstVal<'s> = Val<&'s str, &'s str>; +pub type CstToken<'s> = Token<&'s str, &'s str>; impl<'s> CstToken<'s> { pub const fn slice (&self) -> &str { str_range(self.source, self.start, self.end()) } @@ -188,19 +255,32 @@ impl<'s> CstToken<'s> { self } pub const fn grow_exp (&'s mut self, depth: isize, source: &'s str) -> &mut Self { - self.value = Val::Exp(depth, Cst(CstIter(CstConstIter(source)))); + self.value = Val::Exp(depth, source); self } } -impl<'s> Dsl for Cst<'s> { - type Str = &'s str; type Exp = Cst<'s>; - fn dsl (&self) -> Val { Val::Exp(0, Cst(self.0)) } -} -impl<'s> From<&'s str> for Cst<'s> { - fn from (source: &'s str) -> Self { - Self(CstIter(CstConstIter(source))) - } -} +//impl<'s> From<&'s str> for Ast { + //fn from (source: &'s str) -> Ast { + //let source: Arc = source.into(); + //Ast(CstIter(CstConstIter(source.as_ref())) + //.map(|token|Arc::new(Token { + //source: source.clone(), + //start: token.start, + //length: token.length, + //value: match token.value { + //Val::Nil => Val::Nil, + //Val::Num(u) => Val::Num(u), + //Val::Sym(s) => Val::Sym(s.into()), + //Val::Key(s) => Val::Key(s.into()), + //Val::Str(s) => Val::Str(s.into()), + //Val::Exp(d, x) => Val::Exp(d, x.into()), + //Val::Error(e) => Val::Error(e.into()) + //}, + //})) + //.collect::>() + //.into()) + //} +//} /// DSL-specific error codes. #[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError { #[error("parse failed: not implemented")] @@ -283,20 +363,12 @@ pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s> } start = i; length = 1; - if is_exp_start(c) { - value = Exp(1, Cst(CstIter(CstConstIter(str_range(source, i, i+1))))); - } else if is_str_start(c) { - value = Str(str_range(source, i, i+1)); - } else if is_sym_start(c) { - value = Sym(str_range(source, i, i+1)); - } else if is_key_start(c) { - value = Key(str_range(source, i, i+1)); - } else if is_digit(c) { - value = match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) }; - } else { - value = Error(Unexpected(c)); - break - } + value = if is_exp_start(c) { Exp(1, str_range(source, i, i+1)) } + else if is_str_start(c) { Str(str_range(source, i, i+1)) } + else if is_sym_start(c) { Sym(str_range(source, i, i+1)) } + else if is_key_start(c) { Key(str_range(source, i, i+1)) } + else if is_digit(c) { match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) } } + else { value = Error(Unexpected(c)); break } } else if matches!(value, Str(_)) { if is_str_end(c) { break @@ -323,13 +395,13 @@ pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s> } } else if let Exp(depth, exp) = value { if depth == 0 { - value = Exp(0, Cst(CstIter(CstConstIter(str_range(source, start, start + length))))); + value = Exp(0, str_range(source, start, start + length)); break } length += 1; value = Exp( if c == ')' { depth-1 } else if c == '(' { depth+1 } else { depth }, - Cst(CstIter(CstConstIter(str_range(source, start, start + length)))) + str_range(source, start, start + length) ); } else if let Num(m) = value { if is_num_end(c) { @@ -378,22 +450,6 @@ pub const fn to_digit (c: char) -> Result { _ => return Err(Unexpected(c)) }) } -/// `State` + [Dsl] -> `Self`. -pub trait FromDsl: Sized { - fn try_from_dsl (state: &State, dsl: &impl Dsl) -> Perhaps; - fn from_dsl (state: &State, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { - match Self::try_from_dsl(state, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } -} -/// `self` + `Options` -> [Dsl] -pub trait IntoDsl { /*TODO*/ } -/// `self` + [Dsl] -> `Item` -pub trait DslInto { - fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps; - fn dsl_into (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { - match Self::try_dsl_into(self, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } -} -/// `self` + `Item` -> [Dsl] -pub trait DslFrom { /*TODO*/ } /// Implement type conversions. macro_rules! from(($($Struct:ty { $( @@ -421,3 +477,53 @@ macro_rules! from(($($Struct:ty { $( //<'s> (iter: CstIter<'s>) Ast(iter.map(|x|x.value.into()).collect::>().into()); // (token: Token) Ast(VecDeque::from([dsl_val(token.val())]).into()); } //} + +/// `T` + [Dsl] -> `Self`. +pub trait FromDsl: Sized { + fn from_dsl (state: &T, dsl: &impl Dsl) -> Perhaps; + fn from_dsl_or (state: &T, dsl: &impl Dsl, err: Box) -> Usually { + Self::from_dsl(state, dsl)?.ok_or(err) + } + fn from_dsl_or_else (state: &T, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + Self::from_dsl(state, dsl)?.ok_or_else(err) + } +} + +impl, U> DslInto for U { + fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { + T::from_dsl(self, dsl) + } +} + +/// `self` + [Dsl] -> `T` +pub trait DslInto { + fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into_or (&self, dsl: &impl Dsl, err: Box) -> Usually { + self.dsl_into(dsl)?.ok_or(err) + } + fn dsl_into_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + self.dsl_into(dsl)?.ok_or_else(err) + } +} + +/// `self` + `T` + -> [Dsl] +pub trait IntoDsl { + fn into_dsl (&self, state: &T) -> Perhaps; + fn into_dsl_or (&self, state: &T, err: Box) -> Usually { + self.into_dsl(state)?.ok_or(err) + } + fn into_dsl_or_else (&self, state: &T, err: impl Fn()->Box) -> Usually { + self.into_dsl(state)?.ok_or_else(err) + } +} + +/// `self` + `T` -> [Dsl] +pub trait DslFrom { + fn dsl_from (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_from_or (&self, dsl: &impl Dsl, err: Box) -> Usually { + self.dsl_from(dsl)?.ok_or(err) + } + fn dsl_from_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + self.dsl_from(dsl)?.ok_or_else(err) + } +} diff --git a/dsl/src/dsl_test.rs b/dsl/src/dsl_test.rs new file mode 100644 index 0000000..e69de29 diff --git a/dsl/src/test.rs b/dsl/src/test.rs deleted file mode 100644 index dfb37f4..0000000 --- a/dsl/src/test.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::*; -use proptest::prelude::*; - -#[test] fn test_iters () { - let mut iter = crate::CstIter::new(&":foo :bar"); - let _ = iter.next(); -} - -#[test] const fn test_const_iters () { - let iter = crate::CstConstIter::new(&":foo :bar"); - let _ = iter.next(); -} - -#[test] fn test_num () { - let _digit = Token::to_digit('0'); - let _digit = Token::to_digit('x'); - let _number = Token::to_number(&"123"); - let _number = Token::to_number(&"12asdf3"); -} - //proptest! { - //#[test] fn proptest_source_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::CstIter::new(&source); - ////let _ = iter.next() - //} - //#[test] fn proptest_token_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::TokenIter::new(&source); - ////let _ = iter.next(); - //} - //} - -//#[cfg(test)] mod test_token_prop { - //use crate::{Cst, CstMeta, Value::*}; - //use proptest::prelude::*; - //proptest! { - //#[test] fn test_token_prop ( - //source in "\\PC*", - //start in usize::MIN..usize::MAX, - //length in usize::MIN..usize::MAX, - //) { - //let token = Cst(Nil, CstMeta { source: &source, start, length }); - //let _ = token.slice(); - //} - //} -//} - -#[test] fn test_token () -> Result<(), Box> { - use crate::Val::*; - let source = ":f00"; - let mut token = CstToken::new(source, 0, 1, Sym(":")); - token = token.grow_sym(); - assert_eq!(token, CstToken::new(source, 0, 2, Sym(":f"))); - token = token.grow_sym(); - assert_eq!(token, CstToken::new(source, 0, 3, Sym(":f0"))); - token = token.grow_sym(); - assert_eq!(token, CstToken::new(source, 0, 4, Sym(":f00"))); - - assert_eq!(None, CstIter::new("").next()); - assert_eq!(None, CstIter::new(" \n \r \t ").next()); - - assert_eq!(Err(Unexpected('a')), CstIter::new(" 9a ").next().unwrap().value()); - - assert_eq!(Num(7), CstIter::new("7").next().unwrap().value()); - assert_eq!(Num(100), CstIter::new(" 100 ").next().unwrap().value()); - - assert_eq!(Sym(":123foo"), CstIter::new(" :123foo ").next().unwrap().value()); - assert_eq!(Sym("@bar456"), CstIter::new(" \r\r\n\n@bar456\t\t\t").next().unwrap().value()); - - assert_eq!(Key("foo123"), CstIter::new("foo123").next().unwrap().value()); - assert_eq!(Key("foo/bar"), CstIter::new("foo/bar").next().unwrap().value()); - - //assert_eq!(Str("foo/bar"), CstIter::new("\"foo/bar\"").next().unwrap().value()); - //assert_eq!(Str("foo/bar"), CstIter::new(" \"foo/bar\" ").next().unwrap().value()); - - Ok(()) -} - -//#[cfg(test)] #[test] fn test_examples () -> Result<(), DslErr> { - //// Let's pretend to render some view. - //let source = include_str!("../../tek/src/view_arranger.edn"); - //// The token iterator allows you to get the tokens represented by the source text. - //let mut view = TokenIter(source); - //// The token iterator wraps a const token+source iterator. - //assert_eq!(view.0.0, source); - //let mut expr = view.peek(); - //assert_eq!(view.0.0, source); - //assert_eq!(expr, Some(Token { - //source, start: 0, length: source.len() - 1, value: Exp(0, CstIter::new(&source[1..])) - //})); - ////panic!("{view:?}"); - ////panic!("{:#?}", expr); - ////for example in [ - ////include_str!("../../tui/examples/edn01.edn"), - ////include_str!("../../tui/examples/edn02.edn"), - ////] { - //////let items = Dsl::read_all(example)?; - //////panic!("{layout:?}"); - //////let content = >::from(&layout); - ////} - //Ok(()) -//} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 5032319..5403b66 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -7,7 +7,7 @@ use crate::*; /// /// 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 binding's value is returned. +/// 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 @@ -71,7 +71,7 @@ impl> + Clone> EventMap { } /// Evaluate the active layers for a given state, /// returning the command to be executed, if any. - pub fn handle (&self, state: &mut S, event: Ast) -> Perhaps where + pub fn handle (&self, state: &mut S, event: &E) -> Perhaps where S: DslInto + DslInto, O: Command { diff --git a/output/src/lib.rs b/output/src/lib.rs index 45abdda..334c418 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -3,6 +3,9 @@ #![feature(impl_trait_in_assoc_type)] pub(crate) use std::marker::PhantomData; +pub(crate) use std::fmt::{Debug, Display}; +pub(crate) use std::ops::{Add, Sub, Mul, Div}; +pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}}; pub(crate) use tengri_core::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; diff --git a/output/src/ops.rs b/output/src/ops.rs index 976ab51..846c86c 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -48,32 +48,21 @@ use crate::*; use Direction::*; +use std::marker::PhantomData; +use std::sync::{Arc, RwLock}; -mod map; pub use self::map::*; -mod memo; pub use self::memo::*; -mod stack; pub use self::stack::*; -mod thunk; pub use self::thunk::*; - -/// Renders multiple things on top of each other, -#[macro_export] macro_rules! lay { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }} -} - +/// Stack things on top of each other, +#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => + {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }}); /// Stack southward. -#[macro_export] macro_rules! col { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}; -} - +#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => + {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}); /// Stack northward. -#[macro_export] macro_rules! col_up { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }} -} - +#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => + {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }}); /// Stack eastward. -#[macro_export] macro_rules! row { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}; -} - +#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => + {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}); /// Show an item only when a condition is true. pub struct When(pub bool, pub A); impl When { @@ -408,7 +397,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { impl FromDsl for $Struct$(<$($A),+>)? { - fn try_from_dsl ( + fn from_dsl ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { todo!() @@ -423,7 +412,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { impl FromDsl for $Struct$(<$($A),+>)? { - fn try_from_dsl ( + fn from_dsl ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { todo!() @@ -686,3 +675,353 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ //_ => None //}) } + +/// Lazily-evaluated [Render]able. +pub struct Thunk, F: Fn()->T>( + PhantomData, + F +); +impl, F: Fn()->T> Thunk { + pub const fn new (thunk: F) -> Self { + Self(PhantomData, thunk) + } +} +impl, F: Fn()->T> Content for Thunk { + fn content (&self) -> impl Render { (self.1)() } +} + +pub struct ThunkBox( + PhantomData, + BoxBox>>, +); +impl ThunkBox { + pub const fn new (thunk: BoxBox>>) -> Self { + Self(PhantomData, thunk) + } +} +impl Content for ThunkBox { + fn content (&self) -> impl Render { (&self.1)() } +} +impl FromBox>>> for ThunkBox { + fn from (f: BoxBox>>) -> Self { + Self(PhantomData, f) + } +} +//impl<'a, E: Output, F: Fn()->Box + 'a> + 'a> From for ThunkBox<'a, E> { + //fn from (f: F) -> Self { + //Self(Default::default(), Box::new(f)) + //} +//} + +pub struct ThunkRender(PhantomData, F); +impl ThunkRender { + pub fn new (render: F) -> Self { Self(PhantomData, render) } +} +impl Content for ThunkRender { + fn render (&self, to: &mut E) { (self.1)(to) } +} + +pub struct ThunkLayout< + E: Output, + F1: Fn(E::Area)->E::Area, + F2: Fn(&mut E) +>( + PhantomData, + F1, + F2 +); +implE::Area, F2: Fn(&mut E)> ThunkLayout { + pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) } +} +impl Content for ThunkLayout +where + E: Output, + F1: Fn(E::Area)->E::Area, + F2: Fn(&mut E) +{ + fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) } + fn render (&self, to: &mut E) { (self.2)(to) } +} + +#[derive(Debug, Default)] pub struct Memo { + pub value: T, + pub view: Arc> +} + +impl Memo { + pub fn new (value: T, view: U) -> Self { + Self { value, view: Arc::new(view.into()) } + } + pub fn update ( + &mut self, + newval: T, + render: impl Fn(&mut U, &T, &T)->R + ) -> Option { + if newval != self.value { + let result = render(&mut*self.view.write().unwrap(), &newval, &self.value); + self.value = newval; + return Some(result); + } + None + } +} + +/// Clear a pre-allocated buffer, then write into it. +#[macro_export] macro_rules! rewrite { + ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } +} + +pub struct Stack { + __: PhantomData, + direction: Direction, + callback: F +} +impl Stack { + pub fn new (direction: Direction, callback: F) -> Self { + Self { direction, callback, __: Default::default(), } + } + pub fn north (callback: F) -> Self { + Self::new(North, callback) + } + pub fn south (callback: F) -> Self { + Self::new(South, callback) + } + pub fn east (callback: F) -> Self { + Self::new(East, callback) + } + pub fn west (callback: F) -> Self { + Self::new(West, callback) + } +} +impl)->())->()> Content for Stack { + fn layout (&self, to: E::Area) -> E::Area { + let mut x = to.x(); + let mut y = to.y(); + let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); + let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); + (self.callback)(&mut move |component: &dyn Render|{ + let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); + match self.direction { + South => { + y = y.plus(h); + h_used = h_used.plus(h); + h_remaining = h_remaining.minus(h); + w_used = w_used.max(w); + }, + East => { + x = x.plus(w); + w_used = w_used.plus(w); + w_remaining = w_remaining.minus(w); + h_used = h_used.max(h); + }, + North | West => { + todo!() + }, + _ => unreachable!(), + } + }); + match self.direction { + North | West => { + todo!() + }, + South | East => { + [to.x(), to.y(), w_used.into(), h_used.into()].into() + }, + _ => unreachable!(), + } + } + fn render (&self, to: &mut E) { + let mut x = to.x(); + let mut y = to.y(); + let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); + let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); + (self.callback)(&mut move |component: &dyn Render|{ + let layout = component.layout([x, y, w_remaining, h_remaining].into()); + match self.direction { + South => { + y = y.plus(layout.h()); + h_remaining = h_remaining.minus(layout.h()); + h_used = h_used.plus(layout.h()); + to.place(layout, component); + }, + East => { + x = x.plus(layout.w()); + w_remaining = w_remaining.minus(layout.w()); + w_used = w_used.plus(layout.h()); + to.place(layout, component); + }, + North | West => { + todo!() + }, + _ => unreachable!() + } + }); + } +} + +/*Stack::down(|add|{ + let mut i = 0; + for (_, name) in self.dirs.iter() { + if i >= self.scroll { + add(&Tui::bold(i == self.index, name.as_str()))?; + } + i += 1; + } + for (_, name) in self.files.iter() { + if i >= self.scroll { + add(&Tui::bold(i == self.index, name.as_str()))?; + } + i += 1; + } + add(&format!("{}/{i}", self.index))?; + Ok(()) +}));*/ + +/// Renders items from an iterator. +pub struct Map +where + I: Iterator + Send + Sync, + F: Fn() -> I + Send + Sync, +{ + __: PhantomData<(E, B)>, + /// Function that returns iterator over stacked components + get_iter: F, + /// Function that returns each stacked component + get_item: G, +} + +impl<'a, E, A, B, I, F, G> Map where + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a, +{ + pub const fn new (get_iter: F, get_item: G) -> Self { + Self { + __: PhantomData, + get_iter, + get_item + } + } +} + +impl<'a, E, A, B, I, F> Map>>>, I, F, fn(A, usize)->B> +where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a +{ + pub const fn east ( + size: E::Unit, + get_iter: F, + get_item: impl Fn(A, usize)->B + Send + Sync + ) -> Map< + E, A, + Push>>, + I, F, + impl Fn(A, usize)->Push>> + Send + Sync + > { + Map { + __: PhantomData, + get_iter, + get_item: move |item: A, index: usize|{ + // FIXME: multiply + let mut push: E::Unit = E::Unit::from(0u16); + for _ in 0..index { + push = push + size; + } + Push::x(push, Align::w(Fixed::x(size, get_item(item, index)))) + } + } + } + + pub const fn south ( + size: E::Unit, + get_iter: F, + get_item: impl Fn(A, usize)->B + Send + Sync + ) -> Map< + E, A, + Push>>, + I, F, + impl Fn(A, usize)->Push>> + Send + Sync + > where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a + { + Map { + __: PhantomData, + get_iter, + get_item: move |item: A, index: usize|{ + // FIXME: multiply + let mut push: E::Unit = E::Unit::from(0u16); + for _ in 0..index { + push = push + size; + } + Push::y(push, Align::n(Fixed::y(size, get_item(item, index)))) + } + } + } +} + +impl<'a, E, A, B, I, F, G> Content for Map where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a, + G: Fn(A, usize)->B + Send + Sync +{ + fn layout (&self, area: E::Area) -> E::Area { + let Self { get_iter, get_item, .. } = self; + let mut index = 0; + let [mut min_x, mut min_y] = area.center(); + let [mut max_x, mut max_y] = area.center(); + for item in get_iter() { + let [x,y,w,h] = get_item(item, index).layout(area).xywh(); + min_x = min_x.min(x.into()); + min_y = min_y.min(y.into()); + max_x = max_x.max((x + w).into()); + max_y = max_y.max((y + h).into()); + index += 1; + } + let w = max_x - min_x; + let h = max_y - min_y; + //[min_x.into(), min_y.into(), w.into(), h.into()].into() + area.center_xy([w.into(), h.into()].into()).into() + } + fn render (&self, to: &mut E) { + let Self { get_iter, get_item, .. } = self; + let mut index = 0; + let area = Content::layout(self, to.area()); + for item in get_iter() { + let item = get_item(item, index); + //to.place(area.into(), &item); + to.place(item.layout(area), &item); + index += 1; + } + } +} + +#[inline] pub fn map_south( + item_offset: O::Unit, + item_height: O::Unit, + item: impl Content +) -> impl Content { + Push::y(item_offset, Fixed::y(item_height, Fill::x(item))) +} + +#[inline] pub fn map_south_west( + item_offset: O::Unit, + item_height: O::Unit, + item: impl Content +) -> impl Content { + Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item)))) +} + +#[inline] pub fn map_east( + item_offset: O::Unit, + item_width: O::Unit, + item: impl Content +) -> impl Content { + Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item)))) +} diff --git a/output/src/ops/_collect.rs b/output/src/ops/_collect.rs deleted file mode 100644 index 5559afe..0000000 --- a/output/src/ops/_collect.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Groupings of elements. -use crate::*; - -/// A function or closure that emits renderables. -pub trait Collector: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)) {} - -/// Any function or closure that emits renderables for the given engine matches [CollectCallback]. -impl Collector for F -where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)) {} - -pub trait Render { - fn area (&self, to: E::Area) -> E::Area; - fn render (&self, to: &mut E::Output); -} - -impl> Render for C { - fn area (&self, to: E::Area) -> E::Area { - Content::area(self, to) - } - fn render (&self, to: &mut E::Output) { - Content::render(self, to) - } -} diff --git a/output/src/ops/_reduce.rs b/output/src/ops/_reduce.rs deleted file mode 100644 index dfcc00e..0000000 --- a/output/src/ops/_reduce.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::*; - -pub struct Reduce(pub PhantomData, pub F, pub G) where - A: Send + Sync, B: Send + Sync, - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, - G: Fn(A, B, usize)->A + Send + Sync; - -impl Reduce where - A: Send + Sync, B: Send + Sync, - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, - G: Fn(A, B, usize)->A + Send + Sync -{ - pub const fn new (f: F, g: G) -> Self { Self(Default::default(), f, g) } -} - -impl Content for Reduce where - A: Send + Sync, B: Send + Sync, - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, - G: Fn(A, B, usize)->A + Send + Sync -{ - fn content (&self) -> impl Render { - } -} - -/* - - //pub fn reduce (iterator: I, callback: F) -> Reduce where - //E: Output, - //I: Iterator + Send + Sync, - //R: Render, - //F: Fn(R, T, usize) -> R + Send + Sync - //{ - //Reduce(Default::default(), iterator, callback) - //} -pub struct Reduce(PhantomData<(E, R)>, I, F) where - E: Output, - I: Iterator + Send + Sync, - R: Render, - F: Fn(R, T, usize) -> R + Send + Sync; -impl Content for Reduce where - E: Output, - I: Iterator + Send + Sync, - R: Render, - F: Fn(R, T, usize) -> R + Send + Sync -{ - fn render (&self, to: &mut E) { - todo!() - } -} -*/ - -//macro_rules! define_ops { - //($Trait:ident<$E:ident:$Output:path> { $( - //$(#[$attr:meta $($attr_args:tt)*])* - //( - //$fn:ident - //$(<$($G:ident$(:$Gen:path)?, )+>)? - //$Op:ident - //($($arg:ident:$Arg:ty),*) - //) - //)* }) => { - //impl<$E: $Output> $Trait for E {} - //pub trait $Trait<$E: $Output> { - //$( - //$(#[$attr $($attr_args)*])* - //fn $fn $(<$($G),+>)? - //($($arg:$Arg),*)-> $Op<$($(, $G)+)?> - //$(where $($G: $($Gen + Send + Sync)?),+)? - //{ $Op($($arg),*) } - //)* - //} - //} -//} - -//define_ops! { - //Layout { - //(when ,> - //When(cond: bool, item: A)) - ///// When `cond` is `true`, render `a`, otherwise render `b`. - //(either , B: Render,> - //Either(cond: bool, a: A, b: B)) - ///// If `opt` is `Some(T)` renders `cb(t)`, otherwise nothing. - //(opt B, B: Render,> - //Opt(option: Option, cb: F)) - ///// Maps items of iterator through callback. - //(map , I: Iterator, F: Fn() -> I, G: Fn(A, usize)->B,> - //Map(get_iterator: F, callback: G)) - //} -//} - diff --git a/output/src/ops/map.rs b/output/src/ops/map.rs deleted file mode 100644 index d124569..0000000 --- a/output/src/ops/map.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::*; - -/// Renders items from an iterator. -pub struct Map -where - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, -{ - __: PhantomData<(E, B)>, - /// Function that returns iterator over stacked components - get_iter: F, - /// Function that returns each stacked component - get_item: G, -} - -impl<'a, E, A, B, I, F, G> Map where - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a, -{ - pub const fn new (get_iter: F, get_item: G) -> Self { - Self { - __: PhantomData, - get_iter, - get_item - } - } -} - -impl<'a, E, A, B, I, F> Map>>>, I, F, fn(A, usize)->B> -where - E: Output, - B: Render, - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a -{ - pub const fn east ( - size: E::Unit, - get_iter: F, - get_item: impl Fn(A, usize)->B + Send + Sync - ) -> Map< - E, A, - Push>>, - I, F, - impl Fn(A, usize)->Push>> + Send + Sync - > { - Map { - __: PhantomData, - get_iter, - get_item: move |item: A, index: usize|{ - // FIXME: multiply - let mut push: E::Unit = E::Unit::from(0u16); - for _ in 0..index { - push = push + size; - } - Push::x(push, Align::w(Fixed::x(size, get_item(item, index)))) - } - } - } - - pub const fn south ( - size: E::Unit, - get_iter: F, - get_item: impl Fn(A, usize)->B + Send + Sync - ) -> Map< - E, A, - Push>>, - I, F, - impl Fn(A, usize)->Push>> + Send + Sync - > where - E: Output, - B: Render, - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a - { - Map { - __: PhantomData, - get_iter, - get_item: move |item: A, index: usize|{ - // FIXME: multiply - let mut push: E::Unit = E::Unit::from(0u16); - for _ in 0..index { - push = push + size; - } - Push::y(push, Align::n(Fixed::y(size, get_item(item, index)))) - } - } - } -} - -impl<'a, E, A, B, I, F, G> Content for Map where - E: Output, - B: Render, - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a, - G: Fn(A, usize)->B + Send + Sync -{ - fn layout (&self, area: E::Area) -> E::Area { - let Self { get_iter, get_item, .. } = self; - let mut index = 0; - let [mut min_x, mut min_y] = area.center(); - let [mut max_x, mut max_y] = area.center(); - for item in get_iter() { - let [x,y,w,h] = get_item(item, index).layout(area).xywh(); - min_x = min_x.min(x.into()); - min_y = min_y.min(y.into()); - max_x = max_x.max((x + w).into()); - max_y = max_y.max((y + h).into()); - index += 1; - } - let w = max_x - min_x; - let h = max_y - min_y; - //[min_x.into(), min_y.into(), w.into(), h.into()].into() - area.center_xy([w.into(), h.into()].into()).into() - } - fn render (&self, to: &mut E) { - let Self { get_iter, get_item, .. } = self; - let mut index = 0; - let area = Content::layout(self, to.area()); - for item in get_iter() { - let item = get_item(item, index); - //to.place(area.into(), &item); - to.place(item.layout(area), &item); - index += 1; - } - } -} - -#[inline] pub fn map_south( - item_offset: O::Unit, - item_height: O::Unit, - item: impl Content -) -> impl Content { - Push::y(item_offset, Fixed::y(item_height, Fill::x(item))) -} - -#[inline] pub fn map_south_west( - item_offset: O::Unit, - item_height: O::Unit, - item: impl Content -) -> impl Content { - Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item)))) -} - -#[inline] pub fn map_east( - item_offset: O::Unit, - item_width: O::Unit, - item: impl Content -) -> impl Content { - Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item)))) -} diff --git a/output/src/ops/memo.rs b/output/src/ops/memo.rs deleted file mode 100644 index 8168f2d..0000000 --- a/output/src/ops/memo.rs +++ /dev/null @@ -1,30 +0,0 @@ -//use crate::*; -use std::sync::{Arc, RwLock}; - -#[derive(Debug, Default)] pub struct Memo { - pub value: T, - pub view: Arc> -} - -impl Memo { - pub fn new (value: T, view: U) -> Self { - Self { value, view: Arc::new(view.into()) } - } - pub fn update ( - &mut self, - newval: T, - render: impl Fn(&mut U, &T, &T)->R - ) -> Option { - if newval != self.value { - let result = render(&mut*self.view.write().unwrap(), &newval, &self.value); - self.value = newval; - return Some(result); - } - None - } -} - -/// Clear a pre-allocated buffer, then write into it. -#[macro_export] macro_rules! rewrite { - ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } -} diff --git a/output/src/ops/stack.rs b/output/src/ops/stack.rs deleted file mode 100644 index 59930ce..0000000 --- a/output/src/ops/stack.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::*; -use Direction::*; - -pub struct Stack { - __: PhantomData, - direction: Direction, - callback: F -} -impl Stack { - pub fn new (direction: Direction, callback: F) -> Self { - Self { direction, callback, __: Default::default(), } - } - pub fn north (callback: F) -> Self { - Self::new(North, callback) - } - pub fn south (callback: F) -> Self { - Self::new(South, callback) - } - pub fn east (callback: F) -> Self { - Self::new(East, callback) - } - pub fn west (callback: F) -> Self { - Self::new(West, callback) - } -} -impl)->())->()> Content for Stack { - fn layout (&self, to: E::Area) -> E::Area { - let mut x = to.x(); - let mut y = to.y(); - let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); - let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); - (self.callback)(&mut move |component: &dyn Render|{ - let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); - match self.direction { - South => { - y = y.plus(h); - h_used = h_used.plus(h); - h_remaining = h_remaining.minus(h); - w_used = w_used.max(w); - }, - East => { - x = x.plus(w); - w_used = w_used.plus(w); - w_remaining = w_remaining.minus(w); - h_used = h_used.max(h); - }, - North | West => { - todo!() - }, - _ => unreachable!(), - } - }); - match self.direction { - North | West => { - todo!() - }, - South | East => { - [to.x(), to.y(), w_used.into(), h_used.into()].into() - }, - _ => unreachable!(), - } - } - fn render (&self, to: &mut E) { - let mut x = to.x(); - let mut y = to.y(); - let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); - let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); - (self.callback)(&mut move |component: &dyn Render|{ - let layout = component.layout([x, y, w_remaining, h_remaining].into()); - match self.direction { - South => { - y = y.plus(layout.h()); - h_remaining = h_remaining.minus(layout.h()); - h_used = h_used.plus(layout.h()); - to.place(layout, component); - }, - East => { - x = x.plus(layout.w()); - w_remaining = w_remaining.minus(layout.w()); - w_used = w_used.plus(layout.h()); - to.place(layout, component); - }, - North | West => { - todo!() - }, - _ => unreachable!() - } - }); - } -} - -/*Stack::down(|add|{ - let mut i = 0; - for (_, name) in self.dirs.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; - } - for (_, name) in self.files.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; - } - add(&format!("{}/{i}", self.index))?; - Ok(()) -}));*/ - diff --git a/output/src/ops/thunk.rs b/output/src/ops/thunk.rs deleted file mode 100644 index d0e2877..0000000 --- a/output/src/ops/thunk.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::*; -use std::marker::PhantomData; - -/// Lazily-evaluated [Render]able. -pub struct Thunk, F: Fn()->T>( - PhantomData, - F -); -impl, F: Fn()->T> Thunk { - pub const fn new (thunk: F) -> Self { - Self(PhantomData, thunk) - } -} -impl, F: Fn()->T> Content for Thunk { - fn content (&self) -> impl Render { (self.1)() } -} - -pub struct ThunkBox( - PhantomData, - BoxBox>>, -); -impl ThunkBox { - pub const fn new (thunk: BoxBox>>) -> Self { - Self(PhantomData, thunk) - } -} -impl Content for ThunkBox { - fn content (&self) -> impl Render { (&self.1)() } -} -impl FromBox>>> for ThunkBox { - fn from (f: BoxBox>>) -> Self { - Self(PhantomData, f) - } -} -//impl<'a, E: Output, F: Fn()->Box + 'a> + 'a> From for ThunkBox<'a, E> { - //fn from (f: F) -> Self { - //Self(Default::default(), Box::new(f)) - //} -//} - -pub struct ThunkRender(PhantomData, F); -impl ThunkRender { - pub fn new (render: F) -> Self { Self(PhantomData, render) } -} -impl Content for ThunkRender { - fn render (&self, to: &mut E) { (self.1)(to) } -} - -pub struct ThunkLayout< - E: Output, - F1: Fn(E::Area)->E::Area, - F2: Fn(&mut E) ->( - PhantomData, - F1, - F2 -); -implE::Area, F2: Fn(&mut E)> ThunkLayout { - pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) } -} -impl Content for ThunkLayout -where - E: Output, - F1: Fn(E::Area)->E::Area, - F2: Fn(&mut E) -{ - fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) } - fn render (&self, to: &mut E) { (self.2)(to) } -} diff --git a/output/src/space.rs b/output/src/space.rs index 056e12f..3e1298b 100644 --- a/output/src/space.rs +++ b/output/src/space.rs @@ -1,5 +1,274 @@ -mod area; pub use self::area::*; -mod coordinate; pub use self::coordinate::*; -mod direction; pub use self::direction::*; -mod measure; pub use self::measure::*; -mod size; pub use self::size::*; +use crate::*; +use Direction::*; + +/// A cardinal direction. +#[derive(Copy, Clone, PartialEq, Debug)] +#[cfg_attr(test, derive(Arbitrary))] +pub enum Direction { + North, South, East, West, Above, Below +} + +impl Direction { + pub fn split_fixed (self, area: impl Area, a: N) -> ([N;4],[N;4]) { + let [x, y, w, h] = area.xywh(); + match self { + North => ([x, y.plus(h).minus(a), w, a], [x, y, w, h.minus(a)]), + South => ([x, y, w, a], [x, y.plus(a), w, h.minus(a)]), + East => ([x, y, a, h], [x.plus(a), y, w.minus(a), h]), + West => ([x.plus(w).minus(a), y, a, h], [x, y, w.minus(a), h]), + Above | Below => (area.xywh(), area.xywh()) + } + } +} + +/// A linear coordinate. +pub trait Coordinate: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into +{ + fn zero () -> Self { 0.into() } + fn plus (self, other: Self) -> Self; + fn minus (self, other: Self) -> Self { + if self >= other { + self - other + } else { + 0.into() + } + } +} + +impl Coordinate for u16 { + fn plus (self, other: Self) -> Self { + self.saturating_add(other) + } +} + +pub trait Area: From<[N;4]> + Debug + Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N; + fn h (&self) -> N; + fn zero () -> [N;4] { + [N::zero(), N::zero(), N::zero(), N::zero()] + } + fn from_position (pos: impl Size) -> [N;4] { + let [x, y] = pos.wh(); + [x, y, 0.into(), 0.into()] + } + fn from_size (size: impl Size) -> [N;4] { + let [w, h] = size.wh(); + [0.into(), 0.into(), w, h] + } + fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } + fn xy (&self) -> [N;2] { + [self.x(), self.y()] + } + fn wh (&self) -> [N;2] { + [self.w(), self.h()] + } + fn xywh (&self) -> [N;4] { + [self.x(), self.y(), self.w(), self.h()] + } + fn clip_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), self.h().min(h)] + } + fn clip_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), self.w().min(w), self.h()] + } + fn clip (&self, wh: impl Size) -> [N;4] { + [self.x(), self.y(), wh.w(), wh.h()] + } + fn set_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), w, self.h()] + } + fn set_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), h] + } + fn x2 (&self) -> N { + self.x().plus(self.w()) + } + fn y2 (&self) -> N { + self.y().plus(self.h()) + } + fn lrtb (&self) -> [N;4] { + [self.x(), self.x2(), self.y(), self.y2()] + } + fn center (&self) -> [N;2] { + [self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())] + } + fn center_x (&self, n: N) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()] + } + fn center_y (&self, n: N) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n] + } + fn center_xy (&self, [n, m]: [N;2]) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [(x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m] + } + fn centered (&self) -> [N;2] { + [self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())] + } + fn iter_x (&self) -> impl Iterator where N: std::iter::Step { + self.x()..(self.x()+self.w()) + } + fn iter_y (&self) -> impl Iterator where N: std::iter::Step { + self.y()..(self.y()+self.h()) + } +} + +impl Area for (N, N, N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } + fn w (&self) -> N { self.2 } + fn h (&self) -> N { self.3 } +} + +impl Area for [N;4] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } + fn w (&self) -> N { self[2] } + fn h (&self) -> N { self[3] } +} + +pub trait Size: From<[N;2]> + Debug + Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N { self.x() } + fn h (&self) -> N { self.y() } + fn wh (&self) -> [N;2] { [self.x(), self.y()] } + fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] } + fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] } + fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } + fn zero () -> [N;2] { + [N::zero(), N::zero()] + } + fn to_area_pos (&self) -> [N;4] { + let [x, y] = self.wh(); + [x, y, 0.into(), 0.into()] + } + fn to_area_size (&self) -> [N;4] { + let [w, h] = self.wh(); + [0.into(), 0.into(), w, h] + } +} + +impl Size for (N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } +} + +impl Size for [N;2] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } +} + +pub trait HasSize { + fn size (&self) -> &Measure; + fn width (&self) -> usize { + self.size().w() + } + fn height (&self) -> usize { + self.size().h() + } +} + +impl>> HasSize for T { + fn size (&self) -> &Measure { + self.get() + } +} + +/// A widget that tracks its render width and height +#[derive(Default)] +pub struct Measure { + _engine: PhantomData, + pub x: Arc, + pub y: Arc, +} + +// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small +impl Content for Measure { + fn render (&self, to: &mut E) { + self.x.store(to.area().w().into(), Relaxed); + self.y.store(to.area().h().into(), Relaxed); + } +} + +impl Clone for Measure { + fn clone (&self) -> Self { + Self { + _engine: Default::default(), + x: self.x.clone(), + y: self.y.clone(), + } + } +} + +impl std::fmt::Debug for Measure { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("Measure") + .field("width", &self.x) + .field("height", &self.y) + .finish() + } +} + +impl Measure { + pub fn new () -> Self { + Self { + _engine: PhantomData::default(), + x: Arc::new(0.into()), + y: Arc::new(0.into()), + } + } + pub fn set_w (&self, w: impl Into) -> &Self { + self.x.store(w.into(), Relaxed); + self + } + pub fn set_h (&self, h: impl Into) -> &Self { + self.y.store(h.into(), Relaxed); + self + } + pub fn set_wh (&self, w: impl Into, h: impl Into) -> &Self { + self.set_w(w); + self.set_h(h); + self + } + pub fn w (&self) -> usize { + self.x.load(Relaxed) + } + pub fn h (&self) -> usize { + self.y.load(Relaxed) + } + pub fn wh (&self) -> [usize;2] { + [self.w(), self.h()] + } + pub fn format (&self) -> Arc { + format!("{}x{}", self.w(), self.h()).into() + } + pub fn of > (&self, item: T) -> Bsp, T> { + Bsp::b(Fill::xy(self), item) + } +} diff --git a/output/src/space/area.rs b/output/src/space/area.rs deleted file mode 100644 index 4393790..0000000 --- a/output/src/space/area.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::*; -use std::fmt::Debug; - -pub trait Area: From<[N;4]> + Debug + Copy { - fn x (&self) -> N; - fn y (&self) -> N; - fn w (&self) -> N; - fn h (&self) -> N; - fn zero () -> [N;4] { - [N::zero(), N::zero(), N::zero(), N::zero()] - } - fn from_position (pos: impl Size) -> [N;4] { - let [x, y] = pos.wh(); - [x, y, 0.into(), 0.into()] - } - fn from_size (size: impl Size) -> [N;4] { - let [w, h] = size.wh(); - [0.into(), 0.into(), w, h] - } - fn expect_min (&self, w: N, h: N) -> Usually<&Self> { - if self.w() < w || self.h() < h { - Err(format!("min {w}x{h}").into()) - } else { - Ok(self) - } - } - fn xy (&self) -> [N;2] { - [self.x(), self.y()] - } - fn wh (&self) -> [N;2] { - [self.w(), self.h()] - } - fn xywh (&self) -> [N;4] { - [self.x(), self.y(), self.w(), self.h()] - } - fn clip_h (&self, h: N) -> [N;4] { - [self.x(), self.y(), self.w(), self.h().min(h)] - } - fn clip_w (&self, w: N) -> [N;4] { - [self.x(), self.y(), self.w().min(w), self.h()] - } - fn clip (&self, wh: impl Size) -> [N;4] { - [self.x(), self.y(), wh.w(), wh.h()] - } - fn set_w (&self, w: N) -> [N;4] { - [self.x(), self.y(), w, self.h()] - } - fn set_h (&self, h: N) -> [N;4] { - [self.x(), self.y(), self.w(), h] - } - fn x2 (&self) -> N { - self.x().plus(self.w()) - } - fn y2 (&self) -> N { - self.y().plus(self.h()) - } - fn lrtb (&self) -> [N;4] { - [self.x(), self.x2(), self.y(), self.y2()] - } - fn center (&self) -> [N;2] { - [self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())] - } - fn center_x (&self, n: N) -> [N;4] { - let [x, y, w, h] = self.xywh(); - [(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()] - } - fn center_y (&self, n: N) -> [N;4] { - let [x, y, w, h] = self.xywh(); - [x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n] - } - fn center_xy (&self, [n, m]: [N;2]) -> [N;4] { - let [x, y, w, h] = self.xywh(); - [(x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m] - } - fn centered (&self) -> [N;2] { - [self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())] - } - fn iter_x (&self) -> impl Iterator where N: std::iter::Step { - self.x()..(self.x()+self.w()) - } - fn iter_y (&self) -> impl Iterator where N: std::iter::Step { - self.y()..(self.y()+self.h()) - } -} - -impl Area for (N, N, N, N) { - fn x (&self) -> N { self.0 } - fn y (&self) -> N { self.1 } - fn w (&self) -> N { self.2 } - fn h (&self) -> N { self.3 } -} - -impl Area for [N;4] { - fn x (&self) -> N { self[0] } - fn y (&self) -> N { self[1] } - fn w (&self) -> N { self[2] } - fn h (&self) -> N { self[3] } -} diff --git a/output/src/space/coordinate.rs b/output/src/space/coordinate.rs deleted file mode 100644 index 262be9e..0000000 --- a/output/src/space/coordinate.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::fmt::{Debug, Display}; -use std::ops::{Add, Sub, Mul, Div}; - -/// A linear coordinate. -pub trait Coordinate: Send + Sync + Copy - + Add - + Sub - + Mul - + Div - + Ord + PartialEq + Eq - + Debug + Display + Default - + From + Into - + Into - + Into -{ - fn zero () -> Self { 0.into() } - fn plus (self, other: Self) -> Self; - fn minus (self, other: Self) -> Self { - if self >= other { - self - other - } else { - 0.into() - } - } -} - -impl Coordinate for u16 { - fn plus (self, other: Self) -> Self { - self.saturating_add(other) - } -} diff --git a/output/src/space/direction.rs b/output/src/space/direction.rs deleted file mode 100644 index ed9df46..0000000 --- a/output/src/space/direction.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::*; -use crate::Direction::*; - -/// A cardinal direction. -#[derive(Copy, Clone, PartialEq, Debug)] -#[cfg_attr(test, derive(Arbitrary))] -pub enum Direction { - North, South, East, West, Above, Below -} - -impl Direction { - pub fn split_fixed (self, area: impl Area, a: N) -> ([N;4],[N;4]) { - let [x, y, w, h] = area.xywh(); - match self { - North => ([x, y.plus(h).minus(a), w, a], [x, y, w, h.minus(a)]), - South => ([x, y, w, a], [x, y.plus(a), w, h.minus(a)]), - East => ([x, y, a, h], [x.plus(a), y, w.minus(a), h]), - West => ([x.plus(w).minus(a), y, a, h], [x, y, w.minus(a), h]), - Above | Below => (area.xywh(), area.xywh()) - } - } -} diff --git a/output/src/space/measure.rs b/output/src/space/measure.rs deleted file mode 100644 index 9ed5c2c..0000000 --- a/output/src/space/measure.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::*; -use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}}; - -pub trait HasSize { - fn size (&self) -> &Measure; - fn width (&self) -> usize { - self.size().w() - } - fn height (&self) -> usize { - self.size().h() - } -} - -impl>> HasSize for T { - fn size (&self) -> &Measure { - self.get() - } -} - -/// A widget that tracks its render width and height -#[derive(Default)] -pub struct Measure { - _engine: PhantomData, - pub x: Arc, - pub y: Arc, -} - -// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small -impl Content for Measure { - fn render (&self, to: &mut E) { - self.x.store(to.area().w().into(), Relaxed); - self.y.store(to.area().h().into(), Relaxed); - } -} - -impl Clone for Measure { - fn clone (&self) -> Self { - Self { - _engine: Default::default(), - x: self.x.clone(), - y: self.y.clone(), - } - } -} - -impl std::fmt::Debug for Measure { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("Measure") - .field("width", &self.x) - .field("height", &self.y) - .finish() - } -} - -impl Measure { - pub fn new () -> Self { - Self { - _engine: PhantomData::default(), - x: Arc::new(0.into()), - y: Arc::new(0.into()), - } - } - pub fn set_w (&self, w: impl Into) -> &Self { - self.x.store(w.into(), Relaxed); - self - } - pub fn set_h (&self, h: impl Into) -> &Self { - self.y.store(h.into(), Relaxed); - self - } - pub fn set_wh (&self, w: impl Into, h: impl Into) -> &Self { - self.set_w(w); - self.set_h(h); - self - } - pub fn w (&self) -> usize { - self.x.load(Relaxed) - } - pub fn h (&self) -> usize { - self.y.load(Relaxed) - } - pub fn wh (&self) -> [usize;2] { - [self.w(), self.h()] - } - pub fn format (&self) -> Arc { - format!("{}x{}", self.w(), self.h()).into() - } - pub fn of > (&self, item: T) -> Bsp, T> { - Bsp::b(Fill::xy(self), item) - } -} diff --git a/output/src/space/size.rs b/output/src/space/size.rs deleted file mode 100644 index f9e2d19..0000000 --- a/output/src/space/size.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::*; -use std::fmt::Debug; - -pub trait Size: From<[N;2]> + Debug + Copy { - fn x (&self) -> N; - fn y (&self) -> N; - fn w (&self) -> N { self.x() } - fn h (&self) -> N { self.y() } - fn wh (&self) -> [N;2] { [self.x(), self.y()] } - fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] } - fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] } - fn expect_min (&self, w: N, h: N) -> Usually<&Self> { - if self.w() < w || self.h() < h { - Err(format!("min {w}x{h}").into()) - } else { - Ok(self) - } - } - fn zero () -> [N;2] { - [N::zero(), N::zero()] - } - fn to_area_pos (&self) -> [N;4] { - let [x, y] = self.wh(); - [x, y, 0.into(), 0.into()] - } - fn to_area_size (&self) -> [N;4] { - let [w, h] = self.wh(); - [0.into(), 0.into(), w, h] - } -} - -impl Size for (N, N) { - fn x (&self) -> N { self.0 } - fn y (&self) -> N { self.1 } -} - -impl Size for [N;2] { - fn x (&self) -> N { self[0] } - fn y (&self) -> N { self[1] } -} diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 36b25d5..d0db6e3 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -150,10 +150,9 @@ impl ToTokens for CommandDef { } /// Generated by [tengri_proc::command]. impl ::tengri::dsl::FromDsl<#state> for #command_enum { - fn try_from_dsl ( - state: &#state, - value: &impl ::tengri::dsl::Dsl - ) -> Perhaps { + fn from_dsl (state: &#state, value: &impl ::tengri::dsl::Dsl) + -> Perhaps + { use ::tengri::dsl::Val::*; todo!()//Ok(match token { #(#matchers)* _ => None }) } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 3514696..cdfb2af 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -80,8 +80,8 @@ impl ExposeImpl { write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. impl ::tengri::dsl::FromDsl<#state> for #t { - fn try_from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { - Ok(Some(match dsl.dsl() { + fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { + Ok(Some(match dsl.val() { #arms _ => { return Ok(None) } })) diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 5e11c0f..312bdd9 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -50,7 +50,7 @@ impl ToTokens for ViewDef { impl ViewDef { fn generated (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let Self(ViewMeta { output }, ViewImpl { block, .. }) = self; let self_ty = &block.self_ty; let builtins = self.builtins(); let exposed = self.exposed(); @@ -63,7 +63,7 @@ impl ViewDef { impl<'state> ::tengri::dsl::DslInto< Box + 'state> > for #self_ty { - fn try_dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) + fn dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) -> Perhaps + 'state>> { Ok(match dsl.val() { #builtins #exposed _ => return Ok(None) }) @@ -74,7 +74,7 @@ impl ViewDef { /// Expressions are handled by built-in functions /// that operate over constants and symbols. fn builtins (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let Self(ViewMeta { output }, ViewImpl { .. }) = self; let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { ::tengri::dsl::Val::Exp(_, expr) => return Ok(Some( #builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))? @@ -85,7 +85,7 @@ impl ViewDef { } /// Symbols are handled by user-taked functions that take no parameters but `&self`. fn exposed (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let Self(ViewMeta { .. }, ViewImpl { exposed, .. }) = self; let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { #key => return Ok(Some(self.#value().boxed())), })); diff --git a/tui/src/tui_engine.rs b/tui/src/tui_engine.rs index b8d60f4..9cd527d 100644 --- a/tui/src/tui_engine.rs +++ b/tui/src/tui_engine.rs @@ -3,7 +3,6 @@ use std::time::Duration; mod tui_buffer; pub use self::tui_buffer::*; mod tui_input; pub use self::tui_input::*; -mod tui_keys; pub use self::tui_keys::*; mod tui_output; pub use self::tui_output::*; mod tui_perf; pub use self::tui_perf::*; @@ -72,8 +71,7 @@ pub trait TuiRun + Handle + 'static> { fn run (&self, state: &Arc>) -> Usually<()>; } -impl + Handle + Send + Sync + 'static> -TuiRun for Arc> { +impl + Handle + Send + Sync + 'static> TuiRun for Arc> { fn run (&self, state: &Arc>) -> Usually<()> { let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100)); self.write().unwrap().setup()?; diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 9ee6be6..1d89f97 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -1,24 +1,39 @@ use crate::*; use std::time::Duration; use std::thread::{spawn, JoinHandle}; -use crossterm::event::{poll, read}; +use crossterm::event::{Event, poll, read}; #[derive(Debug, Clone)] -pub struct TuiIn( +pub struct TuiIn { /// Exit flag - pub Arc, + pub exited: Arc, /// Input event - pub crossterm::event::Event, -); - -impl Input for TuiIn { - type Event = crossterm::event::Event; - type Handled = bool; - fn event (&self) -> &crossterm::event::Event { &self.1 } - fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) } - fn done (&self) { self.0.store(true, Relaxed); } + pub event: TuiEvent, +} +impl Input for TuiIn { + type Event = TuiEvent; + type Handled = bool; + fn event (&self) -> &TuiEvent { &self.event } + fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) } + fn done (&self) { self.exited.store(true, Relaxed); } +} +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] +pub struct TuiEvent(Event); +impl Ord for TuiEvent { + fn cmp (&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other) .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf + } +} +impl From for TuiEvent { + fn from (event: Event) -> Self { + Self(event) + } +} +impl From> for TuiEvent { + fn from (x: Arc) -> Self { + TuiEvent(TuiKey::new(x.as_ref()).build().unwrap_or_else(||panic!("invalid key: {x}"))) + } } - impl TuiIn { /// Spawn the input thread. pub fn run_input + Send + Sync + 'static> ( @@ -35,8 +50,7 @@ impl TuiIn { if poll(timer).is_ok() { let event = read().unwrap(); match event { - - crossterm::event::Event::Key(KeyEvent { + Event::Key(KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, @@ -46,7 +60,8 @@ impl TuiIn { }, _ => { let exited = exited.clone(); - if let Err(e) = state.write().unwrap().handle(&TuiIn(exited, event)) { + let event = event.into(); + if let Err(e) = state.write().unwrap().handle(&TuiIn { exited, event }) { panic!("{e}") } } @@ -59,10 +74,101 @@ impl TuiIn { //#[cfg(feature = "dsl")] //impl DslInput for TuiIn { //fn matches_dsl (&self, token: &str) -> bool { - //if let Some(event) = KeyMatcher::new(token).build() { + //if let Some(event) = TuiKey::new(token).build() { //&event == self.event() //} else { //false //} //} //} + +pub struct TuiKey { + valid: bool, + key: Option, + mods: KeyModifiers, +} + +impl TuiKey { + pub fn new (token: impl AsRef) -> Self { + let token = token.as_ref(); + if token.len() < 2 { + Self { valid: false, key: None, mods: KeyModifiers::NONE } + } else if token.chars().next() != Some('@') { + Self { valid: false, key: None, mods: KeyModifiers::NONE } + } else { + Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) + } + } + pub fn build (self) -> Option { + if self.valid && self.key.is_some() { + Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) + } else { + None + } + } + fn next (mut self, token: &str) -> Self { + let mut tokens = token.split('-').peekable(); + while let Some(token) = tokens.next() { + if tokens.peek().is_some() { + match token { + "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, + "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, + "shift" | "Shift" | "s" | "S" => { + self.mods |= KeyModifiers::SHIFT; + // + TODO normalize character case, BackTab, etc. + }, + _ => panic!("unknown modifier {token}"), + } + } else { + self.key = if token.len() == 1 { + Some(KeyCode::Char(token.chars().next().unwrap())) + } else { + Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) + } + } + } + self + } + fn named_key (token: &str) -> Option { + use KeyCode::*; + Some(match token { + "up" => Up, + "down" => Down, + "left" => Left, + "right" => Right, + "esc" | "escape" => Esc, + "enter" | "return" => Enter, + "delete" | "del" => Delete, + "tab" => Tab, + "space" => Char(' '), + "comma" => Char(','), + "period" => Char('.'), + "plus" => Char('+'), + "minus" | "dash" => Char('-'), + "equal" | "equals" => Char('='), + "underscore" => Char('_'), + "backtick" => Char('`'), + "lt" => Char('<'), + "gt" => Char('>'), + "cbopen" | "openbrace" => Char('{'), + "cbclose" | "closebrace" => Char('}'), + "bropen" | "openbracket" => Char('['), + "brclose" | "closebracket" => Char(']'), + "pgup" | "pageup" => PageUp, + "pgdn" | "pagedown" => PageDown, + "f1" => F(1), + "f2" => F(2), + "f3" => F(3), + "f4" => F(4), + "f5" => F(5), + "f6" => F(6), + "f7" => F(7), + "f8" => F(8), + "f9" => F(9), + "f10" => F(10), + "f11" => F(11), + "f12" => F(12), + _ => return None, + }) + } +} diff --git a/tui/src/tui_engine/tui_keys.rs b/tui/src/tui_engine/tui_keys.rs deleted file mode 100644 index cda5899..0000000 --- a/tui/src/tui_engine/tui_keys.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::*; - -pub struct KeyMatcher { - valid: bool, - key: Option, - mods: KeyModifiers, -} - -impl KeyMatcher { - pub fn new (token: impl AsRef) -> Self { - let token = token.as_ref(); - if token.len() < 2 { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else if token.chars().next() != Some('@') { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else { - Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) - } - } - pub fn build (self) -> Option { - if self.valid && self.key.is_some() { - Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) - } else { - None - } - } - fn next (mut self, token: &str) -> Self { - let mut tokens = token.split('-').peekable(); - while let Some(token) = tokens.next() { - if tokens.peek().is_some() { - match token { - "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, - "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, - "shift" | "Shift" | "s" | "S" => { - self.mods |= KeyModifiers::SHIFT; - // + TODO normalize character case, BackTab, etc. - }, - _ => panic!("unknown modifier {token}"), - } - } else { - self.key = if token.len() == 1 { - Some(KeyCode::Char(token.chars().next().unwrap())) - } else { - Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) - } - } - } - self - } - fn named_key (token: &str) -> Option { - use KeyCode::*; - Some(match token { - "up" => Up, - "down" => Down, - "left" => Left, - "right" => Right, - "esc" | "escape" => Esc, - "enter" | "return" => Enter, - "delete" | "del" => Delete, - "tab" => Tab, - "space" => Char(' '), - "comma" => Char(','), - "period" => Char('.'), - "plus" => Char('+'), - "minus" | "dash" => Char('-'), - "equal" | "equals" => Char('='), - "underscore" => Char('_'), - "backtick" => Char('`'), - "lt" => Char('<'), - "gt" => Char('>'), - "cbopen" | "openbrace" => Char('{'), - "cbclose" | "closebrace" => Char('}'), - "bropen" | "openbracket" => Char('['), - "brclose" | "closebracket" => Char(']'), - "pgup" | "pageup" => PageUp, - "pgdn" | "pagedown" => PageDown, - "f1" => F(1), - "f2" => F(2), - "f3" => F(3), - "f4" => F(4), - "f5" => F(5), - "f6" => F(6), - "f7" => F(7), - "f8" => F(8), - "f9" => F(9), - "f10" => F(10), - "f11" => F(11), - "f12" => F(12), - _ => return None, - }) - } -}