diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..173b951 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +Cargo.lock +.vscode diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..769d21a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "prongs" +version = "1.0.0" +authors = ["Ales Katona "] +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"] diff --git a/README.md b/README.md index a69af7e..3b48980 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,68 @@ Input handling schema written in rust. Backend agnostic, provides serializability, assignment and unified interface for working with inputs. Keyboard, mouse and controllers supported. -Current backends include Piston and Gilrs with more on the way. \ No newline at end of file +Current backends include Piston and Gilrs with more on the way. + +## Using prongs library + +To use the prongs library include it in your `Cargo.toml` file. You *MUST* specify a backend via features e.g. +``` +[dependencies] +prongs = { version = "1.0.0", features = ["backend_piston"] } +``` + +### Documentation + +[Documentation link](docs/index.html) + +### Examples + +See [examples repo](../prongs-examples) + +### Design + +Prongs provides a specific backend Schema struct that unifies and simplifies working with input events. +The goal is to provide serializability, input assigning and unify the interface for working with inputs. +Main target audience are game developer and engine developers. + +The main workhorse of the prongs is the Schema. It is a combination of: +1. A keymap (input -> user action mapping) +2. Assigning functionality (create mapping by actuating controls) +3. Processing functionality (apply mapping in main loop) +4. Unified interface and simplification + +Prongs abstracts input events into a three layered structure. The layers are: + +1. Class of input, e.g. mouse or keyboard or controller +2. Instance of input, e.g. key "k", mouse button #2 or axis #2 +3. State of input, e.g. button pressed/released or value of axis + +This is expressed best in the [InputCause](docs/types/enum.InputCause.html) enum. + +The library does not run any event loop due to various differences between how backends handle the event loop. +To use prongs you need to define an actions type that will identify the mappings on input events. +For example: +``` +enum Actions +{ + Up, + Down, + Left, + Right, +} +``` + +You then need to "hook" the `process_event` function into your main event loop for processing. This will +map the input event into the action you specified when you assigned or loaded the Schema configuration. + +Assigning the mapping from input events to user actions is done by using the `assign_input` function. This +can be used to assign actions to input events individually as required (e.g. in a menu when something gets clicked). +Note that `assign_input` handles required de-duplication (e.g. it won't consider a button release as a separate mapping to a button press). +Because the Schema object is fully serializable you can also load existing setup from any previously stored one. + +## Development + +To build you need to specify the required backends e.g. `cargo build --features backend_piston,backend_gilrs` + +Same goes for testing `cargo test --features backend_piston,backend_gilrs` +If you don't specify a backend the tests for it will not be compiled and won't run. \ No newline at end of file diff --git a/docs/all.html b/docs/all.html new file mode 100644 index 0000000..8a0e057 --- /dev/null +++ b/docs/all.html @@ -0,0 +1,3 @@ +List of all items in this crate

[] + + List of all items

Structs

Enums

\ No newline at end of file diff --git a/docs/backend_gilrs/SchemaGilrs.t.html b/docs/backend_gilrs/SchemaGilrs.t.html new file mode 100644 index 0000000..59cba14 --- /dev/null +++ b/docs/backend_gilrs/SchemaGilrs.t.html @@ -0,0 +1,10 @@ + + + + + + +

Redirecting to struct.SchemaGilrs.html...

+ + + \ No newline at end of file diff --git a/docs/backend_gilrs/index.html b/docs/backend_gilrs/index.html new file mode 100644 index 0000000..be4e7c6 --- /dev/null +++ b/docs/backend_gilrs/index.html @@ -0,0 +1,2 @@ +prongs::backend_gilrs - Rust

[][src]Module prongs::backend_gilrs

Structs

+
SchemaGilrs
\ No newline at end of file diff --git a/docs/backend_gilrs/sidebar-items.js b/docs/backend_gilrs/sidebar-items.js new file mode 100644 index 0000000..6659f18 --- /dev/null +++ b/docs/backend_gilrs/sidebar-items.js @@ -0,0 +1 @@ +initSidebarItems({"struct":[["SchemaGilrs",""]]}); \ No newline at end of file diff --git a/docs/backend_gilrs/struct.SchemaGilrs.html b/docs/backend_gilrs/struct.SchemaGilrs.html new file mode 100644 index 0000000..195e451 --- /dev/null +++ b/docs/backend_gilrs/struct.SchemaGilrs.html @@ -0,0 +1,15 @@ +prongs::backend_gilrs::SchemaGilrs - Rust

[][src]Struct prongs::backend_gilrs::SchemaGilrs

pub struct SchemaGilrs<TUserAction> where
    TUserAction: Clone + Serialize
{ /* fields omitted */ }

Methods

impl<TUserAction> SchemaGilrs<TUserAction> where
    TUserAction: Clone + Serialize
[src]

Trait Implementations

impl<TUserAction> Serialize for SchemaGilrs<TUserAction> where
    TUserAction: Clone + Serialize,
    TUserAction: Serialize
[src]

impl<'de, TUserAction> Deserialize<'de> for SchemaGilrs<TUserAction> where
    TUserAction: Clone + Serialize,
    TUserAction: Deserialize<'de>, 
[src]

Auto Trait Implementations

impl<TUserAction> Send for SchemaGilrs<TUserAction> where
    TUserAction: Send

impl<TUserAction> Sync for SchemaGilrs<TUserAction> where
    TUserAction: Sync

Blanket Implementations

impl<T, U> Into for T where
    U: From<T>, 
[src]

impl<T> From for T
[src]

impl<T, U> TryFrom for T where
    T: From<U>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> Borrow for T where
    T: ?Sized
[src]

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T, U> TryInto for T where
    U: TryFrom<T>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> BorrowMut for T where
    T: ?Sized
[src]

impl<T> DeserializeOwned for T where
    T: Deserialize<'de>, 
[src]

\ No newline at end of file diff --git a/docs/backend_piston/SchemaPiston.t.html b/docs/backend_piston/SchemaPiston.t.html new file mode 100644 index 0000000..f10006e --- /dev/null +++ b/docs/backend_piston/SchemaPiston.t.html @@ -0,0 +1,10 @@ + + + + + + +

Redirecting to struct.SchemaPiston.html...

+ + + \ No newline at end of file diff --git a/docs/backend_piston/index.html b/docs/backend_piston/index.html new file mode 100644 index 0000000..9b25b1b --- /dev/null +++ b/docs/backend_piston/index.html @@ -0,0 +1,2 @@ +prongs::backend_piston - Rust

[][src]Module prongs::backend_piston

Structs

+
SchemaPiston
\ No newline at end of file diff --git a/docs/backend_piston/sidebar-items.js b/docs/backend_piston/sidebar-items.js new file mode 100644 index 0000000..3479c5f --- /dev/null +++ b/docs/backend_piston/sidebar-items.js @@ -0,0 +1 @@ +initSidebarItems({"struct":[["SchemaPiston",""]]}); \ No newline at end of file diff --git a/docs/backend_piston/struct.SchemaPiston.html b/docs/backend_piston/struct.SchemaPiston.html new file mode 100644 index 0000000..49d2cff --- /dev/null +++ b/docs/backend_piston/struct.SchemaPiston.html @@ -0,0 +1,15 @@ +prongs::backend_piston::SchemaPiston - Rust

[][src]Struct prongs::backend_piston::SchemaPiston

pub struct SchemaPiston<TUserAction> where
    TUserAction: Clone + Serialize
{ /* fields omitted */ }

Methods

impl<TUserAction> SchemaPiston<TUserAction> where
    TUserAction: Clone + Serialize
[src]

Trait Implementations

impl<TUserAction> Serialize for SchemaPiston<TUserAction> where
    TUserAction: Clone + Serialize,
    TUserAction: Serialize
[src]

impl<'de, TUserAction> Deserialize<'de> for SchemaPiston<TUserAction> where
    TUserAction: Clone + Serialize,
    TUserAction: Deserialize<'de>, 
[src]

Auto Trait Implementations

impl<TUserAction> Send for SchemaPiston<TUserAction> where
    TUserAction: Send

impl<TUserAction> Sync for SchemaPiston<TUserAction> where
    TUserAction: Sync

Blanket Implementations

impl<T, U> Into for T where
    U: From<T>, 
[src]

impl<T> From for T
[src]

impl<T, U> TryFrom for T where
    T: From<U>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> Borrow for T where
    T: ?Sized
[src]

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T, U> TryInto for T where
    U: TryFrom<T>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> BorrowMut for T where
    T: ?Sized
[src]

impl<T> DeserializeOwned for T where
    T: Deserialize<'de>, 
[src]

\ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..304dbb5 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,2 @@ +prongs - Rust

[][src]Crate prongs

Modules

+
backend_gilrs
backend_piston
types
\ No newline at end of file diff --git a/docs/sidebar-items.js b/docs/sidebar-items.js new file mode 100644 index 0000000..5b0c6fa --- /dev/null +++ b/docs/sidebar-items.js @@ -0,0 +1 @@ +initSidebarItems({"mod":[["backend_gilrs",""],["backend_piston",""],["types",""]]}); \ No newline at end of file diff --git a/docs/types/ButtonState.t.html b/docs/types/ButtonState.t.html new file mode 100644 index 0000000..ea9f8e9 --- /dev/null +++ b/docs/types/ButtonState.t.html @@ -0,0 +1,10 @@ + + + + + + +

Redirecting to enum.ButtonState.html...

+ + + \ No newline at end of file diff --git a/docs/types/InputCause.t.html b/docs/types/InputCause.t.html new file mode 100644 index 0000000..76564e0 --- /dev/null +++ b/docs/types/InputCause.t.html @@ -0,0 +1,10 @@ + + + + + + +

Redirecting to enum.InputCause.html...

+ + + \ No newline at end of file diff --git a/docs/types/InputTypeFlags.t.html b/docs/types/InputTypeFlags.t.html new file mode 100644 index 0000000..a4cf433 --- /dev/null +++ b/docs/types/InputTypeFlags.t.html @@ -0,0 +1,10 @@ + + + + + + +

Redirecting to struct.InputTypeFlags.html...

+ + + \ No newline at end of file diff --git a/docs/types/ProcessingResult.t.html b/docs/types/ProcessingResult.t.html new file mode 100644 index 0000000..6d2264e --- /dev/null +++ b/docs/types/ProcessingResult.t.html @@ -0,0 +1,10 @@ + + + + + + +

Redirecting to struct.ProcessingResult.html...

+ + + \ No newline at end of file diff --git a/docs/types/enum.ButtonState.html b/docs/types/enum.ButtonState.html new file mode 100644 index 0000000..961cc75 --- /dev/null +++ b/docs/types/enum.ButtonState.html @@ -0,0 +1,25 @@ +prongs::types::ButtonState - Rust

[][src]Enum prongs::types::ButtonState

pub enum ButtonState {
+    Pressed,
+    Released,
+}

Describes states a key or button can be in

+

+ Variants

+

when key or button is pressed

+

when key or button is released

+

Trait Implementations

impl PartialEq<ButtonState> for ButtonState
[src]

This method tests for !=.

+

impl Eq for ButtonState
[src]

impl Debug for ButtonState
[src]

Auto Trait Implementations

impl Send for ButtonState

impl Sync for ButtonState

Blanket Implementations

impl<T, U> Into for T where
    U: From<T>, 
[src]

impl<T> From for T
[src]

impl<T, U> TryFrom for T where
    T: From<U>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> Borrow for T where
    T: ?Sized
[src]

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T, U> TryInto for T where
    U: TryFrom<T>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> BorrowMut for T where
    T: ?Sized
[src]

\ No newline at end of file diff --git a/docs/types/enum.InputCause.html b/docs/types/enum.InputCause.html new file mode 100644 index 0000000..f56abb6 --- /dev/null +++ b/docs/types/enum.InputCause.html @@ -0,0 +1,26 @@ +prongs::types::InputCause - Rust

[][src]Enum prongs::types::InputCause

pub enum InputCause {
+    Button(ButtonState),
+    Axis(u8f64),
+    Motion(f64f64),
+}

Describes the source of input for a ProcessingResult

+

+ Variants

+

a key or button as source, with given state

+

an axis set to specific value in range <-1.0, 1.0>

+

a motion source (e.g. mouse) with x, y values

+

Methods

impl InputCause
[src]

Trait Implementations

impl PartialEq<InputCause> for InputCause
[src]

impl Debug for InputCause
[src]

Auto Trait Implementations

impl Send for InputCause

impl Sync for InputCause

Blanket Implementations

impl<T, U> Into for T where
    U: From<T>, 
[src]

impl<T> From for T
[src]

impl<T, U> TryFrom for T where
    T: From<U>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> Borrow for T where
    T: ?Sized
[src]

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T, U> TryInto for T where
    U: TryFrom<T>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> BorrowMut for T where
    T: ?Sized
[src]

\ No newline at end of file diff --git a/docs/types/index.html b/docs/types/index.html new file mode 100644 index 0000000..eff6157 --- /dev/null +++ b/docs/types/index.html @@ -0,0 +1,10 @@ +prongs::types - Rust

[][src]Module prongs::types

Structs

+
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

+

Enums

+
ButtonState

Describes states a key or button can be in

+
InputCause

Describes the source of input for a ProcessingResult

+
\ No newline at end of file diff --git a/docs/types/sidebar-items.js b/docs/types/sidebar-items.js new file mode 100644 index 0000000..1327fa0 --- /dev/null +++ b/docs/types/sidebar-items.js @@ -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"]]}); \ No newline at end of file diff --git a/docs/types/struct.InputTypeFlags.html b/docs/types/struct.InputTypeFlags.html new file mode 100644 index 0000000..fc70898 --- /dev/null +++ b/docs/types/struct.InputTypeFlags.html @@ -0,0 +1,93 @@ +prongs::types::InputTypeFlags - Rust

[]Struct prongs::types::InputTypeFlags

pub struct InputTypeFlags { /* fields omitted */ }

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.

+

Methods

impl InputTypeFlags

+

Any controller or mouse button including controller "hat" presses

+

+

Any keyboard key

+

+

Any controller axis

+

+

Any mouse movement

+

+

Key or Button combination

+

+

Axis, Key or Button combination

+

+

Anything else (so you can do Any - Other for e.g. disconnect events etc.)

+

+

Any type

+

Returns an empty set of flags.

+

Returns the set containing all flags.

+

Returns the raw value of the flags currently stored.

+

Convert from underlying bit representation, unless that +representation contains bits that do not correspond to a flag.

+

Convert from underlying bit representation, dropping any bits +that do not correspond to flags.

+

Returns true if no flags are currently stored.

+

Returns true if all flags are currently set.

+

Returns true if there are flags common to both self and other.

+

Returns true all of the flags in other are contained within self.

+

Inserts the specified flags in-place.

+

Removes the specified flags in-place.

+

Toggles the specified flags in-place.

+

Inserts or removes the specified flags depending on the passed value.

+

Trait Implementations

impl PartialOrd<InputTypeFlags> for InputTypeFlags

impl Default for InputTypeFlags
[src]

impl Ord for InputTypeFlags

Compares and returns the maximum of two values. Read more

+

Compares and returns the minimum of two values. Read more

+

impl PartialEq<InputTypeFlags> for InputTypeFlags

impl Clone for InputTypeFlags

Performs copy-assignment from source. Read more

+

impl Extend<InputTypeFlags> for InputTypeFlags

impl Copy for InputTypeFlags

impl Eq for InputTypeFlags

impl Octal for InputTypeFlags

impl Binary for InputTypeFlags

impl Debug for InputTypeFlags

impl UpperHex for InputTypeFlags

impl LowerHex for InputTypeFlags

impl Hash for InputTypeFlags

Feeds a slice of this type into the given [Hasher]. Read more

+

impl Sub<InputTypeFlags> for InputTypeFlags

+

The resulting type after applying the - operator.

+

Returns the set difference of the two sets of flags.

+

impl SubAssign<InputTypeFlags> for InputTypeFlags

Disables all flags enabled in the set.

+

impl Not for InputTypeFlags

+

The resulting type after applying the ! operator.

+

Returns the complement of this set of flags.

+

impl BitAnd<InputTypeFlags> for InputTypeFlags

+

The resulting type after applying the & operator.

+

Returns the intersection between the two sets of flags.

+

impl BitOr<InputTypeFlags> for InputTypeFlags

+

The resulting type after applying the | operator.

+

Returns the union of the two sets of flags.

+

impl BitXor<InputTypeFlags> for InputTypeFlags

+

The resulting type after applying the ^ operator.

+

Returns the left flags, but with all the right flags toggled.

+

impl BitAndAssign<InputTypeFlags> for InputTypeFlags

Disables all flags disabled in the set.

+

impl BitOrAssign<InputTypeFlags> for InputTypeFlags

Adds the set of flags.

+

impl BitXorAssign<InputTypeFlags> for InputTypeFlags

Toggles the set of flags.

+

impl FromIterator<InputTypeFlags> for InputTypeFlags

Auto Trait Implementations

Blanket Implementations

impl<T, U> Into for T where
    U: From<T>, 
[src]

impl<T> ToOwned for T where
    T: Clone
[src]

+

impl<T> From for T
[src]

impl<T, U> TryFrom for T where
    T: From<U>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> Borrow for T where
    T: ?Sized
[src]

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T, U> TryInto for T where
    U: TryFrom<T>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> BorrowMut for T where
    T: ?Sized
[src]

\ No newline at end of file diff --git a/docs/types/struct.ProcessingResult.html b/docs/types/struct.ProcessingResult.html new file mode 100644 index 0000000..7f366f6 --- /dev/null +++ b/docs/types/struct.ProcessingResult.html @@ -0,0 +1,44 @@ +prongs::types::ProcessingResult - Rust

[][src]Struct prongs::types::ProcessingResult

pub struct ProcessingResult<TUserAction> where
    TUserAction: Clone + Serialize
{ + pub action: TUserAction, + pub player_id: Option<usize>, + pub cause: Option<InputCause>, +}

The result of a successful input processing by a Schema

+

+ Fields

+ +

The user defined action to be performed based on the input event

+
+ +

The player_id if assigned to the Schema previously, otherwise None

+
+ +

The InputCause of this input, if known. None only if backend implementation is incomplete.

+

Methods

impl<TUserAction> ProcessingResult<TUserAction> where
    TUserAction: Clone + Serialize
[src]

Specifies if the input cause is engaged (pressed or not equal to 0) +NOTE: some input causes are never engaged, e.g. Motion

+

Specifies if the input cause is an axis

+

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)

+

Trait Implementations

impl<TUserAction: PartialEq> PartialEq<ProcessingResult<TUserAction>> for ProcessingResult<TUserAction> where
    TUserAction: Clone + Serialize
[src]

impl<TUserAction: Debug> Debug for ProcessingResult<TUserAction> where
    TUserAction: Clone + Serialize
[src]

Auto Trait Implementations

impl<TUserAction> Send for ProcessingResult<TUserAction> where
    TUserAction: Send

impl<TUserAction> Sync for ProcessingResult<TUserAction> where
    TUserAction: Sync

Blanket Implementations

impl<T, U> Into for T where
    U: From<T>, 
[src]

impl<T> From for T
[src]

impl<T, U> TryFrom for T where
    T: From<U>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> Borrow for T where
    T: ?Sized
[src]

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T, U> TryInto for T where
    U: TryFrom<T>, 
[src]

+
🔬 This is a nightly-only experimental API. (try_from)

The type returned in the event of a conversion error.

+

impl<T> BorrowMut for T where
    T: ?Sized
[src]

\ No newline at end of file diff --git a/src/backend_gilrs.rs b/src/backend_gilrs.rs new file mode 100644 index 0000000..6590565 --- /dev/null +++ b/src/backend_gilrs.rs @@ -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 +where TUserAction: Clone + Serialize, +{ + schema: Schema, +} + +impl SchemaGilrs +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> { + self.schema.process_event(event) + } + + pub fn set_player_id(&mut self, player_id: Option) { + self.schema.set_player_id(player_id); + } + + pub fn player_id(&self) -> Option { + self.schema.player_id + } + + pub fn controller_id(&self) -> Option { + self.schema.controller_id + } +} + +impl ToEventType for Event +{ + fn to_raw(&self) -> Option { + 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 { + 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 { + 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, + } + } +} diff --git a/src/backend_piston.rs b/src/backend_piston.rs new file mode 100644 index 0000000..6a0e054 --- /dev/null +++ b/src/backend_piston.rs @@ -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 +where TUserAction: Clone + Serialize, +{ + schema: Schema, +} + +impl SchemaPiston +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> { + self.schema.process_event(event) + } + + pub fn set_player_id(&mut self, player_id: Option) { + self.schema.set_player_id(player_id); + } + + pub fn player_id(&self) -> Option { + self.schema.player_id + } + + pub fn controller_id(&self) -> Option { + self.schema.controller_id + } +} + +impl ToEventType for Event +{ + fn to_raw(&self) -> Option { + 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 { + 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 { + 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, + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..962fe1b --- /dev/null +++ b/src/lib.rs @@ -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= in Cargo.toml or --features 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- sub-cargos. +trait ToEventType +where TControllerID: Copy + PartialEq + Serialize +{ + fn to_raw(&self) -> Option; + + fn controller_id(&self) -> Option; + + fn filter_for_assignment(&self, iaf: InputTypeFlags) -> bool; + + fn get_cause(&self) -> Option; +} + +type KeyMap = HashMap; + +const TYPICAL_MAPPING_SIZE: usize = 255; + +#[derive(Serialize, Deserialize)] +struct Schema +where TEventType: ToEventType, + TControllerID: Copy + PartialEq + Serialize, + TUserAction: Clone + Serialize, +{ + name: String, + player_id: Option, + controller_id: Option, + keymap: KeyMap, + + #[serde(skip)] + _phantom: std::marker::PhantomData, +} + +impl Schema +where TEventType: ToEventType, + 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> { + 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) { + self.player_id = player_id; + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..a0a376e --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,3 @@ +mod types; +mod lib; +mod backends; \ No newline at end of file diff --git a/src/tests/backends.rs b/src/tests/backends.rs new file mode 100644 index 0000000..5471a94 --- /dev/null +++ b/src/tests/backends.rs @@ -0,0 +1,5 @@ + +#[cfg(any(feature="default", feature="backend_piston"))] +mod backend_piston; +#[cfg(feature="backend_gilrs")] +mod backend_gilrs; diff --git a/src/tests/backends/backend_gilrs.rs b/src/tests/backends/backend_gilrs.rs new file mode 100644 index 0000000..b9688fc --- /dev/null +++ b/src/tests/backends/backend_gilrs.rs @@ -0,0 +1,17 @@ + +use crate::backend_gilrs::SchemaGilrs; + +#[test] +fn schema_set_player_id() { + let mut schema = SchemaGilrs::::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 diff --git a/src/tests/backends/backend_piston.rs b/src/tests/backends/backend_piston.rs new file mode 100644 index 0000000..4779b53 --- /dev/null +++ b/src/tests/backends/backend_piston.rs @@ -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::::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); +} diff --git a/src/tests/lib.rs b/src/tests/lib.rs new file mode 100644 index 0000000..8c03b04 --- /dev/null +++ b/src/tests/lib.rs @@ -0,0 +1,107 @@ +use crate::{EventType, ToEventType, Schema}; + +// required for schema instantiations +impl ToEventType for u64 +{ + fn to_raw(&self) -> Option { + Some(EventType::new(*self, *self)) + } + + fn controller_id(&self) -> Option { + 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 { + 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::::new("somename").name == "somename"); +} + +#[test] +fn schema_assign_controller() { + let mut schema = Schema::::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) { + 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::::new("somename"); + schema_assign_input_internal(&mut schema); +} + +#[test] +fn schema_process_event() { + let mut schema = Schema::::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:: { + 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::::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)); +} \ No newline at end of file diff --git a/src/tests/types.rs b/src/tests/types.rs new file mode 100644 index 0000000..03b389b --- /dev/null +++ b/src/tests/types.rs @@ -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); +} \ No newline at end of file diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..08ed3cb --- /dev/null +++ b/src/types.rs @@ -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 +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, + /// The [InputCause](enum.InputCause.html) of this input, if known. None only if backend implementation is incomplete. + pub cause: Option, +} + +impl ProcessingResult +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 + } +}