initial commit
parent
194b5b4ccc
commit
13932577e8
@ -0,0 +1,4 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
.vscode
|
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "prongs"
|
||||||
|
version = "1.0.0"
|
||||||
|
authors = ["Ales Katona <almindor@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitflags = "1.0.4"
|
||||||
|
serde = { version = "1.0.85", features = ["derive"] }
|
||||||
|
gilrs = { version = "0.6.3", optional = true }
|
||||||
|
piston = { version = "0.40.0", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
backend_piston = ["piston"]
|
||||||
|
backend_gilrs = ["gilrs"]
|
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="0;URL=struct.SchemaGilrs.html">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Redirecting to <a href="struct.SchemaGilrs.html">struct.SchemaGilrs.html</a>...</p>
|
||||||
|
<script>location.replace("struct.SchemaGilrs.html" + location.search + location.hash);</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1 @@
|
|||||||
|
initSidebarItems({"struct":[["SchemaGilrs",""]]});
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="0;URL=struct.SchemaPiston.html">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Redirecting to <a href="struct.SchemaPiston.html">struct.SchemaPiston.html</a>...</p>
|
||||||
|
<script>location.replace("struct.SchemaPiston.html" + location.search + location.hash);</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1 @@
|
|||||||
|
initSidebarItems({"struct":[["SchemaPiston",""]]});
|
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
|||||||
|
initSidebarItems({"mod":[["backend_gilrs",""],["backend_piston",""],["types",""]]});
|
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="0;URL=enum.ButtonState.html">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Redirecting to <a href="enum.ButtonState.html">enum.ButtonState.html</a>...</p>
|
||||||
|
<script>location.replace("enum.ButtonState.html" + location.search + location.hash);</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="0;URL=enum.InputCause.html">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Redirecting to <a href="enum.InputCause.html">enum.InputCause.html</a>...</p>
|
||||||
|
<script>location.replace("enum.InputCause.html" + location.search + location.hash);</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="0;URL=struct.InputTypeFlags.html">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Redirecting to <a href="struct.InputTypeFlags.html">struct.InputTypeFlags.html</a>...</p>
|
||||||
|
<script>location.replace("struct.InputTypeFlags.html" + location.search + location.hash);</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="0;URL=struct.ProcessingResult.html">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Redirecting to <a href="struct.ProcessingResult.html">struct.ProcessingResult.html</a>...</p>
|
||||||
|
<script>location.replace("struct.ProcessingResult.html" + location.search + location.hash);</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1 @@
|
|||||||
|
initSidebarItems({"enum":[["ButtonState","Describes states a key or button can be in"],["InputCause","Describes the source of input for a ProcessingResult"]],"struct":[["InputTypeFlags","InputTypeFlags are used to specify what type of input is to be allowed when in \"assignment mode\" for event mapping. This allows the caller to filter out unwanted event types in a generic way without having to handle the backend implementation details."],["ProcessingResult","The result of a successful input processing by a Schema"]]});
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,84 @@
|
|||||||
|
use gilrs::{Event, EventType as GilrsEType};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::types::{InputCause, ButtonState, InputTypeFlags, ProcessingResult};
|
||||||
|
use crate::{Schema, ToEventType, EventType};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SchemaGilrs<TUserAction>
|
||||||
|
where TUserAction: Clone + Serialize,
|
||||||
|
{
|
||||||
|
schema: Schema<Event, usize, TUserAction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TUserAction> SchemaGilrs<TUserAction>
|
||||||
|
where TUserAction: Clone + Serialize,
|
||||||
|
{
|
||||||
|
pub fn new(name: &str) -> Self {
|
||||||
|
SchemaGilrs {
|
||||||
|
schema: Schema::new(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign_controller(&mut self, event: &Event, iaf: InputTypeFlags) -> bool {
|
||||||
|
self.schema.assign_controller(event, iaf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign_input(&mut self, event: &Event, action: TUserAction, iaf: InputTypeFlags) -> bool {
|
||||||
|
self.schema.assign_input(event, action, iaf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_event(&mut self, event: &Event) -> Option<ProcessingResult<TUserAction>> {
|
||||||
|
self.schema.process_event(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_player_id(&mut self, player_id: Option<usize>) {
|
||||||
|
self.schema.set_player_id(player_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn player_id(&self) -> Option<usize> {
|
||||||
|
self.schema.player_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn controller_id(&self) -> Option<usize> {
|
||||||
|
self.schema.controller_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToEventType<usize> for Event
|
||||||
|
{
|
||||||
|
fn to_raw(&self) -> Option<EventType> {
|
||||||
|
match self.event {
|
||||||
|
GilrsEType::ButtonPressed(b, _) => Some(EventType::new(1, b as u64)),
|
||||||
|
GilrsEType::ButtonReleased(b, _) => Some(EventType::new(1, b as u64)),
|
||||||
|
GilrsEType::AxisChanged(axis, _, _) => Some(EventType::new(axis as u64, 0)),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn controller_id(&self) -> Option<usize> {
|
||||||
|
Some(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_for_assignment(&self, iaf: InputTypeFlags) -> bool {
|
||||||
|
match self.event {
|
||||||
|
GilrsEType::ButtonPressed(_, _) => iaf.contains(InputTypeFlags::Button),
|
||||||
|
GilrsEType::ButtonReleased(_, _) => false, // do not get releases
|
||||||
|
GilrsEType::ButtonRepeated(_, _) => false, // not sure why this would be used for assignment
|
||||||
|
GilrsEType::ButtonChanged(_, _, _) => false,
|
||||||
|
GilrsEType::AxisChanged(_, val, _) => val.abs() > 0.5 && iaf.contains(InputTypeFlags::Axis), // ensure we capture only the major axis change
|
||||||
|
_ => iaf.contains(InputTypeFlags::Other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cause(&self) -> Option<InputCause> {
|
||||||
|
match self.event {
|
||||||
|
GilrsEType::ButtonPressed(_, _) => Some(InputCause::Button(ButtonState::Pressed)),
|
||||||
|
GilrsEType::ButtonReleased(_, _) => Some(InputCause::Button(ButtonState::Released)), // do not get releases
|
||||||
|
GilrsEType::ButtonRepeated(_, _) => None, // not sure why this would be used for assignment
|
||||||
|
GilrsEType::ButtonChanged(_, _, _) => None,
|
||||||
|
GilrsEType::AxisChanged(axis, val, _) => Some(InputCause::Axis(axis as u8, f64::from(val))),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
use piston::input::{Button, Event, Input, Motion, ButtonState as PButtonState};
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::types::{InputTypeFlags, InputCause, ButtonState, ProcessingResult};
|
||||||
|
use crate::{Schema, ToEventType, EventType};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SchemaPiston<TUserAction>
|
||||||
|
where TUserAction: Clone + Serialize,
|
||||||
|
{
|
||||||
|
schema: Schema<Event, usize, TUserAction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TUserAction> SchemaPiston<TUserAction>
|
||||||
|
where TUserAction: Clone + Serialize,
|
||||||
|
{
|
||||||
|
pub fn new(name: &str) -> Self {
|
||||||
|
SchemaPiston {
|
||||||
|
schema: Schema::new(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign_controller(&mut self, event: &Event, iaf: InputTypeFlags) -> bool {
|
||||||
|
self.schema.assign_controller(event, iaf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign_input(&mut self, event: &Event, action: TUserAction, iaf: InputTypeFlags) -> bool {
|
||||||
|
self.schema.assign_input(event, action, iaf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_event(&mut self, event: &Event) -> Option<ProcessingResult<TUserAction>> {
|
||||||
|
self.schema.process_event(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_player_id(&mut self, player_id: Option<usize>) {
|
||||||
|
self.schema.set_player_id(player_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn player_id(&self) -> Option<usize> {
|
||||||
|
self.schema.player_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn controller_id(&self) -> Option<usize> {
|
||||||
|
self.schema.controller_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToEventType<usize> for Event
|
||||||
|
{
|
||||||
|
fn to_raw(&self) -> Option<EventType> {
|
||||||
|
match self {
|
||||||
|
Event::Input(input) => match input {
|
||||||
|
Input::Button(args) => match args.button {
|
||||||
|
Button::Keyboard(v) => Some(EventType::new(1, v as u64)),
|
||||||
|
Button::Mouse(v) => Some(EventType::new(2, v as u64)),
|
||||||
|
_ => None
|
||||||
|
},
|
||||||
|
Input::Move(args) => match args {
|
||||||
|
Motion::ControllerAxis(v) => Some(EventType::new(3, u64::from(v.axis))),
|
||||||
|
Motion::MouseCursor(_, _) => Some(EventType::new(4, 0)), // we only care that it's mouse move here
|
||||||
|
_ => None
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn controller_id(&self) -> Option<usize> {
|
||||||
|
match self {
|
||||||
|
Event::Input(input) => match input {
|
||||||
|
Input::Move(args) => match args {
|
||||||
|
Motion::ControllerAxis(c) => Some(c.id as usize),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Input::Button(args) => match args.button {
|
||||||
|
Button::Controller(c) => Some(c.id as usize),
|
||||||
|
Button::Hat(c) => Some(c.id as usize),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_for_assignment(&self, iaf: InputTypeFlags) -> bool {
|
||||||
|
match self {
|
||||||
|
Event::Input(input) => match input {
|
||||||
|
Input::Button(args) => match args.state {
|
||||||
|
PButtonState::Press => match args.button {
|
||||||
|
Button::Keyboard(_) => iaf.intersects(InputTypeFlags::Key),
|
||||||
|
Button::Controller(_) | // any non key button here
|
||||||
|
Button::Mouse(_) |
|
||||||
|
Button::Hat(_) => iaf.intersects(InputTypeFlags::Button)
|
||||||
|
},
|
||||||
|
PButtonState::Release => false, // do not assign from release
|
||||||
|
},
|
||||||
|
Input::Move(args) => match args {
|
||||||
|
Motion::MouseCursor(_, _) => iaf.contains(InputTypeFlags::MouseMove),
|
||||||
|
Motion::MouseRelative(_, _) => iaf.contains(InputTypeFlags::MouseMove),
|
||||||
|
Motion::MouseScroll(_, _) => false, // TODO: add proper mouse scroll support to prongs
|
||||||
|
Motion::ControllerAxis(_) => iaf.contains(InputTypeFlags::Axis),
|
||||||
|
Motion::Touch(_) => false, // TODO: add touch support to prongs
|
||||||
|
}
|
||||||
|
_ => iaf.contains(InputTypeFlags::Other),
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cause(&self) -> Option<InputCause> {
|
||||||
|
match self {
|
||||||
|
Event::Input(input) => match input {
|
||||||
|
Input::Button(args) => match args.state {
|
||||||
|
PButtonState::Press => Some(InputCause::Button(ButtonState::Pressed)),
|
||||||
|
PButtonState::Release => Some(InputCause::Button(ButtonState::Released)),
|
||||||
|
},
|
||||||
|
Input::Move(args) => match args {
|
||||||
|
Motion::MouseCursor(x, y) => Some(InputCause::Motion(*x, *y)),
|
||||||
|
Motion::MouseRelative(_, _) => None,
|
||||||
|
Motion::MouseScroll(_, _) => None,
|
||||||
|
Motion::ControllerAxis(_) => None, // InputCause::Axis(), TODO
|
||||||
|
Motion::Touch(_) => None, // TODO: add touch support to prongs
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::types::{InputCause, InputTypeFlags, ProcessingResult};
|
||||||
|
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
// error out specifically if no backend is chosen
|
||||||
|
#[cfg(not(any(feature="backend_piston", feature="backend_gilrs")))]
|
||||||
|
compile_error!("No backend selected, use features=<backend> in Cargo.toml or --features <backend> when building directly.");
|
||||||
|
|
||||||
|
#[cfg(feature="backend_piston")]
|
||||||
|
pub mod backend_piston;
|
||||||
|
#[cfg(feature="backend_gilrs")]
|
||||||
|
pub mod backend_gilrs;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
struct EventType(u128);
|
||||||
|
|
||||||
|
impl EventType
|
||||||
|
{
|
||||||
|
fn new(rt: u64, rv: u64) -> Self {
|
||||||
|
EventType {
|
||||||
|
0: (rt as u128).rotate_left(64) + u128::from(rv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts backend events to more "generic" and serializable
|
||||||
|
/// event types. This usually means stripping down needless data
|
||||||
|
/// and making the result small (u64). For example, for keyboard key mapping
|
||||||
|
/// we only care about the event's "identifier" for the key and it's state (pressed/released)
|
||||||
|
/// If there's more info in the backend event we discard that as it could be tied to the
|
||||||
|
/// instance rather than the "type of input" such as event_id sequence or such.
|
||||||
|
/// This should be typically implemented by the prongs-backend-<name> sub-cargos.
|
||||||
|
trait ToEventType<TControllerID>
|
||||||
|
where TControllerID: Copy + PartialEq + Serialize
|
||||||
|
{
|
||||||
|
fn to_raw(&self) -> Option<EventType>;
|
||||||
|
|
||||||
|
fn controller_id(&self) -> Option<TControllerID>;
|
||||||
|
|
||||||
|
fn filter_for_assignment(&self, iaf: InputTypeFlags) -> bool;
|
||||||
|
|
||||||
|
fn get_cause(&self) -> Option<InputCause>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyMap<TUserAction> = HashMap<EventType, TUserAction>;
|
||||||
|
|
||||||
|
const TYPICAL_MAPPING_SIZE: usize = 255;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Schema<TEventType, TControllerID, TUserAction>
|
||||||
|
where TEventType: ToEventType<TControllerID>,
|
||||||
|
TControllerID: Copy + PartialEq + Serialize,
|
||||||
|
TUserAction: Clone + Serialize,
|
||||||
|
{
|
||||||
|
name: String,
|
||||||
|
player_id: Option<usize>,
|
||||||
|
controller_id: Option<TControllerID>,
|
||||||
|
keymap: KeyMap<TUserAction>,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
_phantom: std::marker::PhantomData<TEventType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TEventType, TControllerID, TUserAction> Schema<TEventType, TControllerID, TUserAction>
|
||||||
|
where TEventType: ToEventType<TControllerID>,
|
||||||
|
TControllerID: Copy + PartialEq + Serialize,
|
||||||
|
TUserAction: Clone + Serialize,
|
||||||
|
{
|
||||||
|
fn new(name: &str) -> Self {
|
||||||
|
Schema {
|
||||||
|
name: name.to_string(),
|
||||||
|
keymap: KeyMap::with_capacity(TYPICAL_MAPPING_SIZE),
|
||||||
|
player_id: None,
|
||||||
|
controller_id: None,
|
||||||
|
_phantom: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_controller(&mut self, event: &TEventType, iaf: InputTypeFlags) -> bool {
|
||||||
|
let event_controller_id = event.controller_id();
|
||||||
|
|
||||||
|
if event_controller_id != self.controller_id
|
||||||
|
&& event_controller_id.is_some()
|
||||||
|
&& event.filter_for_assignment(iaf) {
|
||||||
|
self.controller_id = event.controller_id();
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_input(&mut self, event: &TEventType, action: TUserAction, iaf: InputTypeFlags) -> bool {
|
||||||
|
if event.filter_for_assignment(iaf) {
|
||||||
|
if let Some(event_type) = event.to_raw() {
|
||||||
|
self.keymap.insert(event_type, action);
|
||||||
|
self.assign_controller(event, iaf);
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_event(&mut self, event: &TEventType) -> Option<ProcessingResult<TUserAction>> {
|
||||||
|
if let Some(event_type) = event.to_raw() {
|
||||||
|
if let Some(action) = self.keymap.get(&event_type) {
|
||||||
|
if self.controller_id.is_some() && self.controller_id != event.controller_id() {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(ProcessingResult {
|
||||||
|
action: action.clone(),
|
||||||
|
player_id: self.player_id,
|
||||||
|
cause: event.get_cause(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_player_id(&mut self, player_id: Option<usize>) {
|
||||||
|
self.player_id = player_id;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
mod types;
|
||||||
|
mod lib;
|
||||||
|
mod backends;
|
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
#[cfg(any(feature="default", feature="backend_piston"))]
|
||||||
|
mod backend_piston;
|
||||||
|
#[cfg(feature="backend_gilrs")]
|
||||||
|
mod backend_gilrs;
|
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
use crate::backend_gilrs::SchemaGilrs;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_set_player_id() {
|
||||||
|
let mut schema = SchemaGilrs::<u64>::new("Testing");
|
||||||
|
|
||||||
|
assert_eq!(schema.player_id(), None);
|
||||||
|
schema.set_player_id(Some(5));
|
||||||
|
assert_eq!(schema.player_id(), Some(5));
|
||||||
|
schema.set_player_id(None);
|
||||||
|
assert_eq!(schema.player_id(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unable to test assign_controller or assign_input due to Code being hidden and uninstantiable
|
||||||
|
|
||||||
|
// unable to test Event* since it's not instantiable from outside the gilrs crate
|
@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
use crate::backend_piston::SchemaPiston;
|
||||||
|
use crate::{EventType, ToEventType};
|
||||||
|
use crate::types::{InputTypeFlags, InputCause, ButtonState};
|
||||||
|
use piston::input::{Event, Input, Button, ButtonArgs, Key, ButtonState as PButtonState, ControllerButton, Motion};
|
||||||
|
|
||||||
|
fn new_event_button(button: Button, state: PButtonState) -> Event {
|
||||||
|
Event::Input(Input::Button(ButtonArgs {
|
||||||
|
button: button,
|
||||||
|
scancode: Some(0x20),
|
||||||
|
state: state,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn event_to_raw() {
|
||||||
|
let event = new_event_button(Button::Keyboard(Key::Space), PButtonState::Press);
|
||||||
|
let event_ignorable = Event::Input(Input::Cursor(false));
|
||||||
|
|
||||||
|
assert_eq!(event.to_raw(), Some(EventType((1 as u128).rotate_left(64) + (Key::Space as u128))));
|
||||||
|
assert_eq!(event_ignorable.to_raw(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn event_controller_id() {
|
||||||
|
let event = new_event_button(Button::Controller(ControllerButton::new(5, 1)), PButtonState::Press);
|
||||||
|
let event_ignorable = Event::Input(Input::Cursor(false));
|
||||||
|
assert_eq!(event.controller_id(), Some(5));
|
||||||
|
assert_eq!(event_ignorable.controller_id(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn event_filter_for_assignment() {
|
||||||
|
let event = new_event_button(Button::Controller(ControllerButton::new(5, 1)), PButtonState::Press);
|
||||||
|
let event_other = Event::Input(Input::Cursor(false));
|
||||||
|
|
||||||
|
assert_eq!(event.filter_for_assignment(InputTypeFlags::Axis), false);
|
||||||
|
assert_eq!(event.filter_for_assignment(InputTypeFlags::Key), false);
|
||||||
|
assert_eq!(event.filter_for_assignment(InputTypeFlags::Button), true);
|
||||||
|
assert_eq!(event_other.filter_for_assignment(InputTypeFlags::Other), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn event_get_cause() {
|
||||||
|
let event = new_event_button(Button::Controller(ControllerButton::new(5, 1)), PButtonState::Press);
|
||||||
|
let event_other = Event::Input(Input::Cursor(false));
|
||||||
|
let event_mouse = Event::Input(Input::Move(Motion::MouseCursor(0.4, 0.5)));
|
||||||
|
|
||||||
|
assert_eq!(event.get_cause(), Some(InputCause::Button(ButtonState::Pressed)));
|
||||||
|
assert_eq!(event_other.get_cause(), None);
|
||||||
|
assert_eq!(event_mouse.get_cause(), Some(InputCause::Motion(0.4, 0.5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_set_player_id() {
|
||||||
|
let mut schema = SchemaPiston::<u64>::new("Testing");
|
||||||
|
|
||||||
|
assert_eq!(schema.player_id(), None);
|
||||||
|
schema.set_player_id(Some(5));
|
||||||
|
assert_eq!(schema.player_id(), Some(5));
|
||||||
|
schema.set_player_id(None);
|
||||||
|
assert_eq!(schema.player_id(), None);
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
use crate::{EventType, ToEventType, Schema};
|
||||||
|
|
||||||
|
// required for schema instantiations
|
||||||
|
impl ToEventType<usize> for u64
|
||||||
|
{
|
||||||
|
fn to_raw(&self) -> Option<EventType> {
|
||||||
|
Some(EventType::new(*self, *self))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn controller_id(&self) -> Option<usize> {
|
||||||
|
if *self > 0 { // we can test both variants in schema tests this way
|
||||||
|
return Some(*self as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_for_assignment(&self, iaf: crate::types::InputTypeFlags) -> bool {
|
||||||
|
iaf == crate::types::InputTypeFlags::Other
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cause(&self) -> Option<crate::types::InputCause> {
|
||||||
|
match *self {
|
||||||
|
1 => Some(crate::types::InputCause::Button(crate::types::ButtonState::Pressed)),
|
||||||
|
2 => Some(crate::types::InputCause::Button(crate::types::ButtonState::Released)),
|
||||||
|
3 => Some(crate::types::InputCause::Axis(0, 0.0)),
|
||||||
|
4 => Some(crate::types::InputCause::Axis(0, 1.0)),
|
||||||
|
5 => Some(crate::types::InputCause::Motion(0.0, 0.0)),
|
||||||
|
6 => Some(crate::types::InputCause::Motion(1.0, 1.0)),
|
||||||
|
103 => Some(crate::types::InputCause::Motion(0.2, 0.5)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn event_type_new() {
|
||||||
|
let rt: u64 = 348;
|
||||||
|
let rv: u64 = 982374;
|
||||||
|
assert!(EventType::new(rt, rv).0 == (rt as u128).rotate_left(64) + u128::from(rv));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_new() {
|
||||||
|
assert!(Schema::<u64, usize, usize>::new("somename").name == "somename");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_assign_controller() {
|
||||||
|
let mut schema = Schema::<u64, usize, usize>::new("somename");
|
||||||
|
|
||||||
|
assert!(schema.assign_controller(&0, crate::types::InputTypeFlags::Any) == false);
|
||||||
|
assert!(schema.assign_controller(&1, crate::types::InputTypeFlags::Other) == true);
|
||||||
|
assert!(schema.controller_id == Some(1));
|
||||||
|
assert!(schema.assign_controller(&2, crate::types::InputTypeFlags::Other) == true);
|
||||||
|
assert!(schema.controller_id == Some(2));
|
||||||
|
assert!(schema.assign_controller(&2, crate::types::InputTypeFlags::Other) == false);
|
||||||
|
assert!(schema.controller_id == Some(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema_assign_input_internal(schema: &mut Schema<u64, usize, usize>) {
|
||||||
|
assert_eq!(schema.assign_input(&100, 3, crate::types::InputTypeFlags::Any), false); // filtered out (InputTypeFlags::Any -> ignore)
|
||||||
|
assert_eq!(schema.assign_input(&110, 0, crate::types::InputTypeFlags::Other), true); // empty cause
|
||||||
|
assert_eq!(schema.assign_input(&101, 4, crate::types::InputTypeFlags::Other), true); // 0/1.0 axis
|
||||||
|
assert_eq!(schema.assign_input(&102, 1, crate::types::InputTypeFlags::Other), true); // release button
|
||||||
|
assert_eq!(schema.assign_input(&103, 5, crate::types::InputTypeFlags::Other), true); // 0.0/0.0 motion
|
||||||
|
assert_eq!(schema.keymap.len(), 4);
|
||||||
|
assert_eq!(schema.keymap.get(&EventType::new(110, 110)), Some(&0));
|
||||||
|
assert_eq!(schema.keymap.get(&EventType::new(101, 101)), Some(&4));
|
||||||
|
assert_eq!(schema.keymap.get(&EventType::new(102, 102)), Some(&1));
|
||||||
|
assert_eq!(schema.keymap.get(&EventType::new(103, 103)), Some(&5));
|
||||||
|
|
||||||
|
assert_eq!(schema.assign_input(&103, 6, crate::types::InputTypeFlags::Other), true); // override with motion 1.0/1.0
|
||||||
|
assert_eq!(schema.keymap.get(&EventType::new(103, 103)), Some(&6));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_assign_input() {
|
||||||
|
let mut schema = Schema::<u64, usize, usize>::new("somename");
|
||||||
|
schema_assign_input_internal(&mut schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_process_event() {
|
||||||
|
let mut schema = Schema::<u64, usize, usize>::new("somename");
|
||||||
|
schema_assign_input_internal(&mut schema);
|
||||||
|
|
||||||
|
// NOTE: last assign set the controller_id to "103" so this is the only one that passes here
|
||||||
|
assert_eq!(schema.process_event(&103), Some(crate::types::ProcessingResult::<usize> {
|
||||||
|
action: 6,
|
||||||
|
player_id: None,
|
||||||
|
cause: Some(crate::types::InputCause::Motion(0.2, 0.5)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// doesn't pass since controller_id for event != controller_id set for schema
|
||||||
|
assert_eq!(schema.process_event(&102), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_set_player_id() {
|
||||||
|
let mut schema = Schema::<u64, usize, usize>::new("somename");
|
||||||
|
|
||||||
|
schema.set_player_id(Some(5));
|
||||||
|
assert!(schema.player_id == Some(5));
|
||||||
|
schema.set_player_id(Some(34258));
|
||||||
|
assert!(schema.player_id == Some(34258));
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
use crate::types::{InputCause, ButtonState, ProcessingResult};
|
||||||
|
|
||||||
|
// ------------------- InputCause ------------------- //
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_cause_is_engaged() {
|
||||||
|
assert!(InputCause::Button(ButtonState::Pressed).is_engaged() == true);
|
||||||
|
assert!(InputCause::Button(ButtonState::Released).is_engaged() == false);
|
||||||
|
assert!(InputCause::Axis(0, 0.0).is_engaged() == false);
|
||||||
|
assert!(InputCause::Axis(1, 0.0).is_engaged() == false);
|
||||||
|
assert!(InputCause::Axis(1, 0.1).is_engaged() == true);
|
||||||
|
assert!(InputCause::Motion(0.0, 0.0).is_engaged() == false);
|
||||||
|
assert!(InputCause::Motion(0.0, 1.0).is_engaged() == false);
|
||||||
|
assert!(InputCause::Motion(1.0, 1.0).is_engaged() == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_cause_is_axis() {
|
||||||
|
assert!(InputCause::Button(ButtonState::Released).is_axis() == false);
|
||||||
|
assert!(InputCause::Axis(0, 0.0).is_axis() == true);
|
||||||
|
assert!(InputCause::Motion(0.0, 0.0).is_axis() == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_cause_scalar_value() {
|
||||||
|
assert!(InputCause::Button(ButtonState::Pressed).scalar_value() == 1.0);
|
||||||
|
assert!(InputCause::Button(ButtonState::Released).scalar_value() == 0.0);
|
||||||
|
assert!(InputCause::Axis(0, 0.0).scalar_value() == 0.0);
|
||||||
|
assert!(InputCause::Axis(1, 0.0).scalar_value() == 0.0);
|
||||||
|
assert!(InputCause::Axis(1, 0.1).scalar_value() == 0.1);
|
||||||
|
assert!(InputCause::Motion(0.0, 0.0).scalar_value() == 0.0);
|
||||||
|
assert!(InputCause::Motion(0.0, 1.0).scalar_value() == 0.0);
|
||||||
|
assert!(InputCause::Motion(1.0, 1.0).scalar_value() == 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- ProcessingResult ------------------- //
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn processing_result_input_is_engaged() {
|
||||||
|
let pr_with_cause = |cause| {
|
||||||
|
ProcessingResult {
|
||||||
|
action: 0,
|
||||||
|
player_id: Some(0),
|
||||||
|
cause: Some(cause),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(pr_with_cause(InputCause::Button(ButtonState::Pressed)).input_is_engaged() == true);
|
||||||
|
assert!(pr_with_cause(InputCause::Button(ButtonState::Released)).input_is_engaged() == false);
|
||||||
|
assert!(pr_with_cause(InputCause::Axis(0, 0.0)).input_is_engaged() == false);
|
||||||
|
assert!(pr_with_cause(InputCause::Axis(1, 0.0)).input_is_engaged() == false);
|
||||||
|
assert!(pr_with_cause(InputCause::Axis(1, 0.1)).input_is_engaged() == true);
|
||||||
|
assert!(pr_with_cause(InputCause::Motion(0.0, 0.0)).input_is_engaged() == false);
|
||||||
|
assert!(pr_with_cause(InputCause::Motion(0.0, 1.0)).input_is_engaged() == false);
|
||||||
|
assert!(pr_with_cause(InputCause::Motion(1.0, 1.0)).input_is_engaged() == false);
|
||||||
|
|
||||||
|
assert!(ProcessingResult { action: 0, player_id: Some(0), cause: None }.input_is_engaged() == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn processing_result_input_is_axis() {
|
||||||
|
let pr_with_cause = |cause| {
|
||||||
|
ProcessingResult {
|
||||||
|
action: 0,
|
||||||
|
player_id: Some(0),
|
||||||
|
cause: Some(cause),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(pr_with_cause(InputCause::Button(ButtonState::Pressed)).input_is_axis() == false);
|
||||||
|
assert!(pr_with_cause(InputCause::Axis(0, 0.0)).input_is_axis() == true);
|
||||||
|
assert!(pr_with_cause(InputCause::Motion(0.0, 0.0)).input_is_axis() == false);
|
||||||
|
|
||||||
|
|
||||||
|
assert!(ProcessingResult { action: 0, player_id: Some(0), cause: None }.input_is_axis() == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn processing_result_input_scalar_value() {
|
||||||
|
let pr_with_cause = |cause| {
|
||||||
|
ProcessingResult {
|
||||||
|
action: 0,
|
||||||
|
player_id: Some(0),
|
||||||
|
cause: Some(cause),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(pr_with_cause(InputCause::Button(ButtonState::Pressed)).input_scalar_value() == 1.0);
|
||||||
|
assert!(pr_with_cause(InputCause::Button(ButtonState::Released)).input_scalar_value() == 0.0);
|
||||||
|
assert!(pr_with_cause(InputCause::Axis(0, 0.0)).input_scalar_value() == 0.0);
|
||||||
|
assert!(pr_with_cause(InputCause::Axis(1, 0.0)).input_scalar_value() == 0.0);
|
||||||
|
assert!(pr_with_cause(InputCause::Axis(1, 0.1)).input_scalar_value() == 0.1);
|
||||||
|
assert!(pr_with_cause(InputCause::Motion(0.0, 0.0)).input_scalar_value() == 0.0);
|
||||||
|
assert!(pr_with_cause(InputCause::Motion(0.0, 1.0)).input_scalar_value() == 0.0);
|
||||||
|
assert!(pr_with_cause(InputCause::Motion(1.0, 1.0)).input_scalar_value() == 1.0);
|
||||||
|
|
||||||
|
assert!(ProcessingResult { action: 0, player_id: Some(0), cause: None }.input_scalar_value() == 0.0);
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
use serde::{Serialize};
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
/// InputTypeFlags are used to specify what type of input
|
||||||
|
/// is to be allowed when in "assignment mode" for event mapping.
|
||||||
|
/// This allows the caller to filter out unwanted event types in a
|
||||||
|
/// generic way without having to handle the backend implementation details.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct InputTypeFlags: u64
|
||||||
|
{
|
||||||
|
/// Any controller or mouse button including controller "hat" presses
|
||||||
|
const Button = 0b0000_0001;
|
||||||
|
/// Any keyboard key
|
||||||
|
const Key = 0b0000_0010;
|
||||||
|
/// Any controller axis
|
||||||
|
const Axis = 0b0000_0100;
|
||||||
|
/// Any mouse movement
|
||||||
|
const MouseMove = 0b0000_1000;
|
||||||
|
/// Key or Button combination
|
||||||
|
const KeyOrButton = Self::Button.bits | Self::Key.bits;
|
||||||
|
/// Axis, Key or Button combination
|
||||||
|
const AxisKeyOrButton = Self::Axis.bits | Self::KeyOrButton.bits;
|
||||||
|
/// Anything else (so you can do Any - Other for e.g. disconnect events etc.)
|
||||||
|
const Other = 0b0010_0000;
|
||||||
|
/// Any type
|
||||||
|
const Any = 0xffff_ffff_ffff_ffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes states a key or button can be in
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum ButtonState
|
||||||
|
{
|
||||||
|
/// when key or button is pressed
|
||||||
|
Pressed,
|
||||||
|
/// when key or button is released
|
||||||
|
Released,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes the source of input for a [ProcessingResult](struct.ProcessingResult.html)
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum InputCause
|
||||||
|
{
|
||||||
|
/// a key or button as source, with given state
|
||||||
|
Button(ButtonState),
|
||||||
|
/// an axis set to specific value in range <-1.0, 1.0>
|
||||||
|
Axis(u8, f64), // axis, value TODO: map axis to actual possibilities
|
||||||
|
/// a motion source (e.g. mouse) with x, y values
|
||||||
|
Motion(f64, f64), // x, y
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputCause
|
||||||
|
{
|
||||||
|
pub fn is_engaged(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
InputCause::Button(state) => *state == ButtonState::Pressed,
|
||||||
|
InputCause::Axis(_, value) => *value != 0.0,
|
||||||
|
InputCause::Motion(_, _) => false, // never "engaged"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_axis(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
InputCause::Axis(_, _) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scalar_value(&self) -> f64 {
|
||||||
|
match self {
|
||||||
|
InputCause::Button(state) => if *state == ButtonState::Pressed {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
},
|
||||||
|
InputCause::Axis(_, value) => *value,
|
||||||
|
InputCause::Motion(x, y) => x * y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result of a successful input processing by a Schema
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct ProcessingResult<TUserAction>
|
||||||
|
where TUserAction: Clone + Serialize,
|
||||||
|
{
|
||||||
|
/// The user defined action to be performed based on the input event
|
||||||
|
pub action: TUserAction,
|
||||||
|
/// The player_id if assigned to the Schema previously, otherwise None
|
||||||
|
pub player_id: Option<usize>,
|
||||||
|
/// The [InputCause](enum.InputCause.html) of this input, if known. None only if backend implementation is incomplete.
|
||||||
|
pub cause: Option<InputCause>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TUserAction> ProcessingResult<TUserAction>
|
||||||
|
where TUserAction: Clone + Serialize,
|
||||||
|
{
|
||||||
|
/// Specifies if the input cause is engaged (pressed or not equal to 0)
|
||||||
|
/// *NOTE*: some input causes are never engaged, e.g. Motion
|
||||||
|
pub fn input_is_engaged(&self) -> bool {
|
||||||
|
if let Some(cause) = &self.cause {
|
||||||
|
return cause.is_engaged()
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifies if the input cause is an axis
|
||||||
|
pub fn input_is_axis(&self) -> bool {
|
||||||
|
if let Some(cause) = &self.cause {
|
||||||
|
return cause.is_axis()
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reduces the input cause state value to a single f64
|
||||||
|
/// Key or button states are either 0.0 or 1.0 for pressed
|
||||||
|
/// Axis value stays same
|
||||||
|
/// Motion x, y values are multiplied (x * y)
|
||||||
|
pub fn input_scalar_value(&self) -> f64 {
|
||||||
|
if let Some(cause) = &self.cause {
|
||||||
|
return cause.scalar_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue