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}