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}