leafwing_input_manager/user_input/virtual_axial.rs
1//! This module contains [`VirtualAxis`], [`VirtualDPad`], and [`VirtualDPad3D`].
2
3use crate as leafwing_input_manager;
4use crate::clashing_inputs::BasicInputs;
5use crate::input_processing::{
6 AxisProcessor, DualAxisProcessor, WithAxisProcessingPipelineExt,
7 WithDualAxisProcessingPipelineExt,
8};
9use crate::prelude::updating::CentralInputStore;
10use crate::prelude::{Axislike, DualAxislike, TripleAxislike, UserInput};
11use crate::user_input::Buttonlike;
12use crate::InputControlKind;
13use bevy::math::{Vec2, Vec3};
14#[cfg(feature = "gamepad")]
15use bevy::prelude::GamepadButton;
16#[cfg(feature = "keyboard")]
17use bevy::prelude::KeyCode;
18use bevy::prelude::{Entity, Reflect, World};
19use leafwing_input_manager_macros::serde_typetag;
20use serde::{Deserialize, Serialize};
21
22/// A virtual single-axis control constructed from two [`Buttonlike`]s.
23/// One button represents the negative direction (left for the X-axis, down for the Y-axis),
24/// while the other represents the positive direction (right for the X-axis, up for the Y-axis).
25///
26/// # Value Processing
27///
28/// You can customize how the values are processed using a pipeline of processors.
29/// See [`WithAxisProcessingPipelineExt`] for details.
30///
31/// The raw value is determined based on the state of the associated buttons:
32/// - `-1.0` if only the negative button is currently pressed.
33/// - `1.0` if only the positive button is currently pressed.
34/// - `0.0` if neither button is pressed, or both are pressed simultaneously.
35///
36/// ```rust
37/// use bevy::prelude::*;
38/// use bevy::input::InputPlugin;
39/// use leafwing_input_manager::prelude::*;
40/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput;
41/// use leafwing_input_manager::plugin::CentralInputStorePlugin;
42///
43/// let mut app = App::new();
44/// app.add_plugins((InputPlugin, CentralInputStorePlugin));
45///
46/// // Define a virtual Y-axis using arrow "up" and "down" keys
47/// let axis = VirtualAxis::vertical_arrow_keys();
48///
49/// // Pressing either key activates the input
50/// KeyCode::ArrowUp.press(app.world_mut());
51/// app.update();
52/// assert_eq!(app.read_axis_value(axis), 1.0);
53///
54/// // You can configure a processing pipeline (e.g., doubling the value)
55/// let doubled = VirtualAxis::vertical_arrow_keys().sensitivity(2.0);
56/// assert_eq!(app.read_axis_value(doubled), 2.0);
57/// ```
58#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
59#[must_use]
60pub struct VirtualAxis {
61 /// The button that represents the negative direction.
62 pub negative: Box<dyn Buttonlike>,
63
64 /// The button that represents the positive direction.
65 pub positive: Box<dyn Buttonlike>,
66
67 /// A processing pipeline that handles input values.
68 pub processors: Vec<AxisProcessor>,
69}
70
71impl VirtualAxis {
72 /// Creates a new [`VirtualAxis`] with two given [`Buttonlike`]s.
73 /// No processing is applied to raw data.
74 #[inline]
75 pub fn new(negative: impl Buttonlike, positive: impl Buttonlike) -> Self {
76 Self {
77 negative: Box::new(negative),
78 positive: Box::new(positive),
79 processors: Vec::new(),
80 }
81 }
82
83 /// The [`VirtualAxis`] using the vertical arrow key mappings.
84 ///
85 /// - [`KeyCode::ArrowDown`] for negative direction.
86 /// - [`KeyCode::ArrowUp`] for positive direction.
87 #[cfg(feature = "keyboard")]
88 #[inline]
89 pub fn vertical_arrow_keys() -> Self {
90 Self::new(KeyCode::ArrowDown, KeyCode::ArrowUp)
91 }
92
93 /// The [`VirtualAxis`] using the horizontal arrow key mappings.
94 ///
95 /// - [`KeyCode::ArrowLeft`] for negative direction.
96 /// - [`KeyCode::ArrowRight`] for positive direction.
97 #[cfg(feature = "keyboard")]
98 #[inline]
99 pub fn horizontal_arrow_keys() -> Self {
100 Self::new(KeyCode::ArrowLeft, KeyCode::ArrowRight)
101 }
102
103 /// The [`VirtualAxis`] using the common W/S key mappings.
104 ///
105 /// - [`KeyCode::KeyS`] for negative direction.
106 /// - [`KeyCode::KeyW`] for positive direction.
107 #[cfg(feature = "keyboard")]
108 #[inline]
109 pub fn ws() -> Self {
110 Self::new(KeyCode::KeyS, KeyCode::KeyW)
111 }
112
113 /// The [`VirtualAxis`] using the common A/D key mappings.
114 ///
115 /// - [`KeyCode::KeyA`] for negative direction.
116 /// - [`KeyCode::KeyD`] for positive direction.
117 #[cfg(feature = "keyboard")]
118 #[inline]
119 pub fn ad() -> Self {
120 Self::new(KeyCode::KeyA, KeyCode::KeyD)
121 }
122
123 /// The [`VirtualAxis`] using the vertical numpad key mappings.
124 ///
125 /// - [`KeyCode::Numpad2`] for negative direction.
126 /// - [`KeyCode::Numpad8`] for positive direction.
127 #[cfg(feature = "keyboard")]
128 #[inline]
129 pub fn vertical_numpad() -> Self {
130 Self::new(KeyCode::Numpad2, KeyCode::Numpad8)
131 }
132
133 /// The [`VirtualAxis`] using the horizontal numpad key mappings.
134 ///
135 /// - [`KeyCode::Numpad4`] for negative direction.
136 /// - [`KeyCode::Numpad6`] for positive direction.
137 #[cfg(feature = "keyboard")]
138 #[inline]
139 pub fn horizontal_numpad() -> Self {
140 Self::new(KeyCode::Numpad4, KeyCode::Numpad6)
141 }
142
143 /// The [`VirtualAxis`] using the horizontal D-Pad button mappings.
144 /// No processing is applied to raw data from the gamepad.
145 ///
146 /// - [`GamepadButton::DPadLeft`] for negative direction.
147 /// - [`GamepadButton::DPadRight`] for positive direction.
148 #[cfg(feature = "gamepad")]
149 #[inline]
150 pub fn dpad_x() -> Self {
151 Self::new(GamepadButton::DPadLeft, GamepadButton::DPadRight)
152 }
153
154 /// The [`VirtualAxis`] using the vertical D-Pad button mappings.
155 /// No processing is applied to raw data from the gamepad.
156 ///
157 /// - [`GamepadButton::DPadDown`] for negative direction.
158 /// - [`GamepadButton::DPadUp`] for positive direction.
159 #[cfg(feature = "gamepad")]
160 #[inline]
161 pub fn dpad_y() -> Self {
162 Self::new(GamepadButton::DPadDown, GamepadButton::DPadUp)
163 }
164
165 /// The [`VirtualAxis`] using the horizontal action pad button mappings.
166 /// No processing is applied to raw data from the gamepad.
167 ///
168 /// - [`GamepadButton::West`] for negative direction.
169 /// - [`GamepadButton::East`] for positive direction.
170 #[cfg(feature = "gamepad")]
171 #[inline]
172 pub fn action_pad_x() -> Self {
173 Self::new(GamepadButton::West, GamepadButton::East)
174 }
175
176 /// The [`VirtualAxis`] using the vertical action pad button mappings.
177 /// No processing is applied to raw data from the gamepad.
178 ///
179 /// - [`GamepadButton::South`] for negative direction.
180 /// - [`GamepadButton::North`] for positive direction.
181 #[cfg(feature = "gamepad")]
182 #[inline]
183 pub fn action_pad_y() -> Self {
184 Self::new(GamepadButton::South, GamepadButton::North)
185 }
186}
187
188impl UserInput for VirtualAxis {
189 /// [`VirtualAxis`] acts as a virtual axis input.
190 #[inline]
191 fn kind(&self) -> InputControlKind {
192 InputControlKind::Axis
193 }
194
195 /// [`VirtualAxis`] represents a compositions of two buttons.
196 #[inline]
197 fn decompose(&self) -> BasicInputs {
198 BasicInputs::Composite(vec![self.negative.clone(), self.positive.clone()])
199 }
200}
201
202#[serde_typetag]
203impl Axislike for VirtualAxis {
204 /// Retrieves the current value of this axis after processing by the associated processors.
205 #[inline]
206 fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
207 let negative = self.negative.value(input_store, gamepad);
208 let positive = self.positive.value(input_store, gamepad);
209 let value = positive - negative;
210 self.processors
211 .iter()
212 .fold(value, |value, processor| processor.process(value))
213 }
214
215 /// Sets the value of corresponding button based on the given `value`.
216 ///
217 /// When `value` is non-zero, set its absolute value to the value of:
218 /// - the negative button if the `value` is negative;
219 /// - the positive button if the `value` is positive.
220 fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
221 if value < 0.0 {
222 self.negative
223 .set_value_as_gamepad(world, value.abs(), gamepad);
224 } else if value > 0.0 {
225 self.positive.set_value_as_gamepad(world, value, gamepad);
226 }
227 }
228}
229
230impl WithAxisProcessingPipelineExt for VirtualAxis {
231 #[inline]
232 fn reset_processing_pipeline(mut self) -> Self {
233 self.processors.clear();
234 self
235 }
236
237 #[inline]
238 fn replace_processing_pipeline(
239 mut self,
240 processors: impl IntoIterator<Item = AxisProcessor>,
241 ) -> Self {
242 self.processors = processors.into_iter().collect();
243 self
244 }
245
246 #[inline]
247 fn with_processor(mut self, processor: impl Into<AxisProcessor>) -> Self {
248 self.processors.push(processor.into());
249 self
250 }
251}
252
253/// A virtual dual-axis control constructed from four [`Buttonlike`]s.
254/// Each button represents a specific direction (up, down, left, right),
255/// functioning similarly to a directional pad (D-pad) on both X and Y axes,
256/// and offering intermediate diagonals by means of two-button combinations.
257///
258/// By default, it reads from **any connected gamepad**.
259/// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones.
260///
261/// # Value Processing
262///
263/// You can customize how the values are processed using a pipeline of processors.
264/// See [`WithDualAxisProcessingPipelineExt`] for details.
265///
266/// The raw axis values are determined based on the state of the associated buttons:
267/// - `-1.0` if only the negative button is currently pressed (Down/Left).
268/// - `1.0` if only the positive button is currently pressed (Up/Right).
269/// - `0.0` if neither button is pressed, or both are pressed simultaneously.
270///
271/// ```rust
272/// use bevy::prelude::*;
273/// use bevy::input::InputPlugin;
274/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput;
275/// use leafwing_input_manager::prelude::*;
276/// use leafwing_input_manager::plugin::CentralInputStorePlugin;
277///
278/// let mut app = App::new();
279/// app.add_plugins((InputPlugin, CentralInputStorePlugin));
280///
281/// // Define a virtual D-pad using the WASD keys
282/// let input = VirtualDPad::wasd();
283///
284/// // Pressing the W key activates the corresponding axis
285/// KeyCode::KeyW.press(app.world_mut());
286/// app.update();
287/// assert_eq!(app.read_dual_axis_values(input), Vec2::new(0.0, 1.0));
288///
289/// // You can configure a processing pipeline (e.g., doubling the Y value)
290/// let doubled = VirtualDPad::wasd().sensitivity_y(2.0);
291/// assert_eq!(app.read_dual_axis_values(doubled), Vec2::new(0.0, 2.0));
292/// ```
293#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
294#[must_use]
295pub struct VirtualDPad {
296 /// The button for the upward direction.
297 pub up: Box<dyn Buttonlike>,
298
299 /// The button for the downward direction.
300 pub down: Box<dyn Buttonlike>,
301
302 /// The button for the leftward direction.
303 pub left: Box<dyn Buttonlike>,
304
305 /// The button for the rightward direction.
306 pub right: Box<dyn Buttonlike>,
307
308 /// A processing pipeline that handles input values.
309 pub processors: Vec<DualAxisProcessor>,
310}
311
312impl VirtualDPad {
313 /// Creates a new [`VirtualDPad`] with four given [`Buttonlike`]s.
314 /// Each button represents a specific direction (up, down, left, right).
315 #[inline]
316 pub fn new(
317 up: impl Buttonlike,
318 down: impl Buttonlike,
319 left: impl Buttonlike,
320 right: impl Buttonlike,
321 ) -> Self {
322 Self {
323 up: Box::new(up),
324 down: Box::new(down),
325 left: Box::new(left),
326 right: Box::new(right),
327 processors: Vec::new(),
328 }
329 }
330
331 /// The [`VirtualDPad`] using the common arrow key mappings.
332 ///
333 /// - [`KeyCode::ArrowUp`] for upward direction.
334 /// - [`KeyCode::ArrowDown`] for downward direction.
335 /// - [`KeyCode::ArrowLeft`] for leftward direction.
336 /// - [`KeyCode::ArrowRight`] for rightward direction.
337 #[cfg(feature = "keyboard")]
338 #[inline]
339 pub fn arrow_keys() -> Self {
340 Self::new(
341 KeyCode::ArrowUp,
342 KeyCode::ArrowDown,
343 KeyCode::ArrowLeft,
344 KeyCode::ArrowRight,
345 )
346 }
347
348 /// The [`VirtualDPad`] using the common WASD key mappings.
349 ///
350 /// - [`KeyCode::KeyW`] for upward direction.
351 /// - [`KeyCode::KeyS`] for downward direction.
352 /// - [`KeyCode::KeyA`] for leftward direction.
353 /// - [`KeyCode::KeyD`] for rightward direction.
354 #[cfg(feature = "keyboard")]
355 #[inline]
356 pub fn wasd() -> Self {
357 Self::new(KeyCode::KeyW, KeyCode::KeyS, KeyCode::KeyA, KeyCode::KeyD)
358 }
359
360 /// The [`VirtualDPad`] using the common numpad key mappings.
361 ///
362 /// - [`KeyCode::Numpad8`] for upward direction.
363 /// - [`KeyCode::Numpad2`] for downward direction.
364 /// - [`KeyCode::Numpad4`] for leftward direction.
365 /// - [`KeyCode::Numpad6`] for rightward direction.
366 #[cfg(feature = "keyboard")]
367 #[inline]
368 pub fn numpad() -> Self {
369 Self::new(
370 KeyCode::Numpad8,
371 KeyCode::Numpad2,
372 KeyCode::Numpad4,
373 KeyCode::Numpad6,
374 )
375 }
376
377 /// Creates a new [`VirtualDPad`] using the common D-Pad button mappings.
378 ///
379 /// - [`GamepadButton::DPadUp`] for upward direction.
380 /// - [`GamepadButton::DPadDown`] for downward direction.
381 /// - [`GamepadButton::DPadLeft`] for leftward direction.
382 /// - [`GamepadButton::DPadRight`] for rightward direction.
383 #[cfg(feature = "gamepad")]
384 #[inline]
385 pub fn dpad() -> Self {
386 Self::new(
387 GamepadButton::DPadUp,
388 GamepadButton::DPadDown,
389 GamepadButton::DPadLeft,
390 GamepadButton::DPadRight,
391 )
392 }
393
394 /// Creates a new [`VirtualDPad`] using the common action pad button mappings.
395 ///
396 /// - [`GamepadButton::North`] for upward direction.
397 /// - [`GamepadButton::South`] for downward direction.
398 /// - [`GamepadButton::West`] for leftward direction.
399 /// - [`GamepadButton::East`] for rightward direction.
400 #[cfg(feature = "gamepad")]
401 #[inline]
402 pub fn action_pad() -> Self {
403 Self::new(
404 GamepadButton::North,
405 GamepadButton::South,
406 GamepadButton::West,
407 GamepadButton::East,
408 )
409 }
410}
411
412impl UserInput for VirtualDPad {
413 /// [`VirtualDPad`] acts as a dual-axis input.
414 #[inline]
415 fn kind(&self) -> InputControlKind {
416 InputControlKind::DualAxis
417 }
418
419 /// Returns the four [`GamepadButton`]s used by this D-pad.
420 #[inline]
421 fn decompose(&self) -> BasicInputs {
422 BasicInputs::Composite(vec![
423 self.up.clone(),
424 self.down.clone(),
425 self.left.clone(),
426 self.right.clone(),
427 ])
428 }
429}
430
431#[serde_typetag]
432impl DualAxislike for VirtualDPad {
433 /// Retrieves the current X and Y values of this D-pad after processing by the associated processors.
434 #[inline]
435 fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec2 {
436 let up = self.up.value(input_store, gamepad);
437 let down = self.down.value(input_store, gamepad);
438 let left = self.left.value(input_store, gamepad);
439 let right = self.right.value(input_store, gamepad);
440 let value = Vec2::new(right - left, up - down);
441 self.processors
442 .iter()
443 .fold(value, |value, processor| processor.process(value))
444 }
445
446 /// Sets the value of corresponding button on each axis based on the given `value`.
447 ///
448 /// When `value` along an axis is non-zero, set its absolute value to the value of:
449 /// - the negative button of the axis if the `value` is negative;
450 /// - the positive button of the axis if the `value` is positive.
451 fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option<Entity>) {
452 let Vec2 { x, y } = value;
453
454 if x < 0.0 {
455 self.left.set_value_as_gamepad(world, x.abs(), gamepad);
456 } else if x > 0.0 {
457 self.right.set_value_as_gamepad(world, x, gamepad);
458 }
459
460 if y < 0.0 {
461 self.down.set_value_as_gamepad(world, y.abs(), gamepad);
462 } else if y > 0.0 {
463 self.up.set_value_as_gamepad(world, y, gamepad);
464 }
465 }
466}
467
468impl WithDualAxisProcessingPipelineExt for VirtualDPad {
469 #[inline]
470 fn reset_processing_pipeline(mut self) -> Self {
471 self.processors.clear();
472 self
473 }
474
475 #[inline]
476 fn replace_processing_pipeline(
477 mut self,
478 processor: impl IntoIterator<Item = DualAxisProcessor>,
479 ) -> Self {
480 self.processors = processor.into_iter().collect();
481 self
482 }
483
484 #[inline]
485 fn with_processor(mut self, processor: impl Into<DualAxisProcessor>) -> Self {
486 self.processors.push(processor.into());
487 self
488 }
489}
490
491/// A virtual triple-axis control constructed from six [`Buttonlike`]s.
492/// Each button represents a specific direction (up, down, left, right, forward, backward),
493/// functioning similarly to a three-dimensional directional pad (D-pad) on all X, Y, and Z axes,
494/// and offering intermediate diagonals by means of two/three-key combinations.
495///
496/// The raw axis values are determined based on the state of the associated buttons:
497/// - `-1.0` if only the negative button is currently pressed (Down/Left/Forward).
498/// - `1.0` if only the positive button is currently pressed (Up/Right/Backward).
499/// - `0.0` if neither button is pressed, or both are pressed simultaneously.
500#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
501#[must_use]
502pub struct VirtualDPad3D {
503 /// The button for the upward direction.
504 pub up: Box<dyn Buttonlike>,
505
506 /// The button for the downward direction.
507 pub down: Box<dyn Buttonlike>,
508
509 /// The button for the leftward direction.
510 pub left: Box<dyn Buttonlike>,
511
512 /// The button for the rightward direction.
513 pub right: Box<dyn Buttonlike>,
514
515 /// The button for the forward direction.
516 pub forward: Box<dyn Buttonlike>,
517
518 /// The button for the backward direction.
519 pub backward: Box<dyn Buttonlike>,
520}
521
522impl VirtualDPad3D {
523 /// Creates a new [`VirtualDPad3D`] with six given [`Buttonlike`]s.
524 /// Each button represents a specific direction (up, down, left, right, forward, backward).
525 #[inline]
526 pub fn new(
527 up: impl Buttonlike,
528 down: impl Buttonlike,
529 left: impl Buttonlike,
530 right: impl Buttonlike,
531 forward: impl Buttonlike,
532 backward: impl Buttonlike,
533 ) -> Self {
534 Self {
535 up: Box::new(up),
536 down: Box::new(down),
537 left: Box::new(left),
538 right: Box::new(right),
539 forward: Box::new(forward),
540 backward: Box::new(backward),
541 }
542 }
543}
544
545impl UserInput for VirtualDPad3D {
546 /// [`VirtualDPad3D`] acts as a virtual triple-axis input.
547 #[inline]
548 fn kind(&self) -> InputControlKind {
549 InputControlKind::TripleAxis
550 }
551
552 /// [`VirtualDPad3D`] represents a compositions of six [`Buttonlike`]s.
553 #[inline]
554 fn decompose(&self) -> BasicInputs {
555 BasicInputs::Composite(vec![
556 self.up.clone(),
557 self.down.clone(),
558 self.left.clone(),
559 self.right.clone(),
560 self.forward.clone(),
561 self.backward.clone(),
562 ])
563 }
564}
565
566#[serde_typetag]
567impl TripleAxislike for VirtualDPad3D {
568 /// Retrieves the current X, Y, and Z values of this D-pad.
569 #[inline]
570 fn axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec3 {
571 let up = self.up.value(input_store, gamepad);
572 let down = self.down.value(input_store, gamepad);
573 let left = self.left.value(input_store, gamepad);
574 let right = self.right.value(input_store, gamepad);
575 let forward = self.forward.value(input_store, gamepad);
576 let backward = self.backward.value(input_store, gamepad);
577 Vec3::new(right - left, up - down, backward - forward)
578 }
579
580 /// Sets the value of corresponding button on each axis based on the given `value`.
581 ///
582 /// When `value` along an axis is non-zero, set its absolute value to the value of:
583 /// - the negative button of the axis if the `value` is negative;
584 /// - the positive button of the axis if the `value` is positive.
585 fn set_axis_triple_as_gamepad(&self, world: &mut World, value: Vec3, gamepad: Option<Entity>) {
586 let Vec3 { x, y, z } = value;
587
588 if x < 0.0 {
589 self.left.set_value_as_gamepad(world, x.abs(), gamepad);
590 } else if x > 0.0 {
591 self.right.set_value_as_gamepad(world, x, gamepad);
592 }
593
594 if y < 0.0 {
595 self.down.set_value_as_gamepad(world, y.abs(), gamepad);
596 } else if y > 0.0 {
597 self.up.set_value_as_gamepad(world, y, gamepad);
598 }
599
600 if z < 0.0 {
601 self.forward.set_value_as_gamepad(world, z.abs(), gamepad);
602 } else if z > 0.0 {
603 self.backward.set_value_as_gamepad(world, z, gamepad);
604 }
605 }
606}
607
608#[cfg(feature = "keyboard")]
609#[cfg(test)]
610mod tests {
611 use bevy::input::InputPlugin;
612 use bevy::prelude::*;
613
614 use crate::plugin::CentralInputStorePlugin;
615 use crate::prelude::updating::CentralInputStore;
616 use crate::prelude::*;
617
618 fn test_app() -> App {
619 let mut app = App::new();
620 app.add_plugins(InputPlugin)
621 .add_plugins(CentralInputStorePlugin);
622 app
623 }
624
625 #[test]
626 fn test_virtual() {
627 let x = VirtualAxis::horizontal_arrow_keys();
628 let xy = VirtualDPad::arrow_keys();
629 let xyz = VirtualDPad3D::new(
630 KeyCode::ArrowUp,
631 KeyCode::ArrowDown,
632 KeyCode::ArrowLeft,
633 KeyCode::ArrowRight,
634 KeyCode::KeyF,
635 KeyCode::KeyB,
636 );
637
638 // No inputs
639 let mut app = test_app();
640 app.update();
641 let inputs = app.world().resource::<CentralInputStore>();
642
643 let gamepad = Entity::PLACEHOLDER;
644
645 assert_eq!(x.value(inputs, gamepad), 0.0);
646 assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::ZERO);
647 assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::ZERO);
648
649 // Press arrow left
650 let mut app = test_app();
651 KeyCode::ArrowLeft.press(app.world_mut());
652 app.update();
653 let inputs = app.world().resource::<CentralInputStore>();
654
655 assert_eq!(x.value(inputs, gamepad), -1.0);
656 assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(-1.0, 0.0));
657 assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(-1.0, 0.0, 0.0));
658
659 // Press arrow up
660 let mut app = test_app();
661 KeyCode::ArrowUp.press(app.world_mut());
662 app.update();
663 let inputs = app.world().resource::<CentralInputStore>();
664
665 assert_eq!(x.value(inputs, gamepad), 0.0);
666 assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(0.0, 1.0));
667 assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(0.0, 1.0, 0.0));
668
669 // Press arrow right
670 let mut app = test_app();
671 KeyCode::ArrowRight.press(app.world_mut());
672 app.update();
673 let inputs = app.world().resource::<CentralInputStore>();
674
675 assert_eq!(x.value(inputs, gamepad), 1.0);
676 assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(1.0, 0.0));
677 assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(1.0, 0.0, 0.0));
678
679 // Press key B
680 let mut app = test_app();
681 KeyCode::KeyB.press(app.world_mut());
682 app.update();
683 let inputs = app.world().resource::<CentralInputStore>();
684
685 assert_eq!(x.value(inputs, gamepad), 0.0);
686 assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.0));
687 assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(0.0, 0.0, 1.0));
688 }
689}