leafwing_input_manager/user_input/
chord.rs1use 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#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
49#[must_use]
50pub struct ButtonlikeChord(
51 pub(crate) Vec<Box<dyn Buttonlike>>,
57);
58
59impl ButtonlikeChord {
60 #[inline]
64 pub fn new<U: Buttonlike>(inputs: impl IntoIterator<Item = U>) -> Self {
65 Self::default().with_multiple(inputs)
66 }
67
68 #[inline]
71 pub fn from_single(input: impl Buttonlike) -> Self {
72 Self::default().with(input)
73 }
74
75 #[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 #[inline]
83 pub fn with(mut self, input: impl Buttonlike) -> Self {
84 self.push_boxed_unique(Box::new(input));
85 self
86 }
87
88 #[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 #[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 #[inline]
110 fn kind(&self) -> InputControlKind {
111 InputControlKind::Button
112 }
113
114 #[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 #[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 #[inline]
171 fn from_iter<T: IntoIterator<Item = U>>(iter: T) -> Self {
172 Self::default().with_multiple(iter)
173 }
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
179#[must_use]
180pub struct AxislikeChord {
181 pub button: Box<dyn Buttonlike>,
183 pub axis: Box<dyn Axislike>,
185}
186
187impl AxislikeChord {
188 #[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 #[inline]
201 fn kind(&self) -> InputControlKind {
202 InputControlKind::Axis
203 }
204
205 #[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#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
234#[must_use]
235pub struct DualAxislikeChord {
236 pub button: Box<dyn Buttonlike>,
238 pub dual_axis: Box<dyn DualAxislike>,
240}
241
242impl DualAxislikeChord {
243 #[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 #[inline]
256 fn kind(&self) -> InputControlKind {
257 InputControlKind::DualAxis
258 }
259
260 #[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#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
296#[must_use]
297pub struct TripleAxislikeChord {
298 pub button: Box<dyn Buttonlike>,
300 pub triple_axis: Box<dyn TripleAxislike>,
302}
303
304impl TripleAxislikeChord {
305 #[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 #[inline]
318 fn kind(&self) -> InputControlKind {
319 InputControlKind::TripleAxis
320 }
321
322 #[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 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 gamepad,
379 connection: GamepadConnection::Connected {
380 name: "TestController".into(),
381 vendor_id: None,
382 product_id: None,
383 },
384 }));
385
386 app.update();
388 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 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 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 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 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}