mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2025-12-06 11:46:42 +01:00
simplify
This commit is contained in:
parent
291b917970
commit
238ac2e888
25 changed files with 1018 additions and 1147 deletions
|
|
@ -4,6 +4,9 @@ description = "UI metaframework, tiny S-expression-based DSL."
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/dsl.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tengri_core = { path = "../core" }
|
tengri_core = { path = "../core" }
|
||||||
konst = { workspace = true }
|
konst = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
extern crate const_panic;
|
extern crate const_panic;
|
||||||
use const_panic::{concat_panic, PanicFmt};
|
use const_panic::{concat_panic, PanicFmt};
|
||||||
pub(crate) use ::tengri_core::*;
|
pub(crate) use ::tengri_core::*;
|
||||||
|
use std::ops::Deref;
|
||||||
pub(crate) use std::error::Error;
|
pub(crate) use std::error::Error;
|
||||||
pub(crate) use std::fmt::Debug;
|
pub(crate) use std::fmt::Debug;
|
||||||
pub(crate) use std::sync::Arc;
|
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 konst::string::{split_at, str_range, char_indices};
|
||||||
pub(crate) use thiserror::Error;
|
pub(crate) use thiserror::Error;
|
||||||
pub(crate) use self::DslError::*;
|
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<Self::Str, Self::Exp>;
|
||||||
|
fn err (&self) -> Option<DslError> {self.val().err()}
|
||||||
|
fn nil (&self) -> bool {self.val().nil()}
|
||||||
|
fn num (&self) -> Option<usize> {self.val().num()}
|
||||||
|
fn sym (&self) -> Option<Self::Str> {self.val().sym()}
|
||||||
|
fn key (&self) -> Option<Self::Str> {self.val().key()}
|
||||||
|
fn str (&self) -> Option<Self::Str> {self.val().str()}
|
||||||
|
fn exp (&self) -> Option<Self::Exp> {self.val().exp()}
|
||||||
|
fn exp_depth (&self) -> Option<isize> {self.val().exp_depth()}
|
||||||
|
fn exp_head (&self) -> Val<Self::Str, Self::Exp> {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::Str, Self::Exp>, 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<Self::Str, Self::Exp> { Val::Exp(0, self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Dsl for Cst<'s> {
|
||||||
|
type Str = &'s str;
|
||||||
|
type Exp = Cst<'s>;
|
||||||
|
fn val (&self) -> Val<Self::Str, Self::Exp> { Val::Exp(0, *self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dsl for Ast {
|
||||||
|
type Str = Arc<str>;
|
||||||
|
type Exp = Ast;
|
||||||
|
fn val (&self) -> Val<Self::Str, Ast> { Val::Exp(0, self.clone()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Str: DslStr, Exp: DslExp> Dsl for Token<Str, Exp> {
|
||||||
|
type Str = Str;
|
||||||
|
type Exp = Exp;
|
||||||
|
fn val (&self) -> Val<Str, Exp> { self.value.clone() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Str: DslStr, Exp: DslExp> Dsl for Val<Str, Exp> {
|
||||||
|
type Str = Str;
|
||||||
|
type Exp = Exp;
|
||||||
|
fn val (&self) -> Val<Str, Exp> { self.clone() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The expression representation for a [Dsl] implementation.
|
||||||
|
/// [Cst] uses [CstIter]. [Ast] uses [VecDeque].
|
||||||
|
pub trait DslExp: PartialEq + Clone + Debug + Dsl {}
|
||||||
|
impl<T: PartialEq + Clone + Debug + Dsl> DslExp for T {}
|
||||||
|
/// The string representation for a [Dsl] implementation.
|
||||||
|
/// [Cst] uses `&'s str`. [Ast] uses `Arc<str>`.
|
||||||
|
pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef<str> + Deref<Target = str> {
|
||||||
|
fn as_str (&self) -> &str { self.as_ref() }
|
||||||
|
fn as_arc (&self) -> Arc<str> { self.as_ref().into() }
|
||||||
|
}
|
||||||
|
impl<Str: PartialEq + Clone + Default + Debug + AsRef<str> + Deref<Target = str>> DslStr for Str {}
|
||||||
/// Enumeration of values that may figure in an expression.
|
/// Enumeration of values that may figure in an expression.
|
||||||
/// Generic over string and expression storage.
|
/// Generic over string and expression storage.
|
||||||
#[derive(Clone, Debug, PartialEq, Default)]
|
#[derive(Clone, Debug, PartialEq, Default)]
|
||||||
|
|
@ -39,31 +113,20 @@ pub enum Val<Str, Exp> {
|
||||||
Error(DslError),
|
Error(DslError),
|
||||||
}
|
}
|
||||||
impl<Str: Copy, Exp: Copy> Copy for Val<Str, Exp> {}
|
impl<Str: Copy, Exp: Copy> Copy for Val<Str, Exp> {}
|
||||||
impl<Str: DslStr, Exp: DslExp> Val<Str, Exp> {
|
impl<S1, E1> Val<S1, E1> {
|
||||||
pub fn convert <T: Dsl> (&self) -> Val<T::Str, T::Exp> where
|
pub fn convert_to <S2, E2> (&self, to_str: impl Fn(&S1)->S2, to_exp: impl Fn(&E1)->E2) -> Val<S2, E2> {
|
||||||
T::Str: for<'a> From<&'a Str>,
|
|
||||||
T::Exp: for<'a> From<&'a Exp>
|
|
||||||
{
|
|
||||||
match self {
|
match self {
|
||||||
Val::Nil => Val::Nil,
|
Val::Nil => Val::Nil,
|
||||||
Val::Num(u) => Val::Num(*u),
|
Val::Num(u) => Val::Num(*u),
|
||||||
Val::Sym(s) => Val::Sym(s.into()),
|
Val::Sym(s) => Val::Sym(to_str(s)),
|
||||||
Val::Key(s) => Val::Key(s.into()),
|
Val::Key(s) => Val::Key(to_str(s)),
|
||||||
Val::Str(s) => Val::Str(s.into()),
|
Val::Str(s) => Val::Str(to_str(s)),
|
||||||
Val::Exp(d, x) => Val::Exp(*d, x.into()),
|
Val::Exp(d, x) => Val::Exp(*d, to_exp(x)),
|
||||||
Val::Error(e) => Val::Error(*e)
|
Val::Error(e) => Val::Error(*e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// The expression representation for a [Dsl] implementation.
|
impl<Str: DslStr, Exp: DslExp + Dsl<Str=Str>> Val<Str, Exp> {
|
||||||
/// [Cst] uses [CstIter]. [Ast] uses [VecDeque].
|
|
||||||
pub trait DslExp: PartialEq + Clone + Default + Debug + Dsl {}
|
|
||||||
impl<T: PartialEq + Clone + Default + Debug + Dsl> DslExp for T {}
|
|
||||||
/// The string representation for a [Dsl] implementation.
|
|
||||||
/// [Cst] uses `&'s str`. [Ast] uses `Arc<str>`.
|
|
||||||
pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef<str> + std::ops::Deref<Target = str> {}
|
|
||||||
impl<T: PartialEq + Clone + Default + Debug + AsRef<str> + std::ops::Deref<Target = str>> DslStr for T {}
|
|
||||||
impl<Str: DslStr, Exp: DslExp + Dsl<Str=Str, Exp=Exp>> Val<Str, Exp> {
|
|
||||||
pub const fn err (&self) -> Option<DslError> {match self{Val::Error(e)=>Some(*e), _=>None}}
|
pub const fn err (&self) -> Option<DslError> {match self{Val::Error(e)=>Some(*e), _=>None}}
|
||||||
pub const fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}}
|
pub const fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}}
|
||||||
pub const fn num (&self) -> Option<usize> {match self{Val::Num(n)=>Some(*n), _=>None}}
|
pub const fn num (&self) -> Option<usize> {match self{Val::Num(n)=>Some(*n), _=>None}}
|
||||||
|
|
@ -85,7 +148,9 @@ pub struct Token<Str, Exp> {
|
||||||
/// Length of span.
|
/// Length of span.
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
}
|
}
|
||||||
|
/// Tokens are copiable where possible.
|
||||||
impl<Str: Copy, Exp: Copy> Copy for Token<Str, Exp> {}
|
impl<Str: Copy, Exp: Copy> Copy for Token<Str, Exp> {}
|
||||||
|
/// Token methods.
|
||||||
impl<Str, Exp> Token<Str, Exp> {
|
impl<Str, Exp> Token<Str, Exp> {
|
||||||
pub const fn end (&self) -> usize {
|
pub const fn end (&self) -> usize {
|
||||||
self.start.saturating_add(self.length) }
|
self.start.saturating_add(self.length) }
|
||||||
|
|
@ -99,83 +164,85 @@ impl<Str, Exp> Token<Str, Exp> {
|
||||||
Self { value: self.value, ..*self }
|
Self { value: self.value, ..*self }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// To the [Dsl], a token is equivalent to its `value` field.
|
|
||||||
impl<Str: DslStr, Exp: DslExp> Dsl for Token<Str, Exp> {
|
|
||||||
type Str = Str; type Exp = Exp;
|
|
||||||
fn dsl (&self) -> Val<Str, Exp> { 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<Self::Str, Self::Exp>;
|
|
||||||
fn err (&self) -> Option<DslError> {self.dsl().err()}
|
|
||||||
fn nil (&self) -> bool {self.dsl().nil()}
|
|
||||||
fn num (&self) -> Option<usize> {self.dsl().num()}
|
|
||||||
fn sym (&self) -> Option<Self::Str> {self.dsl().sym()}
|
|
||||||
fn key (&self) -> Option<Self::Str> {self.dsl().key()}
|
|
||||||
fn str (&self) -> Option<Self::Str> {self.dsl().str()}
|
|
||||||
fn exp (&self) -> Option<Self::Exp> {self.dsl().exp()}
|
|
||||||
fn exp_depth (&self) -> Option<isize> {self.dsl().exp_depth()}
|
|
||||||
fn exp_head (&self) -> Val<Self::Str, Self::Exp> {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<Str: DslStr, Exp: DslExp> Dsl for Val<Str, Exp> {
|
|
||||||
type Str = Str; type Exp = Exp;
|
|
||||||
fn dsl (&self) -> Val<Str, Exp> { 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<VecDeque<Arc<Token<Arc<str>, Ast>>>>);
|
|
||||||
pub type AstVal = Val<Arc<str>, Ast>;
|
|
||||||
pub type AstToken = Token<Arc<str>, Ast>;
|
|
||||||
impl Dsl for Ast {
|
|
||||||
type Str = Arc<str>; type Exp = Ast;
|
|
||||||
fn dsl (&self) -> Val<Arc<str>, Ast> { Val::Exp(0, Ast(self.0.clone())) }
|
|
||||||
}
|
|
||||||
impl<'s> From<&'s str> for Ast {
|
|
||||||
fn from (source: &'s str) -> Self {
|
|
||||||
let source: Arc<str> = 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::<VecDeque<_>>()
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'s> From<Cst<'s>> 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
|
/// The concrete syntax tree (CST) implements zero-copy
|
||||||
/// parsing of the DSL from a string reference. CST items
|
/// parsing of the DSL from a string reference. CST items
|
||||||
/// preserve info about their location in the source.
|
/// preserve info about their location in the source.
|
||||||
/// CST stores strings as source references and expressions as [CstIter] instances.
|
/// CST stores strings as source references and expressions as [CstIter] instances.
|
||||||
#[derive(Debug, Copy, Clone, Default, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Default)]
|
||||||
pub struct Cst<'s>(pub CstIter<'s>);
|
pub enum Cst<'s> {
|
||||||
pub type CstVal<'s> = Val<&'s str, Cst<'s>>;
|
#[default] __,
|
||||||
pub type CstToken<'s> = Token<&'s str, Cst<'s>>;
|
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<CstToken<'s>> for Cst<'s> {
|
||||||
|
fn from (token: CstToken<'s>) -> Self {
|
||||||
|
Cst::Token(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'s> From<CstVal<'s>> for Cst<'s> {
|
||||||
|
fn from (val: CstVal<'s>) -> Self {
|
||||||
|
Cst::Val(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'s> From<CstIter<'s>> for Cst<'s> {
|
||||||
|
fn from (iter: CstIter<'s>) -> Self {
|
||||||
|
Cst::Iter(iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'s> From<CstConstIter<'s>> for Cst<'s> {
|
||||||
|
fn from (iter: CstConstIter<'s>) -> Self {
|
||||||
|
Cst::ConstIter(iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'s> From<Cst<'s>> 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::<str>::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<str>),
|
||||||
|
Token(Token<Arc<str>, Box<Ast>>),
|
||||||
|
Val(Val<Arc<str>, Box<Ast>>),
|
||||||
|
Exp(Arc<VecDeque<Arc<Token<Arc<str>, 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<Ast> + 's> From<Token<Str, Exp>> for Ast {
|
||||||
|
fn from (Token { source, start, length, value }: Token<Str, Exp>) -> Self {
|
||||||
|
let source: Arc<str> = 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> {
|
impl<'s> CstToken<'s> {
|
||||||
pub const fn slice (&self) -> &str {
|
pub const fn slice (&self) -> &str {
|
||||||
str_range(self.source, self.start, self.end()) }
|
str_range(self.source, self.start, self.end()) }
|
||||||
|
|
@ -188,19 +255,32 @@ impl<'s> CstToken<'s> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub const fn grow_exp (&'s mut self, depth: isize, source: &'s str) -> &mut 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
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'s> Dsl for Cst<'s> {
|
//impl<'s> From<&'s str> for Ast {
|
||||||
type Str = &'s str; type Exp = Cst<'s>;
|
//fn from (source: &'s str) -> Ast {
|
||||||
fn dsl (&self) -> Val<Self::Str, Self::Exp> { Val::Exp(0, Cst(self.0)) }
|
//let source: Arc<str> = source.into();
|
||||||
}
|
//Ast(CstIter(CstConstIter(source.as_ref()))
|
||||||
impl<'s> From<&'s str> for Cst<'s> {
|
//.map(|token|Arc::new(Token {
|
||||||
fn from (source: &'s str) -> Self {
|
//source: source.clone(),
|
||||||
Self(CstIter(CstConstIter(source)))
|
//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::<VecDeque<_>>()
|
||||||
|
//.into())
|
||||||
|
//}
|
||||||
|
//}
|
||||||
/// DSL-specific error codes.
|
/// DSL-specific error codes.
|
||||||
#[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError {
|
#[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError {
|
||||||
#[error("parse failed: not implemented")]
|
#[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;
|
start = i;
|
||||||
length = 1;
|
length = 1;
|
||||||
if is_exp_start(c) {
|
value = if is_exp_start(c) { Exp(1, str_range(source, i, i+1)) }
|
||||||
value = Exp(1, Cst(CstIter(CstConstIter(str_range(source, i, i+1)))));
|
else if is_str_start(c) { Str(str_range(source, i, i+1)) }
|
||||||
} else if is_str_start(c) {
|
else if is_sym_start(c) { Sym(str_range(source, i, i+1)) }
|
||||||
value = Str(str_range(source, i, i+1));
|
else if is_key_start(c) { Key(str_range(source, i, i+1)) }
|
||||||
} else if is_sym_start(c) {
|
else if is_digit(c) { match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) } }
|
||||||
value = Sym(str_range(source, i, i+1));
|
else { value = Error(Unexpected(c)); break }
|
||||||
} 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
|
|
||||||
}
|
|
||||||
} else if matches!(value, Str(_)) {
|
} else if matches!(value, Str(_)) {
|
||||||
if is_str_end(c) {
|
if is_str_end(c) {
|
||||||
break
|
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 {
|
} else if let Exp(depth, exp) = value {
|
||||||
if depth == 0 {
|
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
|
break
|
||||||
}
|
}
|
||||||
length += 1;
|
length += 1;
|
||||||
value = Exp(
|
value = Exp(
|
||||||
if c == ')' { depth-1 } else if c == '(' { depth+1 } else { depth },
|
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 {
|
} else if let Num(m) = value {
|
||||||
if is_num_end(c) {
|
if is_num_end(c) {
|
||||||
|
|
@ -378,22 +450,6 @@ pub const fn to_digit (c: char) -> Result<usize, DslError> {
|
||||||
_ => return Err(Unexpected(c))
|
_ => return Err(Unexpected(c))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// `State` + [Dsl] -> `Self`.
|
|
||||||
pub trait FromDsl<State>: Sized {
|
|
||||||
fn try_from_dsl (state: &State, dsl: &impl Dsl) -> Perhaps<Self>;
|
|
||||||
fn from_dsl (state: &State, dsl: &impl Dsl, err: impl Fn()->Box<dyn Error>) -> Usually<Self> {
|
|
||||||
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<Item> {
|
|
||||||
fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps<Item>;
|
|
||||||
fn dsl_into (&self, dsl: &impl Dsl, err: impl Fn()->Box<dyn Error>) -> Usually<Item> {
|
|
||||||
match Self::try_dsl_into(self, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } }
|
|
||||||
}
|
|
||||||
/// `self` + `Item` -> [Dsl]
|
|
||||||
pub trait DslFrom { /*TODO*/ }
|
|
||||||
|
|
||||||
/// Implement type conversions.
|
/// Implement type conversions.
|
||||||
macro_rules! from(($($Struct:ty { $(
|
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::<VecDeque<_>>().into());
|
//<'s> (iter: CstIter<'s>) Ast(iter.map(|x|x.value.into()).collect::<VecDeque<_>>().into());
|
||||||
//<D: Dsl> (token: Token<D>) Ast(VecDeque::from([dsl_val(token.val())]).into()); }
|
//<D: Dsl> (token: Token<D>) Ast(VecDeque::from([dsl_val(token.val())]).into()); }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
/// `T` + [Dsl] -> `Self`.
|
||||||
|
pub trait FromDsl<T>: Sized {
|
||||||
|
fn from_dsl (state: &T, dsl: &impl Dsl) -> Perhaps<Self>;
|
||||||
|
fn from_dsl_or (state: &T, dsl: &impl Dsl, err: Box<dyn Error>) -> Usually<Self> {
|
||||||
|
Self::from_dsl(state, dsl)?.ok_or(err)
|
||||||
|
}
|
||||||
|
fn from_dsl_or_else (state: &T, dsl: &impl Dsl, err: impl Fn()->Box<dyn Error>) -> Usually<Self> {
|
||||||
|
Self::from_dsl(state, dsl)?.ok_or_else(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: FromDsl<U>, U> DslInto<T> for U {
|
||||||
|
fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps<T> {
|
||||||
|
T::from_dsl(self, dsl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `self` + [Dsl] -> `T`
|
||||||
|
pub trait DslInto<T> {
|
||||||
|
fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps<T>;
|
||||||
|
fn dsl_into_or (&self, dsl: &impl Dsl, err: Box<dyn Error>) -> Usually<T> {
|
||||||
|
self.dsl_into(dsl)?.ok_or(err)
|
||||||
|
}
|
||||||
|
fn dsl_into_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box<dyn Error>) -> Usually<T> {
|
||||||
|
self.dsl_into(dsl)?.ok_or_else(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `self` + `T` + -> [Dsl]
|
||||||
|
pub trait IntoDsl<T> {
|
||||||
|
fn into_dsl (&self, state: &T) -> Perhaps<impl Dsl>;
|
||||||
|
fn into_dsl_or (&self, state: &T, err: Box<dyn Error>) -> Usually<impl Dsl> {
|
||||||
|
self.into_dsl(state)?.ok_or(err)
|
||||||
|
}
|
||||||
|
fn into_dsl_or_else (&self, state: &T, err: impl Fn()->Box<dyn Error>) -> Usually<impl Dsl> {
|
||||||
|
self.into_dsl(state)?.ok_or_else(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `self` + `T` -> [Dsl]
|
||||||
|
pub trait DslFrom<T> {
|
||||||
|
fn dsl_from (&self, dsl: &impl Dsl) -> Perhaps<impl Dsl>;
|
||||||
|
fn dsl_from_or (&self, dsl: &impl Dsl, err: Box<dyn Error>) -> Usually<impl Dsl> {
|
||||||
|
self.dsl_from(dsl)?.ok_or(err)
|
||||||
|
}
|
||||||
|
fn dsl_from_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box<dyn Error>) -> Usually<impl Dsl> {
|
||||||
|
self.dsl_from(dsl)?.ok_or_else(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
0
dsl/src/dsl_test.rs
Normal file
0
dsl/src/dsl_test.rs
Normal file
104
dsl/src/test.rs
104
dsl/src/test.rs
|
|
@ -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<dyn std::error::Error>> {
|
|
||||||
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 = <dyn ViewContext<::tengri_engine::tui::Tui>>::from(&layout);
|
|
||||||
////}
|
|
||||||
//Ok(())
|
|
||||||
//}
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::*;
|
||||||
///
|
///
|
||||||
/// When a key is pressed, the bindings for it are checked in sequence.
|
/// When a key is pressed, the bindings for it are checked in sequence.
|
||||||
/// When the first non-conditional or true conditional binding is executed,
|
/// 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<E, D: Dsl>(
|
#[derive(Debug)] pub struct EventMap<E, D: Dsl>(
|
||||||
/// Map of each event (e.g. key combination) to
|
/// Map of each event (e.g. key combination) to
|
||||||
/// all command expressions bound to it by
|
/// all command expressions bound to it by
|
||||||
|
|
@ -71,7 +71,7 @@ impl<E: Ord + Debug + From<Arc<str>> + Clone> EventMap<E, Ast> {
|
||||||
}
|
}
|
||||||
/// Evaluate the active layers for a given state,
|
/// Evaluate the active layers for a given state,
|
||||||
/// returning the command to be executed, if any.
|
/// returning the command to be executed, if any.
|
||||||
pub fn handle <S, O> (&self, state: &mut S, event: Ast) -> Perhaps<O> where
|
pub fn handle <S, O> (&self, state: &mut S, event: &E) -> Perhaps<O> where
|
||||||
S: DslInto<bool> + DslInto<O>,
|
S: DslInto<bool> + DslInto<O>,
|
||||||
O: Command<S>
|
O: Command<S>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@
|
||||||
#![feature(impl_trait_in_assoc_type)]
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
|
|
||||||
pub(crate) use std::marker::PhantomData;
|
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::*;
|
pub(crate) use tengri_core::*;
|
||||||
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
|
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,32 +48,21 @@
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use Direction::*;
|
use Direction::*;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
mod map; pub use self::map::*;
|
/// Stack things on top of each other,
|
||||||
mod memo; pub use self::memo::*;
|
#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) =>
|
||||||
mod stack; pub use self::stack::*;
|
{{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }});
|
||||||
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 southward.
|
/// Stack southward.
|
||||||
#[macro_export] macro_rules! col {
|
#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) =>
|
||||||
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }};
|
{{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }});
|
||||||
}
|
|
||||||
|
|
||||||
/// Stack northward.
|
/// Stack northward.
|
||||||
#[macro_export] macro_rules! col_up {
|
#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) =>
|
||||||
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }}
|
{{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }});
|
||||||
}
|
|
||||||
|
|
||||||
/// Stack eastward.
|
/// Stack eastward.
|
||||||
#[macro_export] macro_rules! row {
|
#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) =>
|
||||||
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }};
|
{{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }});
|
||||||
}
|
|
||||||
|
|
||||||
/// 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> {
|
impl<A> When<A> {
|
||||||
|
|
@ -408,7 +397,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{
|
||||||
$op:literal $(/)? [$head: ident, $tail: ident] $expr:expr
|
$op:literal $(/)? [$head: ident, $tail: ident] $expr:expr
|
||||||
) => {
|
) => {
|
||||||
impl<S,$($($A),+)?> FromDsl<S> for $Struct$(<$($A),+>)? {
|
impl<S,$($($A),+)?> FromDsl<S> for $Struct$(<$($A),+>)? {
|
||||||
fn try_from_dsl (
|
fn from_dsl (
|
||||||
_state: &S, _dsl: &impl Dsl
|
_state: &S, _dsl: &impl Dsl
|
||||||
) -> Perhaps<Self> {
|
) -> Perhaps<Self> {
|
||||||
todo!()
|
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
|
$op:literal $(/)? [$head: ident, $tail: ident] $expr:expr
|
||||||
) => {
|
) => {
|
||||||
impl<S,$($($A),+)?> FromDsl<S> for $Struct$(<$($A),+>)? {
|
impl<S,$($($A),+)?> FromDsl<S> for $Struct$(<$($A),+>)? {
|
||||||
fn try_from_dsl (
|
fn from_dsl (
|
||||||
_state: &S, _dsl: &impl Dsl
|
_state: &S, _dsl: &impl Dsl
|
||||||
) -> Perhaps<Self> {
|
) -> Perhaps<Self> {
|
||||||
todo!()
|
todo!()
|
||||||
|
|
@ -686,3 +675,353 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{
|
||||||
//_ => None
|
//_ => None
|
||||||
//})
|
//})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lazily-evaluated [Render]able.
|
||||||
|
pub struct Thunk<E: Output, T: Render<E>, F: Fn()->T>(
|
||||||
|
PhantomData<E>,
|
||||||
|
F
|
||||||
|
);
|
||||||
|
impl<E: Output, T: Render<E>, F: Fn()->T> Thunk<E, T, F> {
|
||||||
|
pub const fn new (thunk: F) -> Self {
|
||||||
|
Self(PhantomData, thunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<E: Output, T: Render<E>, F: Fn()->T> Content<E> for Thunk<E, T, F> {
|
||||||
|
fn content (&self) -> impl Render<E> { (self.1)() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ThunkBox<E: Output>(
|
||||||
|
PhantomData<E>,
|
||||||
|
Box<dyn Fn()->Box<dyn Render<E>>>,
|
||||||
|
);
|
||||||
|
impl<E: Output> ThunkBox<E> {
|
||||||
|
pub const fn new (thunk: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
|
||||||
|
Self(PhantomData, thunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<E: Output> Content<E> for ThunkBox<E> {
|
||||||
|
fn content (&self) -> impl Render<E> { (&self.1)() }
|
||||||
|
}
|
||||||
|
impl<E: Output> From<Box<dyn Fn()->Box<dyn Render<E>>>> for ThunkBox<E> {
|
||||||
|
fn from (f: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
|
||||||
|
Self(PhantomData, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//impl<'a, E: Output, F: Fn()->Box<dyn Render<E> + 'a> + 'a> From<F> for ThunkBox<'a, E> {
|
||||||
|
//fn from (f: F) -> Self {
|
||||||
|
//Self(Default::default(), Box::new(f))
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
pub struct ThunkRender<E: Output, F: Fn(&mut E)>(PhantomData<E>, F);
|
||||||
|
impl<E: Output, F: Fn(&mut E)> ThunkRender<E, F> {
|
||||||
|
pub fn new (render: F) -> Self { Self(PhantomData, render) }
|
||||||
|
}
|
||||||
|
impl<E: Output, F: Fn(&mut E)> Content<E> for ThunkRender<E, F> {
|
||||||
|
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<E>,
|
||||||
|
F1,
|
||||||
|
F2
|
||||||
|
);
|
||||||
|
impl<E: Output, F1: Fn(E::Area)->E::Area, F2: Fn(&mut E)> ThunkLayout<E, F1, F2> {
|
||||||
|
pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) }
|
||||||
|
}
|
||||||
|
impl<E, F1, F2> Content<E> for ThunkLayout<E, F1, F2>
|
||||||
|
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<T, U> {
|
||||||
|
pub value: T,
|
||||||
|
pub view: Arc<RwLock<U>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq, U> Memo<T, U> {
|
||||||
|
pub fn new (value: T, view: U) -> Self {
|
||||||
|
Self { value, view: Arc::new(view.into()) }
|
||||||
|
}
|
||||||
|
pub fn update <R> (
|
||||||
|
&mut self,
|
||||||
|
newval: T,
|
||||||
|
render: impl Fn(&mut U, &T, &T)->R
|
||||||
|
) -> Option<R> {
|
||||||
|
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<E, F> {
|
||||||
|
__: PhantomData<E>,
|
||||||
|
direction: Direction,
|
||||||
|
callback: F
|
||||||
|
}
|
||||||
|
impl<E, F> Stack<E, F> {
|
||||||
|
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<E: Output, F: Fn(&mut dyn FnMut(&dyn Render<E>)->())->()> Content<E> for Stack<E, F> {
|
||||||
|
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<E>|{
|
||||||
|
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<E>|{
|
||||||
|
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<E, A, B, I, F, G>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = A> + 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<E, A, B, I, F, G> where
|
||||||
|
I: Iterator<Item = A> + 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<E, A, Push<E::Unit, Align<Fixed<E::Unit, Fill<B>>>>, I, F, fn(A, usize)->B>
|
||||||
|
where
|
||||||
|
E: Output,
|
||||||
|
B: Render<E>,
|
||||||
|
I: Iterator<Item = A> + 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<E::Unit, Align<Fixed<E::Unit, B>>>,
|
||||||
|
I, F,
|
||||||
|
impl Fn(A, usize)->Push<E::Unit, Align<Fixed<E::Unit, B>>> + 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<E::Unit, Align<Fixed<E::Unit, B>>>,
|
||||||
|
I, F,
|
||||||
|
impl Fn(A, usize)->Push<E::Unit, Align<Fixed<E::Unit, B>>> + Send + Sync
|
||||||
|
> where
|
||||||
|
E: Output,
|
||||||
|
B: Render<E>,
|
||||||
|
I: Iterator<Item = A> + 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<E> for Map<E, A, B, I, F, G> where
|
||||||
|
E: Output,
|
||||||
|
B: Render<E>,
|
||||||
|
I: Iterator<Item = A> + 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<O: Output>(
|
||||||
|
item_offset: O::Unit,
|
||||||
|
item_height: O::Unit,
|
||||||
|
item: impl Content<O>
|
||||||
|
) -> impl Content<O> {
|
||||||
|
Push::y(item_offset, Fixed::y(item_height, Fill::x(item)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline] pub fn map_south_west<O: Output>(
|
||||||
|
item_offset: O::Unit,
|
||||||
|
item_height: O::Unit,
|
||||||
|
item: impl Content<O>
|
||||||
|
) -> impl Content<O> {
|
||||||
|
Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline] pub fn map_east<O: Output>(
|
||||||
|
item_offset: O::Unit,
|
||||||
|
item_width: O::Unit,
|
||||||
|
item: impl Content<O>
|
||||||
|
) -> impl Content<O> {
|
||||||
|
Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item))))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
//! Groupings of elements.
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// A function or closure that emits renderables.
|
|
||||||
pub trait Collector<E: Engine>: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)) {}
|
|
||||||
|
|
||||||
/// Any function or closure that emits renderables for the given engine matches [CollectCallback].
|
|
||||||
impl<E, F> Collector<E> for F
|
|
||||||
where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)) {}
|
|
||||||
|
|
||||||
pub trait Render<E: Engine> {
|
|
||||||
fn area (&self, to: E::Area) -> E::Area;
|
|
||||||
fn render (&self, to: &mut E::Output);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine, C: Content<E>> Render<E> 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub struct Reduce<A, B, I, F, G>(pub PhantomData<A>, pub F, pub G) where
|
|
||||||
A: Send + Sync, B: Send + Sync,
|
|
||||||
I: Iterator<Item = B> + Send + Sync,
|
|
||||||
F: Fn() -> I + Send + Sync,
|
|
||||||
G: Fn(A, B, usize)->A + Send + Sync;
|
|
||||||
|
|
||||||
impl<A, B, I, F, G> Reduce<A, B, I, F, G> where
|
|
||||||
A: Send + Sync, B: Send + Sync,
|
|
||||||
I: Iterator<Item = B> + 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<E: Output, A, B, I, F, G> Content<E> for Reduce<A, B, I, F, G> where
|
|
||||||
A: Send + Sync, B: Send + Sync,
|
|
||||||
I: Iterator<Item = B> + Send + Sync,
|
|
||||||
F: Fn() -> I + Send + Sync,
|
|
||||||
G: Fn(A, B, usize)->A + Send + Sync
|
|
||||||
{
|
|
||||||
fn content (&self) -> impl Render<E> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
//pub fn reduce <E, T, I, R, F>(iterator: I, callback: F) -> Reduce<E, T, I, R, F> where
|
|
||||||
//E: Output,
|
|
||||||
//I: Iterator<Item = T> + Send + Sync,
|
|
||||||
//R: Render<E>,
|
|
||||||
//F: Fn(R, T, usize) -> R + Send + Sync
|
|
||||||
//{
|
|
||||||
//Reduce(Default::default(), iterator, callback)
|
|
||||||
//}
|
|
||||||
pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F) where
|
|
||||||
E: Output,
|
|
||||||
I: Iterator<Item = T> + Send + Sync,
|
|
||||||
R: Render<E>,
|
|
||||||
F: Fn(R, T, usize) -> R + Send + Sync;
|
|
||||||
impl<E, T, I, R, F> Content<E> for Reduce<E, T, I, R, F> where
|
|
||||||
E: Output,
|
|
||||||
I: Iterator<Item = T> + Send + Sync,
|
|
||||||
R: Render<E>,
|
|
||||||
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<E> 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<E: Output> {
|
|
||||||
//(when <A: Render<E>,>
|
|
||||||
//When(cond: bool, item: A))
|
|
||||||
///// When `cond` is `true`, render `a`, otherwise render `b`.
|
|
||||||
//(either <A: Render<E>, B: Render<E>,>
|
|
||||||
//Either(cond: bool, a: A, b: B))
|
|
||||||
///// If `opt` is `Some(T)` renders `cb(t)`, otherwise nothing.
|
|
||||||
//(opt <A, F: Fn(A) -> B, B: Render<E>,>
|
|
||||||
//Opt(option: Option<A>, cb: F))
|
|
||||||
///// Maps items of iterator through callback.
|
|
||||||
//(map <A, B: Render<E>, I: Iterator<Item = A>, F: Fn() -> I, G: Fn(A, usize)->B,>
|
|
||||||
//Map(get_iterator: F, callback: G))
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Renders items from an iterator.
|
|
||||||
pub struct Map<E, A, B, I, F, G>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = A> + 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<E, A, B, I, F, G> where
|
|
||||||
I: Iterator<Item = A> + 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<E, A, Push<E::Unit, Align<Fixed<E::Unit, Fill<B>>>>, I, F, fn(A, usize)->B>
|
|
||||||
where
|
|
||||||
E: Output,
|
|
||||||
B: Render<E>,
|
|
||||||
I: Iterator<Item = A> + 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<E::Unit, Align<Fixed<E::Unit, B>>>,
|
|
||||||
I, F,
|
|
||||||
impl Fn(A, usize)->Push<E::Unit, Align<Fixed<E::Unit, B>>> + 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<E::Unit, Align<Fixed<E::Unit, B>>>,
|
|
||||||
I, F,
|
|
||||||
impl Fn(A, usize)->Push<E::Unit, Align<Fixed<E::Unit, B>>> + Send + Sync
|
|
||||||
> where
|
|
||||||
E: Output,
|
|
||||||
B: Render<E>,
|
|
||||||
I: Iterator<Item = A> + 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<E> for Map<E, A, B, I, F, G> where
|
|
||||||
E: Output,
|
|
||||||
B: Render<E>,
|
|
||||||
I: Iterator<Item = A> + 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<O: Output>(
|
|
||||||
item_offset: O::Unit,
|
|
||||||
item_height: O::Unit,
|
|
||||||
item: impl Content<O>
|
|
||||||
) -> impl Content<O> {
|
|
||||||
Push::y(item_offset, Fixed::y(item_height, Fill::x(item)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline] pub fn map_south_west<O: Output>(
|
|
||||||
item_offset: O::Unit,
|
|
||||||
item_height: O::Unit,
|
|
||||||
item: impl Content<O>
|
|
||||||
) -> impl Content<O> {
|
|
||||||
Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item))))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline] pub fn map_east<O: Output>(
|
|
||||||
item_offset: O::Unit,
|
|
||||||
item_width: O::Unit,
|
|
||||||
item: impl Content<O>
|
|
||||||
) -> impl Content<O> {
|
|
||||||
Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item))))
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
//use crate::*;
|
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
|
|
||||||
#[derive(Debug, Default)] pub struct Memo<T, U> {
|
|
||||||
pub value: T,
|
|
||||||
pub view: Arc<RwLock<U>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialEq, U> Memo<T, U> {
|
|
||||||
pub fn new (value: T, view: U) -> Self {
|
|
||||||
Self { value, view: Arc::new(view.into()) }
|
|
||||||
}
|
|
||||||
pub fn update <R> (
|
|
||||||
&mut self,
|
|
||||||
newval: T,
|
|
||||||
render: impl Fn(&mut U, &T, &T)->R
|
|
||||||
) -> Option<R> {
|
|
||||||
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)*) } }
|
|
||||||
}
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use Direction::*;
|
|
||||||
|
|
||||||
pub struct Stack<E, F> {
|
|
||||||
__: PhantomData<E>,
|
|
||||||
direction: Direction,
|
|
||||||
callback: F
|
|
||||||
}
|
|
||||||
impl<E, F> Stack<E, F> {
|
|
||||||
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<E: Output, F: Fn(&mut dyn FnMut(&dyn Render<E>)->())->()> Content<E> for Stack<E, F> {
|
|
||||||
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<E>|{
|
|
||||||
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<E>|{
|
|
||||||
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(())
|
|
||||||
}));*/
|
|
||||||
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
/// Lazily-evaluated [Render]able.
|
|
||||||
pub struct Thunk<E: Output, T: Render<E>, F: Fn()->T>(
|
|
||||||
PhantomData<E>,
|
|
||||||
F
|
|
||||||
);
|
|
||||||
impl<E: Output, T: Render<E>, F: Fn()->T> Thunk<E, T, F> {
|
|
||||||
pub const fn new (thunk: F) -> Self {
|
|
||||||
Self(PhantomData, thunk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<E: Output, T: Render<E>, F: Fn()->T> Content<E> for Thunk<E, T, F> {
|
|
||||||
fn content (&self) -> impl Render<E> { (self.1)() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ThunkBox<E: Output>(
|
|
||||||
PhantomData<E>,
|
|
||||||
Box<dyn Fn()->Box<dyn Render<E>>>,
|
|
||||||
);
|
|
||||||
impl<E: Output> ThunkBox<E> {
|
|
||||||
pub const fn new (thunk: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
|
|
||||||
Self(PhantomData, thunk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<E: Output> Content<E> for ThunkBox<E> {
|
|
||||||
fn content (&self) -> impl Render<E> { (&self.1)() }
|
|
||||||
}
|
|
||||||
impl<E: Output> From<Box<dyn Fn()->Box<dyn Render<E>>>> for ThunkBox<E> {
|
|
||||||
fn from (f: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
|
|
||||||
Self(PhantomData, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//impl<'a, E: Output, F: Fn()->Box<dyn Render<E> + 'a> + 'a> From<F> for ThunkBox<'a, E> {
|
|
||||||
//fn from (f: F) -> Self {
|
|
||||||
//Self(Default::default(), Box::new(f))
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
pub struct ThunkRender<E: Output, F: Fn(&mut E)>(PhantomData<E>, F);
|
|
||||||
impl<E: Output, F: Fn(&mut E)> ThunkRender<E, F> {
|
|
||||||
pub fn new (render: F) -> Self { Self(PhantomData, render) }
|
|
||||||
}
|
|
||||||
impl<E: Output, F: Fn(&mut E)> Content<E> for ThunkRender<E, F> {
|
|
||||||
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<E>,
|
|
||||||
F1,
|
|
||||||
F2
|
|
||||||
);
|
|
||||||
impl<E: Output, F1: Fn(E::Area)->E::Area, F2: Fn(&mut E)> ThunkLayout<E, F1, F2> {
|
|
||||||
pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) }
|
|
||||||
}
|
|
||||||
impl<E, F1, F2> Content<E> for ThunkLayout<E, F1, F2>
|
|
||||||
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) }
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,274 @@
|
||||||
mod area; pub use self::area::*;
|
use crate::*;
|
||||||
mod coordinate; pub use self::coordinate::*;
|
use Direction::*;
|
||||||
mod direction; pub use self::direction::*;
|
|
||||||
mod measure; pub use self::measure::*;
|
/// A cardinal direction.
|
||||||
mod size; pub use self::size::*;
|
#[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 <N: Coordinate> (self, area: impl Area<N>, 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<Self, Output=Self>
|
||||||
|
+ Sub<Self, Output=Self>
|
||||||
|
+ Mul<Self, Output=Self>
|
||||||
|
+ Div<Self, Output=Self>
|
||||||
|
+ Ord + PartialEq + Eq
|
||||||
|
+ Debug + Display + Default
|
||||||
|
+ From<u16> + Into<u16>
|
||||||
|
+ Into<usize>
|
||||||
|
+ Into<f64>
|
||||||
|
{
|
||||||
|
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<N: Coordinate>: 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>) -> [N;4] {
|
||||||
|
let [x, y] = pos.wh();
|
||||||
|
[x, y, 0.into(), 0.into()]
|
||||||
|
}
|
||||||
|
fn from_size (size: impl Size<N>) -> [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>) -> [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<Item = N> where N: std::iter::Step {
|
||||||
|
self.x()..(self.x()+self.w())
|
||||||
|
}
|
||||||
|
fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
||||||
|
self.y()..(self.y()+self.h())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Coordinate> Area<N> 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<N: Coordinate> Area<N> 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<N: Coordinate>: 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<N: Coordinate> Size<N> for (N, N) {
|
||||||
|
fn x (&self) -> N { self.0 }
|
||||||
|
fn y (&self) -> N { self.1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Coordinate> Size<N> for [N;2] {
|
||||||
|
fn x (&self) -> N { self[0] }
|
||||||
|
fn y (&self) -> N { self[1] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasSize<E: Output> {
|
||||||
|
fn size (&self) -> &Measure<E>;
|
||||||
|
fn width (&self) -> usize {
|
||||||
|
self.size().w()
|
||||||
|
}
|
||||||
|
fn height (&self) -> usize {
|
||||||
|
self.size().h()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Output, T: Has<Measure<E>>> HasSize<E> for T {
|
||||||
|
fn size (&self) -> &Measure<E> {
|
||||||
|
self.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A widget that tracks its render width and height
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Measure<E: Output> {
|
||||||
|
_engine: PhantomData<E>,
|
||||||
|
pub x: Arc<AtomicUsize>,
|
||||||
|
pub y: Arc<AtomicUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
|
||||||
|
impl<E: Output> Content<E> for Measure<E> {
|
||||||
|
fn render (&self, to: &mut E) {
|
||||||
|
self.x.store(to.area().w().into(), Relaxed);
|
||||||
|
self.y.store(to.area().h().into(), Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Output> Clone for Measure<E> {
|
||||||
|
fn clone (&self) -> Self {
|
||||||
|
Self {
|
||||||
|
_engine: Default::default(),
|
||||||
|
x: self.x.clone(),
|
||||||
|
y: self.y.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Output> std::fmt::Debug for Measure<E> {
|
||||||
|
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<E: Output> Measure<E> {
|
||||||
|
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<usize>) -> &Self {
|
||||||
|
self.x.store(w.into(), Relaxed);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn set_h (&self, h: impl Into<usize>) -> &Self {
|
||||||
|
self.y.store(h.into(), Relaxed);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) -> &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<str> {
|
||||||
|
format!("{}x{}", self.w(), self.h()).into()
|
||||||
|
}
|
||||||
|
pub fn of <T: Render<E>> (&self, item: T) -> Bsp<Fill<&Self>, T> {
|
||||||
|
Bsp::b(Fill::xy(self), item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
pub trait Area<N: Coordinate>: 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>) -> [N;4] {
|
|
||||||
let [x, y] = pos.wh();
|
|
||||||
[x, y, 0.into(), 0.into()]
|
|
||||||
}
|
|
||||||
fn from_size (size: impl Size<N>) -> [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>) -> [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<Item = N> where N: std::iter::Step {
|
|
||||||
self.x()..(self.x()+self.w())
|
|
||||||
}
|
|
||||||
fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
|
||||||
self.y()..(self.y()+self.h())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Coordinate> Area<N> 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<N: Coordinate> Area<N> 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] }
|
|
||||||
}
|
|
||||||
|
|
@ -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<Self, Output=Self>
|
|
||||||
+ Sub<Self, Output=Self>
|
|
||||||
+ Mul<Self, Output=Self>
|
|
||||||
+ Div<Self, Output=Self>
|
|
||||||
+ Ord + PartialEq + Eq
|
|
||||||
+ Debug + Display + Default
|
|
||||||
+ From<u16> + Into<u16>
|
|
||||||
+ Into<usize>
|
|
||||||
+ Into<f64>
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 <N: Coordinate> (self, area: impl Area<N>, 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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}};
|
|
||||||
|
|
||||||
pub trait HasSize<E: Output> {
|
|
||||||
fn size (&self) -> &Measure<E>;
|
|
||||||
fn width (&self) -> usize {
|
|
||||||
self.size().w()
|
|
||||||
}
|
|
||||||
fn height (&self) -> usize {
|
|
||||||
self.size().h()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Output, T: Has<Measure<E>>> HasSize<E> for T {
|
|
||||||
fn size (&self) -> &Measure<E> {
|
|
||||||
self.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A widget that tracks its render width and height
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Measure<E: Output> {
|
|
||||||
_engine: PhantomData<E>,
|
|
||||||
pub x: Arc<AtomicUsize>,
|
|
||||||
pub y: Arc<AtomicUsize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
|
|
||||||
impl<E: Output> Content<E> for Measure<E> {
|
|
||||||
fn render (&self, to: &mut E) {
|
|
||||||
self.x.store(to.area().w().into(), Relaxed);
|
|
||||||
self.y.store(to.area().h().into(), Relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Output> Clone for Measure<E> {
|
|
||||||
fn clone (&self) -> Self {
|
|
||||||
Self {
|
|
||||||
_engine: Default::default(),
|
|
||||||
x: self.x.clone(),
|
|
||||||
y: self.y.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Output> std::fmt::Debug for Measure<E> {
|
|
||||||
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<E: Output> Measure<E> {
|
|
||||||
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<usize>) -> &Self {
|
|
||||||
self.x.store(w.into(), Relaxed);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn set_h (&self, h: impl Into<usize>) -> &Self {
|
|
||||||
self.y.store(h.into(), Relaxed);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) -> &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<str> {
|
|
||||||
format!("{}x{}", self.w(), self.h()).into()
|
|
||||||
}
|
|
||||||
pub fn of <T: Render<E>> (&self, item: T) -> Bsp<Fill<&Self>, T> {
|
|
||||||
Bsp::b(Fill::xy(self), item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
pub trait Size<N: Coordinate>: 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<N: Coordinate> Size<N> for (N, N) {
|
|
||||||
fn x (&self) -> N { self.0 }
|
|
||||||
fn y (&self) -> N { self.1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Coordinate> Size<N> for [N;2] {
|
|
||||||
fn x (&self) -> N { self[0] }
|
|
||||||
fn y (&self) -> N { self[1] }
|
|
||||||
}
|
|
||||||
|
|
@ -150,10 +150,9 @@ impl ToTokens for CommandDef {
|
||||||
}
|
}
|
||||||
/// Generated by [tengri_proc::command].
|
/// Generated by [tengri_proc::command].
|
||||||
impl ::tengri::dsl::FromDsl<#state> for #command_enum {
|
impl ::tengri::dsl::FromDsl<#state> for #command_enum {
|
||||||
fn try_from_dsl (
|
fn from_dsl (state: &#state, value: &impl ::tengri::dsl::Dsl)
|
||||||
state: &#state,
|
-> Perhaps<Self>
|
||||||
value: &impl ::tengri::dsl::Dsl
|
{
|
||||||
) -> Perhaps<Self> {
|
|
||||||
use ::tengri::dsl::Val::*;
|
use ::tengri::dsl::Val::*;
|
||||||
todo!()//Ok(match token { #(#matchers)* _ => None })
|
todo!()//Ok(match token { #(#matchers)* _ => None })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,8 @@ impl ExposeImpl {
|
||||||
write_quote_to(out, quote! {
|
write_quote_to(out, quote! {
|
||||||
/// Generated by [tengri_proc::expose].
|
/// Generated by [tengri_proc::expose].
|
||||||
impl ::tengri::dsl::FromDsl<#state> for #t {
|
impl ::tengri::dsl::FromDsl<#state> for #t {
|
||||||
fn try_from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> {
|
fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> {
|
||||||
Ok(Some(match dsl.dsl() {
|
Ok(Some(match dsl.val() {
|
||||||
#arms
|
#arms
|
||||||
_ => { return Ok(None) }
|
_ => { return Ok(None) }
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ impl ToTokens for ViewDef {
|
||||||
|
|
||||||
impl ViewDef {
|
impl ViewDef {
|
||||||
fn generated (&self) -> impl ToTokens {
|
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 self_ty = &block.self_ty;
|
||||||
let builtins = self.builtins();
|
let builtins = self.builtins();
|
||||||
let exposed = self.exposed();
|
let exposed = self.exposed();
|
||||||
|
|
@ -63,7 +63,7 @@ impl ViewDef {
|
||||||
impl<'state> ::tengri::dsl::DslInto<
|
impl<'state> ::tengri::dsl::DslInto<
|
||||||
Box<dyn ::tengri::output::Render<#output> + 'state>
|
Box<dyn ::tengri::output::Render<#output> + 'state>
|
||||||
> for #self_ty {
|
> for #self_ty {
|
||||||
fn try_dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl)
|
fn dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl)
|
||||||
-> Perhaps<Box<dyn ::tengri::output::Render<#output> + 'state>>
|
-> Perhaps<Box<dyn ::tengri::output::Render<#output> + 'state>>
|
||||||
{
|
{
|
||||||
Ok(match dsl.val() { #builtins #exposed _ => return Ok(None) })
|
Ok(match dsl.val() { #builtins #exposed _ => return Ok(None) })
|
||||||
|
|
@ -74,7 +74,7 @@ impl ViewDef {
|
||||||
/// Expressions are handled by built-in functions
|
/// Expressions are handled by built-in functions
|
||||||
/// that operate over constants and symbols.
|
/// that operate over constants and symbols.
|
||||||
fn builtins (&self) -> impl ToTokens {
|
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! {
|
let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! {
|
||||||
::tengri::dsl::Val::Exp(_, expr) => return Ok(Some(
|
::tengri::dsl::Val::Exp(_, expr) => return Ok(Some(
|
||||||
#builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))?
|
#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`.
|
/// Symbols are handled by user-taked functions that take no parameters but `&self`.
|
||||||
fn exposed (&self) -> impl ToTokens {
|
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! {
|
let exposed = exposed.iter().map(|(key, value)|write_quote(quote! {
|
||||||
#key => return Ok(Some(self.#value().boxed())),
|
#key => return Ok(Some(self.#value().boxed())),
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use std::time::Duration;
|
||||||
|
|
||||||
mod tui_buffer; pub use self::tui_buffer::*;
|
mod tui_buffer; pub use self::tui_buffer::*;
|
||||||
mod tui_input; pub use self::tui_input::*;
|
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_output; pub use self::tui_output::*;
|
||||||
mod tui_perf; pub use self::tui_perf::*;
|
mod tui_perf; pub use self::tui_perf::*;
|
||||||
|
|
||||||
|
|
@ -72,8 +71,7 @@ pub trait TuiRun<R: Render<TuiOut> + Handle<TuiIn> + 'static> {
|
||||||
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
|
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Render<TuiOut> + Handle<TuiIn> + Send + Sync + 'static>
|
impl<T: Render<TuiOut> + Handle<TuiIn> + Send + Sync + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
|
||||||
TuiRun<T> for Arc<RwLock<Tui>> {
|
|
||||||
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
|
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
|
||||||
let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100));
|
let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100));
|
||||||
self.write().unwrap().setup()?;
|
self.write().unwrap().setup()?;
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,39 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::thread::{spawn, JoinHandle};
|
use std::thread::{spawn, JoinHandle};
|
||||||
use crossterm::event::{poll, read};
|
use crossterm::event::{Event, poll, read};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TuiIn(
|
pub struct TuiIn {
|
||||||
/// Exit flag
|
/// Exit flag
|
||||||
pub Arc<AtomicBool>,
|
pub exited: Arc<AtomicBool>,
|
||||||
/// Input event
|
/// Input event
|
||||||
pub crossterm::event::Event,
|
pub event: TuiEvent,
|
||||||
);
|
}
|
||||||
|
impl Input for TuiIn {
|
||||||
impl Input for TuiIn {
|
type Event = TuiEvent;
|
||||||
type Event = crossterm::event::Event;
|
type Handled = bool;
|
||||||
type Handled = bool;
|
fn event (&self) -> &TuiEvent { &self.event }
|
||||||
fn event (&self) -> &crossterm::event::Event { &self.1 }
|
fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
|
||||||
fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) }
|
fn done (&self) { self.exited.store(true, Relaxed); }
|
||||||
fn done (&self) { self.0.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<Event> for TuiEvent {
|
||||||
|
fn from (event: Event) -> Self {
|
||||||
|
Self(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Arc<str>> for TuiEvent {
|
||||||
|
fn from (x: Arc<str>) -> Self {
|
||||||
|
TuiEvent(TuiKey::new(x.as_ref()).build().unwrap_or_else(||panic!("invalid key: {x}")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TuiIn {
|
impl TuiIn {
|
||||||
/// Spawn the input thread.
|
/// Spawn the input thread.
|
||||||
pub fn run_input <T: Handle<TuiIn> + Send + Sync + 'static> (
|
pub fn run_input <T: Handle<TuiIn> + Send + Sync + 'static> (
|
||||||
|
|
@ -35,8 +50,7 @@ impl TuiIn {
|
||||||
if poll(timer).is_ok() {
|
if poll(timer).is_ok() {
|
||||||
let event = read().unwrap();
|
let event = read().unwrap();
|
||||||
match event {
|
match event {
|
||||||
|
Event::Key(KeyEvent {
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Char('c'),
|
code: KeyCode::Char('c'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
kind: KeyEventKind::Press,
|
kind: KeyEventKind::Press,
|
||||||
|
|
@ -46,7 +60,8 @@ impl TuiIn {
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let exited = exited.clone();
|
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}")
|
panic!("{e}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,10 +74,101 @@ impl TuiIn {
|
||||||
//#[cfg(feature = "dsl")]
|
//#[cfg(feature = "dsl")]
|
||||||
//impl DslInput for TuiIn {
|
//impl DslInput for TuiIn {
|
||||||
//fn matches_dsl (&self, token: &str) -> bool {
|
//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()
|
//&event == self.event()
|
||||||
//} else {
|
//} else {
|
||||||
//false
|
//false
|
||||||
//}
|
//}
|
||||||
//}
|
//}
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
pub struct TuiKey {
|
||||||
|
valid: bool,
|
||||||
|
key: Option<KeyCode>,
|
||||||
|
mods: KeyModifiers,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TuiKey {
|
||||||
|
pub fn new (token: impl AsRef<str>) -> 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<Event> {
|
||||||
|
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<KeyCode> {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub struct KeyMatcher {
|
|
||||||
valid: bool,
|
|
||||||
key: Option<KeyCode>,
|
|
||||||
mods: KeyModifiers,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyMatcher {
|
|
||||||
pub fn new (token: impl AsRef<str>) -> 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<Event> {
|
|
||||||
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<KeyCode> {
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue