mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2025-12-06 03:36:42 +01:00
This commit is contained in:
parent
b52c1f5828
commit
24ac52d807
10 changed files with 238 additions and 203 deletions
|
|
@ -17,12 +17,14 @@ mod dsl_conv; pub use self::dsl_conv::*;
|
|||
pub type DslResult<T> = Result<T, DslError>;
|
||||
/// DSL-specific optional result type.
|
||||
pub type DslPerhaps<T> = Result<Option<T>, DslError>;
|
||||
// Some things that can be DSL source:
|
||||
impl Dsl for String { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } }
|
||||
impl Dsl for Arc<str> { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } }
|
||||
impl<'s> Dsl for &'s str { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } }
|
||||
// Designates a string as parsable DSL.
|
||||
flex_trait!(Dsl: Debug + Send + Sync + Sized {
|
||||
fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") }
|
||||
});
|
||||
impl Dsl for Arc<str> { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } }
|
||||
impl<'s> Dsl for &'s str { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } }
|
||||
impl<D: Dsl> Dsl for Option<D> {
|
||||
fn src (&self) -> DslPerhaps<&str> {Ok(if let Some(dsl) = self { dsl.src()? } else { None })}
|
||||
}
|
||||
|
|
@ -45,12 +47,11 @@ impl<D: Dsl> DslExp for D {} pub trait DslExp: Dsl {
|
|||
fn exp (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(exp_peek_inner_only))}
|
||||
fn head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))}
|
||||
fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail))}
|
||||
/// my other car is a cdr :<
|
||||
fn each (&self, mut cb: impl FnMut(&str)->Usually<()>) -> Usually<()> {
|
||||
Ok(if let Some(head) = self.head()? {
|
||||
println!("| HEAD ->\n{head}");
|
||||
cb(head)?;
|
||||
if let Some(tail) = self.tail()? {
|
||||
println!("| TAIL ->\n{tail}");
|
||||
tail.each(cb)?;
|
||||
}
|
||||
})
|
||||
|
|
@ -194,7 +195,7 @@ pub const fn exp_seek_length (source: &str) -> DslPerhaps<usize> {
|
|||
}
|
||||
def_peek_seek!(sym_peek, sym_peek_only, sym_seek, sym_seek_start, sym_seek_length);
|
||||
pub const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') }
|
||||
pub const fn is_sym_char (c: char) -> bool { matches!(c, ':'|'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/') }
|
||||
pub const fn is_sym_char (c: char) -> bool { is_sym_start(c) || matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/') }
|
||||
pub const fn is_sym_end (c: char) -> bool { is_space(c) || matches!(c, ')') }
|
||||
pub const fn sym_seek_start (source: &str) -> DslPerhaps<usize> {
|
||||
for_each!((i, c) in char_indices(source) => if is_sym_start(c) {
|
||||
|
|
|
|||
|
|
@ -12,24 +12,24 @@ pub trait FromDsl<T>: Sized {
|
|||
}
|
||||
|
||||
/// `self` + [Dsl] -> `T`
|
||||
pub trait DslInto<'s, T> {
|
||||
fn dsl_into (&'s self, dsl: &impl Dsl) -> Perhaps<T>;
|
||||
fn dsl_into_or (&'s self, dsl: &impl Dsl, err: Box<dyn Error>) -> Usually<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 (&'s self, dsl: &impl Dsl, err: impl Fn()->Box<dyn Error>) -> Usually<T> {
|
||||
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 DslFrom<T> {
|
||||
fn dsl_from (&self, dsl: &T) -> Perhaps<impl Dsl>;
|
||||
fn dsl_from_or (&self, dsl: &T, err: Box<dyn Error>) -> Usually<impl Dsl> {
|
||||
self.dsl_from(dsl)?.ok_or(err)
|
||||
fn dsl_from (&self, val: &T) -> Perhaps<impl Dsl>;
|
||||
fn dsl_from_or (&self, val: &T, err: Box<dyn Error>) -> Usually<impl Dsl> {
|
||||
self.dsl_from(val)?.ok_or(err)
|
||||
}
|
||||
fn dsl_from_or_else (&self, dsl: &T, err: impl Fn()->Box<dyn Error>) -> Usually<impl Dsl> {
|
||||
self.dsl_from(dsl)?.ok_or_else(err)
|
||||
fn dsl_from_or_else (&self, val: &T, err: impl Fn()->Box<dyn Error>) -> Usually<impl Dsl> {
|
||||
self.dsl_from(val)?.ok_or_else(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,14 +44,14 @@ pub trait IntoDsl<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'s, T: FromDsl<U>, U> DslInto<'s, T> for U {
|
||||
fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps<T> {
|
||||
T::from_dsl(self, dsl)
|
||||
}
|
||||
}
|
||||
//impl<'s, T: FromDsl<U>, U> DslInto<'s, T> for U {
|
||||
//fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps<T> {
|
||||
//T::from_dsl(self, dsl)
|
||||
//}
|
||||
//}
|
||||
|
||||
impl<T: DslFrom<U>, U> IntoDsl<T> for U {
|
||||
fn into_dsl (&self, state: &T) -> Perhaps<impl Dsl> {
|
||||
T::dsl_from(state, self)
|
||||
}
|
||||
}
|
||||
//impl<T: DslFrom<U>, U> IntoDsl<T> for U {
|
||||
//fn into_dsl (&self, state: &T) -> Perhaps<impl Dsl> {
|
||||
//T::dsl_from(state, self)
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ type EventMapImpl<E, C> = BTreeMap<E, Vec<Binding<C>>>;
|
|||
/// When the first non-conditional or true conditional binding is executed,
|
||||
/// that .event()binding's value is returned.
|
||||
#[derive(Debug)]
|
||||
pub struct EventMap<E, C>(EventMapImpl<E, C>);
|
||||
pub struct EventMap<E, C>(pub EventMapImpl<E, C>);
|
||||
/// An input binding.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Binding<C> {
|
||||
|
|
@ -60,59 +60,63 @@ impl<E: Clone + Ord, C> EventMap<E, C> {
|
|||
.flatten()
|
||||
}
|
||||
/// Create event map from path to text file.
|
||||
pub fn from_path <P: AsRef<Path>> (path: P) -> Usually<Self> where E: From<Arc<str>> {
|
||||
pub fn load_from_path <'s> (
|
||||
&'s mut self, path: impl AsRef<Path>
|
||||
) -> Usually<&mut Self> where Self: DslInto<C> + DslInto<E> {
|
||||
if exists(path.as_ref())? {
|
||||
Self::from_source(read_to_string(path)?)
|
||||
let source = read_to_string(&path)?;
|
||||
let path: Arc<PathBuf> = Arc::new(path.as_ref().into());
|
||||
self.load_from_source(&source, &Some(&path))
|
||||
} else {
|
||||
return Err(format!("(e5) not found: {:?}", path.as_ref()).into())
|
||||
}
|
||||
}
|
||||
/// Create event map from string.
|
||||
pub fn from_source (source: impl AsRef<str>) -> Usually<Self> where E: From<Arc<str>> {
|
||||
Self::from_dsl(&mut source.as_ref())
|
||||
}
|
||||
/// Create event map from DSL tokenizer.
|
||||
pub fn from_dsl <'s, > (dsl: &'s mut impl Dsl) -> Usually<Self> where E: From<Arc<str>> {
|
||||
let mut map: Self = Default::default();
|
||||
if let Some(dsl) = dsl.exp()? {
|
||||
let mut head = dsl.head()?;
|
||||
let mut tail = dsl.tail()?;
|
||||
loop {
|
||||
if let Some(ref token) = head {
|
||||
if let Some(ref text) = token.text()? {
|
||||
map.0.extend(Self::from_path(PathBuf::from(text))?.0);
|
||||
continue
|
||||
}
|
||||
//if let Some(ref exp) = token.exp()? {
|
||||
////_ if let Some(sym) = token.head()?.sym()?.as_ref() => {
|
||||
////todo!()
|
||||
////},
|
||||
////_ if Some(&"if") == token.head()?.key()?.as_ref() => {
|
||||
////todo!()
|
||||
////},
|
||||
//return Err(format!("unexpected: {:?}", token.exp()).into())
|
||||
//}
|
||||
return Err(format!("unexpected: {token:?}").into())
|
||||
} else {
|
||||
break
|
||||
}
|
||||
if let Some(next) = tail {
|
||||
head = next.head()?;
|
||||
tail = next.tail()?;
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let Some(text) = dsl.text()? {
|
||||
todo!("load from file path")
|
||||
} else {
|
||||
todo!("return error for invalid input")
|
||||
}
|
||||
Ok(map)
|
||||
pub fn load_from_source (
|
||||
&mut self, dsl: impl Dsl, path: &Option<&Arc<PathBuf>>
|
||||
) -> Usually<&mut Self> where Self: DslInto<C> + DslInto<E> {
|
||||
dsl.each(|dsl|self.load_from_source_one(&dsl, path).map(move|_|()))?;
|
||||
Ok(self)
|
||||
}
|
||||
/// Load one event binding into the event map.
|
||||
pub fn load_from_source_one <'s> (
|
||||
&'s mut self, dsl: impl Dsl, path: &Option<&Arc<PathBuf>>
|
||||
) -> Usually<&mut Self> where Self: DslInto<C> + DslInto<E> {
|
||||
if let Some(exp) = dsl.head()?.exp()?
|
||||
&& let Some(sym) = exp.head()?.sym()?
|
||||
&& let Some(tail) = exp.tail()?
|
||||
{
|
||||
let event = self.dsl_into_or_else(&sym, ||panic!())?;
|
||||
let command = self.dsl_into_or_else(&tail, ||panic!())?;
|
||||
Ok(self.add(event, Binding {
|
||||
command, condition: None, description: None, source: path.cloned()
|
||||
}))
|
||||
} else {
|
||||
Err(format!("unexpected: {:?}", dsl.head()?).into())
|
||||
}
|
||||
}
|
||||
//})Ok(if let Some(sym) = dsl.head()?.exp()?.head()?.sym()? {
|
||||
//if let Some(tail) = dsl.head()?.exp()?.tail()? {
|
||||
//let event: E = sym.into();
|
||||
//let binding: Binding<C> = Binding { command: tail.into(), condition: None, description: None, source: None };
|
||||
//if let Some(bindings) = map.0.get_mut(&event) {
|
||||
//bindings.push(binding);
|
||||
//} else {
|
||||
//map.0.insert(event, vec![binding]);
|
||||
//}
|
||||
//} else {
|
||||
//panic!("empty binding: {}", dsl.head()?.exp()?.unwrap_or_default())
|
||||
//}
|
||||
//} else if let Some(ref text) = dsl.text()? {
|
||||
//map.0.extend(Self::from_path(PathBuf::from(text))?.0);
|
||||
//} else {
|
||||
//return Err(format!("unexpected: {dsl:?}").into())
|
||||
//}));
|
||||
//Ok(map)
|
||||
//}
|
||||
}
|
||||
impl<C> Binding<C> {
|
||||
fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
|
||||
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
|
||||
let mut command: Option<C> = None;
|
||||
let mut condition: Option<Condition> = None;
|
||||
let mut description: Option<Arc<str>> = None;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ pub trait Output: Send + Sync + Sized {
|
|||
/// Mutable pointer to area
|
||||
fn area_mut (&mut self) -> &mut Self::Area;
|
||||
/// Render widget in area
|
||||
fn place <T: Render<Self> + ?Sized> (&mut self, area: Self::Area, content: &T);
|
||||
fn place <'t, T: Render<Self> + ?Sized> (&mut self, area: Self::Area, content: &'t T);
|
||||
#[inline] fn x (&self) -> Self::Unit { self.area().x() }
|
||||
#[inline] fn y (&self) -> Self::Unit { self.area().y() }
|
||||
#[inline] fn w (&self) -> Self::Unit { self.area().w() }
|
||||
|
|
|
|||
|
|
@ -7,25 +7,26 @@ pub(crate) struct ViewDef(pub(crate) ViewMeta, pub(crate) ViewImpl);
|
|||
pub(crate) struct ViewMeta { pub(crate) output: Ident }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ViewImpl {
|
||||
block: ItemImpl,
|
||||
exposed: BTreeMap<String, Ident>,
|
||||
}
|
||||
pub(crate) struct ViewImpl { block: ItemImpl, exposed: BTreeMap<String, Ident>, }
|
||||
|
||||
/// `#[view]` takes 1 arg ([Engine] implementation for which it's valid).
|
||||
impl Parse for ViewMeta {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
Ok(Self {
|
||||
output: input.parse::<Ident>()?,
|
||||
})
|
||||
Ok(Self { output: input.parse::<Ident>()?, })
|
||||
}
|
||||
}
|
||||
|
||||
/// `#[view]` exposes each function as corresponding symbol.
|
||||
///
|
||||
/// Maybe it's best to genericize that pattern rather than have
|
||||
/// 3 whole things for the layers of the program.
|
||||
impl Parse for ViewImpl {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
let block = input.parse::<ItemImpl>()?;
|
||||
let mut exposed: BTreeMap<String, Ident> = Default::default();
|
||||
for item in block.items.iter() {
|
||||
if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, .. }, .. }) = item {
|
||||
if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, inputs, .. }, .. }) = item
|
||||
&& inputs.len() == 1 {
|
||||
let key = format!(":{}", AsKebabCase(format!("{}", &ident)));
|
||||
if exposed.contains_key(&key) {
|
||||
return Err(input.error(format!("already defined: {ident}")));
|
||||
|
|
@ -37,6 +38,7 @@ impl Parse for ViewImpl {
|
|||
}
|
||||
}
|
||||
|
||||
/// `#[view]`'s output is a generated block of symbol bindings.
|
||||
impl ToTokens for ViewDef {
|
||||
fn to_tokens (&self, out: &mut TokenStream2) {
|
||||
let Self(_, ViewImpl { block, .. }) = self;
|
||||
|
|
@ -78,10 +80,8 @@ impl ViewDef {
|
|||
/// Makes [#self_ty] able to construct the [Render]able
|
||||
/// which might correspond to a given [TokenStream],
|
||||
/// while taking [#self_ty]'s state into consideration.
|
||||
impl<'state> ::tengri::dsl::DslInto<'state,
|
||||
Box<dyn ::tengri::output::Render<#output> + 'state>
|
||||
> for #self_ty {
|
||||
fn dsl_into (&'state self, dsl: &impl ::tengri::dsl::Dsl)
|
||||
impl<'state> ::tengri::dsl::DslInto<Box<dyn ::tengri::output::Render<#output> + 'state>> for #self_ty {
|
||||
fn dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl)
|
||||
-> Perhaps<Box<dyn ::tengri::output::Render<#output> + 'state>>
|
||||
{
|
||||
#(#builtins)*
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
mod tui_engine; pub use self::tui_engine::*;
|
||||
mod tui_content; pub use self::tui_content::*;
|
||||
pub(crate) use ::tengri_core::*;
|
||||
#[cfg(feature = "dsl")] pub use ::tengri_dsl::*;
|
||||
pub use ::tengri_input as input; pub(crate) use ::tengri_input::*;
|
||||
pub use ::tengri_output as output; pub(crate) use ::tengri_output::*;
|
||||
pub(crate) use atomic_float::AtomicF64;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::time::Duration;
|
|||
|
||||
mod tui_buffer; pub use self::tui_buffer::*;
|
||||
mod tui_input; pub use self::tui_input::*;
|
||||
mod tui_event; pub use self::tui_event::*;
|
||||
mod tui_output; pub use self::tui_output::*;
|
||||
mod tui_perf; pub use self::tui_perf::*;
|
||||
|
||||
|
|
|
|||
147
tui/src/tui_engine/tui_event.rs
Normal file
147
tui/src/tui_engine/tui_event.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
use crate::*;
|
||||
#[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 TuiEvent {
|
||||
pub fn from_crossterm (event: Event) -> Self {
|
||||
Self(event)
|
||||
}
|
||||
pub fn from_dsl (dsl: impl Dsl) -> Perhaps<Self> {
|
||||
Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self))
|
||||
}
|
||||
}
|
||||
pub struct TuiKey(Option<KeyCode>, KeyModifiers);
|
||||
impl TuiKey {
|
||||
const SPLIT: char = '/';
|
||||
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
|
||||
if let Some(symbol) = dsl.sym()? {
|
||||
let symbol = symbol.trim();
|
||||
Ok(if symbol == ":char" {
|
||||
Self(None, KeyModifiers::NONE)
|
||||
} else if symbol.chars().nth(0) == Some('@') {
|
||||
let mut key = None;
|
||||
let mut modifiers = KeyModifiers::NONE;
|
||||
let mut tokens = symbol[1..].split(Self::SPLIT).peekable();
|
||||
while let Some(token) = tokens.next() {
|
||||
println!("{token}");
|
||||
if tokens.peek().is_some() {
|
||||
match token {
|
||||
"ctrl" | "Ctrl" | "c" | "C" => modifiers |= KeyModifiers::CONTROL,
|
||||
"alt" | "Alt" | "m" | "M" => modifiers |= KeyModifiers::ALT,
|
||||
"shift" | "Shift" | "s" | "S" => {
|
||||
modifiers |= KeyModifiers::SHIFT;
|
||||
// + TODO normalize character case, BackTab, etc.
|
||||
},
|
||||
_ => panic!("unknown modifier {token}"),
|
||||
}
|
||||
} else {
|
||||
key = if token.len() == 1 {
|
||||
Some(KeyCode::Char(token.chars().next().unwrap()))
|
||||
} else {
|
||||
Some(named_key(token).unwrap_or_else(||panic!("unknown character {token}")))
|
||||
}
|
||||
}
|
||||
}
|
||||
Self(key, modifiers)
|
||||
} else {
|
||||
return Err(format!("TuiKey: unexpected: {symbol}").into())
|
||||
})
|
||||
} else {
|
||||
return Err(format!("TuiKey: unspecified").into())
|
||||
}
|
||||
}
|
||||
pub fn to_crossterm (&self) -> Option<Event> {
|
||||
self.0.map(|code|Event::Key(KeyEvent {
|
||||
code,
|
||||
modifiers: self.1,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}))
|
||||
}
|
||||
}
|
||||
pub 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,
|
||||
"backspace" => Backspace,
|
||||
"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,
|
||||
})
|
||||
}
|
||||
//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
|
||||
//}
|
||||
|
|
@ -17,23 +17,6 @@ impl Input for TuiIn {
|
|||
fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
|
||||
fn done (&self) { self.exited.store(true, Relaxed); }
|
||||
}
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
|
||||
pub struct TuiEvent(Event);
|
||||
impl Ord for TuiEvent {
|
||||
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other) .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf
|
||||
}
|
||||
}
|
||||
impl From<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 {
|
||||
/// Spawn the input thread.
|
||||
pub fn run_input <T: Handle<TuiIn> + Send + Sync + 'static> (
|
||||
|
|
@ -60,7 +43,7 @@ impl TuiIn {
|
|||
},
|
||||
_ => {
|
||||
let exited = exited.clone();
|
||||
let event = event.into();
|
||||
let event = TuiEvent::from_crossterm(event);
|
||||
if let Err(e) = state.write().unwrap().handle(&TuiIn { exited, event }) {
|
||||
panic!("{e}")
|
||||
}
|
||||
|
|
@ -70,105 +53,3 @@ impl TuiIn {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
//#[cfg(feature = "dsl")]
|
||||
//impl DslInput for TuiIn {
|
||||
//fn matches_dsl (&self, token: &str) -> bool {
|
||||
//if let Some(event) = TuiKey::new(token).build() {
|
||||
//&event == self.event()
|
||||
//} else {
|
||||
//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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ impl Output for TuiOut {
|
|||
type Area = [Self::Unit;4];
|
||||
#[inline] fn area (&self) -> [u16;4] { self.area }
|
||||
#[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area }
|
||||
#[inline] fn place <T: Render<Self> + ?Sized> (&mut self, area: [u16;4], content: &T) {
|
||||
#[inline] fn place <'t, T: Render<Self> + ?Sized> (&mut self, area: [u16;4], content: &'t T) {
|
||||
let last = self.area();
|
||||
*self.area_mut() = area;
|
||||
content.render(self);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue