core, input, output, dsl: factor away 'flexi' traits

This commit is contained in:
🪞👃🪞 2025-07-29 17:03:48 +03:00
parent 8cbd7dd8e8
commit 9f7d0efda5
11 changed files with 355 additions and 339 deletions

View file

@ -1,5 +1,25 @@
use crate::*;
/// Event source
pub trait Input: Sized {
/// Type of input event
type Event;
/// Result of handling input
type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo
/// Currently handled event
fn event (&self) -> &Self::Event;
/// Whether component should exit
fn is_done (&self) -> bool;
/// Mark component as done
fn done (&self);
}
flex_trait_mut!(Handle <E: Input> {
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
Ok(None)
}
});
pub trait Command<S>: Send + Sync + Sized {
fn execute (self, state: &mut S) -> Perhaps<Self>;
fn delegate <T> (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>

View file

@ -1,8 +1,10 @@
use crate::*;
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
type EventMapImpl<E, C> = BTreeMap<E, Vec<Binding<C>>>;
/// A collection of input bindings.
///
/// Each contained layer defines a mapping from input event to command invocation
@ -14,8 +16,26 @@ type EventMapImpl<E, C> = BTreeMap<E, Vec<Binding<C>>>;
/// that .event()binding's value is returned.
#[derive(Debug)]
pub struct EventMap<E, C>(EventMapImpl<E, C>);
/// An input binding.
#[derive(Debug)]
pub struct Binding<C> {
pub command: C,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
/// Input bindings are only returned if this evaluates to true
pub struct Condition(Box<dyn Fn()->bool + Send + Sync>);
impl_debug!(Condition |self, w| { write!(w, "*") });
/// Default is always empty map regardless if `E` and `C` implement [Default].
impl<E, C> Default for EventMap<E, C> { fn default () -> Self { Self(Default::default()) } }
impl<E, C> Default for EventMap<E, C> {
fn default () -> Self { Self(Default::default()) }
}
impl<E: Clone + Ord, C> EventMap<E, C> {
/// Create a new event map
pub fn new () -> Self {
@ -28,7 +48,7 @@ impl<E: Clone + Ord, C> EventMap<E, C> {
}
/// Add a binding to an event map.
pub fn add (&mut self, event: E, binding: Binding<C>) -> &mut Self {
if (!self.0.contains_key(&event)) {
if !self.0.contains_key(&event) {
self.0.insert(event.clone(), Default::default());
}
self.0.get_mut(&event).unwrap().push(binding);
@ -57,47 +77,46 @@ impl<E: Clone + Ord, C> EventMap<E, C> {
Self::from_dsl(&mut source.as_ref())
}
/// Create event map from DSL tokenizer.
pub fn from_dsl <'s, D: DslExp<'s> + 's> (dsl: &'s mut D) -> Usually<Self> where E: From<Arc<str>> {
pub fn from_dsl <'s, > (dsl: &'s mut impl Dsl) -> Usually<Self> where E: From<Arc<str>> {
let mut map: Self = Default::default();
let mut head: Option<D> = dsl.head()?;
let mut tail: Option<D> = 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(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
}
//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)
}
}
/// An input binding.
#[derive(Debug)]
pub struct Binding<C> {
pub command: C,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
impl<C> Binding<C> {
fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
let mut command: Option<C> = None;
@ -111,8 +130,6 @@ impl<C> Binding<C> {
}
}
}
pub struct Condition(Box<dyn Fn()->bool + Send + Sync>);
impl_debug!(Condition |self, w| { write!(w, "*") });
fn unquote (x: &str) -> &str {
let mut chars = x.chars();

View file

@ -1,82 +0,0 @@
use crate::*;
use std::sync::{Mutex, Arc, RwLock};
/// Event source
pub trait Input: Sized {
/// Type of input event
type Event;
/// Result of handling input
type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo
/// Currently handled event
fn event (&self) -> &Self::Event;
/// Whether component should exit
fn is_done (&self) -> bool;
/// Mark component as done
fn done (&self);
}
/// Implement the [Handle] trait.
#[macro_export] macro_rules! handle {
(|$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl<E: Engine> ::tengri::input::Handle<E> for $Struct {
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
$handler
}
}
};
($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl ::tengri::input::Handle<$E> for $Struct {
fn handle (&mut $self, $input: &$E) ->
Perhaps<<$E as ::tengri::input::Input>::Handled>
{
$handler
}
}
}
}
flex_trait_mut!(Handle <E: Input> {
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
Ok(None)
}
});
//pub trait Handle<E: Input> {
//fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
//Ok(None)
//}
//}
//impl<E: Input, H: Handle<E>> Handle<E> for &mut H {
//fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
//(*self).handle(context)
//}
//}
//impl<E: Input, H: Handle<E>> Handle<E> for Option<H> {
//fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
//if let Some(handle) = self {
//handle.handle(context)
//} else {
//Ok(None)
//}
//}
//}
//impl<H, E: Input> Handle<E> for Mutex<H> where H: Handle<E> {
//fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
//self.get_mut().unwrap().handle(context)
//}
//}
//impl<H, E: Input> Handle<E> for Arc<Mutex<H>> where H: Handle<E> {
//fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
//self.lock().unwrap().handle(context)
//}
//}
//impl<H, E: Input> Handle<E> for RwLock<H> where H: Handle<E> {
//fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
//self.write().unwrap().handle(context)
//}
//}
//impl<H, E: Input> Handle<E> for Arc<RwLock<H>> where H: Handle<E> {
//fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
//self.write().unwrap().handle(context)
//}
//}

View file

@ -1,10 +1,30 @@
/** Implement `Command` for given `State` and `handler` */
/// Implement [Command] for given `State` and `handler`
#[macro_export] macro_rules! command {
($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
impl$(<$($l),+>)? Command<$State> for $Command {
impl$(<$($l),+>)? ::tengri::input::Command<$State> for $Command {
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
Ok($handler)
}
}
};
}
/// Implement [Handle] for given `State` and `handler`.
#[macro_export] macro_rules! handle {
(|$self:ident:$State:ty,$input:ident|$handler:expr) => {
impl<E: Engine> ::tengri::input::Handle<E> for $State {
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
$handler
}
}
};
($E:ty: |$self:ident:$State:ty,$input:ident|$handler:expr) => {
impl ::tengri::input::Handle<$E> for $State {
fn handle (&mut $self, $input: &$E) ->
Perhaps<<$E as ::tengri::input::Input>::Handled>
{
$handler
}
}
}
}

28
input/src/input_test.rs Normal file
View file

@ -0,0 +1,28 @@
use crate::*;
#[test] fn test_stub_input () -> Usually<()> {
use crate::*;
struct TestInput(bool);
enum TestEvent { Test1 }
impl Input for TestInput {
type Event = TestEvent;
type Handled = ();
fn event (&self) -> &Self::Event {
&TestEvent::Test1
}
fn is_done (&self) -> bool {
self.0
}
fn done (&self) {}
}
let _ = TestInput(true).event();
assert!(TestInput(true).is_done());
assert!(!TestInput(false).is_done());
Ok(())
}
#[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> {
let _keymap = CstIter::new("");
Ok(())
}

View file

@ -3,41 +3,14 @@
pub(crate) use std::fmt::Debug;
pub(crate) use std::sync::Arc;
pub(crate) use std::collections::{BTreeMap, HashMap};
pub(crate) use std::collections::BTreeMap;
pub(crate) use std::path::{Path, PathBuf};
pub(crate) use std::fs::exists;
pub(crate) use tengri_core::*;
mod input_macros;
mod input_command; pub use self::input_command::*;
mod input_handle; pub use self::input_handle::*;
mod input; pub use self::input::*;
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
#[cfg(feature = "dsl")] mod input_dsl;
#[cfg(feature = "dsl")] pub use self::input_dsl::*;
#[cfg(test)] #[test] fn test_stub_input () -> Usually<()> {
use crate::*;
struct TestInput(bool);
enum TestEvent { Test1 }
impl Input for TestInput {
type Event = TestEvent;
type Handled = ();
fn event (&self) -> &Self::Event {
&TestEvent::Test1
}
fn is_done (&self) -> bool {
self.0
}
fn done (&self) {}
}
let _ = TestInput(true).event();
assert!(TestInput(true).is_done());
assert!(!TestInput(false).is_done());
Ok(())
}
#[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> {
let _keymap = CstIter::new("");
Ok(())
}
#[cfg(test)] mod input_test;