tek/src/bind.rs
okay stopped screaming 244e2b388e wip: nomralize
2026-03-21 23:29:20 +02:00

130 lines
4.3 KiB
Rust

use crate::*;
/// A control axis.
///
/// ```
/// let axis = tek::ControlAxis::X;
/// ```
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
X, Y, Z, I
}
/// Collection of input bindings.
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, Bind<TuiEvent, Arc<str>>>>>;
pub(crate) fn load_bind (binds: &Binds, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
binds.write().unwrap().insert(name.as_ref().into(), Bind::load(body)?);
Ok(())
}
/// An map of input events (e.g. [TuiEvent]) to [Binding]s.
///
/// ```
/// let lang = "(@x (nop)) (@y (nop) (nop))";
/// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap();
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
/// ```
#[derive(Debug)] pub struct Bind<E, C>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
pub BTreeMap<E, Vec<Binding<C>>>
);
/// A sequence of zero or more commands (e.g. [AppCommand]),
/// optionally filtered by [Condition] to form layers.
///
/// ```
/// //FIXME: Why does it overflow?
/// //let binding: Binding<()> = tek::Binding { ..Default::default() };
/// ```
#[derive(Debug, Clone)] pub struct Binding<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
/// Condition that must evaluate to true in order to enable an input layer.
///
/// ```
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
/// ```
#[derive(Clone)] pub struct Condition(
pub Arc<Box<dyn Fn()->bool + Send + Sync>>
);
impl Bind<TuiEvent, Arc<str>> {
pub fn load (lang: &impl Language) -> Usually<Self> {
let mut map = Bind::new();
lang.each(|item|if item.expr().head() == Ok(Some("see")) {
// TODO
Ok(())
} else if let Ok(Some(_word)) = item.expr().head().word() {
if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? {
map.add(key, Binding {
commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(),
condition: None,
description: None,
source: None
});
Ok(())
} else if Some(":char") == item.expr()?.head()? {
// TODO
return Ok(())
} else {
return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into())
}
} else {
return Err(format!("Config::load_bind: unexpected: {item:?}").into())
})?;
Ok(map)
}
}
/// Default is always empty map regardless if `E` and `C` implement [Default].
impl<E, C> Default for Bind<E, C> {
fn default () -> Self { Self(Default::default()) }
}
impl<C: Default> Default for Binding<C> {
fn default () -> Self {
Self {
commands: Default::default(),
condition: Default::default(),
description: Default::default(),
source: Default::default(),
}
}
}
impl<E: Clone + Ord, C> Bind<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()
}
}
impl_debug!(Condition |self, w| { write!(w, "*") });