input: refactor, move dsl stuff to tek config
Some checks are pending
/ build (push) Waiting to run

This commit is contained in:
🪞👃🪞 2025-08-23 13:47:45 +03:00
parent 0621793930
commit e3e3c163da
12 changed files with 104 additions and 182 deletions

View file

@ -18,10 +18,18 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.13.0" version = "0.14.0"
edition = "2024" edition = "2024"
[workspace.dependencies] [workspace.dependencies]
tengri = { path = "./tengri" }
tengri_core = { path = "./core" }
tengri_input = { path = "./input" }
tengri_output = { path = "./output" }
tengri_tui = { path = "./tui" }
tengri_dsl = { path = "./dsl" }
tengri_proc = { path = "./proc" }
anyhow = { version = "1.0" } anyhow = { version = "1.0" }
atomic_float = { version = "1" } atomic_float = { version = "1" }
better-panic = { version = "0.3.0" } better-panic = { version = "0.3.0" }

View file

@ -366,7 +366,7 @@ pub trait DslNs<'t, T: 't>: 't {
} }
} }
/// Resolve as literal if valid. /// Resolve as literal if valid.
fn from_literal <D: Dsl> (&self, dsl: &D) -> Perhaps<T> { fn from_literal <D: Dsl> (&self, _: &D) -> Perhaps<T> {
Ok(None) Ok(None)
} }
/// Resolve a symbol if known. /// Resolve a symbol if known.

View file

@ -4,12 +4,11 @@ description = "UI metaframework, input layer."
version = { workspace = true } version = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
[features] [lib]
dsl = [ "tengri_dsl" ] path = "input.rs"
[dependencies] [dependencies]
tengri_core = { path = "../core" } tengri_core = { path = "../core" }
tengri_dsl = { optional = true, path = "../dsl" }
[dev-dependencies] [dev-dependencies]
tengri_tui = { path = "../tui" } tengri_tui = { path = "../tui" }

View file

@ -1,16 +1,6 @@
# `tengri_engine` ***tengri_input*** is where tengri's input handling is defined.
## rendering the following items are provided:
* `Input` trait, for defining for input sources
## input handling * `Handle` trait and `handle!` macro, for defining input handlers
* `Command` trait and the `command!` macro, for defining commands that inputs may result in
the **input thread** polls for keyboard events
and passes them onto the application's `Handle::handle` method.
thus, for a type to be a valid application for engine `E`,
it must implement the trait `Handle<E>`, which allows it
to respond to user input.
this thread has write access to the application state,
and is responsible for mutating it in response to
user activity.

77
input/input.rs Normal file
View file

@ -0,0 +1,77 @@
#![feature(associated_type_defaults)]
#![feature(if_let_guard)]
pub(crate) use tengri_core::*;
#[cfg(test)] mod input_test;
/// 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>
where Self: Sized
{
Ok(self.execute(state)?.map(wrap))
}
}
impl<S, T: Command<S>> Command<S> for Option<T> {
fn execute (self, _: &mut S) -> Perhaps<Self> {
Ok(None)
}
fn delegate <U> (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
where Self: Sized
{
Ok(None)
}
}
/// 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),+>)? ::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
}
}
}
}

View file

@ -1,37 +0,0 @@
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>
where Self: Sized
{
Ok(self.execute(state)?.map(wrap))
}
}
impl<S, T: Command<S>> Command<S> for Option<T> {
fn execute (self, _: &mut S) -> Perhaps<Self> {
Ok(None)
}
fn delegate <U> (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
where Self: Sized
{
Ok(None)
}
}

View file

@ -1,76 +0,0 @@
use crate::*;
use std::{sync::Arc, collections::BTreeMap, path::PathBuf};
/// 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>(
/// 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>>>
);
/// An input binding.
#[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>>,
}
impl<C> Binding<C> {
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
let command: Option<C> = None;
let condition: Option<Condition> = None;
let description: Option<Arc<str>> = None;
let source: Option<Arc<PathBuf>> = None;
if let Some(command) = command {
Ok(Self { commands: [command].into(), condition, description, source })
} else {
Err(format!("no command in {dsl:?}").into())
}
}
}
/// 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()
}
}

View file

@ -1,30 +0,0 @@
/// 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),+>)? ::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
}
}
}
}

View file

@ -1,9 +0,0 @@
#![feature(associated_type_defaults)]
#![feature(if_let_guard)]
pub(crate) use tengri_core::*;
mod input_macros;
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)] mod input_test;

View file

@ -9,10 +9,10 @@ default = [ "input", "output", "tui" ]
input = [ "tengri_input" ] input = [ "tengri_input" ]
output = [ "tengri_output" ] output = [ "tengri_output" ]
tui = [ "tengri_tui" ] tui = [ "tengri_tui" ]
dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl", "tengri_tui/dsl" ] dsl = [ "tengri_dsl", "tengri_output/dsl", "tengri_tui/dsl" ]
[dependencies] [dependencies]
tengri_core = { path = "../core" } tengri_core = { workspace = true }
tengri_dsl = { optional = true, path = "../dsl" } tengri_dsl = { optional = true, path = "../dsl" }
tengri_input = { optional = true, path = "../input" } tengri_input = { optional = true, path = "../input" }
tengri_output = { optional = true, path = "../output" } tengri_output = { optional = true, path = "../output" }

View file

@ -5,13 +5,13 @@ version = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
[features] [features]
dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl" ] dsl = [ "tengri_dsl", "tengri_output/dsl" ]
[dependencies] [dependencies]
tengri_core = { path = "../core" } tengri_core = { workspace = true }
tengri_input = { path = "../input" } tengri_input = { workspace = true }
tengri_output = { path = "../output" } tengri_output = { workspace = true }
tengri_dsl = { optional = true, path = "../dsl" } tengri_dsl = { workspace = true, optional = true }
palette = { workspace = true } palette = { workspace = true }
rand = { workspace = true } rand = { workspace = true }
@ -24,6 +24,6 @@ quanta = { workspace = true }
unicode-width = { workspace = true } unicode-width = { workspace = true }
[dev-dependencies] [dev-dependencies]
tengri = { path = "../tengri", features = [ "dsl" ] } tengri = { workspace = true, features = [ "dsl" ] }
tengri_dsl = { path = "../dsl" } tengri_dsl = { workspace = true }
tengri_proc = { path = "../proc" } tengri_proc = { workspace = true }