use crate::*; pub struct EventMap<'a, S, I: PartialEq, C> { pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option)], pub fallback: Option<&'a dyn Fn(&S, &I) -> Option> } impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> { pub fn handle (&self, state: &S, input: &I) -> Option { for (binding, handler) in self.bindings.iter() { if input == binding { return handler(state) } } if let Some(fallback) = self.fallback { fallback(state, input) } else { None } } } #[macro_export] macro_rules! keymap { ( $(<$lt:lifetime>)? $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty { $($key:expr => $handler:expr),* $(,)? } $(,)? ) => { pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap { fallback: None, bindings: &[ $(($key, &|$state|Some($handler)),)* ] }; input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?); }; ( $(<$lt:lifetime>)? $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty { $($key:expr => $handler:expr),* $(,)? }, $default:expr ) => { pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap { fallback: Some(&|$state, $input|Some($default)), bindings: &[ $(($key, &|$state|Some($handler)),)* ] }; input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?); }; } #[macro_export] macro_rules! input_to_command { (<$($l:lifetime),+> $Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => { impl<$($l),+> InputToCommand<$Input, $State> for $Command { fn input_to_command ($state: &$State, $input: &$Input) -> Option { Some($handler) } } }; ($Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => { impl InputToCommand<$Input, $State> for $Command { fn input_to_command ($state: &$State, $input: &$Input) -> Option { Some($handler) } } } } pub trait InputToCommand: Command + Sized { fn input_to_command (state: &S, input: &I) -> Option; fn execute_with_state (state: &mut S, input: &I) -> Perhaps { Ok(if let Some(command) = Self::input_to_command(state, input) { let _undo = command.execute(state)?; Some(true) } else { None }) } }