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 messages 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 self.get_pressed(input_store, gamepad).unwrap_or(false)
142 }
143
144 /// Checks if the input is currently active.
145 #[must_use]
146 fn get_pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<bool>;
147
148 /// Checks if the input is currently inactive.
149 fn released(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool {
150 !self.pressed(input_store, gamepad)
151 }
152
153 /// Gets the current value of the button as an `f32`.
154 ///
155 /// The returned value should be between `0.0` and `1.0`,
156 /// with `0.0` representing the input being fully released
157 /// and `1.0` representing the input being fully pressed.
158 #[must_use]
159 fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
160 f32::from(self.pressed(input_store, gamepad))
161 }
162
163 /// Gets the current value of the button as an `f32`.
164 /// Or `None` if the input has never been pressed or set.
165 ///
166 /// The returned value should be between `0.0` and `1.0`,
167 /// with `0.0` representing the input being fully released
168 /// and `1.0` representing the input being fully pressed.
169 fn get_value(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<f32> {
170 self.get_pressed(input_store, gamepad).map(f32::from)
171 }
172
173 /// Simulates a press of the buttonlike input by sending the appropriate message.
174 ///
175 /// This method defaults to calling [`Buttonlike::press_as_gamepad`] if not overridden,
176 /// as is the case for gamepad-reliant inputs.
177 fn press(&self, world: &mut World) {
178 self.press_as_gamepad(world, None);
179 }
180
181 /// Simulate a press of the buttonlike input, pretending to be the provided gamepad [`Entity`].
182 ///
183 /// This method defaults to calling [`Buttonlike::press`] if not overridden,
184 /// as is the case for things like mouse buttons and keyboard keys.
185 ///
186 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
187 /// if the provided gamepad is `None`.
188 fn press_as_gamepad(&self, world: &mut World, _gamepad: Option<Entity>) {
189 self.press(world);
190 }
191
192 /// Simulates a release of the buttonlike input by sending the appropriate message.
193 ///
194 /// This method defaults to calling [`Buttonlike::release_as_gamepad`] if not overridden,
195 /// as is the case for gamepad-reliant inputs.
196 fn release(&self, world: &mut World) {
197 self.release_as_gamepad(world, None);
198 }
199
200 /// Simulate a release of the buttonlike input, pretending to be the provided gamepad [`Entity`].
201 ///
202 /// This method defaults to calling [`Buttonlike::release`] if not overridden,
203 /// as is the case for things like mouse buttons and keyboard keys.
204 ///
205 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
206 /// if the provided gamepad is `None`.
207 fn release_as_gamepad(&self, world: &mut World, _gamepad: Option<Entity>) {
208 self.release(world);
209 }
210
211 /// Simulate a value change of the buttonlike input by sending the appropriate message.
212 ///
213 /// This method defaults to calling [`Buttonlike::set_value_as_gamepad`] if not overridden,
214 /// as is the case for gamepad-reliant inputs.
215 ///
216 /// Also updates the state of the button based on the `value`:
217 /// - If `value > 0.0`, the button will be pressed.
218 /// - If `value <= 0.0`, the button will be released.
219 fn set_value(&self, world: &mut World, value: f32) {
220 self.set_value_as_gamepad(world, value, None);
221 }
222
223 /// Simulate a value change of the buttonlike input, pretending to be the provided gamepad [`Entity`].
224 ///
225 /// This method defaults to calling [`Buttonlike::set_value`] if not overridden,
226 /// as is the case for things like a mouse wheel.
227 ///
228 /// Also updates the state of the button based on the `value`:
229 /// - If `value > 0.0`, the button will be pressed.
230 /// - If `value <= 0.0`, the button will be released.
231 ///
232 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
233 /// if the provided gamepad is `None`.
234 fn set_value_as_gamepad(&self, world: &mut World, value: f32, _gamepad: Option<Entity>) {
235 self.set_value(world, value);
236 }
237}
238
239/// A trait used for axis-like user inputs, which provide a continuous value.
240pub trait Axislike:
241 UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize
242{
243 /// Gets the current value of the input as an `f32`.
244 #[must_use]
245 fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
246 self.get_value(input_store, gamepad).unwrap_or(0.0)
247 }
248
249 /// Gets the current value of the input as an `f32`.
250 #[must_use]
251 fn get_value(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<f32>;
252
253 /// Simulate an axis-like input by sending the appropriate message.
254 ///
255 /// This method defaults to calling [`Axislike::set_value_as_gamepad`] if not overridden,
256 /// as is the case for gamepad-reliant inputs.
257 fn set_value(&self, world: &mut World, value: f32) {
258 self.set_value_as_gamepad(world, value, None);
259 }
260
261 /// Simulate an axis-like input, pretending to be the provided gamepad [`Entity`].
262 ///
263 /// This method defaults to calling [`Axislike::set_value`] if not overridden,
264 /// as is the case for things like a mouse wheel.
265 ///
266 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
267 /// if the provided gamepad is `None`.
268 fn set_value_as_gamepad(&self, world: &mut World, value: f32, _gamepad: Option<Entity>) {
269 self.set_value(world, value);
270 }
271}
272
273/// A trait used for dual-axis-like user inputs, which provide separate X and Y values.
274pub trait DualAxislike:
275 UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize
276{
277 /// Gets the values of this input along the X and Y axes (if applicable).
278 #[must_use]
279 fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec2 {
280 self.get_axis_pair(input_store, gamepad)
281 .unwrap_or(Vec2::ZERO)
282 }
283
284 /// Gets the values of this input along the X and Y axes (if applicable).
285 #[must_use]
286 fn get_axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec2>;
287
288 /// Simulate a dual-axis-like input by sending the appropriate message.
289 ///
290 /// This method defaults to calling [`DualAxislike::set_axis_pair_as_gamepad`] if not overridden,
291 /// as is the case for gamepad-reliant inputs.
292 fn set_axis_pair(&self, world: &mut World, value: Vec2) {
293 self.set_axis_pair_as_gamepad(world, value, None);
294 }
295
296 /// Simulate a dual-axis-like input, pretending to be the provided gamepad [`Entity`].
297 ///
298 /// This method defaults to calling [`DualAxislike::set_axis_pair`] if not overridden,
299 /// as is the case for things like a mouse wheel.
300 ///
301 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
302 /// if the provided gamepad is `None`.
303 fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, _gamepad: Option<Entity>) {
304 self.set_axis_pair(world, value);
305 }
306}
307
308/// A trait used for triple-axis-like user inputs, which provide separate X, Y, and Z values.
309pub trait TripleAxislike:
310 UserInput + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize
311{
312 /// Gets the values of this input along the X, Y, and Z axes (if applicable).
313 #[must_use]
314 fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec3 {
315 self.get_axis_triple(input_store, gamepad)
316 .unwrap_or(Vec3::ZERO)
317 }
318
319 /// Gets the values of this input along the X, Y, and Z axes (if applicable).
320 ///
321 /// Or `None` if the input has never been pressed or set.
322 fn get_axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec3>;
323
324 /// Simulate a triple-axis-like input by sending the appropriate message.
325 ///
326 /// This method defaults to calling [`TripleAxislike::set_axis_triple_as_gamepad`] if not overridden,
327 /// as is the case for gamepad-reliant inputs.
328 fn set_axis_triple(&self, world: &mut World, value: Vec3) {
329 self.set_axis_triple_as_gamepad(world, value, None);
330 }
331
332 /// Simulate a triple-axis-like input, pretending to be the provided gamepad [`Entity`].
333 ///
334 /// This method defaults to calling [`TripleAxislike::set_axis_triple`] if not overridden,
335 /// as is the case for things like a space mouse.
336 ///
337 /// Use [`find_gamepad`] inside of this method to search for a gamepad to press the button on
338 /// if the provided gamepad is `None`.
339 fn set_axis_triple_as_gamepad(&self, world: &mut World, value: Vec3, _gamepad: Option<Entity>) {
340 self.set_axis_triple(world, value);
341 }
342}
343
344/// A wrapper type to get around the lack of [trait upcasting coercion](https://github.com/rust-lang/rust/issues/65991).
345///
346/// To return a generic [`UserInput`] trait object from a function, you can use this wrapper type.
347
348#[derive(Reflect, Debug, Clone, PartialEq, Eq, Hash, Serialize)]
349pub enum UserInputWrapper {
350 /// Wraps a [`Buttonlike`] input.
351 Button(Box<dyn Buttonlike>),
352 /// Wraps an [`Axislike`] input.
353 Axis(Box<dyn Axislike>),
354 /// Wraps a [`DualAxislike`] input.
355 DualAxis(Box<dyn DualAxislike>),
356 /// Wraps a [`TripleAxislike`] input.
357 TripleAxis(Box<dyn TripleAxislike>),
358}
359
360impl UserInput for UserInputWrapper {
361 #[track_caller]
362 fn kind(&self) -> InputControlKind {
363 match self {
364 UserInputWrapper::Button(input) => {
365 debug_assert!(input.kind() == InputControlKind::Button);
366 input.kind()
367 }
368 UserInputWrapper::Axis(input) => {
369 debug_assert!(input.kind() == InputControlKind::Axis);
370 input.kind()
371 }
372 UserInputWrapper::DualAxis(input) => {
373 debug_assert!(input.kind() == InputControlKind::DualAxis);
374 input.kind()
375 }
376 UserInputWrapper::TripleAxis(input) => {
377 debug_assert!(input.kind() == InputControlKind::TripleAxis);
378 input.kind()
379 }
380 }
381 }
382
383 fn decompose(&self) -> BasicInputs {
384 match self {
385 UserInputWrapper::Button(input) => input.decompose(),
386 UserInputWrapper::Axis(input) => input.decompose(),
387 UserInputWrapper::DualAxis(input) => input.decompose(),
388 UserInputWrapper::TripleAxis(input) => input.decompose(),
389 }
390 }
391}