leafwing_input_manager/user_input/mod.rs
1//! Helpful abstractions over user inputs of all sorts.
2//!
3//! This module simplifies user input handling in Bevy applications
4//! by providing abstractions and utilities for various input devices
5//! like gamepads, keyboards, and mice. It offers a unified interface
6//! for querying input values and states, reducing boilerplate code
7//! and making user interactions easier to manage.
8//!
9//! The foundation of this module lies in the [`UserInput`] trait,
10//! used to define the behavior expected from a specific user input source.
11//!
12//! Need something specific? You can also create your own inputs by implementing the trait for specific needs.
13//!
14//! Feel free to suggest additions to the built-in inputs if you have a common use case!
15//!
16//! ## Control Types
17//!
18//! [`UserInput`]s use the method [`UserInput::kind`] returning an [`InputControlKind`]
19//! to classify the behavior of the input (buttons, analog axes, etc.).
20//!
21//! - [`InputControlKind::Button`]: Represents a digital input with an on/off state (e.g., button press).
22//! These inputs typically provide two values, typically `0.0` (inactive) and `1.0` (fully active).
23//!
24//! - [`InputControlKind::Axis`]: Represents an analog input (e.g., mouse wheel)
25//! with a continuous value typically ranging from `-1.0` (fully left/down) to `1.0` (fully right/up).
26//! Non-zero values are considered active.
27//!
28//! - [`InputControlKind::DualAxis`]: Represents a combination of two analog axes (e.g., thumb stick).
29//! These inputs provide separate X and Y values typically ranging from `-1.0` to `1.0`.
30//! Non-zero values are considered active.
31//!
32//! ## Basic Inputs
33//!
34//! [`UserInput`]s use the method [`UserInput::decompose`] returning a [`BasicInputs`]
35//! used for clashing detection, see [clashing input check](crate::clashing_inputs) for details.
36//!
37//! ## Built-in Inputs
38//!
39//! ### Gamepad Inputs
40//!
41//! - Check gamepad button presses using Bevy's [`GamepadButton`] directly.
42//! - Access physical sticks using [`GamepadStick`], [`GamepadControlAxis`], and [`GamepadControlDirection`].
43//!
44//! ### Keyboard Inputs
45//!
46//! - Check physical keys presses using Bevy's [`KeyCode`] directly.
47//! - Use [`ModifierKey`] to check for either left or right modifier keys is pressed.
48//!
49//! ### Mouse Inputs
50//!
51//! - Check mouse buttons presses using Bevy's [`MouseButton`] directly.
52//! - Track mouse motion with [`MouseMove`], [`MouseMoveAxis`], and [`MouseMoveDirection`].
53//! - Capture mouse wheel events with [`MouseScroll`], [`MouseScrollAxis`], and [`MouseScrollDirection`].
54//!
55//! ### Virtual Axial Controls
56//!
57//! - [`VirtualAxis`]: Create a virtual axis control from two buttons.
58//!
59//! - [`VirtualDPad`]: Create a virtual dual-axis control from four buttons.
60//!
61//! - [`VirtualDPad3D`]: Create a virtual triple-axis control from six buttons.
62//!
63//! ### Chords
64//!
65//! - [`ButtonlikeChord`]: A combined input that groups multiple [`Buttonlike`]s together,
66//! allowing you to define complex input combinations like hotkeys, shortcuts, and macros.
67//!
68//! - [`AxislikeChord`]: A combined input that groups a [`Buttonlike`] and an [`Axislike`] together,
69//! allowing you to only read the dual axis data when the button is pressed.
70//!
71//! - [`DualAxislikeChord`]: A combined input that groups a [`Buttonlike`] and a [`DualAxislike`] together,
72//! allowing you to only read the dual axis data when the button is pressed.
73//!
74//! - [`TripleAxislikeChord`]: A combined input that groups a [`Buttonlike`] and a [`TripleAxislike`] together,
75//! allowing you to only read the dual axis data when the button is pressed.
76//!
77//! [`GamepadButton`]: bevy::prelude::GamepadButton
78//! [`KeyCode`]: bevy::prelude::KeyCode
79//! [`MouseButton`]: bevy::prelude::MouseButton
80
81use std::fmt::Debug;
82
83use bevy::math::{Vec2, Vec3};
84use bevy::prelude::{Entity, World};
85use bevy::reflect::{erased_serde, Reflect};
86use dyn_clone::DynClone;
87use dyn_eq::DynEq;
88use dyn_hash::DynHash;
89use serde::Serialize;
90use updating::CentralInputStore;
91
92use crate::clashing_inputs::BasicInputs;
93use crate::InputControlKind;
94
95pub use self::chord::*;
96#[cfg(feature = "gamepad")]
97pub use self::gamepad::*;
98#[cfg(feature = "keyboard")]
99pub use self::keyboard::*;
100#[cfg(feature = "mouse")]
101pub use self::mouse::*;
102pub use self::trait_serde::RegisterUserInput;
103pub use self::virtual_axial::*;
104
105pub mod chord;
106#[cfg(feature = "gamepad")]
107pub mod gamepad;
108#[cfg(feature = "keyboard")]
109pub mod keyboard;
110#[cfg(feature = "mouse")]
111pub mod mouse;
112pub mod testing_utils;
113mod trait_reflection;
114mod trait_serde;
115pub mod updating;
116pub mod virtual_axial;
117
118/// A trait for defining the behavior expected from different user input sources.
119pub trait UserInput: Send + Sync + Debug {
120 /// Defines the kind of behavior that the input should be.
121 fn kind(&self) -> InputControlKind;
122
123 /// Returns the set of primitive inputs that make up this input.
124 ///
125 /// These inputs are used to detect clashes between different user inputs,
126 /// and are stored in a [`BasicInputs`] for easy comparison.
127 ///
128 /// For inputs that represent a simple, atomic control,
129 /// this method should always return a [`BasicInputs::Simple`] that only contains the input itself.
130 fn decompose(&self) -> BasicInputs;
131}
132
133/// A trait used for buttonlike user inputs, which can be pressed or released
134/// with a value for how much they are pressed.
135pub trait Buttonlike:
136 UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize
137{
138 /// Checks if the input is currently active.
139 #[must_use]
140 fn pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool;
141
142 /// Checks if the input is currently inactive.
143 fn released(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool {
144 !self.pressed(input_store, gamepad)
145 }
146
147 /// Gets the current value of the button as an `f32`.
148 ///
149 /// The returned value should be between `0.0` and `1.0`,
150 /// with `0.0` representing the input being fully released
151 /// and `1.0` representing the input being fully pressed.
152 #[must_use]
153 fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
154 f32::from(self.pressed(input_store, gamepad))
155 }
156
157 /// Simulates a press of the buttonlike input by sending the appropriate event.
158 ///
159 /// This method defaults to calling [`Buttonlike::press_as_gamepad`] if not overridden,
160 /// as is the case for gamepad-reliant inputs.
161 fn press(&self, world: &mut World) {
162 self.press_as_gamepad(world, None);
163 }
164
165 /// Simulate a press of the buttonlike input, pretending to be the provided gamepad [`Entity`].
166 ///
167 /// This method defaults to calling [`Buttonlike::press`] if not overridden,
168 /// as is the case for things like mouse buttons and keyboard keys.
169 ///
170 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
171 /// if the provided gamepad is `None`.
172 fn press_as_gamepad(&self, world: &mut World, _gamepad: Option<Entity>) {
173 self.press(world);
174 }
175
176 /// Simulates a release of the buttonlike input by sending the appropriate event.
177 ///
178 /// This method defaults to calling [`Buttonlike::release_as_gamepad`] if not overridden,
179 /// as is the case for gamepad-reliant inputs.
180 fn release(&self, world: &mut World) {
181 self.release_as_gamepad(world, None);
182 }
183
184 /// Simulate a release of the buttonlike input, pretending to be the provided gamepad [`Entity`].
185 ///
186 /// This method defaults to calling [`Buttonlike::release`] if not overridden,
187 /// as is the case for things like mouse buttons and keyboard keys.
188 ///
189 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
190 /// if the provided gamepad is `None`.
191 fn release_as_gamepad(&self, world: &mut World, _gamepad: Option<Entity>) {
192 self.release(world);
193 }
194
195 /// Simulate a value change of the buttonlike input by sending the appropriate event.
196 ///
197 /// This method defaults to calling [`Buttonlike::set_value_as_gamepad`] if not overridden,
198 /// as is the case for gamepad-reliant inputs.
199 ///
200 /// Also updates the state of the button based on the `value`:
201 /// - If `value > 0.0`, the button will be pressed.
202 /// - If `value <= 0.0`, the button will be released.
203 fn set_value(&self, world: &mut World, value: f32) {
204 self.set_value_as_gamepad(world, value, None);
205 }
206
207 /// Simulate a value change of the buttonlike input, pretending to be the provided gamepad [`Entity`].
208 ///
209 /// This method defaults to calling [`Buttonlike::set_value`] if not overridden,
210 /// as is the case for things like a mouse wheel.
211 ///
212 /// Also updates the state of the button based on the `value`:
213 /// - If `value > 0.0`, the button will be pressed.
214 /// - If `value <= 0.0`, the button will be released.
215 ///
216 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
217 /// if the provided gamepad is `None`.
218 fn set_value_as_gamepad(&self, world: &mut World, value: f32, _gamepad: Option<Entity>) {
219 self.set_value(world, value);
220 }
221}
222
223/// A trait used for axis-like user inputs, which provide a continuous value.
224pub trait Axislike:
225 UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize
226{
227 /// Gets the current value of the input as an `f32`.
228 #[must_use]
229 fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32;
230
231 /// Simulate an axis-like input by sending the appropriate event.
232 ///
233 /// This method defaults to calling [`Axislike::set_value_as_gamepad`] if not overridden,
234 /// as is the case for gamepad-reliant inputs.
235 fn set_value(&self, world: &mut World, value: f32) {
236 self.set_value_as_gamepad(world, value, None);
237 }
238
239 /// Simulate an axis-like input, pretending to be the provided gamepad [`Entity`].
240 ///
241 /// This method defaults to calling [`Axislike::set_value`] if not overridden,
242 /// as is the case for things like a mouse wheel.
243 ///
244 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
245 /// if the provided gamepad is `None`.
246 fn set_value_as_gamepad(&self, world: &mut World, value: f32, _gamepad: Option<Entity>) {
247 self.set_value(world, value);
248 }
249}
250
251/// A trait used for dual-axis-like user inputs, which provide separate X and Y values.
252pub trait DualAxislike:
253 UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize
254{
255 /// Gets the values of this input along the X and Y axes (if applicable).
256 #[must_use]
257 fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec2;
258
259 /// Simulate a dual-axis-like input by sending the appropriate event.
260 ///
261 /// This method defaults to calling [`DualAxislike::set_axis_pair_as_gamepad`] if not overridden,
262 /// as is the case for gamepad-reliant inputs.
263 fn set_axis_pair(&self, world: &mut World, value: Vec2) {
264 self.set_axis_pair_as_gamepad(world, value, None);
265 }
266
267 /// Simulate a dual-axis-like input, pretending to be the provided gamepad [`Entity`].
268 ///
269 /// This method defaults to calling [`DualAxislike::set_axis_pair`] if not overridden,
270 /// as is the case for things like a mouse wheel.
271 ///
272 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
273 /// if the provided gamepad is `None`.
274 fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, _gamepad: Option<Entity>) {
275 self.set_axis_pair(world, value);
276 }
277}
278
279/// A trait used for triple-axis-like user inputs, which provide separate X, Y, and Z values.
280pub trait TripleAxislike:
281 UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize
282{
283 /// Gets the values of this input along the X, Y, and Z axes (if applicable).
284 #[must_use]
285 fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec3;
286
287 /// Simulate a triple-axis-like input by sending the appropriate event.
288 ///
289 /// This method defaults to calling [`TripleAxislike::set_axis_triple_as_gamepad`] if not overridden,
290 /// as is the case for gamepad-reliant inputs.
291 fn set_axis_triple(&self, world: &mut World, value: Vec3) {
292 self.set_axis_triple_as_gamepad(world, value, None);
293 }
294
295 /// Simulate a triple-axis-like input, pretending to be the provided gamepad [`Entity`].
296 ///
297 /// This method defaults to calling [`TripleAxislike::set_axis_triple`] if not overridden,
298 /// as is the case for things like a space mouse.
299 ///
300 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
301 /// if the provided gamepad is `None`.
302 fn set_axis_triple_as_gamepad(&self, world: &mut World, value: Vec3, _gamepad: Option<Entity>) {
303 self.set_axis_triple(world, value);
304 }
305}
306
307/// A wrapper type to get around the lack of [trait upcasting coercion](https://github.com/rust-lang/rust/issues/65991).
308///
309/// To return a generic [`UserInput`] trait object from a function, you can use this wrapper type.
310
311#[derive(Reflect, Debug, Clone, PartialEq, Eq, Hash, Serialize)]
312pub enum UserInputWrapper {
313 /// Wraps a [`Buttonlike`] input.
314 Button(Box<dyn Buttonlike>),
315 /// Wraps an [`Axislike`] input.
316 Axis(Box<dyn Axislike>),
317 /// Wraps a [`DualAxislike`] input.
318 DualAxis(Box<dyn DualAxislike>),
319 /// Wraps a [`TripleAxislike`] input.
320 TripleAxis(Box<dyn TripleAxislike>),
321}
322
323impl UserInput for UserInputWrapper {
324 #[track_caller]
325 fn kind(&self) -> InputControlKind {
326 match self {
327 UserInputWrapper::Button(input) => {
328 debug_assert!(input.kind() == InputControlKind::Button);
329 input.kind()
330 }
331 UserInputWrapper::Axis(input) => {
332 debug_assert!(input.kind() == InputControlKind::Axis);
333 input.kind()
334 }
335 UserInputWrapper::DualAxis(input) => {
336 debug_assert!(input.kind() == InputControlKind::DualAxis);
337 input.kind()
338 }
339 UserInputWrapper::TripleAxis(input) => {
340 debug_assert!(input.kind() == InputControlKind::TripleAxis);
341 input.kind()
342 }
343 }
344 }
345
346 fn decompose(&self) -> BasicInputs {
347 match self {
348 UserInputWrapper::Button(input) => input.decompose(),
349 UserInputWrapper::Axis(input) => input.decompose(),
350 UserInputWrapper::DualAxis(input) => input.decompose(),
351 UserInputWrapper::TripleAxis(input) => input.decompose(),
352 }
353 }
354}