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 get_value(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<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 Some(
211 self.processors
212 .iter()
213 .fold(value, |value, processor| processor.process(value)),
214 )
215 }
216
217 /// Sets the value of corresponding button based on the given `value`.
218 ///
219 /// When `value` is non-zero, set its absolute value to the value of:
220 /// - the negative button if the `value` is negative;
221 /// - the positive button if the `value` is positive.
222 fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
223 if value < 0.0 {
224 self.negative
225 .set_value_as_gamepad(world, value.abs(), gamepad);
226 } else if value > 0.0 {
227 self.positive.set_value_as_gamepad(world, value, gamepad);
228 }
229 }
230}
231
232impl WithAxisProcessingPipelineExt for VirtualAxis {
233 #[inline]
234 fn reset_processing_pipeline(mut self) -> Self {
235 self.processors.clear();
236 self
237 }
238
239 #[inline]
240 fn replace_processing_pipeline(
241 mut self,
242 processors: impl IntoIterator<Item = AxisProcessor>,
243 ) -> Self {
244 self.processors = processors.into_iter().collect();
245 self
246 }
247
248 #[inline]
249 fn with_processor(mut self, processor: impl Into<AxisProcessor>) -> Self {
250 self.processors.push(processor.into());
251 self
252 }
253}
254
255/// A virtual dual-axis control constructed from four [`Buttonlike`]s.
256/// Each button represents a specific direction (up, down, left, right),
257/// functioning similarly to a directional pad (D-pad) on both X and Y axes,
258/// and offering intermediate diagonals by means of two-button combinations.
259///
260/// By default, it reads from **any connected gamepad**.
261/// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones.
262///
263/// # Value Processing
264///
265/// You can customize how the values are processed using a pipeline of processors.
266/// See [`WithDualAxisProcessingPipelineExt`] for details.
267///
268/// The raw axis values are determined based on the state of the associated buttons:
269/// - `-1.0` if only the negative button is currently pressed (Down/Left).
270/// - `1.0` if only the positive button is currently pressed (Up/Right).
271/// - `0.0` if neither button is pressed, or both are pressed simultaneously.
272///
273/// ```rust
274/// use bevy::prelude::*;
275/// use bevy::input::InputPlugin;
276/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput;
277/// use leafwing_input_manager::prelude::*;
278/// use leafwing_input_manager::plugin::CentralInputStorePlugin;
279///
280/// let mut app = App::new();
281/// app.add_plugins((InputPlugin, CentralInputStorePlugin));
282///
283/// // Define a virtual D-pad using the WASD keys
284/// let input = VirtualDPad::wasd();
285///
286/// // Pressing the W key activates the corresponding axis
287/// KeyCode::KeyW.press(app.world_mut());
288/// app.update();
289/// assert_eq!(app.read_dual_axis_values(input), Vec2::new(0.0, 1.0));
290///
291/// // You can configure a processing pipeline (e.g., doubling the Y value)
292/// let doubled = VirtualDPad::wasd().sensitivity_y(2.0);
293/// assert_eq!(app.read_dual_axis_values(doubled), Vec2::new(0.0, 2.0));
294/// ```
295#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
296#[must_use]
297pub struct VirtualDPad {
298 /// The button for the upward direction.
299 pub up: Box<dyn Buttonlike>,
300
301 /// The button for the downward direction.
302 pub down: Box<dyn Buttonlike>,
303
304 /// The button for the leftward direction.
305 pub left: Box<dyn Buttonlike>,
306
307 /// The button for the rightward direction.
308 pub right: Box<dyn Buttonlike>,
309
310 /// A processing pipeline that handles input values.
311 pub processors: Vec<DualAxisProcessor>,
312}
313
314impl VirtualDPad {
315 /// Creates a new [`VirtualDPad`] with four given [`Buttonlike`]s.
316 /// Each button represents a specific direction (up, down, left, right).
317 #[inline]
318 pub fn new(
319 up: impl Buttonlike,
320 down: impl Buttonlike,
321 left: impl Buttonlike,
322 right: impl Buttonlike,
323 ) -> Self {
324 Self {
325 up: Box::new(up),
326 down: Box::new(down),
327 left: Box::new(left),
328 right: Box::new(right),
329 processors: Vec::new(),
330 }
331 }
332
333 /// The [`VirtualDPad`] using the common arrow key mappings.
334 ///
335 /// - [`KeyCode::ArrowUp`] for upward direction.
336 /// - [`KeyCode::ArrowDown`] for downward direction.
337 /// - [`KeyCode::ArrowLeft`] for leftward direction.
338 /// - [`KeyCode::ArrowRight`] for rightward direction.
339 #[cfg(feature = "keyboard")]
340 #[inline]
341 pub fn arrow_keys() -> Self {
342 Self::new(
343 KeyCode::ArrowUp,
344 KeyCode::ArrowDown,
345 KeyCode::ArrowLeft,
346 KeyCode::ArrowRight,
347 )
348 }
349
350 /// The [`VirtualDPad`] using the common WASD key mappings.
351 ///
352 /// - [`KeyCode::KeyW`] for upward direction.
353 /// - [`KeyCode::KeyS`] for downward direction.
354 /// - [`KeyCode::KeyA`] for leftward direction.
355 /// - [`KeyCode::KeyD`] for rightward direction.
356 #[cfg(feature = "keyboard")]
357 #[inline]
358 pub fn wasd() -> Self {
359 Self::new(KeyCode::KeyW, KeyCode::KeyS, KeyCode::KeyA, KeyCode::KeyD)
360 }
361
362 /// The [`VirtualDPad`] using the common numpad key mappings.
363 ///
364 /// - [`KeyCode::Numpad8`] for upward direction.
365 /// - [`KeyCode::Numpad2`] for downward direction.
366 /// - [`KeyCode::Numpad4`] for leftward direction.
367 /// - [`KeyCode::Numpad6`] for rightward direction.
368 #[cfg(feature = "keyboard")]
369 #[inline]
370 pub fn numpad() -> Self {
371 Self::new(
372 KeyCode::Numpad8,
373 KeyCode::Numpad2,
374 KeyCode::Numpad4,
375 KeyCode::Numpad6,
376 )
377 }
378
379 /// Creates a new [`VirtualDPad`] using the common D-Pad button mappings.
380 ///
381 /// - [`GamepadButton::DPadUp`] for upward direction.
382 /// - [`GamepadButton::DPadDown`] for downward direction.
383 /// - [`GamepadButton::DPadLeft`] for leftward direction.
384 /// - [`GamepadButton::DPadRight`] for rightward direction.
385 #[cfg(feature = "gamepad")]
386 #[inline]
387 pub fn dpad() -> Self {
388 Self::new(
389 GamepadButton::DPadUp,
390 GamepadButton::DPadDown,
391 GamepadButton::DPadLeft,
392 GamepadButton::DPadRight,
393 )
394 }
395
396 /// Creates a new [`VirtualDPad`] using the common action pad button mappings.
397 ///
398 /// - [`GamepadButton::North`] for upward direction.
399 /// - [`GamepadButton::South`] for downward direction.
400 /// - [`GamepadButton::West`] for leftward direction.
401 /// - [`GamepadButton::East`] for rightward direction.
402 #[cfg(feature = "gamepad")]
403 #[inline]
404 pub fn action_pad() -> Self {
405 Self::new(
406 GamepadButton::North,
407 GamepadButton::South,
408 GamepadButton::West,
409 GamepadButton::East,
410 )
411 }
412}
413
414impl UserInput for VirtualDPad {
415 /// [`VirtualDPad`] acts as a dual-axis input.
416 #[inline]
417 fn kind(&self) -> InputControlKind {
418 InputControlKind::DualAxis
419 }
420
421 /// Returns the four [`GamepadButton`]s used by this D-pad.
422 #[inline]
423 fn decompose(&self) -> BasicInputs {
424 BasicInputs::Composite(vec![
425 self.up.clone(),
426 self.down.clone(),
427 self.left.clone(),
428 self.right.clone(),
429 ])
430 }
431}
432
433#[serde_typetag]
434impl DualAxislike for VirtualDPad {
435 /// Retrieves the current X and Y values of this D-pad after processing by the associated processors.
436 #[inline]
437 fn get_axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec2> {
438 let up = self.up.get_value(input_store, gamepad);
439 let down = self.down.get_value(input_store, gamepad);
440 let left = self.left.get_value(input_store, gamepad);
441 let right = self.right.get_value(input_store, gamepad);
442
443 if up.is_none() && down.is_none() && left.is_none() && right.is_none() {
444 return None;
445 }
446
447 let up = up.unwrap_or(0.0);
448 let down = down.unwrap_or(0.0);
449 let left = left.unwrap_or(0.0);
450 let right = right.unwrap_or(0.0);
451
452 let value = Vec2::new(right - left, up - down);
453 Some(
454 self.processors
455 .iter()
456 .fold(value, |value, processor| processor.process(value)),
457 )
458 }
459
460 /// Sets the value of corresponding button on each axis based on the given `value`.
461 ///
462 /// When `value` along an axis is non-zero, set its absolute value to the value of:
463 /// - the negative button of the axis if the `value` is negative;
464 /// - the positive button of the axis if the `value` is positive.
465 fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option<Entity>) {
466 let Vec2 { x, y } = value;
467
468 if x < 0.0 {
469 self.left.set_value_as_gamepad(world, x.abs(), gamepad);
470 } else if x > 0.0 {
471 self.right.set_value_as_gamepad(world, x, gamepad);
472 }
473
474 if y < 0.0 {
475 self.down.set_value_as_gamepad(world, y.abs(), gamepad);
476 } else if y > 0.0 {
477 self.up.set_value_as_gamepad(world, y, gamepad);
478 }
479 }
480}
481
482impl WithDualAxisProcessingPipelineExt for VirtualDPad {
483 #[inline]
484 fn reset_processing_pipeline(mut self) -> Self {
485 self.processors.clear();
486 self
487 }
488
489 #[inline]
490 fn replace_processing_pipeline(
491 mut self,
492 processor: impl IntoIterator<Item = DualAxisProcessor>,
493 ) -> Self {
494 self.processors = processor.into_iter().collect();
495 self
496 }
497
498 #[inline]
499 fn with_processor(mut self, processor: impl Into<DualAxisProcessor>) -> Self {
500 self.processors.push(processor.into());
501 self
502 }
503}
504
505/// A virtual triple-axis control constructed from six [`Buttonlike`]s.
506/// Each button represents a specific direction (up, down, left, right, forward, backward),
507/// functioning similarly to a three-dimensional directional pad (D-pad) on all X, Y, and Z axes,
508/// and offering intermediate diagonals by means of two/three-key combinations.
509///
510/// The raw axis values are determined based on the state of the associated buttons:
511/// - `-1.0` if only the negative button is currently pressed (Down/Left/Forward).
512/// - `1.0` if only the positive button is currently pressed (Up/Right/Backward).
513/// - `0.0` if neither button is pressed, or both are pressed simultaneously.
514#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
515#[must_use]
516pub struct VirtualDPad3D {
517 /// The button for the upward direction.
518 pub up: Box<dyn Buttonlike>,
519
520 /// The button for the downward direction.
521 pub down: Box<dyn Buttonlike>,
522
523 /// The button for the leftward direction.
524 pub left: Box<dyn Buttonlike>,
525
526 /// The button for the rightward direction.
527 pub right: Box<dyn Buttonlike>,
528
529 /// The button for the forward direction.
530 pub forward: Box<dyn Buttonlike>,
531
532 /// The button for the backward direction.
533 pub backward: Box<dyn Buttonlike>,
534}
535
536impl VirtualDPad3D {
537 /// Creates a new [`VirtualDPad3D`] with six given [`Buttonlike`]s.
538 /// Each button represents a specific direction (up, down, left, right, forward, backward).
539 #[inline]
540 pub fn new(
541 up: impl Buttonlike,
542 down: impl Buttonlike,
543 left: impl Buttonlike,
544 right: impl Buttonlike,
545 forward: impl Buttonlike,
546 backward: impl Buttonlike,
547 ) -> Self {
548 Self {
549 up: Box::new(up),
550 down: Box::new(down),
551 left: Box::new(left),
552 right: Box::new(right),
553 forward: Box::new(forward),
554 backward: Box::new(backward),
555 }
556 }
557}
558
559impl UserInput for VirtualDPad3D {
560 /// [`VirtualDPad3D`] acts as a virtual triple-axis input.
561 #[inline]
562 fn kind(&self) -> InputControlKind {
563 InputControlKind::TripleAxis
564 }
565
566 /// [`VirtualDPad3D`] represents a compositions of six [`Buttonlike`]s.
567 #[inline]
568 fn decompose(&self) -> BasicInputs {
569 BasicInputs::Composite(vec![
570 self.up.clone(),
571 self.down.clone(),
572 self.left.clone(),
573 self.right.clone(),
574 self.forward.clone(),
575 self.backward.clone(),
576 ])
577 }
578}
579
580#[serde_typetag]
581impl TripleAxislike for VirtualDPad3D {
582 /// Retrieves the current X, Y, and Z values of this D-pad.
583 #[inline]
584 fn get_axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec3> {
585 let up = self.up.get_value(input_store, gamepad);
586 let down = self.down.get_value(input_store, gamepad);
587 let left = self.left.get_value(input_store, gamepad);
588 let right = self.right.get_value(input_store, gamepad);
589 let forward = self.forward.get_value(input_store, gamepad);
590 let backward = self.backward.get_value(input_store, gamepad);
591
592 if up.is_none()
593 && down.is_none()
594 && left.is_none()
595 && right.is_none()
596 && forward.is_none()
597 && backward.is_none()
598 {
599 return None;
600 }
601
602 let up = up.unwrap_or(0.0);
603 let down = down.unwrap_or(0.0);
604 let left = left.unwrap_or(0.0);
605 let right = right.unwrap_or(0.0);
606 let forward = forward.unwrap_or(0.0);
607 let backward = backward.unwrap_or(0.0);
608
609 Some(Vec3::new(right - left, up - down, backward - forward))
610 }
611
612 /// Sets the value of corresponding button on each axis based on the given `value`.
613 ///
614 /// When `value` along an axis is non-zero, set its absolute value to the value of:
615 /// - the negative button of the axis if the `value` is negative;
616 /// - the positive button of the axis if the `value` is positive.
617 fn set_axis_triple_as_gamepad(&self, world: &mut World, value: Vec3, gamepad: Option<Entity>) {
618 let Vec3 { x, y, z } = value;
619
620 if x < 0.0 {
621 self.left.set_value_as_gamepad(world, x.abs(), gamepad);
622 } else if x > 0.0 {
623 self.right.set_value_as_gamepad(world, x, gamepad);
624 }
625
626 if y < 0.0 {
627 self.down.set_value_as_gamepad(world, y.abs(), gamepad);
628 } else if y > 0.0 {
629 self.up.set_value_as_gamepad(world, y, gamepad);
630 }
631
632 if z < 0.0 {
633 self.forward.set_value_as_gamepad(world, z.abs(), gamepad);
634 } else if z > 0.0 {
635 self.backward.set_value_as_gamepad(world, z, gamepad);
636 }
637 }
638}
639
640#[cfg(feature = "keyboard")]
641#[cfg(test)]
642mod tests {
643 use bevy::input::InputPlugin;
644 use bevy::prelude::*;
645
646 use crate::plugin::CentralInputStorePlugin;
647 use crate::prelude::updating::CentralInputStore;
648 use crate::prelude::*;
649
650 fn test_app() -> App {
651 let mut app = App::new();
652 app.add_plugins(InputPlugin)
653 .add_plugins(CentralInputStorePlugin);
654 app
655 }
656
657 #[test]
658 fn test_virtual() {
659 let x = VirtualAxis::horizontal_arrow_keys();
660 let xy = VirtualDPad::arrow_keys();
661 let xyz = VirtualDPad3D::new(
662 KeyCode::ArrowUp,
663 KeyCode::ArrowDown,
664 KeyCode::ArrowLeft,
665 KeyCode::ArrowRight,
666 KeyCode::KeyF,
667 KeyCode::KeyB,
668 );
669
670 // No inputs
671 let mut app = test_app();
672 app.update();
673 let inputs = app.world().resource::<CentralInputStore>();
674
675 let gamepad = Entity::PLACEHOLDER;
676
677 assert_eq!(x.value(inputs, gamepad), 0.0);
678 assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::ZERO);
679 assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::ZERO);
680
681 // Press arrow left
682 let mut app = test_app();
683 KeyCode::ArrowLeft.press(app.world_mut());
684 app.update();
685 let inputs = app.world().resource::<CentralInputStore>();
686
687 assert_eq!(x.value(inputs, gamepad), -1.0);
688 assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(-1.0, 0.0));
689 assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(-1.0, 0.0, 0.0));
690
691 // Press arrow up
692 let mut app = test_app();
693 KeyCode::ArrowUp.press(app.world_mut());
694 app.update();
695 let inputs = app.world().resource::<CentralInputStore>();
696
697 assert_eq!(x.value(inputs, gamepad), 0.0);
698 assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(0.0, 1.0));
699 assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(0.0, 1.0, 0.0));
700
701 // Press arrow right
702 let mut app = test_app();
703 KeyCode::ArrowRight.press(app.world_mut());
704 app.update();
705 let inputs = app.world().resource::<CentralInputStore>();
706
707 assert_eq!(x.value(inputs, gamepad), 1.0);
708 assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(1.0, 0.0));
709 assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(1.0, 0.0, 0.0));
710
711 // Press key B
712 let mut app = test_app();
713 KeyCode::KeyB.press(app.world_mut());
714 app.update();
715 let inputs = app.world().resource::<CentralInputStore>();
716
717 assert_eq!(x.value(inputs, gamepad), 0.0);
718 assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.0));
719 assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(0.0, 0.0, 1.0));
720 }
721}