use crate::*; use std::marker::PhantomData; /// A [Command] that can be constructed from a [Token]. pub trait DslCommand<'state, C>: TryFromDsl<'state, C> + Command {} impl<'state, C, T: TryFromDsl<'state, C> + Command> DslCommand<'state, C> for T {} /// [Input] state that can be matched against a [Value]. pub trait DslInput: Input { fn matches_dsl (&self, token: &str) -> bool; } /// A pre-configured mapping of input events to commands. pub trait KeyMap<'state, S, C: DslCommand<'state, S>, I: DslInput> { /// Try to find a command that matches the current input event. fn command (&'state self, state: &'state S, input: &'state I) -> Option; } /// A [SourceIter] can be a [KeyMap]. impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> for SourceIter<'state> { fn command (&'state self, state: &'state S, input: &'state I) -> Option { let mut iter = self.clone(); while let Some((token, rest)) = iter.next() { iter = rest; match token { Token { value: Value::Exp(0, exp_iter), .. } => { let mut exp_iter = exp_iter.clone(); match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { return Some(command) } } }, _ => panic!("invalid config (expected symbol)") } }, _ => panic!("invalid config (expected expression)") } } None } } /// A [TokenIter] can be a [KeyMap]. impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> for TokenIter<'state> { fn command (&'state self, state: &'state S, input: &'state I) -> Option { let mut iter = self.clone(); while let Some(next) = iter.next() { match next { Token { value: Value::Exp(0, exp_iter), .. } => { let mut exp_iter = exp_iter.clone(); match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { return Some(command) } } }, _ => panic!("invalid config (expected symbol)") } }, _ => panic!("invalid config (expected expression)") } } None } } pub type InputLayerCond<'state, S> = Boxbool + Send + Sync + 'state>; /// A collection of pre-configured mappings of input events to commands, /// which may be made available subject to given conditions. pub struct InputMap<'state, S, C, I, M> where C: DslCommand<'state, S>, I: DslInput, M: KeyMap<'state, S, C, I> + Send + Sync { __: &'state PhantomData<(S, C, I)>, pub layers: Vec<(InputLayerCond<'state, S>, M)>, } impl<'state, S, C, I, M> Default for InputMap<'state, S, C, I, M> where C: DslCommand<'state, S>, I: DslInput, M: KeyMap<'state, S, C, I> + Send + Sync { fn default () -> Self { Self { __: &PhantomData, layers: vec![] } } } impl<'state, S, C, I, M> InputMap<'state, S, C, I, M> where C: DslCommand<'state, S>, I: DslInput, M: KeyMap<'state, S, C, I> + Send + Sync { pub fn new (keymap: M) -> Self { Self::default().layer(keymap) } pub fn layer (mut self, keymap: M) -> Self { self.add_layer(keymap); self } pub fn add_layer (&mut self, keymap: M) -> &mut Self { self.add_layer_if(Box::new(|_|true), keymap); self } pub fn layer_if (mut self, condition: InputLayerCond<'state, S>, keymap: M) -> Self { self.add_layer_if(condition, keymap); self } pub fn add_layer_if (&mut self, condition: InputLayerCond<'state, S>, keymap: M) -> &mut Self { self.layers.push((Box::new(condition), keymap)); self } } impl<'state, S, C, I, M> std::fmt::Debug for InputMap<'state, S, C, I, M> where C: DslCommand<'state, S>, I: DslInput, M: KeyMap<'state, S, C, I> + Send + Sync { fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!(f, "[InputMap: {} layer(s)]", self.layers.len()); Ok(()) } } /// An [InputMap] can be a [KeyMap]. impl<'state, S, C, I, M> KeyMap<'state, S, C, I> for InputMap<'state, S, C, I, M> where C: DslCommand<'state, S>, I: DslInput, M: KeyMap<'state, S, C, I> + Send + Sync { fn command (&'state self, state: &'state S, input: &'state I) -> Option { for (condition, keymap) in self.layers.iter() { if !condition(state) { continue } if let Some(command) = keymap.command(state, input) { return Some(command) } } None } }