use std::{collections::HashMap, ops::Deref}; use lyra_ecs::{world::World, system::IntoSystem}; use crate::plugin::Plugin; use super::{Button, KeyCode, InputButtons}; #[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 ActionSource { Keyboard(KeyCode), Gamepad(GamepadFormat, GamepadInput), } 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 { 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(u32); impl From for LayoutId { fn from(value: u32) -> Self { Self(value) } } #[derive(Clone, Default)] pub struct Layout { mappings: HashMap, 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(u32); impl From for ActionMappingId { fn from(value: u32) -> Self { Self(value) } } #[derive(Clone)] pub struct ActionMapping { layout: LayoutId, id: ActionMappingId, action_binds: HashMap>, } impl ActionMapping { pub fn new(layout: LayoutId, id: ActionMappingId) -> Self { Self { layout, id, action_binds: HashMap::new(), } } /// Creates a binding for the action. /// /// If the action is not in this layout, this will panic! /// /// Parameters: /// * `action_label` - The label corresponding to the action in this Layout. /// * `bind` - The Binding to add to the Action. pub fn bind(mut self, action_label: &str, bindings: &[Binding]) -> Self { let mut bindings = bindings.to_vec(); let action_binds = self.action_binds.entry(action_label.to_string()).or_default(); action_binds.append(&mut bindings); self } /// Creates multiple binding for the action. /// /// If the action is not in this layout, this will panic! /// /// Parameters: /// * `action_label` - The label corresponding to the action in this Layout. /// * `bindings` - The list of Bindings to add to the Action. /* pub fn add_bindings(&mut self, action_label: String, bindings: &[Binding]) -> &mut Self { let mut bindings = bindings.to_vec(); let action_binds = self.action_binds.entry(action_label) .or_insert_with(Vec::new); action_binds.append(&mut bindings); self } */ pub fn finish(self) -> Self { self } } #[derive(Clone, Default)] pub struct ActionHandler { pub actions: HashMap, pub layouts: HashMap, pub current_layout: LayoutId, pub current_mapping: ActionMappingId, } impl ActionHandler { pub fn new() -> Self { Self::default() } pub fn add_layout(mut self, id: LayoutId) -> Self { self.layouts.insert(id, Layout::new()); self } pub fn add_action(mut self, label: &str, action: Action) -> Self { self.actions.insert(label.to_string(), action); self } pub fn add_mapping(mut self, mapping: ActionMapping) -> Self { let layout = self.layouts.get_mut(&mapping.layout).unwrap(); layout.add_mapping(mapping); self } /// 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(&self, action_name: &str) -> bool { let action = self.actions.get(action_name) .unwrap_or_else(|| panic!("Action {action_name} 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(&self, action_name: &str) -> bool { let action = self.actions.get(action_name) .unwrap_or_else(|| panic!("Action {action_name} 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(&self, action_name: &str) -> bool { let action = self.actions.get(action_name) .unwrap_or_else(|| panic!("Action {action_name} 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(&self, action_name: &str) -> ActionState { let action = self.actions.get(action_name) .unwrap_or_else(|| panic!("Action {action_name} 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(&self, action_name: &str) -> Option { let action = self.actions.get(action_name) .unwrap_or_else(|| panic!("Action {action_name} 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(&self, action_name: &str) -> Option { let action = self.actions.get(action_name) .unwrap_or_else(|| panic!("Action {action_name} 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(&self, action_name: &str) -> Option { let action = self.actions.get(action_name) .unwrap_or_else(|| panic!("Action {action_name} was not found")); match action.state { ActionState::Axis(v) => Some(v), _ => None, } } } fn actions_system(world: &mut World) -> anyhow::Result<()> { let keys = world.try_get_resource::>() .map(|r| r.deref().clone()); if let Some(keys) = keys { let mut handler = world.try_get_resource_mut::() .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_name, binds) in mapping.action_binds.clone().iter() { // TODO: dont clone let mut new_state = None; for bind in binds.iter() { match bind.source { ActionSource::Keyboard(key) => { // JustPressed needs to be first, since is_pressed includes buttons that // were just pressed. 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)); } }, ActionSource::Gamepad(_, _) => todo!(), } } let action = handler.actions.get_mut(action_name).expect("Action name for binding is invalid!"); if let Some(new_state) = new_state { action.state = new_state; } else { 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.with_system("input_actions", actions_system.into_system(), &[]); } }