lyra-engine/lyra-game/src/input/action.rs

603 lines
16 KiB
Rust

use std::{collections::HashMap, ops::Deref, hash::{Hash, DefaultHasher, Hasher}, fmt::Debug};
use glam::Vec2;
use lyra_ecs::World;
use lyra_reflect::Reflect;
use crate::{plugin::Plugin, game::GameStages, EventQueue};
use super::{Button, InputButtons, KeyCode, MouseButton, MouseMotion};
pub trait ActionLabel: Debug {
/// Returns a unique hash of the label.
fn label_hash(&self) -> u64;
}
impl<T: Hash + Debug> ActionLabel for T {
fn label_hash(&self) -> u64 {
let mut s = DefaultHasher::new();
self.hash(&mut s);
s.finish()
}
}
#[derive(Clone)]
pub struct ActionLabelWrapper(String, u64);
impl Debug for ActionLabelWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl ActionLabel for ActionLabelWrapper {
fn label_hash(&self) -> u64 {
self.1
}
}
impl<A: ActionLabel + Hash> From<A> for ActionLabelWrapper {
fn from(value: A) -> Self {
let lbl = format!("{:?}", value);
Self(lbl, value.label_hash())
}
}
/// Some commonly used action labels.
///
/// The built-in systems uses these labels
#[derive(Clone, Copy, Hash, Debug)]
pub enum CommonActionLabel {
MoveForwardBackward,
MoveLeftRight,
MoveUpDown,
LookLeftRight,
LookUpDown,
LookRoll,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum GamepadFormat {
DualAxis,
Joystick,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum GamepadButton {
FaceBottom,
FaceLeft,
FaceRight,
FaceTop,
VirtualConfirm,
VirtualDeny,
LThumbstick,
RThumbstick,
DPadUp,
DPadDown,
DPadLeft,
DPadRight,
LShoulder,
RShoulder,
LTrigger,
RTrigger,
Special,
LSpecial,
RSpecial,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum GamepadAxis {
LThumbstickX,
LThumbstickY,
RThumbstickX,
RThumbstickY,
LTrigger,
RTrigger,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum GamepadInput {
Button(GamepadButton),
Axis(GamepadAxis),
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum MouseAxis {
X,
Y,
ScrollWheel,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum MouseInput {
Button(MouseButton),
Axis(MouseAxis),
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ActionSource {
Keyboard(KeyCode),
Gamepad(GamepadFormat, GamepadInput),
Mouse(MouseInput)
}
impl ActionSource {
/// Convert this Source into a Binding. Uses a default Binding modifier value of `1.0`.
pub fn into_binding(self) -> Binding {
Binding::from_source(self)
}
/// Convert this Source into a Binding, setting the modifier.
pub fn into_binding_modifier(self, modifier: f32) -> Binding {
Binding::from_source_modifier(self, modifier)
}
}
#[derive(Clone, Debug)]
pub struct Binding {
pub source: ActionSource,
pub modifier: f32,
}
impl Binding {
/// Create a binding from a Source. Uses a default value of `1.0` as the modifier.
pub fn from_source(source: ActionSource) -> Self {
// TODO: Some types of input will have a default modifier, mainly axis'
Self {
source,
modifier: 1.0,
}
}
/// Create a binding from a Source, with a modifier
pub fn from_source_modifier(source: ActionSource, modifier: f32) -> Self {
Self {
source,
modifier,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ActionState {
/// No input is being given by the user for the button/axis.
Idle,
/// The button is pressed
Pressed(f32),
/// The button was just pressed
JustPressed(f32),
/// The button was just released
JustReleased,
//Released,
/// The axis moved
Axis(f32),
/// Some other state with a modifier
Other(f32),
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ActionKind {
Button,
Axis
}
#[derive(Clone, Debug)]
pub struct Action {
pub kind: ActionKind,
pub state: ActionState,
}
impl Action {
pub fn new(kind: ActionKind) -> Self {
Self {
kind,
state: ActionState::Idle,
}
}
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct LayoutId(pub u32);
impl From<u32> for LayoutId {
fn from(value: u32) -> Self {
Self(value)
}
}
#[derive(Clone, Default)]
pub struct Layout {
mappings: HashMap<ActionMappingId, ActionMapping>,
active_mapping: ActionMappingId,
}
impl Layout {
pub fn new() -> Self {
Self::default()
}
pub fn add_mapping(&mut self, mapping: ActionMapping) -> &mut Self {
self.mappings.insert(mapping.id, mapping);
self
}
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ActionMappingId(pub u32);
impl From<u32> for ActionMappingId {
fn from(value: u32) -> Self {
Self(value)
}
}
#[derive(Clone)]
pub struct ActionMapping {
layout: LayoutId,
id: ActionMappingId,
action_binds: HashMap<u64, Vec<Binding>>,
}
impl ActionMapping {
pub fn new(layout: LayoutId, id: ActionMappingId) -> Self {
Self {
layout,
id,
action_binds: HashMap::new(),
}
}
pub fn builder(layout: LayoutId, id: ActionMappingId) -> ActionMappingBuilder {
ActionMappingBuilder::new(ActionMapping::new(layout, id))
}
/// Creates a binding for the action.
///
/// If the action is not in this layout, this will panic!
///
/// Parameters:
/// * `action` - The label corresponding to the action in this Layout.
/// * `bind` - The Binding to add to the Action.
pub fn bind<L>(&mut self, action: L, bindings: &[Binding]) -> &mut Self
where
L: ActionLabel
{
let mut bindings = bindings.to_vec();
let action_binds = self.action_binds.entry(action.label_hash()).or_default();
action_binds.append(&mut bindings);
self
}
pub fn finish(self) -> Self {
self
}
}
pub struct ActionMappingBuilder {
mapping: ActionMapping,
}
impl ActionMappingBuilder {
fn new(mapping: ActionMapping) -> Self {
Self {
mapping,
}
}
pub fn bind<L>(mut self, action: L, bindings: &[Binding]) -> Self
where
L: ActionLabel
{
let mut bindings = bindings.to_vec();
let action_binds = self.mapping.action_binds.entry(action.label_hash()).or_default();
action_binds.append(&mut bindings);
self
}
pub fn finish(self) -> ActionMapping {
self.mapping
}
}
#[derive(Clone, Default, Reflect)]
pub struct ActionHandler {
#[reflect(skip)] // TODO: dont just skip all these
pub actions: HashMap<u64, Action>,
#[reflect(skip)]
pub layouts: HashMap<LayoutId, Layout>,
#[reflect(skip)]
pub current_layout: LayoutId,
#[reflect(skip)]
pub current_mapping: ActionMappingId,
}
impl ActionHandler {
pub fn new() -> Self {
Self::default()
}
pub fn builder() -> ActionHandlerBuilder {
ActionHandlerBuilder::default()
}
pub fn add_layout(&mut self, id: LayoutId) {
self.layouts.insert(id, Layout::new());
}
pub fn action<L>(&self, label: L) -> Option<&Action>
where
L: ActionLabel
{
self.actions.get(&label.label_hash())
}
pub fn add_action<L>(&mut self, label: L, action: Action)
where
L: ActionLabel
{
self.actions.insert(label.label_hash(), action);
}
pub fn add_mapping(&mut self, mapping: ActionMapping) {
let layout = self.layouts.get_mut(&mapping.layout).unwrap();
layout.add_mapping(mapping);
}
/// Returns true if the action is pressed (or was just pressed).
///
/// This will panic if the action name does not correspond to an action.
pub fn is_action_pressed<L>(&self, action: L) -> bool
where
L: ActionLabel
{
let action = self.actions.get(&action.label_hash())
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
matches!(action.state, ActionState::Pressed(_) | ActionState::JustPressed(_))
}
/// Returns true if the action was just pressed.
///
/// This will panic if the action name does not correspond to an action.
pub fn was_action_just_pressed<L>(&self, action: L) -> bool
where
L: ActionLabel
{
let action = self.actions.get(&action.label_hash())
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
matches!(action.state, ActionState::JustPressed(_))
}
/// Returns true if the action was just released.
///
/// This will panic if the action name does not correspond to an action.
pub fn was_action_just_released<L>(&self, action: L) -> bool
where
L: ActionLabel
{
let action = self.actions.get(&action.label_hash())
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
matches!(action.state, ActionState::JustReleased)
}
/// Returns an action's state.
///
/// This will panic if the action name does not correspond to an action.
pub fn get_action_state<L>(&self, action: L) -> ActionState
where
L: ActionLabel
{
let action = self.actions.get(&action.label_hash())
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
action.state
}
/// Returns the action's modifier if it is pressed (or was just pressed).
/// Returns `None` if the action's state is not `ActionState::Pressed` or `ActionState::JustPressed`.
///
/// This will panic if the action name does not correspond to an action.
pub fn get_pressed_modifier<L>(&self, action: L) -> Option<f32>
where
L: ActionLabel
{
let action = self.actions.get(&action.label_hash())
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
match action.state {
ActionState::Pressed(v) | ActionState::JustPressed(v) => Some(v),
_ => None,
}
}
/// Returns the action's modifier if it was just pressed.
/// Returns `None` if the action's state is not `ActionState::JustPressed`.
///
/// This will panic if the action name does not correspond to an action.
pub fn get_just_pressed_modifier<L>(&self, action: L) -> Option<f32>
where
L: ActionLabel
{
let action = self.actions.get(&action.label_hash())
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
match action.state {
ActionState::JustPressed(v) => Some(v),
_ => None,
}
}
/// Returns the action's modifier if its an updated axis.
/// Returns `None` if the action's state is not `ActionState::Axis`.
///
/// This will panic if the action name does not correspond to an action.
pub fn get_axis_modifier<L>(&self, action: L) -> Option<f32>
where
L: ActionLabel
{
let action = self.actions.get(&action.label_hash())
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
match action.state {
ActionState::Axis(v) => Some(v),
_ => None,
}
}
}
#[derive(Default)]
pub struct ActionHandlerBuilder {
handler: ActionHandler,
}
impl ActionHandlerBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_layout(mut self, id: LayoutId) -> Self {
self.handler.layouts.insert(id, Layout::new());
self
}
pub fn add_action<L>(mut self, label: L, action: Action) -> Self
where
L: ActionLabel
{
self.handler.actions.insert(label.label_hash(), action);
self
}
pub fn add_mapping(mut self, mapping: ActionMapping) -> Self {
let layout = self.handler.layouts.get_mut(&mapping.layout).unwrap();
layout.add_mapping(mapping);
self
}
pub fn finish(self) -> ActionHandler {
self.handler
}
}
fn actions_system(world: &mut World) -> anyhow::Result<()> {
let keys = world.try_get_resource::<InputButtons<KeyCode>>()
.map(|r| r.deref().clone());
let mouse_events = world
.try_get_resource::<EventQueue>()
.and_then(|q| q.read_events::<MouseMotion>());
//let mouse = world.try_get_resource()
// clear the states of all axises each frame
{
let mut handler = world.try_get_resource_mut::<ActionHandler>()
.expect("No Input Action handler was created in the world!");
let layout = handler.layouts.get(&handler.current_layout).expect("No active layout");
let mapping = layout.mappings.get(&layout.active_mapping).expect("No active mapping");
for (action, _) in mapping.action_binds.clone().iter() {
let action = handler.actions.get_mut(action).expect("Action name for binding is invalid!");
if action.kind == ActionKind::Axis {
action.state = ActionState::Axis(0.0);
}
}
}
let motion_avg = if let Some(mouse_events) = mouse_events {
let count = mouse_events.len();
let mut sum = Vec2::ZERO;
for mm in mouse_events {
sum += mm.delta;
}
Some(sum / count as f32)
} else { None };
let mut handler = world.try_get_resource_mut::<ActionHandler>()
.expect("No Input Action handler was created in the world!");
let layout = handler.layouts.get(&handler.current_layout).expect("No active layout");
let mapping = layout.mappings.get(&layout.active_mapping).expect("No active mapping");
for (action_lbl, binds) in mapping.action_binds.clone().iter() {
let action = handler.actions.get_mut(action_lbl).expect("Action name for binding is invalid!");
let mut new_state = None;
for bind in binds.iter() {
match bind.source {
ActionSource::Keyboard(key) => if let Some(keys) = &keys {
// JustPressed needs to be first, since is_pressed includes buttons that
// were just pressed.
match action.kind {
ActionKind::Button => {
if keys.was_just_pressed(key) {
new_state = Some(ActionState::JustPressed(bind.modifier));
} else if keys.is_pressed(key) {
new_state = Some(ActionState::Pressed(bind.modifier));
}
},
ActionKind::Axis => {
if keys.is_pressed(key) {
new_state = Some(ActionState::Axis(bind.modifier));
}
}
}
},
ActionSource::Gamepad(_, _) => todo!(),
ActionSource::Mouse(m) => match m {
MouseInput::Button(_) => todo!(),
MouseInput::Axis(a) => if let Some(motion_avg) = motion_avg {
match a {
MouseAxis::X => {
new_state = Some(ActionState::Axis(motion_avg.x));
},
MouseAxis::Y => {
new_state = Some(ActionState::Axis(motion_avg.y));
},
MouseAxis::ScrollWheel => todo!(),
}
},
},
}
}
if let Some(new_state) = new_state {
action.state = new_state;
} else if action.kind == ActionKind::Button {
match action.state {
ActionState::Idle => {},
ActionState::JustReleased => action.state = ActionState::Idle,
_ => action.state = ActionState::JustReleased,
}
}
}
Ok(())
}
#[derive(Default, Clone, Copy)]
pub struct InputActionPlugin;
impl Plugin for InputActionPlugin {
fn setup(&self, game: &mut crate::game::Game) {
game.add_system_to_stage(GameStages::PreUpdate, "input_actions", actions_system, &[]);
}
}