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/// prmessageing 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 get_pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<bool> {
133        for input in &self.0 {
134            if !(input.get_pressed(input_store, gamepad)?) {
135                return Some(false);
136            }
137        }
138        Some(true)
139    }
140
141    fn press(&self, world: &mut World) {
142        for input in &self.0 {
143            input.press(world);
144        }
145    }
146
147    fn release(&self, world: &mut World) {
148        for input in &self.0 {
149            input.release(world);
150        }
151    }
152
153    fn press_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
154        for input in &self.0 {
155            input.press_as_gamepad(world, gamepad);
156        }
157    }
158
159    fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
160        for input in &self.0 {
161            input.release_as_gamepad(world, gamepad);
162        }
163    }
164}
165
166impl<U: Buttonlike> FromIterator<U> for ButtonlikeChord {
167    /// Creates a [`ButtonlikeChord`] from an iterator over multiple [`Buttonlike`]s, avoiding duplicates.
168    /// Note that all elements within the iterator must be of the same type (homogeneous).
169    /// You can still use other methods to add different types of inputs into the chord.
170    #[inline]
171    fn from_iter<T: IntoIterator<Item = U>>(iter: T) -> Self {
172        Self::default().with_multiple(iter)
173    }
174}
175
176/// A combined input that groups a [`Buttonlike`] and a [`Axislike`] together,
177/// allowing you to only read the axis value when the button is pressed.
178#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
179#[must_use]
180pub struct AxislikeChord {
181    /// The button that must be pressed to read the axis value.
182    pub button: Box<dyn Buttonlike>,
183    /// The axis value that is read when the button is pressed.
184    pub axis: Box<dyn Axislike>,
185}
186
187impl AxislikeChord {
188    /// Creates a new [`AxislikeChord`] from the given [`Buttonlike`] and [`Axislike`].
189    #[inline]
190    pub fn new(button: impl Buttonlike, axis: impl Axislike) -> Self {
191        Self {
192            button: Box::new(button),
193            axis: Box::new(axis),
194        }
195    }
196}
197
198impl UserInput for AxislikeChord {
199    /// [`AxislikeChord`] acts as a virtual axis.
200    #[inline]
201    fn kind(&self) -> InputControlKind {
202        InputControlKind::Axis
203    }
204
205    /// Retrieves a list of simple, atomic [`Buttonlike`]s that compose the chord.
206    #[inline]
207    fn decompose(&self) -> BasicInputs {
208        BasicInputs::compose(self.button.decompose(), self.axis.decompose())
209    }
210}
211
212#[serde_typetag]
213impl Axislike for AxislikeChord {
214    fn get_value(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<f32> {
215        if self.button.pressed(input_store, gamepad) {
216            self.axis.get_value(input_store, gamepad)
217        } else {
218            Some(0.0)
219        }
220    }
221
222    fn set_value(&self, world: &mut World, value: f32) {
223        self.axis.set_value(world, value);
224    }
225
226    fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
227        self.axis.set_value_as_gamepad(world, value, gamepad);
228    }
229}
230
231/// A combined input that groups a [`Buttonlike`] and a [`DualAxislike`] together,
232/// allowing you to only read the dual axis data when the button is pressed.
233#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
234#[must_use]
235pub struct DualAxislikeChord {
236    /// The button that must be pressed to read the axis values.
237    pub button: Box<dyn Buttonlike>,
238    /// The dual axis data that is read when the button is pressed.
239    pub dual_axis: Box<dyn DualAxislike>,
240}
241
242impl DualAxislikeChord {
243    /// Creates a new [`DualAxislikeChord`] from the given [`Buttonlike`] and [`DualAxislike`].
244    #[inline]
245    pub fn new(button: impl Buttonlike, dual_axis: impl DualAxislike) -> Self {
246        Self {
247            button: Box::new(button),
248            dual_axis: Box::new(dual_axis),
249        }
250    }
251}
252
253impl UserInput for DualAxislikeChord {
254    /// [`DualAxislikeChord`] acts as a virtual dual-axis.
255    #[inline]
256    fn kind(&self) -> InputControlKind {
257        InputControlKind::DualAxis
258    }
259
260    /// Retrieves a list of simple, atomic [`Buttonlike`]s that compose the chord.
261    #[inline]
262    fn decompose(&self) -> BasicInputs {
263        BasicInputs::compose(self.button.decompose(), self.dual_axis.decompose())
264    }
265}
266
267#[serde_typetag]
268impl DualAxislike for DualAxislikeChord {
269    fn get_axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec2> {
270        let pressed = self.button.get_pressed(input_store, gamepad)?;
271        if pressed {
272            self.dual_axis.get_axis_pair(input_store, gamepad)
273        } else {
274            Some(Vec2::ZERO)
275        }
276    }
277
278    fn set_axis_pair(&self, world: &mut World, axis_pair: Vec2) {
279        self.dual_axis.set_axis_pair(world, axis_pair);
280    }
281
282    fn set_axis_pair_as_gamepad(
283        &self,
284        world: &mut World,
285        axis_pair: Vec2,
286        gamepad: Option<Entity>,
287    ) {
288        self.dual_axis
289            .set_axis_pair_as_gamepad(world, axis_pair, gamepad);
290    }
291}
292
293/// A combined input that groups a [`Buttonlike`] and a [`TripleAxislike`] together,
294/// allowing you to only read the dual axis data when the button is pressed.
295#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
296#[must_use]
297pub struct TripleAxislikeChord {
298    /// The button that must be pressed to read the axis values.
299    pub button: Box<dyn Buttonlike>,
300    /// The triple axis data that is read when the button is pressed.
301    pub triple_axis: Box<dyn TripleAxislike>,
302}
303
304impl TripleAxislikeChord {
305    /// Creates a new [`TripleAxislikeChord`] from the given [`Buttonlike`] and [`TripleAxislike`].
306    #[inline]
307    pub fn new(button: impl Buttonlike, triple_axis: impl TripleAxislike) -> Self {
308        Self {
309            button: Box::new(button),
310            triple_axis: Box::new(triple_axis),
311        }
312    }
313}
314
315impl UserInput for TripleAxislikeChord {
316    /// [`TripleAxislikeChord`] acts as a virtual triple-axis.
317    #[inline]
318    fn kind(&self) -> InputControlKind {
319        InputControlKind::TripleAxis
320    }
321
322    /// Retrieves a list of simple, atomic [`Buttonlike`]s that compose the chord.
323    #[inline]
324    fn decompose(&self) -> BasicInputs {
325        BasicInputs::compose(self.button.decompose(), self.triple_axis.decompose())
326    }
327}
328
329#[serde_typetag]
330impl TripleAxislike for TripleAxislikeChord {
331    fn get_axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec3> {
332        let pressed = self.button.get_pressed(input_store, gamepad)?;
333        if pressed {
334            self.triple_axis.get_axis_triple(input_store, gamepad)
335        } else {
336            Some(Vec3::ZERO)
337        }
338    }
339
340    fn set_axis_triple(&self, world: &mut World, axis_triple: Vec3) {
341        self.triple_axis.set_axis_triple(world, axis_triple);
342    }
343
344    fn set_axis_triple_as_gamepad(
345        &self,
346        world: &mut World,
347        axis_triple: Vec3,
348        gamepad: Option<Entity>,
349    ) {
350        self.triple_axis
351            .set_axis_triple_as_gamepad(world, axis_triple, gamepad);
352    }
353}
354
355#[cfg(feature = "keyboard")]
356#[cfg(test)]
357mod tests {
358    use super::ButtonlikeChord;
359    use crate::plugin::CentralInputStorePlugin;
360    use crate::user_input::updating::CentralInputStore;
361    use crate::user_input::Buttonlike;
362    use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent};
363    use bevy::input::InputPlugin;
364    use bevy::prelude::*;
365
366    fn test_app() -> App {
367        let mut app = App::new();
368        app.add_plugins(MinimalPlugins)
369            .add_plugins(InputPlugin)
370            .add_plugins(CentralInputStorePlugin);
371
372        // WARNING: you MUST register your gamepad during tests,
373        // or all gamepad input mocking actions will fail
374        let gamepad = app.world_mut().spawn(()).id();
375        let mut gamepad_messages = app.world_mut().resource_mut::<Messages<GamepadEvent>>();
376        gamepad_messages.write(GamepadEvent::Connection(GamepadConnectionEvent {
377            // This MUST be consistent with any other mocked messages
378            gamepad,
379            connection: GamepadConnection::Connected {
380                name: "TestController".into(),
381                vendor_id: None,
382                product_id: None,
383            },
384        }));
385
386        // Ensure that the gamepad is picked up by the appropriate system
387        app.update();
388        // Ensure that the connection message is flushed through
389        app.update();
390        app
391    }
392
393    #[test]
394    fn test_chord_with_buttons_only() {
395        let chord = ButtonlikeChord::new([KeyCode::KeyC, KeyCode::KeyH])
396            .with(KeyCode::KeyO)
397            .with_multiple([KeyCode::KeyR, KeyCode::KeyD]);
398
399        let required_keys = [
400            KeyCode::KeyC,
401            KeyCode::KeyH,
402            KeyCode::KeyO,
403            KeyCode::KeyR,
404            KeyCode::KeyD,
405        ];
406
407        let expected_inners = required_keys
408            .iter()
409            .map(|key| Box::new(*key) as Box<dyn Buttonlike>)
410            .collect::<Vec<_>>();
411        assert_eq!(chord.0, expected_inners);
412
413        // No keys pressed, resulting in a released chord with a value of zero.
414        let mut app = test_app();
415        app.update();
416        let gamepad = app.world_mut().spawn(()).id();
417        let inputs = app.world().resource::<CentralInputStore>();
418        assert!(!chord.pressed(inputs, gamepad));
419
420        // All required keys pressed, resulting in a pressed chord with a value of one.
421        let mut app = test_app();
422        for key in required_keys {
423            key.press(app.world_mut());
424        }
425        app.update();
426        let inputs = app.world().resource::<CentralInputStore>();
427        assert!(chord.pressed(inputs, gamepad));
428
429        // Some required keys pressed, but not all required keys for the chord,
430        // resulting in a released chord with a value of zero.
431        for i in 1..=4 {
432            let mut app = test_app();
433            for key in required_keys.iter().take(i) {
434                key.press(app.world_mut());
435            }
436            app.update();
437            let inputs = app.world().resource::<CentralInputStore>();
438            assert!(!chord.pressed(inputs, gamepad));
439        }
440
441        // Five keys pressed, but not all required keys for the chord,
442        // resulting in a released chord with a value of zero.
443        let mut app = test_app();
444        for key in required_keys.iter().take(4) {
445            key.press(app.world_mut());
446        }
447        KeyCode::KeyB.press(app.world_mut());
448        app.update();
449        let inputs = app.world().resource::<CentralInputStore>();
450        assert!(!chord.pressed(inputs, gamepad));
451    }
452}