leafwing_input_manager/input_processing/single_axis/range.rs
1//! Range processors for single-axis inputs
2
3use std::hash::{Hash, Hasher};
4
5use bevy::{math::FloatOrd, prelude::Reflect};
6use serde::{Deserialize, Serialize};
7
8use super::AxisProcessor;
9
10/// Specifies an acceptable min-max range for valid single-axis inputs,
11/// restricting all value stays within intended limits
12/// to avoid unexpected behavior caused by extreme inputs.
13///
14/// ```rust
15/// use leafwing_input_manager::prelude::*;
16///
17/// // Restrict values to [-2.0, 1.5].
18/// let bounds = AxisBounds::new(-2.0, 1.5);
19///
20/// // The ways to create an AxisProcessor.
21/// let processor = AxisProcessor::from(bounds);
22/// assert_eq!(processor, AxisProcessor::ValueBounds(bounds));
23///
24/// for value in -300..300 {
25/// let value = value as f32 * 0.01;
26/// assert_eq!(bounds.clamp(value), value.clamp(-2.0, 1.5));
27/// }
28/// ```
29#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
30#[must_use]
31pub struct AxisBounds {
32 /// The minimum value of valid inputs.
33 pub(crate) min: f32,
34
35 /// The maximum value of valid inputs.
36 pub(crate) max: f32,
37}
38
39impl AxisBounds {
40 /// Unlimited [`AxisBounds`].
41 pub const FULL_RANGE: Self = Self {
42 min: f32::MIN,
43 max: f32::MAX,
44 };
45
46 /// Creates an [`AxisBounds`] that restricts values to the given range `[min, max]`.
47 ///
48 /// # Requirements
49 ///
50 /// - `min` <= `max`.
51 ///
52 /// # Panics
53 ///
54 /// Panics if the requirements aren't met.
55 #[inline]
56 pub fn new(min: f32, max: f32) -> Self {
57 // PartialOrd for f32 ensures that NaN values are checked during comparisons.
58 assert!(min <= max);
59 Self { min, max }
60 }
61
62 /// Creates an [`AxisBounds`] that restricts values within the range `[-threshold, threshold]`.
63 ///
64 /// # Requirements
65 ///
66 /// - `threshold` >= `0.0`.
67 ///
68 /// # Panics
69 ///
70 /// Panics if the requirements aren't met.
71 #[doc(alias = "magnitude")]
72 #[inline]
73 pub fn symmetric(threshold: f32) -> Self {
74 Self::new(-threshold, threshold)
75 }
76
77 /// Creates an [`AxisBounds`] that restricts values to a minimum value.
78 #[inline]
79 pub const fn at_least(min: f32) -> Self {
80 Self {
81 min,
82 ..Self::FULL_RANGE
83 }
84 }
85
86 /// Creates an [`AxisBounds`] that restricts values to a maximum value.
87 #[inline]
88 pub const fn at_most(max: f32) -> Self {
89 Self {
90 max,
91 ..Self::FULL_RANGE
92 }
93 }
94
95 /// Returns the minimum and maximum bounds.
96 #[must_use]
97 #[inline]
98 pub fn min_max(&self) -> (f32, f32) {
99 (self.min(), self.max())
100 }
101
102 /// Returns the minimum bound.
103 #[must_use]
104 #[inline]
105 pub fn min(&self) -> f32 {
106 self.min
107 }
108
109 /// Returns the maximum bound.
110 #[must_use]
111 #[inline]
112 pub fn max(&self) -> f32 {
113 self.max
114 }
115
116 /// Is the given `input_value` within the bounds?
117 #[must_use]
118 #[inline]
119 pub fn contains(&self, input_value: f32) -> bool {
120 self.min <= input_value && input_value <= self.max
121 }
122
123 /// Clamps `input_value` within the bounds.
124 #[must_use]
125 #[inline]
126 pub fn clamp(&self, input_value: f32) -> f32 {
127 // clamp() includes checks if either bound is set to NaN,
128 // but the constructors guarantee that all bounds will not be NaN.
129 input_value.min(self.max).max(self.min)
130 }
131}
132
133impl Default for AxisBounds {
134 /// Creates an [`AxisBounds`] that restricts values to the range `[-1.0, 1.0]`.
135 #[inline]
136 fn default() -> Self {
137 Self {
138 min: -1.0,
139 max: 1.0,
140 }
141 }
142}
143
144impl From<AxisBounds> for AxisProcessor {
145 fn from(value: AxisBounds) -> Self {
146 Self::ValueBounds(value)
147 }
148}
149
150impl Eq for AxisBounds {}
151
152impl Hash for AxisBounds {
153 fn hash<H: Hasher>(&self, state: &mut H) {
154 FloatOrd(self.min).hash(state);
155 FloatOrd(self.max).hash(state);
156 }
157}
158
159/// Specifies an exclusion range for excluding single-axis inputs,
160/// helping filter out minor fluctuations and unintended movements.
161///
162/// In simple terms, this processor behaves like an [`AxisDeadZone`] without normalization.
163///
164/// # Examples
165///
166/// ```rust
167/// use leafwing_input_manager::prelude::*;
168///
169/// // Exclude values between -0.2 and 0.3
170/// let exclusion = AxisExclusion::new(-0.2, 0.3);
171///
172/// // The ways to create an AxisProcessor.
173/// let processor = AxisProcessor::from(exclusion);
174/// assert_eq!(processor, AxisProcessor::Exclusion(exclusion));
175///
176/// for value in -300..300 {
177/// let value = value as f32 * 0.01;
178///
179/// if -0.2 <= value && value <= 0.3 {
180/// assert!(exclusion.contains(value));
181/// assert_eq!(exclusion.exclude(value), 0.0);
182/// } else {
183/// assert!(!exclusion.contains(value));
184/// assert_eq!(exclusion.exclude(value), value);
185/// }
186/// }
187/// ```
188#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
189#[must_use]
190pub struct AxisExclusion {
191 /// The maximum negative value treated as zero.
192 pub(crate) negative_max: f32,
193
194 /// The minimum positive value treated as zero.
195 pub(crate) positive_min: f32,
196}
197
198impl AxisExclusion {
199 /// Zero-size [`AxisExclusion`], leaving values as is.
200 pub const ZERO: Self = Self {
201 negative_max: 0.0,
202 positive_min: 0.0,
203 };
204
205 /// Creates an [`AxisExclusion`] that ignores values within the range `[negative_max, positive_min]`.
206 ///
207 /// # Requirements
208 ///
209 /// - `negative_max` <= `0.0` <= `positive_min`.
210 ///
211 /// # Panics
212 ///
213 /// Panics if the requirements aren't met.
214 #[inline]
215 pub fn new(negative_max: f32, positive_min: f32) -> Self {
216 assert!(negative_max <= 0.0);
217 assert!(positive_min >= 0.0);
218 Self {
219 negative_max,
220 positive_min,
221 }
222 }
223
224 /// Creates an [`AxisExclusion`] that ignores values within the range `[-threshold, threshold]`.
225 ///
226 /// # Requirements
227 ///
228 /// - `threshold` >= `0.0`.
229 ///
230 /// # Panics
231 ///
232 /// Panics if the requirements aren't met.
233 #[doc(alias = "magnitude")]
234 #[inline]
235 pub fn symmetric(threshold: f32) -> Self {
236 Self::new(-threshold, threshold)
237 }
238
239 /// Creates an [`AxisExclusion`] that only passes positive values that greater than `positive_min`.
240 ///
241 /// # Requirements
242 ///
243 /// - `positive_min` >= `0.0`.
244 ///
245 /// # Panics
246 ///
247 /// Panics if the requirements aren't met.
248 #[inline]
249 pub fn only_positive(positive_min: f32) -> Self {
250 Self::new(f32::NEG_INFINITY, positive_min)
251 }
252
253 /// Creates an [`AxisExclusion`] that only passes negative values that less than `negative_max`.
254 ///
255 /// # Requirements
256 ///
257 /// - `negative_max` <= `0.0`.
258 ///
259 /// # Panics
260 ///
261 /// Panics if the requirements aren't met.
262 #[inline]
263 pub fn only_negative(negative_max: f32) -> Self {
264 Self::new(negative_max, f32::INFINITY)
265 }
266
267 /// Returns the minimum and maximum bounds.
268 #[must_use]
269 #[inline]
270 pub fn min_max(&self) -> (f32, f32) {
271 (self.negative_max, self.positive_min)
272 }
273
274 /// Returns the minimum bound.
275 #[must_use]
276 #[inline]
277 pub fn min(&self) -> f32 {
278 self.negative_max
279 }
280
281 /// Returns the maximum bounds.
282 #[must_use]
283 #[inline]
284 pub fn max(&self) -> f32 {
285 self.positive_min
286 }
287
288 /// Is `input_value` within the deadzone?
289 #[must_use]
290 #[inline]
291 pub fn contains(&self, input_value: f32) -> bool {
292 self.negative_max <= input_value && input_value <= self.positive_min
293 }
294
295 /// Excludes values within the specified range.
296 #[must_use]
297 #[inline]
298 pub fn exclude(&self, input_value: f32) -> f32 {
299 if self.contains(input_value) {
300 0.0
301 } else {
302 input_value
303 }
304 }
305
306 /// Creates an [`AxisDeadZone`] using `self` as the exclusion range.
307 #[inline]
308 pub fn scaled(self) -> AxisDeadZone {
309 AxisDeadZone::new(self.negative_max, self.positive_min)
310 }
311}
312
313impl Default for AxisExclusion {
314 /// Creates an [`AxisExclusion`] that ignores values within the range `[-0.1, 0.1]`.
315 #[inline]
316 fn default() -> Self {
317 Self {
318 negative_max: -0.1,
319 positive_min: 0.1,
320 }
321 }
322}
323
324impl From<AxisExclusion> for AxisProcessor {
325 fn from(value: AxisExclusion) -> Self {
326 Self::Exclusion(value)
327 }
328}
329
330impl Eq for AxisExclusion {}
331
332impl Hash for AxisExclusion {
333 fn hash<H: Hasher>(&self, state: &mut H) {
334 FloatOrd(self.negative_max).hash(state);
335 FloatOrd(self.positive_min).hash(state);
336 }
337}
338
339/// A scaled version of [`AxisExclusion`] with the bounds
340/// set to [`AxisBounds::magnitude(1.0)`](AxisBounds::default)
341/// that normalizes non-excluded input values into the "live zone",
342/// the remaining range within the bounds after dead zone exclusion.
343///
344/// # Examples
345///
346/// ```rust
347/// use bevy::prelude::*;
348/// use leafwing_input_manager::prelude::*;
349///
350/// // Exclude values between -0.2 and 0.3
351/// let deadzone = AxisDeadZone::new(-0.2, 0.3);
352///
353/// // Another way to create an AxisDeadzone.
354/// let exclusion = AxisExclusion::new(-0.2, 0.3);
355/// assert_eq!(exclusion.scaled(), deadzone);
356///
357/// // The ways to create an AxisProcessor.
358/// let processor = AxisProcessor::from(deadzone);
359/// assert_eq!(processor, AxisProcessor::DeadZone(deadzone));
360///
361/// // The bounds after normalization.
362/// let bounds = deadzone.bounds();
363/// assert_eq!(bounds.min(), -1.0);
364/// assert_eq!(bounds.max(), 1.0);
365///
366/// for value in -300..300 {
367/// let value = value as f32 * 0.01;
368///
369/// // Values within the dead zone are treated as zero.
370/// if -0.2 <= value && value <= 0.3 {
371/// assert!(deadzone.within_exclusion(value));
372/// assert_eq!(deadzone.normalize(value), 0.0);
373/// }
374///
375/// // Values within the live zone are scaled linearly.
376/// else if -1.0 <= value && value < -0.2 {
377/// assert!(deadzone.within_livezone_lower(value));
378///
379/// let expected = f32::inverse_lerp(-1.0, -0.2, value) - 1.0;
380/// assert!((deadzone.normalize(value) - expected).abs() <= f32::EPSILON);
381/// } else if 0.3 < value && value <= 1.0 {
382/// assert!(deadzone.within_livezone_upper(value));
383///
384/// let expected = f32::inverse_lerp(0.3, 1.0, value);
385/// assert!((deadzone.normalize(value) - expected).abs() <= f32::EPSILON);
386/// }
387///
388/// // Values outside the bounds are restricted to the range.
389/// else {
390/// assert!(!deadzone.within_bounds(value));
391/// assert_eq!(deadzone.normalize(value), value.clamp(-1.0, 1.0));
392/// }
393/// }
394/// ```
395#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
396#[must_use]
397pub struct AxisDeadZone {
398 /// The [`AxisExclusion`] used for normalization.
399 pub(crate) exclusion: AxisExclusion,
400
401 /// Pre-calculated reciprocal of the lower live zone size,
402 /// preventing division during normalization.
403 pub(crate) livezone_lower_recip: f32,
404
405 /// Pre-calculated reciprocal of the upper live zone size,
406 /// preventing division during normalization.
407 pub(crate) livezone_upper_recip: f32,
408}
409
410impl AxisDeadZone {
411 /// Zero-size [`AxisDeadZone`], only restricting values to the range `[-1.0, 1.0]`.
412 pub const ZERO: Self = Self {
413 exclusion: AxisExclusion::ZERO,
414 livezone_lower_recip: 1.0,
415 livezone_upper_recip: 1.0,
416 };
417
418 /// Creates an [`AxisDeadZone`] that excludes input values within the range `[negative_max, positive_min]`
419 /// and then normalizes non-excluded input values into the valid range `[-1.0, 1.0]`.
420 ///
421 /// # Requirements
422 ///
423 /// - `negative_max` <= `0.0` <= `positive_min`.
424 ///
425 /// # Panics
426 ///
427 /// Panics if the requirements aren't met.
428 #[inline]
429 pub fn new(negative_max: f32, positive_min: f32) -> Self {
430 let (bound_min, bound_max) = AxisBounds::default().min_max();
431 Self {
432 exclusion: AxisExclusion::new(negative_max, positive_min),
433 livezone_lower_recip: (negative_max - bound_min).recip(),
434 livezone_upper_recip: (bound_max - positive_min).recip(),
435 }
436 }
437
438 /// Creates an [`AxisDeadZone`] that excludes input values within the range `[-threshold, threshold]`
439 /// and then normalizes non-excluded input values into the valid range `[-1.0, 1.0]`.
440 ///
441 /// # Requirements
442 ///
443 /// - `threshold` >= `0.0`.
444 ///
445 /// # Panics
446 ///
447 /// Panics if the requirements aren't met.
448 #[doc(alias = "magnitude")]
449 #[inline]
450 pub fn symmetric(threshold: f32) -> Self {
451 Self::new(-threshold, threshold)
452 }
453
454 /// Creates an [`AxisDeadZone`] that only passes positive values that greater than `positive_min`
455 /// and then normalizes them into the valid range `[-1.0, 1.0]`.
456 ///
457 /// # Requirements
458 ///
459 /// - `positive_min` >= `0.0`.
460 ///
461 /// # Panics
462 ///
463 /// Panics if the requirements aren't met.
464 #[inline]
465 pub fn only_positive(positive_min: f32) -> Self {
466 Self::new(f32::NEG_INFINITY, positive_min)
467 }
468
469 /// Creates an [`AxisDeadZone`] that only passes negative values that less than `negative_max`
470 /// and then normalizes them into the valid range `[-1.0, 1.0]`.
471 ///
472 /// # Requirements
473 ///
474 /// - `negative_max` <= `0.0`.
475 ///
476 /// # Panics
477 ///
478 /// Panics if the requirements aren't met.
479 #[inline]
480 pub fn only_negative(negative_max: f32) -> Self {
481 Self::new(negative_max, f32::INFINITY)
482 }
483
484 /// Returns the [`AxisExclusion`] used by this deadzone.
485 #[inline]
486 pub fn exclusion(&self) -> AxisExclusion {
487 self.exclusion
488 }
489
490 /// Returns the [`AxisBounds`] used by this deadzone.
491 #[inline]
492 pub fn bounds(&self) -> AxisBounds {
493 AxisBounds::default()
494 }
495
496 /// Returns the minimum and maximum bounds of the lower live zone used for normalization.
497 ///
498 /// In simple terms, this returns `(bounds.min, exclusion.min)`.
499 #[must_use]
500 #[inline]
501 pub fn livezone_lower_min_max(&self) -> (f32, f32) {
502 (self.bounds().min(), self.exclusion.min())
503 }
504
505 /// Returns the minimum and maximum bounds of the upper live zone used for normalization.
506 ///
507 /// In simple terms, this returns `(exclusion.max, bounds.max)`.
508 #[must_use]
509 #[inline]
510 pub fn livezone_upper_min_max(&self) -> (f32, f32) {
511 (self.exclusion.max(), self.bounds().max())
512 }
513
514 /// Is the given `input_value` within the exclusion range?
515 #[must_use]
516 #[inline]
517 pub fn within_exclusion(&self, input_value: f32) -> bool {
518 self.exclusion.contains(input_value)
519 }
520
521 /// Is the given `input_value` within the bounds?
522 #[must_use]
523 #[inline]
524 pub fn within_bounds(&self, input_value: f32) -> bool {
525 self.bounds().contains(input_value)
526 }
527
528 /// Is the given `input_value` within the lower live zone?
529 #[must_use]
530 #[inline]
531 pub fn within_livezone_lower(&self, input_value: f32) -> bool {
532 let (min, max) = self.livezone_lower_min_max();
533 min <= input_value && input_value <= max
534 }
535
536 /// Is the given `input_value` within the upper live zone?
537 #[must_use]
538 #[inline]
539 pub fn within_livezone_upper(&self, input_value: f32) -> bool {
540 let (min, max) = self.livezone_upper_min_max();
541 min <= input_value && input_value <= max
542 }
543
544 /// Normalizes input values into the live zone.
545 #[must_use]
546 pub fn normalize(&self, input_value: f32) -> f32 {
547 // Clamp out-of-bounds values to [-1, 1],
548 // and then exclude values within the dead zone,
549 // and finally linearly scale the result to the live zone.
550 if input_value <= 0.0 {
551 let (bound, deadzone) = self.livezone_lower_min_max();
552 let clamped_input = input_value.max(bound);
553 let distance_to_deadzone = (clamped_input - deadzone).min(0.0);
554 distance_to_deadzone * self.livezone_lower_recip
555 } else {
556 let (deadzone, bound) = self.livezone_upper_min_max();
557 let clamped_input = input_value.min(bound);
558 let distance_to_deadzone = (clamped_input - deadzone).max(0.0);
559 distance_to_deadzone * self.livezone_upper_recip
560 }
561 }
562}
563
564impl Default for AxisDeadZone {
565 /// Creates an [`AxisDeadZone`] that excludes input values within the deadzone `[-0.1, 0.1]`.
566 #[inline]
567 fn default() -> Self {
568 AxisDeadZone::new(-0.1, 0.1)
569 }
570}
571
572impl From<AxisDeadZone> for AxisProcessor {
573 fn from(value: AxisDeadZone) -> Self {
574 Self::DeadZone(value)
575 }
576}
577
578impl Eq for AxisDeadZone {}
579
580impl Hash for AxisDeadZone {
581 fn hash<H: Hasher>(&self, state: &mut H) {
582 self.exclusion.hash(state);
583 }
584}
585
586#[cfg(test)]
587mod tests {
588 use super::*;
589 use bevy::prelude::FloatExt;
590
591 #[test]
592 fn test_axis_value_bounds() {
593 fn test_bounds(bounds: AxisBounds, min: f32, max: f32) {
594 assert_eq!(bounds.min(), min);
595 assert_eq!(bounds.max(), max);
596 assert_eq!(bounds.min_max(), (min, max));
597
598 let processor = AxisProcessor::ValueBounds(bounds);
599 assert_eq!(AxisProcessor::from(bounds), processor);
600
601 for value in -300..300 {
602 let value = value as f32 * 0.01;
603
604 assert_eq!(bounds.clamp(value), processor.process(value));
605
606 if min <= value && value <= max {
607 assert!(bounds.contains(value));
608 } else {
609 assert!(!bounds.contains(value));
610 }
611
612 assert_eq!(bounds.clamp(value), value.clamp(min, max));
613 }
614 }
615
616 let bounds = AxisBounds::FULL_RANGE;
617 test_bounds(bounds, f32::MIN, f32::MAX);
618
619 let bounds = AxisBounds::default();
620 test_bounds(bounds, -1.0, 1.0);
621
622 let bounds = AxisBounds::new(-2.0, 2.5);
623 test_bounds(bounds, -2.0, 2.5);
624
625 let bounds = AxisBounds::symmetric(2.0);
626 test_bounds(bounds, -2.0, 2.0);
627
628 let bounds = AxisBounds::at_least(-1.0);
629 test_bounds(bounds, -1.0, f32::MAX);
630
631 let bounds = AxisBounds::at_most(1.5);
632 test_bounds(bounds, f32::MIN, 1.5);
633 }
634
635 #[test]
636 fn test_axis_exclusion() {
637 fn test_exclusion(exclusion: AxisExclusion, min: f32, max: f32) {
638 assert_eq!(exclusion.min(), min);
639 assert_eq!(exclusion.max(), max);
640 assert_eq!(exclusion.min_max(), (min, max));
641
642 let processor = AxisProcessor::Exclusion(exclusion);
643 assert_eq!(AxisProcessor::from(exclusion), processor);
644
645 for value in -300..300 {
646 let value = value as f32 * 0.01;
647
648 assert_eq!(exclusion.exclude(value), processor.process(value));
649
650 if min <= value && value <= max {
651 assert!(exclusion.contains(value));
652 assert_eq!(exclusion.exclude(value), 0.0);
653 } else {
654 assert!(!exclusion.contains(value));
655 assert_eq!(exclusion.exclude(value), value);
656 }
657 }
658 }
659
660 let exclusion = AxisExclusion::ZERO;
661 test_exclusion(exclusion, 0.0, 0.0);
662
663 let exclusion = AxisExclusion::default();
664 test_exclusion(exclusion, -0.1, 0.1);
665
666 let exclusion = AxisExclusion::new(-2.0, 2.5);
667 test_exclusion(exclusion, -2.0, 2.5);
668
669 let exclusion = AxisExclusion::symmetric(1.5);
670 test_exclusion(exclusion, -1.5, 1.5);
671 }
672
673 #[test]
674 fn test_axis_deadzone() {
675 fn test_deadzone(deadzone: AxisDeadZone, min: f32, max: f32) {
676 let exclusion = deadzone.exclusion();
677 assert_eq!(exclusion.min_max(), (min, max));
678
679 assert_eq!(deadzone.livezone_lower_min_max(), (-1.0, min));
680 let width_recip = (min + 1.0).recip();
681 assert!((deadzone.livezone_lower_recip - width_recip).abs() <= f32::EPSILON);
682
683 assert_eq!(deadzone.livezone_upper_min_max(), (max, 1.0));
684 let width_recip = (1.0 - max).recip();
685 assert!((deadzone.livezone_upper_recip - width_recip).abs() <= f32::EPSILON);
686
687 assert_eq!(AxisExclusion::new(min, max).scaled(), deadzone);
688
689 let processor = AxisProcessor::DeadZone(deadzone);
690 assert_eq!(AxisProcessor::from(deadzone), processor);
691
692 for value in -300..300 {
693 let value = value as f32 * 0.01;
694
695 assert_eq!(deadzone.normalize(value), processor.process(value));
696
697 // Values within the dead zone are treated as zero.
698 if min <= value && value <= max {
699 assert!(deadzone.within_exclusion(value));
700 assert_eq!(deadzone.normalize(value), 0.0);
701 }
702 // Values within the live zone are scaled linearly.
703 else if -1.0 <= value && value < min {
704 assert!(deadzone.within_livezone_lower(value));
705
706 let expected = f32::inverse_lerp(-1.0, min, value) - 1.0;
707 let delta = (deadzone.normalize(value) - expected).abs();
708 assert!(delta <= f32::EPSILON);
709 } else if max < value && value <= 1.0 {
710 assert!(deadzone.within_livezone_upper(value));
711
712 let expected = f32::inverse_lerp(max, 1.0, value);
713 let delta = (deadzone.normalize(value) - expected).abs();
714 assert!(delta <= f32::EPSILON);
715 }
716 // Values outside the bounds are restricted to the nearest valid value.
717 else {
718 assert!(!deadzone.within_bounds(value));
719 assert_eq!(deadzone.normalize(value), value.clamp(-1.0, 1.0));
720 }
721 }
722 }
723
724 let deadzone = AxisDeadZone::ZERO;
725 test_deadzone(deadzone, 0.0, 0.0);
726
727 let deadzone = AxisDeadZone::default();
728 test_deadzone(deadzone, -0.1, 0.1);
729
730 let deadzone = AxisDeadZone::new(-0.2, 0.3);
731 test_deadzone(deadzone, -0.2, 0.3);
732
733 let deadzone = AxisDeadZone::symmetric(0.4);
734 test_deadzone(deadzone, -0.4, 0.4);
735 }
736}