mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2025-12-06 03:36: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]
|
[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" }
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
|
|
@ -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
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" ]
|
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" }
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue