mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2025-12-06 11:46:42 +01:00
This commit is contained in:
parent
0621793930
commit
e3e3c163da
12 changed files with 104 additions and 182 deletions
10
Cargo.toml
10
Cargo.toml
|
|
@ -18,10 +18,18 @@ members = [
|
|||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
edition = "2024"
|
||||
|
||||
[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" }
|
||||
atomic_float = { version = "1" }
|
||||
better-panic = { version = "0.3.0" }
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ pub trait DslNs<'t, T: 't>: 't {
|
|||
}
|
||||
}
|
||||
/// 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)
|
||||
}
|
||||
/// Resolve a symbol if known.
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@ description = "UI metaframework, input layer."
|
|||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
[features]
|
||||
dsl = [ "tengri_dsl" ]
|
||||
[lib]
|
||||
path = "input.rs"
|
||||
|
||||
[dependencies]
|
||||
tengri_core = { path = "../core" }
|
||||
tengri_dsl = { optional = true, path = "../dsl" }
|
||||
|
||||
[dev-dependencies]
|
||||
tengri_tui = { path = "../tui" }
|
||||
|
|
|
|||
|
|
@ -1,16 +1,6 @@
|
|||
# `tengri_engine`
|
||||
***tengri_input*** is where tengri's input handling is defined.
|
||||
|
||||
## rendering
|
||||
|
||||
## input handling
|
||||
|
||||
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.
|
||||
the following items are provided:
|
||||
* `Input` trait, for defining for input sources
|
||||
* `Handle` trait and `handle!` macro, for defining input handlers
|
||||
* `Command` trait and the `command!` macro, for defining commands that inputs may result in
|
||||
|
|
|
|||
77
input/input.rs
Normal file
77
input/input.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -9,10 +9,10 @@ default = [ "input", "output", "tui" ]
|
|||
input = [ "tengri_input" ]
|
||||
output = [ "tengri_output" ]
|
||||
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]
|
||||
tengri_core = { path = "../core" }
|
||||
tengri_core = { workspace = true }
|
||||
tengri_dsl = { optional = true, path = "../dsl" }
|
||||
tengri_input = { optional = true, path = "../input" }
|
||||
tengri_output = { optional = true, path = "../output" }
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ version = { workspace = true }
|
|||
edition = { workspace = true }
|
||||
|
||||
[features]
|
||||
dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl" ]
|
||||
dsl = [ "tengri_dsl", "tengri_output/dsl" ]
|
||||
|
||||
[dependencies]
|
||||
tengri_core = { path = "../core" }
|
||||
tengri_input = { path = "../input" }
|
||||
tengri_output = { path = "../output" }
|
||||
tengri_dsl = { optional = true, path = "../dsl" }
|
||||
tengri_core = { workspace = true }
|
||||
tengri_input = { workspace = true }
|
||||
tengri_output = { workspace = true }
|
||||
tengri_dsl = { workspace = true, optional = true }
|
||||
|
||||
palette = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
|
|
@ -24,6 +24,6 @@ quanta = { workspace = true }
|
|||
unicode-width = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tengri = { path = "../tengri", features = [ "dsl" ] }
|
||||
tengri_dsl = { path = "../dsl" }
|
||||
tengri_proc = { path = "../proc" }
|
||||
tengri = { workspace = true, features = [ "dsl" ] }
|
||||
tengri_dsl = { workspace = true }
|
||||
tengri_proc = { workspace = true }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue