leafwing_input_manager/user_input/
chord.rs

1//! This module contains [`ButtonlikeChord`] and its impls.
2
3use bevy::math::{Vec2, Vec3};
4use bevy::prelude::{Entity, Reflect, World};
5use leafwing_input_manager_macros::serde_typetag;
6use serde::{Deserialize, Serialize};
7
8use crate as leafwing_input_manager;
9use crate::clashing_inputs::BasicInputs;
10use crate::user_input::{Buttonlike, TripleAxislike, UserInput};
11use crate::InputControlKind;
12
13use super::updating::CentralInputStore;
14use super::{Axislike, DualAxislike};
15
16/// A combined input that groups multiple [`Buttonlike`]s together,
17/// allowing you to define complex input combinations like hotkeys, shortcuts, and macros.
18///
19/// A chord is pressed only if all its constituent buttons are pressed simultaneously.
20///
21/// Adding duplicate buttons within a chord will ignore the extras,
22/// preventing redundant data fetching from multiple instances of the same input.
23///
24/// ```rust
25/// use bevy::prelude::*;
26/// use bevy::input::InputPlugin;
27/// use leafwing_input_manager::plugin::CentralInputStorePlugin;
28/// use leafwing_input_manager::prelude::*;
29/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput;
30///
31/// let mut app = App::new();
32/// app.add_plugins((InputPlugin, CentralInputStorePlugin));
33///
34/// // Define a chord using A and B keys
35/// let input = ButtonlikeChord::new([KeyCode::KeyA, KeyCode::KeyB]);
36///
37/// // Pressing only one key doesn't activate the input
38/// KeyCode::KeyA.press(app.world_mut());
39/// app.update();
40/// assert!(!app.read_pressed(input.clone()));
41///
42/// // Pressing both keys activates the input
43/// KeyCode::KeyA.press(app.world_mut());
44/// KeyCode::KeyB.press(app.world_mut());
45/// app.update();
46/// assert!(app.read_pressed(input.clone()));
47/// ```
48#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
49#[must_use]
50pub struct ButtonlikeChord(
51    // Note: We can't use a HashSet here because of
52    // https://users.rust-lang.org/t/hash-not-implemented-why-cant-it-be-derived/92416/8
53    // We can't use a BTreeSet because the underlying types don't impl Ord
54    // We don't want to use a PetitSet here because of memory bloat
55    // So a vec it is!
56    pub(crate) Vec<Box<dyn Buttonlike>>,
57);
58
59impl ButtonlikeChord {
60    /// Creates a [`ButtonlikeChord`] from multiple [`Buttonlike`]s, avoiding duplicates.
61    /// Note that all elements within the iterator must be of the same type (homogeneous).
62    /// You can still use other methods to add different types of inputs into the chord.
63    #[inline]
64    pub fn new<U: Buttonlike>(inputs: impl IntoIterator<Item = U>) -> Self {
65        Self::default().with_multiple(inputs)
66    }
67
68    /// Creates a [`ButtonlikeChord`] that only contains the given [`Buttonlike`].
69    /// You can still use other methods to add different types of inputs into the chord.
70    #[inline]
71    pub fn from_single(input: impl Buttonlike) -> Self {
72        Self::default().with(input)
73    }
74
75    /// Creates a [`ButtonlikeChord`] that combines the provided modifier and the given [`Buttonlike`].
76    #[cfg(feature = "keyboard")]
77    pub fn modified(modifier: super::keyboard::ModifierKey, input: impl Buttonlike) -> Self {
78        Self::default().with(modifier).with(input)
79    }
80
81    /// Adds the given [`Buttonlike`] into this chord, avoiding duplicates.
82    #[inline]
83    pub fn with(mut self, input: impl Buttonlike) -> Self {
84        self.push_boxed_unique(Box::new(input));
85        self
86    }
87
88    /// Adds multiple [`Buttonlike`]s into this chord, avoiding duplicates.
89    /// Note that all elements within the iterator must be of the same type (homogeneous).
90    #[inline]
91    pub fn with_multiple<U: Buttonlike>(mut self, inputs: impl IntoIterator<Item = U>) -> Self {
92        for input in inputs.into_iter() {
93            self.push_boxed_unique(Box::new(input));
94        }
95        self
96    }
97
98    /// Adds the given boxed dyn [`Buttonlike`] to this chord, avoiding duplicates.
99    #[inline]
100    fn push_boxed_unique(&mut self, input: Box<dyn Buttonlike>) {
101        if !self.0.contains(&input) {
102            self.0.push(input);
103        }
104    }
105}
106
107impl UserInput for ButtonlikeChord {
108    /// [`ButtonlikeChord`] acts as a virtual button.
109    #[inline]
110    fn kind(&self) -> InputControlKind {
111        InputControlKind::Button
112    }
113
114    /// Retrieves a list of simple, atomic [`Buttonlike`]s that compose the chord.
115    ///
116    /// The length of the basic inputs is the sum of the lengths of the inner inputs.
117    #[inline]
118    fn decompose(&self) -> BasicInputs {
119        let inputs = self
120            .0
121            .iter()
122            .flat_map(|input| input.decompose().inputs())
123            .collect();
124        BasicInputs::Chord(inputs)
125    }
126}
127
128#[serde_typetag]
129impl Buttonlike for ButtonlikeChord {
130    /// Checks if all the inner inputs within the chord are active simultaneously.
131    #[inline]
132    fn pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool {
133        self.0
134            .iter()
135            .all(|input| input.pressed(input_store, gamepad))
136    }
137
138    fn press(&self, world: &mut World) {
139        for input in &self.0 {
140            input.press(world);
141        }
142    }
143
144    fn release(&self, world: &mut World) {
145        for input in &self.0 {
146            input.release(world);
147        }
148    }
149
150    fn press_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
151        for input in &self.0 {
152            input.press_as_gamepad(world, gamepad);
153        }
154    }
155
156    fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
157        for input in &self.0 {
158            input.release_as_gamepad(world, gamepad);
159        }
160    }
161}
162
163impl<U: Buttonlike> FromIterator<U> for ButtonlikeChord {
164    /// Creates a [`ButtonlikeChord`] from an iterator over multiple [`Buttonlike`]s, avoiding duplicates.
165    /// Note that all elements within the iterator must be of the same type (homogeneous).
166    /// You can still use other methods to add different types of inputs into the chord.
167    #[inline]
168    fn from_iter<T: IntoIterator<Item = U>>(iter: T) -> Self {
169        Self::default().with_multiple(iter)
170    }
171}
172
173/// A combined input that groups a [`Buttonlike`] and a [`Axislike`] together,
174/// allowing you to only read the axis value when the button is pressed.
175#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
176#[must_use]
177pub struct AxislikeChord {
178    /// The button that must be pressed to read the axis value.
179    pub button: Box<dyn Buttonlike>,
180    /// The axis value that is read when the button is pressed.
181    pub axis: Box<dyn Axislike>,
182}
183
184impl AxislikeChord {
185    /// Creates a new [`AxislikeChord`] from the given [`Buttonlike`] and [`Axislike`].
186    #[inline]
187    pub fn new(button: impl Buttonlike, axis: impl Axislike) -> Self {
188        Self {
189            button: Box::new(button),
190            axis: Box::new(axis),
191        }
192    }
193}
194
195impl UserInput for AxislikeChord {
196    /// [`AxislikeChord`] acts as a virtual axis.
197    #[inline]
198    fn kind(&self) -> InputControlKind {
199        InputControlKind::Axis
200    }
201
202    /// Retrieves a list of simple, atomic [`Buttonlike`]s that compose the chord.
203    #[inline]
204    fn decompose(&self) -> BasicInputs {
205        BasicInputs::compose(self.button.decompose(), self.axis.decompose())
206    }
207}
208
209#[serde_typetag]
210impl Axislike for AxislikeChord {
211    fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
212        if self.button.pressed(input_store, gamepad) {
213            self.axis.value(input_store, gamepad)
214        } else {
215            0.0
216        }
217    }
218
219    fn set_value(&self, world: &mut World, value: f32) {
220        self.axis.set_value(world, value);
221    }
222
223    fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
224        self.axis.set_value_as_gamepad(world, value, gamepad);
225    }
226}
227
228/// A combined input that groups a [`Buttonlike`] and a [`DualAxislike`] together,
229/// allowing you to only read the dual axis data when the button is pressed.
230#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
231#[must_use]
232pub struct DualAxislikeChord {
233    /// The button that must be pressed to read the axis values.
234    pub button: Box<dyn Buttonlike>,
235    /// The dual axis data that is read when the button is pressed.
236    pub dual_axis: Box<dyn DualAxislike>,
237}
238
239impl DualAxislikeChord {
240    /// Creates a new [`DualAxislikeChord`] from the given [`Buttonlike`] and [`DualAxislike`].
241    #[inline]
242    pub fn new(button: impl Buttonlike, dual_axis: impl DualAxislike) -> Self {
243        Self {
244            button: Box::new(button),
245            dual_axis: Box::new(dual_axis),
246        }
247    }
248}
249
250impl UserInput for DualAxislikeChord {
251    /// [`DualAxislikeChord`] acts as a virtual dual-axis.
252    #[inline]
253    fn kind(&self) -> InputControlKind {
254        InputControlKind::DualAxis
255    }
256
257    /// Retrieves a list of simple, atomic [`Buttonlike`]s that compose the chord.
258    #[inline]
259    fn decompose(&self) -> BasicInputs {
260        BasicInputs::compose(self.button.decompose(), self.dual_axis.decompose())
261    }
262}
263
264#[serde_typetag]
265impl DualAxislike for DualAxislikeChord {
266    fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec2 {
267        if self.button.pressed(input_store, gamepad) {
268            self.dual_axis.axis_pair(input_store, gamepad)
269        } else {
270            Vec2::ZERO
271        }
272    }
273
274    fn set_axis_pair(&self, world: &mut World, axis_pair: Vec2) {
275        self.dual_axis.set_axis_pair(world, axis_pair);
276    }
277
278    fn set_axis_pair_as_gamepad(
279        &self,
280        world: &mut World,
281        axis_pair: Vec2,
282        gamepad: Option<Entity>,
283    ) {
284        self.dual_axis
285            .set_axis_pair_as_gamepad(world, axis_pair, gamepad);
286    }
287}
288
289/// A combined input that groups a [`Buttonlike`] and a [`TripleAxislike`] together,
290/// allowing you to only read the dual axis data when the button is pressed.
291#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
292#[must_use]
293pub struct TripleAxislikeChord {
294    /// The button that must be pressed to read the axis values.
295    pub button: Box<dyn Buttonlike>,
296    /// The triple axis data that is read when the button is pressed.
297    pub triple_axis: Box<dyn TripleAxislike>,
298}
299
300impl TripleAxislikeChord {
301    /// Creates a new [`TripleAxislikeChord`] from the given [`Buttonlike`] and [`TripleAxislike`].
302    #[inline]
303    pub fn new(button: impl Buttonlike, triple_axis: impl TripleAxislike) -> Self {
304        Self {
305            button: Box::new(button),
306            triple_axis: Box::new(triple_axis),
307        }
308    }
309}
310
311impl UserInput for TripleAxislikeChord {
312    /// [`TripleAxislikeChord`] acts as a virtual triple-axis.
313    #[inline]
314    fn kind(&self) -> InputControlKind {
315        InputControlKind::TripleAxis
316    }
317
318    /// Retrieves a list of simple, atomic [`Buttonlike`]s that compose the chord.
319    #[inline]
320    fn decompose(&self) -> BasicInputs {
321        BasicInputs::compose(self.button.decompose(), self.triple_axis.decompose())
322    }
323}
324
325#[serde_typetag]
326impl TripleAxislike for TripleAxislikeChord {
327    fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec3 {
328        if self.button.pressed(input_store, gamepad) {
329            self.triple_axis.axis_triple(input_store, gamepad)
330        } else {
331            Vec3::ZERO
332        }
333    }
334
335    fn set_axis_triple(&self, world: &mut World, axis_triple: Vec3) {
336        self.triple_axis.set_axis_triple(world, axis_triple);
337    }
338
339    fn set_axis_triple_as_gamepad(
340        &self,
341        world: &mut World,
342        axis_triple: Vec3,
343        gamepad: Option<Entity>,
344    ) {
345        self.triple_axis
346            .set_axis_triple_as_gamepad(world, axis_triple, gamepad);
347    }
348}
349
350#[cfg(feature = "keyboard")]
351#[cfg(test)]
352mod tests {
353    use super::ButtonlikeChord;
354    use crate::plugin::CentralInputStorePlugin;
355    use crate::user_input::updating::CentralInputStore;
356    use crate::user_input::Buttonlike;
357    use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent};
358    use bevy::input::InputPlugin;
359    use bevy::prelude::*;
360
361    fn test_app() -> App {
362        let mut app = App::new();
363        app.add_plugins(MinimalPlugins)
364            .add_plugins(InputPlugin)
365            .add_plugins(CentralInputStorePlugin);
366
367        // WARNING: you MUST register your gamepad during tests,
368        // or all gamepad input mocking actions will fail
369        let gamepad = app.world_mut().spawn(()).id();
370        let mut gamepad_events = app.world_mut().resource_mut::<Events<GamepadEvent>>();
371        gamepad_events.send(GamepadEvent::Connection(GamepadConnectionEvent {
372            // This MUST be consistent with any other mocked events
373            gamepad,
374            connection: GamepadConnection::Connected {
375                name: "TestController".into(),
376                vendor_id: None,
377                product_id: None,
378            },
379        }));
380
381        // Ensure that the gamepad is picked up by the appropriate system
382        app.update();
383        // Ensure that the connection event is flushed through
384        app.update();
385        app
386    }
387
388    #[test]
389    fn test_chord_with_buttons_only() {
390        let chord = ButtonlikeChord::new([KeyCode::KeyC, KeyCode::KeyH])
391            .with(KeyCode::KeyO)
392            .with_multiple([KeyCode::KeyR, KeyCode::KeyD]);
393
394        let required_keys = [
395            KeyCode::KeyC,
396            KeyCode::KeyH,
397            KeyCode::KeyO,
398            KeyCode::KeyR,
399            KeyCode::KeyD,
400        ];
401
402        let expected_inners = required_keys
403            .iter()
404            .map(|key| Box::new(*key) as Box<dyn Buttonlike>)
405            .collect::<Vec<_>>();
406        assert_eq!(chord.0, expected_inners);
407
408        // No keys pressed, resulting in a released chord with a value of zero.
409        let mut app = test_app();
410        app.update();
411        let gamepad = app.world_mut().spawn(()).id();
412        let inputs = app.world().resource::<CentralInputStore>();
413        assert!(!chord.pressed(inputs, gamepad));
414
415        // All required keys pressed, resulting in a pressed chord with a value of one.
416        let mut app = test_app();
417        for key in required_keys {
418            key.press(app.world_mut());
419        }
420        app.update();
421        let inputs = app.world().resource::<CentralInputStore>();
422        assert!(chord.pressed(inputs, gamepad));
423
424        // Some required keys pressed, but not all required keys for the chord,
425        // resulting in a released chord with a value of zero.
426        for i in 1..=4 {
427            let mut app = test_app();
428            for key in required_keys.iter().take(i) {
429                key.press(app.world_mut());
430            }
431            app.update();
432            let inputs = app.world().resource::<CentralInputStore>();
433            assert!(!chord.pressed(inputs, gamepad));
434        }
435
436        // Five keys pressed, but not all required keys for the chord,
437        // resulting in a released chord with a value of zero.
438        let mut app = test_app();
439        for key in required_keys.iter().take(4) {
440            key.press(app.world_mut());
441        }
442        KeyCode::KeyB.press(app.world_mut());
443        app.update();
444        let inputs = app.world().resource::<CentralInputStore>();
445        assert!(!chord.pressed(inputs, gamepad));
446    }
447}