use bevy_utils::HashSet; use std::hash::Hash; // unused import, but needed for intra doc link to work #[allow(unused_imports)] use bevy_ecs::schedule::State; /// A "press-able" input of type `T`. /// /// ## Usage /// /// This type can be used as a resource to keep the current state of an input, by reacting to /// events from the input. For a given input value: /// /// * [`Input::pressed`] will return `true` between a press and a release event. /// * [`Input::just_pressed`] will return `true` for one frame after a press event. /// * [`Input::just_released`] will return `true` for one frame after a release event. /// /// ## Multiple systems /// /// In case multiple systems are checking for [`Input::just_pressed`] or [`Input::just_released`] /// but only one should react, for example in the case of triggering /// [`State`](bevy_ecs::schedule::State) change, you should consider clearing the input state, either by: /// /// * Using [`Input::clear_just_pressed`] or [`Input::clear_just_released`] instead. /// * Calling [`Input::clear`] or [`Input::reset`] immediately after the state change. /// /// ## Note /// /// When adding this resource for a new input type, you should: /// /// * Call the [`Input::press`] method for each press event. /// * Call the [`Input::release`] method for each release event. /// * Call the [`Input::clear`] method at each frame start, before processing events. #[derive(Debug, Clone)] pub struct Input { /// A collection of every button that is currently being pressed. pressed: HashSet, /// A collection of every button that has just been pressed. just_pressed: HashSet, /// A collection of every button that has just been released. just_released: HashSet, } impl Default for Input { fn default() -> Self { Self { pressed: Default::default(), just_pressed: Default::default(), just_released: Default::default(), } } } impl Input where T: Copy + Eq + Hash, { /// Registers a press for the given `input`. pub fn press(&mut self, input: T) { // Returns `true` if the `input` wasn't pressed. if self.pressed.insert(input) { self.just_pressed.insert(input); } } /// Returns `true` if the `input` has been pressed. pub fn pressed(&self, input: T) -> bool { self.pressed.contains(&input) } /// Returns `true` if any item in `inputs` has been pressed. pub fn any_pressed(&self, inputs: impl IntoIterator) -> bool { inputs.into_iter().any(|it| self.pressed(it)) } /// Registers a release for the given `input`. pub fn release(&mut self, input: T) { // Returns `true` if the `input` was pressed. if self.pressed.remove(&input) { self.just_released.insert(input); } } /// Returns `true` if the `input` has just been pressed. pub fn just_pressed(&self, input: T) -> bool { self.just_pressed.contains(&input) } /// Returns `true` if any item in `inputs` has just been pressed. pub fn any_just_pressed(&self, inputs: impl IntoIterator) -> bool { inputs.into_iter().any(|it| self.just_pressed(it)) } /// Clears the `just_pressed` state of the `input` and returns `true` if the `input` has just been pressed. /// /// Future calls to [`Input::just_pressed`] for the given input will return false until a new press event occurs. pub fn clear_just_pressed(&mut self, input: T) -> bool { self.just_pressed.remove(&input) } /// Returns `true` if the `input` has just been released. pub fn just_released(&self, input: T) -> bool { self.just_released.contains(&input) } /// Returns `true` if any item in `inputs` has just been released. pub fn any_just_released(&self, inputs: impl IntoIterator) -> bool { inputs.into_iter().any(|it| self.just_released(it)) } /// Clears the `just_released` state of the `input` and returns `true` if the `input` has just been released. /// /// Future calls to [`Input::just_released`] for the given input will return false until a new release event occurs. pub fn clear_just_released(&mut self, input: T) -> bool { self.just_released.remove(&input) } /// Clears the `pressed`, `just_pressed` and `just_released` data of the `input`. pub fn reset(&mut self, input: T) { self.pressed.remove(&input); self.just_pressed.remove(&input); self.just_released.remove(&input); } /// Clears the `just pressed` and `just released` data for every input. pub fn clear(&mut self) { self.just_pressed.clear(); self.just_released.clear(); } /// An iterator visiting every pressed input in arbitrary order. pub fn get_pressed(&self) -> impl ExactSizeIterator { self.pressed.iter() } /// An iterator visiting every just pressed input in arbitrary order. pub fn get_just_pressed(&self) -> impl ExactSizeIterator { self.just_pressed.iter() } /// An iterator visiting every just released input in arbitrary order. pub fn get_just_released(&self) -> impl ExactSizeIterator { self.just_released.iter() } } #[cfg(test)] mod test { #[test] fn input_test() { use crate::Input; /// Used for testing `Input` functionality #[derive(Copy, Clone, Eq, PartialEq, Hash)] enum DummyInput { Input1, Input2, } let mut input = Input::default(); // Test pressing input.press(DummyInput::Input1); input.press(DummyInput::Input2); // Check if they were "just pressed" (pressed on this update) assert!(input.just_pressed(DummyInput::Input1)); assert!(input.just_pressed(DummyInput::Input2)); // Check if they are also marked as pressed assert!(input.pressed(DummyInput::Input1)); assert!(input.pressed(DummyInput::Input2)); // Clear the `input`, removing just pressed and just released input.clear(); // After calling clear, inputs should still be pressed but not be just_pressed // Check if they're marked "just pressed" assert!(!input.just_pressed(DummyInput::Input1)); assert!(!input.just_pressed(DummyInput::Input2)); // Check if they're marked as pressed assert!(input.pressed(DummyInput::Input1)); assert!(input.pressed(DummyInput::Input2)); // Release the inputs and check state input.release(DummyInput::Input1); input.release(DummyInput::Input2); // Check if they're marked as "just released" (released on this update) assert!(input.just_released(DummyInput::Input1)); assert!(input.just_released(DummyInput::Input2)); // Check that they're not incorrectly marked as pressed assert!(!input.pressed(DummyInput::Input1)); assert!(!input.pressed(DummyInput::Input2)); // Clear the `Input` and check for removal from `just_released` input.clear(); // Check that they're not incorrectly marked as just released assert!(!input.just_released(DummyInput::Input1)); assert!(!input.just_released(DummyInput::Input2)); // Set up an `Input` to test resetting. let mut input = Input::default(); input.press(DummyInput::Input1); input.release(DummyInput::Input2); // Reset the `Input` and test it was reset correctly. input.reset(DummyInput::Input1); input.reset(DummyInput::Input2); assert!(!input.just_pressed(DummyInput::Input1)); assert!(!input.pressed(DummyInput::Input1)); assert!(!input.just_released(DummyInput::Input2)); } }