mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2025-12-08 20:56:44 +01:00
136 lines
5.5 KiB
Rust
136 lines
5.5 KiB
Rust
use crate::*;
|
|
use std::{sync::Arc, collections::BTreeMap, path::{Path, PathBuf}, fs::{exists, read_to_string}};
|
|
/// 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
|
|
/// over a given state. Furthermore, each layer may have an associated cond,
|
|
/// so that only certain layers are active at a given time depending on state.
|
|
///
|
|
/// When a key is pressed, the bindings for it are checked in sequence.
|
|
/// 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>(pub EventMapImpl<E, C>);
|
|
/// An input binding.
|
|
#[derive(Debug, Clone)]
|
|
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
|
|
#[derive(Clone)]
|
|
pub struct Condition(Arc<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: Clone + Ord, C> EventMap<E, C> {
|
|
/// Create a new event map
|
|
pub fn new () -> Self {
|
|
Default::default()
|
|
}
|
|
/// Add a binding to an owned event map.
|
|
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
|
|
self.add(event, binding);
|
|
self
|
|
}
|
|
/// 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) {
|
|
self.0.insert(event.clone(), Default::default());
|
|
}
|
|
self.0.get_mut(&event).unwrap().push(binding);
|
|
self
|
|
}
|
|
/// Return the binding(s) that correspond to an event.
|
|
pub fn query (&self, event: &E) -> Option<&[Binding<C>]> {
|
|
self.0.get(event).map(|x|x.as_slice())
|
|
}
|
|
/// Return the first binding that corresponds to an event, considering conditions.
|
|
pub fn dispatch (&self, event: &E) -> Option<&Binding<C>> {
|
|
self.query(event)
|
|
.map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next())
|
|
.flatten()
|
|
}
|
|
/// Create event map from path to text file.
|
|
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())? {
|
|
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 DSL tokenizer.
|
|
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> {
|
|
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;
|
|
let mut source: Option<Arc<PathBuf>> = None;
|
|
if let Some(command) = command {
|
|
Ok(Self { command, condition, description, source })
|
|
} else {
|
|
Err(format!("no command in {dsl:?}").into())
|
|
}
|
|
}
|
|
}
|
|
fn unquote (x: &str) -> &str {
|
|
let mut chars = x.chars();
|
|
chars.next();
|
|
//chars.next_back();
|
|
chars.as_str()
|
|
}
|