leafwing_input_manager/input_processing/dual_axis/
mod.rs

1//! Processors for dual-axis input values
2
3use std::hash::{Hash, Hasher};
4
5use bevy::{
6    math::FloatOrd,
7    prelude::{BVec2, Reflect, Vec2},
8};
9use serde::{Deserialize, Serialize};
10
11use crate::input_processing::AxisProcessor;
12
13pub use self::circle::*;
14pub use self::custom::*;
15pub use self::range::*;
16
17mod circle;
18mod custom;
19mod range;
20
21/// A processor for dual-axis input values,
22/// accepting a [`Vec2`] input and producing a [`Vec2`] output.
23#[must_use]
24#[non_exhaustive]
25#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
26pub enum DualAxisProcessor {
27    /// Converts input values into three discrete values along each axis,
28    /// similar to [`Vec2::signum()`] but returning `0.0` for zero values.
29    ///
30    /// ```rust
31    /// use bevy::prelude::*;
32    /// use leafwing_input_manager::prelude::*;
33    ///
34    /// // 1.0 for positive values
35    /// assert_eq!(DualAxisProcessor::Digital.process(Vec2::splat(2.5)), Vec2::ONE);
36    /// assert_eq!(DualAxisProcessor::Digital.process(Vec2::splat(0.5)), Vec2::ONE);
37    ///
38    /// // 0.0 for zero values
39    /// assert_eq!(DualAxisProcessor::Digital.process(Vec2::ZERO), Vec2::ZERO);
40    /// assert_eq!(DualAxisProcessor::Digital.process(-Vec2::ZERO), Vec2::ZERO);
41    ///
42    /// // -1.0 for negative values
43    /// assert_eq!(DualAxisProcessor::Digital.process(Vec2::splat(-0.5)), Vec2::NEG_ONE);
44    /// assert_eq!(DualAxisProcessor::Digital.process(Vec2::splat(-2.5)), Vec2::NEG_ONE);
45    ///
46    /// // Mixed digital values
47    /// assert_eq!(DualAxisProcessor::Digital.process(Vec2::new(0.5, -0.5)), Vec2::new(1.0, -1.0));
48    /// assert_eq!(DualAxisProcessor::Digital.process(Vec2::new(-0.5, 0.5)), Vec2::new(-1.0, 1.0));
49    /// ```
50    Digital,
51
52    /// A wrapper around [`DualAxisInverted`] to represent inversion.
53    Inverted(DualAxisInverted),
54
55    /// A wrapper around [`DualAxisSensitivity`] to represent sensitivity.
56    Sensitivity(DualAxisSensitivity),
57
58    /// A wrapper around [`DualAxisBounds`] to represent value bounds.
59    ValueBounds(DualAxisBounds),
60
61    /// A wrapper around [`DualAxisExclusion`] to represent unscaled deadzone.
62    Exclusion(DualAxisExclusion),
63
64    /// A wrapper around [`DualAxisDeadZone`] to represent scaled deadzone.
65    DeadZone(DualAxisDeadZone),
66
67    /// A wrapper around [`CircleBounds`] to represent circular value bounds.
68    CircleBounds(CircleBounds),
69
70    /// A wrapper around [`CircleExclusion`] to represent unscaled deadzone.
71    CircleExclusion(CircleExclusion),
72
73    /// A wrapper around [`CircleDeadZone`] to represent scaled deadzone.
74    CircleDeadZone(CircleDeadZone),
75
76    /// A user-defined processor that implements [`CustomDualAxisProcessor`].
77    Custom(Box<dyn CustomDualAxisProcessor>),
78}
79
80impl DualAxisProcessor {
81    /// Computes the result by processing the `input_value`.
82    #[must_use]
83    #[inline]
84    pub fn process(&self, input_value: Vec2) -> Vec2 {
85        match self {
86            Self::Digital => Vec2::new(
87                AxisProcessor::Digital.process(input_value.x),
88                AxisProcessor::Digital.process(input_value.y),
89            ),
90            Self::Inverted(inversion) => inversion.invert(input_value),
91            Self::Sensitivity(sensitivity) => sensitivity.scale(input_value),
92            Self::ValueBounds(bounds) => bounds.clamp(input_value),
93            Self::Exclusion(exclusion) => exclusion.exclude(input_value),
94            Self::DeadZone(deadzone) => deadzone.normalize(input_value),
95            Self::CircleBounds(bounds) => bounds.clamp(input_value),
96            Self::CircleExclusion(exclusion) => exclusion.exclude(input_value),
97            Self::CircleDeadZone(deadzone) => deadzone.normalize(input_value),
98            Self::Custom(processor) => processor.process(input_value),
99        }
100    }
101}
102
103/// Provides methods for configuring and manipulating the processing pipeline for dual-axis input.
104pub trait WithDualAxisProcessingPipelineExt: Sized {
105    /// Resets the processing pipeline, removing any currently applied processors.
106    fn reset_processing_pipeline(self) -> Self;
107
108    /// Replaces the current processing pipeline with the given [`DualAxisProcessor`]s.
109    fn replace_processing_pipeline(
110        self,
111        processors: impl IntoIterator<Item = DualAxisProcessor>,
112    ) -> Self;
113
114    /// Appends the given [`DualAxisProcessor`] as the next processing step.
115    fn with_processor(self, processor: impl Into<DualAxisProcessor>) -> Self;
116
117    /// Appends an [`DualAxisProcessor::Digital`] processor as the next processing step,
118    /// similar to [`Vec2::signum`] but returning `0.0` for zero values.
119    #[inline]
120    fn digital(self) -> Self {
121        self.with_processor(DualAxisProcessor::Digital)
122    }
123
124    /// Appends a [`DualAxisInverted::ALL`] processor as the next processing step,
125    /// flipping the sign of values on both axes.
126    #[inline]
127    fn inverted(self) -> Self {
128        self.with_processor(DualAxisInverted::ALL)
129    }
130
131    /// Appends a [`DualAxisInverted::ONLY_X`] processor as the next processing step,
132    /// only flipping the sign of the X-axis values.
133    #[inline]
134    fn inverted_x(self) -> Self {
135        self.with_processor(DualAxisInverted::ONLY_X)
136    }
137
138    /// Appends a [`DualAxisInverted::ONLY_Y`] processor as the next processing step,
139    /// only flipping the sign of the Y-axis values.
140    #[inline]
141    fn inverted_y(self) -> Self {
142        self.with_processor(DualAxisInverted::ONLY_Y)
143    }
144
145    /// Appends a [`DualAxisSensitivity`] processor as the next processing step,
146    /// multiplying values on both axes with the given sensitivity factor.
147    #[inline]
148    fn sensitivity(self, sensitivity: f32) -> Self {
149        self.with_processor(DualAxisSensitivity::all(sensitivity))
150    }
151
152    /// Appends a [`DualAxisSensitivity`] processor as the next processing step,
153    /// only multiplying the X-axis values with the given sensitivity factor.
154    #[inline]
155    fn sensitivity_x(self, sensitivity: f32) -> Self {
156        self.with_processor(DualAxisSensitivity::only_x(sensitivity))
157    }
158
159    /// Appends a [`DualAxisSensitivity`] processor as the next processing step,
160    /// only multiplying the Y-axis values with the given sensitivity factor.
161    #[inline]
162    fn sensitivity_y(self, sensitivity: f32) -> Self {
163        self.with_processor(DualAxisSensitivity::only_y(sensitivity))
164    }
165
166    /// Appends a [`DualAxisBounds`] processor as the next processing step,
167    /// restricting values within the same range `[min, max]` on both axes.
168    #[inline]
169    fn with_bounds(self, min: f32, max: f32) -> Self {
170        self.with_processor(DualAxisBounds::all(min, max))
171    }
172
173    /// Appends a [`DualAxisBounds`] processor as the next processing step,
174    /// restricting values within the same range `[-threshold, threshold]` on both axes.
175    #[inline]
176    fn with_bounds_symmetric(self, threshold: f32) -> Self {
177        self.with_processor(DualAxisBounds::symmetric_all(threshold))
178    }
179
180    /// Appends a [`DualAxisBounds`] processor as the next processing step,
181    /// only restricting values within the range `[min, max]` on the X-axis.
182    #[inline]
183    fn with_bounds_x(self, min: f32, max: f32) -> Self {
184        self.with_processor(DualAxisBounds::only_x(min, max))
185    }
186
187    /// Appends a [`DualAxisBounds`] processor as the next processing step,
188    /// restricting values within the range `[-threshold, threshold]` on the X-axis.
189    #[inline]
190    fn with_bounds_x_symmetric(self, threshold: f32) -> Self {
191        self.with_processor(DualAxisBounds::symmetric_all(threshold))
192    }
193
194    /// Appends a [`DualAxisBounds`] processor as the next processing step,
195    /// only restricting values within the range `[min, max]` on the Y-axis.
196    #[inline]
197    fn with_bounds_y(self, min: f32, max: f32) -> Self {
198        self.with_processor(DualAxisBounds::only_y(min, max))
199    }
200
201    /// Appends a [`DualAxisBounds`] processor as the next processing step,
202    /// restricting values within the range `[-threshold, threshold]` on the Y-axis.
203    #[inline]
204    fn with_bounds_y_symmetric(self, threshold: f32) -> Self {
205        self.with_processor(DualAxisBounds::symmetric_all(threshold))
206    }
207
208    /// Appends a [`DualAxisBounds`] processor as the next processing step,
209    /// restricting values to a minimum value on both axes.
210    #[inline]
211    fn at_least(self, min: f32) -> Self {
212        self.with_processor(DualAxisBounds::at_least_all(min))
213    }
214
215    /// Appends a [`DualAxisBounds`] processor as the next processing step,
216    /// restricting X values to a minimum value.
217    #[inline]
218    fn at_least_only_x(self, min: f32) -> Self {
219        self.with_processor(DualAxisBounds::at_least_only_x(min))
220    }
221
222    /// Appends a [`DualAxisBounds`] processor as the next processing step,
223    /// restricting Y values to a minimum value.
224    #[inline]
225    fn at_least_only_y(self, min: f32) -> Self {
226        self.with_processor(DualAxisBounds::at_least_only_y(min))
227    }
228
229    /// Appends a [`DualAxisBounds`] processor as the next processing step,
230    /// restricting values to a maximum value on both axes.
231    #[inline]
232    fn at_most(self, min: f32) -> Self {
233        self.with_processor(DualAxisBounds::at_most_all(min))
234    }
235
236    /// Appends a [`DualAxisBounds`] processor as the next processing step,
237    /// restricting X values to a maximum value.
238    #[inline]
239    fn at_most_only_x(self, min: f32) -> Self {
240        self.with_processor(DualAxisBounds::at_most_only_x(min))
241    }
242
243    /// Appends a [`DualAxisBounds`] processor as the next processing step,
244    /// restricting Y values to a maximum value.
245    #[inline]
246    fn at_most_only_y(self, min: f32) -> Self {
247        self.with_processor(DualAxisBounds::at_most_only_y(min))
248    }
249
250    /// Appends a [`CircleBounds`] processor as the next processing step,
251    /// restricting values to a `max` magnitude.
252    ///
253    /// # Requirements
254    ///
255    /// - `max` >= `0.0`.
256    ///
257    /// # Panics
258    ///
259    /// Panics if the requirements aren't met.
260    #[inline]
261    fn with_circle_bounds(self, max: f32) -> Self {
262        self.with_processor(CircleBounds::new(max))
263    }
264
265    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
266    /// excluding values within the dead zone range `[negative_max, positive_min]` on both axes,
267    /// treating them as zeros, then normalizing non-excluded input values into the "live zone",
268    /// the remaining range within the [`DualAxisBounds::symmetric_all(1.0)`](DualAxisBounds::default)
269    /// after dead zone exclusion.
270    ///
271    /// # Requirements
272    ///
273    /// - `negative_max` <= `0.0` <= `positive_min`.
274    ///
275    /// # Panics
276    ///
277    /// Panics if the requirements aren't met.
278    #[inline]
279    fn with_deadzone(self, negative_max: f32, positive_min: f32) -> Self {
280        self.with_processor(DualAxisDeadZone::all(negative_max, positive_min))
281    }
282
283    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
284    /// excluding values within the dead zone range `[-threshold, threshold]` on both axes,
285    /// treating them as zeros, then normalizing non-excluded input values into the "live zone",
286    /// the remaining range within the [`DualAxisBounds::symmetric_all(1.0)`](DualAxisBounds::default)
287    /// after dead zone exclusion.
288    ///
289    /// # Requirements
290    ///
291    /// - `threshold` >= `0.0`.
292    ///
293    /// # Panics
294    ///
295    /// Panics if the requirements aren't met.
296    #[inline]
297    fn with_deadzone_symmetric(self, threshold: f32) -> Self {
298        self.with_processor(DualAxisDeadZone::symmetric_all(threshold))
299    }
300
301    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
302    /// only passing positive values that greater than `positive_min` on both axes
303    /// and then normalizing them into the "live zone" range `[positive_min, 1.0]`.
304    ///
305    /// # Requirements
306    ///
307    /// - `positive_min` >= `0.0`.
308    ///
309    /// # Panics
310    ///
311    /// Panics if the requirements aren't met.
312    #[inline]
313    fn only_positive(self, positive_min: f32) -> Self {
314        self.with_processor(DualAxisDeadZone::only_positive_all(positive_min))
315    }
316
317    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
318    /// only passing negative values that less than `negative_max` on both axes
319    /// and then normalizing them into the "live zone" range `[-1.0, negative_max]`.
320    ///
321    /// # Requirements
322    ///
323    /// - `negative_max` <= `0.0`.
324    ///
325    /// # Panics
326    ///
327    /// Panics if the requirements aren't met.
328    #[inline]
329    fn only_negative(self, negative_max: f32) -> Self {
330        self.with_processor(DualAxisDeadZone::only_negative_all(negative_max))
331    }
332
333    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
334    /// excluding values within the range `[negative_max, positive_min]` on the X-axis,
335    /// treating them as zeros, then normalizing non-excluded X values into the "live zone",
336    /// the remaining range within the [`AxisBounds::symmetric(1.0)`](super::AxisBounds::default)
337    /// after dead zone exclusion.
338    ///
339    /// # Requirements
340    ///
341    /// - `negative_max` <= `0.0` <= `positive_min`.
342    ///
343    /// # Panics
344    ///
345    /// Panics if the requirements aren't met.
346    #[inline]
347    fn with_deadzone_x(self, negative_max: f32, positive_min: f32) -> Self {
348        self.with_processor(DualAxisDeadZone::only_x(negative_max, positive_min))
349    }
350
351    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
352    /// excluding values within the range `[-threshold, threshold]` on the X-axis,
353    /// treating them as zeros, then normalizing non-excluded X values into the "live zone",
354    /// the remaining range within the [`AxisBounds::symmetric(1.0)`](super::AxisBounds::default)
355    /// after dead zone exclusion.
356    ///
357    /// # Requirements
358    ///
359    /// - `threshold` >= `0.0`.
360    ///
361    /// # Panics
362    ///
363    /// Panics if the requirements aren't met.
364    #[inline]
365    fn with_deadzone_x_symmetric(self, threshold: f32) -> Self {
366        self.with_processor(DualAxisDeadZone::symmetric_only_x(threshold))
367    }
368
369    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
370    /// only excluding X values that less than or equal to `positive_min`, treating them as zeros
371    /// and then normalizing non-excluded X values into the "live zone" range `[positive_min, 1.0]`.
372    ///
373    /// # Requirements
374    ///
375    /// - `positive_min` >= `0.0`.
376    ///
377    /// # Panics
378    ///
379    /// Panics if the requirements aren't met.
380    #[inline]
381    fn only_positive_x(self, positive_min: f32) -> Self {
382        self.with_processor(DualAxisDeadZone::only_positive_x(positive_min))
383    }
384
385    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
386    /// only excluding X values that greater than or equal to `negative_max`, treating them as zeros
387    /// and then normalizing non-excluded X values into the "live zone" range `[-1.0, negative_max]`.
388    ///
389    /// # Requirements
390    ///
391    /// - `negative_max` <= `0.0`.
392    ///
393    /// # Panics
394    ///
395    /// Panics if the requirements aren't met.
396    #[inline]
397    fn only_negative_x(self, negative_max: f32) -> Self {
398        self.with_processor(DualAxisDeadZone::only_negative_x(negative_max))
399    }
400
401    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
402    /// excluding values within the range `[negative_max, positive_min]` on the Y-axis,
403    /// treating them as zeros, then normalizing non-excluded Y values into the "live zone",
404    /// the remaining range within the [`AxisBounds::symmetric(1.0)`](super::AxisBounds::default)
405    /// after dead zone exclusion.
406    ///
407    /// # Requirements
408    ///
409    /// - `negative_max` <= `0.0` <= `positive_min`.
410    ///
411    /// # Panics
412    ///
413    /// Panics if the requirements aren't met.
414    #[inline]
415    fn with_deadzone_y(self, negative_max: f32, positive_min: f32) -> Self {
416        self.with_processor(DualAxisDeadZone::only_y(negative_max, positive_min))
417    }
418
419    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
420    /// excluding values within the range `[-threshold, threshold]` on the Y-axis,
421    /// treating them as zeros, then normalizing non-excluded Y values into the "live zone",
422    /// the remaining range within the [`AxisBounds::symmetric(1.0)`](super::AxisBounds::default)
423    /// after dead zone exclusion.
424    ///
425    /// # Requirements
426    ///
427    /// - `threshold` >= `0.0`.
428    ///
429    /// # Panics
430    ///
431    /// Panics if the requirements aren't met.
432    #[inline]
433    fn with_deadzone_y_symmetric(self, threshold: f32) -> Self {
434        self.with_processor(DualAxisDeadZone::symmetric_only_y(threshold))
435    }
436
437    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
438    /// only excluding Y values that less than or equal to `positive_min`, treating them as zeros
439    /// and then normalizing non-excluded Y values into the range `[positive_min, 1.0]`.
440    ///
441    /// # Requirements
442    ///
443    /// - `positive_min` >= `0.0`.
444    ///
445    /// # Panics
446    ///
447    /// Panics if the requirements aren't met.
448    #[inline]
449    fn only_positive_y(self, positive_min: f32) -> Self {
450        self.with_processor(DualAxisDeadZone::only_positive_y(positive_min))
451    }
452
453    /// Appends a [`DualAxisDeadZone`] processor as the next processing step,
454    /// only excluding Y values that greater than or equal to `negative_max`, treating them as zeros
455    /// and then normalizing non-excluded Y values into the range `[-1.0, negative_max]`.
456    ///
457    /// # Requirements
458    ///
459    /// - `negative_max` <= `0.0`.
460    ///
461    /// # Panics
462    ///
463    /// Panics if the requirements aren't met.
464    #[inline]
465    fn only_negative_y(self, negative_max: f32) -> Self {
466        self.with_processor(DualAxisDeadZone::only_negative_y(negative_max))
467    }
468
469    /// Appends a [`CircleDeadZone`] processor as the next processing step,
470    /// ignoring values below a `min` magnitude, treating them as zeros,
471    /// then normalizing non-excluded input values into the "live zone",
472    /// the remaining range within the [`CircleBounds::new(1.0)`](CircleBounds::default)
473    /// after dead zone exclusion.
474    ///
475    /// # Requirements
476    ///
477    /// - `min` >= `0.0`.
478    ///
479    /// # Panics
480    ///
481    /// Panics if the requirements aren't met.
482    #[inline]
483    fn with_circle_deadzone(self, min: f32) -> Self {
484        self.with_processor(CircleDeadZone::new(min))
485    }
486
487    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
488    /// ignoring values within the range `[negative_max, positive_min]` on both axes,
489    /// treating them as zeros.
490    ///
491    /// # Requirements
492    ///
493    /// - `negative_max` <= `0.0` <= `positive_min`.
494    ///
495    /// # Panics
496    ///
497    /// Panics if the requirements aren't met.
498    #[inline]
499    fn with_deadzone_unscaled(self, negative_max: f32, positive_min: f32) -> Self {
500        self.with_processor(DualAxisExclusion::all(negative_max, positive_min))
501    }
502
503    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
504    /// ignoring values within the range `[-threshold, threshold]` on both axes,
505    /// treating them as zeros.
506    ///
507    /// # Requirements
508    ///
509    /// - `threshold` >= `0.0`.
510    ///
511    /// # Panics
512    ///
513    /// Panics if the requirements aren't met.
514    #[inline]
515    fn with_deadzone_symmetric_unscaled(self, threshold: f32) -> Self {
516        self.with_processor(DualAxisExclusion::symmetric_all(threshold))
517    }
518
519    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
520    /// only passing positive values that greater than `positive_min` on both axes,
521    /// treating them as zeros.
522    ///
523    /// # Requirements
524    ///
525    /// - `positive_min` >= `0.0`.
526    ///
527    /// # Panics
528    ///
529    /// Panics if the requirements aren't met.
530    #[inline]
531    fn only_positive_unscaled(self, positive_min: f32) -> Self {
532        self.with_processor(DualAxisExclusion::only_positive_all(positive_min))
533    }
534
535    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
536    /// only passing negative values that less than `negative_max` on both axes,
537    /// treating them as zeros.
538    ///
539    /// # Requirements
540    ///
541    /// - `negative_max` <= `0.0`.
542    ///
543    /// # Panics
544    ///
545    /// Panics if the requirements aren't met.
546    #[inline]
547    fn only_negative_unscaled(self, negative_max: f32) -> Self {
548        self.with_processor(DualAxisExclusion::only_negative_all(negative_max))
549    }
550
551    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
552    /// only ignoring values within the range `[negative_max, positive_min]` on the X-axis,
553    /// treating them as zeros.
554    ///
555    /// # Requirements
556    ///
557    /// - `negative_max` <= `0.0` <= `positive_min`.
558    ///
559    /// # Panics
560    ///
561    /// Panics if the requirements aren't met.
562    #[inline]
563    fn with_deadzone_x_unscaled(self, negative_max: f32, positive_min: f32) -> Self {
564        self.with_processor(DualAxisExclusion::only_x(negative_max, positive_min))
565    }
566
567    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
568    /// only ignoring values within the range `[-threshold, threshold]` on the X-axis,
569    /// treating them as zeros.
570    ///
571    /// # Requirements
572    ///
573    /// - `threshold` >= `0.0`.
574    ///
575    /// # Panics
576    ///
577    /// Panics if the requirements aren't met.
578    #[inline]
579    fn with_deadzone_x_symmetric_unscaled(self, threshold: f32) -> Self {
580        self.with_processor(DualAxisExclusion::symmetric_only_x(threshold))
581    }
582
583    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
584    /// only excluding X values that less than or equal to `positive_min`,
585    /// treating them as zeros.
586    ///
587    /// # Requirements
588    ///
589    /// - `positive_min` >= `0.0`.
590    ///
591    /// # Panics
592    ///
593    /// Panics if the requirements aren't met.
594    #[inline]
595    fn only_positive_x_unscaled(self, positive_min: f32) -> Self {
596        self.with_processor(DualAxisExclusion::only_positive_x(positive_min))
597    }
598
599    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
600    /// only excluding X values that greater than or equal to `negative_max`,
601    /// treating them as zeros.
602    ///
603    /// # Requirements
604    ///
605    /// - `negative_max` <= `0.0`.
606    ///
607    /// # Panics
608    ///
609    /// Panics if the requirements aren't met.
610    #[inline]
611    fn only_negative_x_unscaled(self, negative_max: f32) -> Self {
612        self.with_processor(DualAxisExclusion::only_negative_x(negative_max))
613    }
614
615    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
616    /// only ignoring values within the range `[negative_max, positive_min]` on the Y-axis,
617    /// treating them as zeros.
618    ///
619    /// # Requirements
620    ///
621    /// - `negative_max` <= `0.0` <= `positive_min`.
622    ///
623    /// # Panics
624    ///
625    /// Panics if the requirements aren't met.
626    #[inline]
627    fn with_deadzone_y_unscaled(self, negative_max: f32, positive_min: f32) -> Self {
628        self.with_processor(DualAxisExclusion::only_y(negative_max, positive_min))
629    }
630
631    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
632    /// only ignoring values within the range `[-threshold, threshold]` on the Y-axis,
633    /// treating them as zeros.
634    ///
635    /// # Requirements
636    ///
637    /// - `threshold` >= `0.0`.
638    ///
639    /// # Panics
640    ///
641    /// Panics if the requirements aren't met.
642    #[inline]
643    fn with_deadzone_y_symmetric_unscaled(self, threshold: f32) -> Self {
644        self.with_processor(DualAxisExclusion::symmetric_only_y(threshold))
645    }
646
647    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
648    /// only excluding Y values that less than or equal to `positive_min`,
649    /// treating them as zeros.
650    ///
651    /// # Requirements
652    ///
653    /// - `positive_min` >= `0.0`.
654    ///
655    /// # Panics
656    ///
657    /// Panics if the requirements aren't met.
658    #[inline]
659    fn only_positive_y_unscaled(self, positive_min: f32) -> Self {
660        self.with_processor(DualAxisExclusion::only_positive_y(positive_min))
661    }
662
663    /// Appends a [`DualAxisExclusion`] processor as the next processing step,
664    /// only excluding Y values that greater than or equal to `negative_max`,
665    /// treating them as zeros.
666    ///
667    /// # Requirements
668    ///
669    /// - `negative_max` <= `0.0`.
670    ///
671    /// # Panics
672    ///
673    /// Panics if the requirements aren't met.
674    #[inline]
675    fn only_negative_y_unscaled(self, negative_max: f32) -> Self {
676        self.with_processor(DualAxisExclusion::only_negative_y(negative_max))
677    }
678
679    /// Appends a [`CircleExclusion`] processor as the next processing step,
680    /// ignoring values below a `min` magnitude, treating them as zeros.
681    ///
682    /// # Requirements
683    ///
684    /// - `min` >= `0.0`.
685    ///
686    /// # Panics
687    ///
688    /// Panics if the requirements aren't met.
689    #[inline]
690    fn with_circle_deadzone_unscaled(self, min: f32) -> Self {
691        self.with_processor(CircleExclusion::new(min))
692    }
693}
694
695/// Flips the sign of dual-axis input values, resulting in a directional reversal of control.
696///
697/// ```rust
698/// use bevy::prelude::*;
699/// use leafwing_input_manager::prelude::*;
700///
701/// let value = Vec2::new(1.5, 2.0);
702/// let Vec2 { x, y } = value;
703///
704/// assert_eq!(DualAxisInverted::ALL.invert(value), -value);
705/// assert_eq!(DualAxisInverted::ALL.invert(-value), value);
706///
707/// assert_eq!(DualAxisInverted::ONLY_X.invert(value), Vec2::new(-x, y));
708/// assert_eq!(DualAxisInverted::ONLY_X.invert(-value), Vec2::new(x, -y));
709///
710/// assert_eq!(DualAxisInverted::ONLY_Y.invert(value), Vec2::new(x, -y));
711/// assert_eq!(DualAxisInverted::ONLY_Y.invert(-value), Vec2::new(-x, y));
712#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
713#[must_use]
714pub struct DualAxisInverted(Vec2);
715
716impl DualAxisInverted {
717    /// The [`DualAxisInverted`] that inverts both axes.
718    pub const ALL: Self = Self(Vec2::NEG_ONE);
719
720    /// The [`DualAxisInverted`] that only inverts the X-axis inputs.
721    pub const ONLY_X: Self = Self(Vec2::new(-1.0, 1.0));
722
723    /// The [`DualAxisInverted`] that only inverts the Y-axis inputs.
724    pub const ONLY_Y: Self = Self(Vec2::new(1.0, -1.0));
725
726    /// Are inputs inverted on both axes?
727    #[must_use]
728    #[inline]
729    pub fn inverted(&self) -> BVec2 {
730        self.0.cmpeq(Vec2::NEG_ONE)
731    }
732
733    /// Multiples the `input_value` by the specified inversion vector.
734    #[must_use]
735    #[inline]
736    pub fn invert(&self, input_value: Vec2) -> Vec2 {
737        self.0 * input_value
738    }
739}
740
741impl From<DualAxisInverted> for DualAxisProcessor {
742    fn from(value: DualAxisInverted) -> Self {
743        Self::Inverted(value)
744    }
745}
746
747impl Eq for DualAxisInverted {}
748
749impl Hash for DualAxisInverted {
750    fn hash<H: Hasher>(&self, state: &mut H) {
751        FloatOrd(self.0.x).hash(state);
752        FloatOrd(self.0.y).hash(state);
753    }
754}
755
756/// Scales dual-axis input values using a specified multiplier to fine-tune the responsiveness of control.
757///
758/// ```rust
759/// use bevy::prelude::*;
760/// use leafwing_input_manager::prelude::*;
761///
762/// let value = Vec2::new(1.5, 2.5);
763/// let Vec2 { x, y } = value;
764///
765/// // Negated X and halved Y
766/// let neg_x_half_y = DualAxisSensitivity::new(-1.0, 0.5);
767/// assert_eq!(neg_x_half_y.scale(value).x, -x);
768/// assert_eq!(neg_x_half_y.scale(value).y, 0.5 * y);
769///
770/// // Doubled X and doubled Y
771/// let double = DualAxisSensitivity::all(2.0);
772/// assert_eq!(double.scale(value), 2.0 * value);
773///
774/// // Halved X
775/// let half_x = DualAxisSensitivity::only_x(0.5);
776/// assert_eq!(half_x.scale(value).x, 0.5 * x);
777/// assert_eq!(half_x.scale(value).y, y);
778///
779/// // Negated and doubled Y
780/// let neg_double_y = DualAxisSensitivity::only_y(-2.0);
781/// assert_eq!(neg_double_y.scale(value).x, x);
782/// assert_eq!(neg_double_y.scale(value).y, -2.0 * y);
783/// ```
784#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
785#[must_use]
786pub struct DualAxisSensitivity(pub(crate) Vec2);
787
788impl DualAxisSensitivity {
789    /// Creates a [`DualAxisSensitivity`] with the given values for each axis separately.
790    #[inline]
791    pub const fn new(sensitivity_x: f32, sensitivity_y: f32) -> Self {
792        Self(Vec2::new(sensitivity_x, sensitivity_y))
793    }
794
795    /// Creates a [`DualAxisSensitivity`] with the same value for both axes.
796    #[inline]
797    pub const fn all(sensitivity: f32) -> Self {
798        Self::new(sensitivity, sensitivity)
799    }
800
801    /// Creates a [`DualAxisSensitivity`] that only affects the X-axis using the given value.
802    #[inline]
803    pub const fn only_x(sensitivity: f32) -> Self {
804        Self::new(sensitivity, 1.0)
805    }
806
807    /// Creates a [`DualAxisSensitivity`] that only affects the Y-axis using the given value.
808    #[inline]
809    pub const fn only_y(sensitivity: f32) -> Self {
810        Self::new(1.0, sensitivity)
811    }
812
813    /// Returns the sensitivity values.
814    #[must_use]
815    #[inline]
816    pub fn sensitivities(&self) -> Vec2 {
817        self.0
818    }
819
820    /// Multiples the `input_value` by the specified sensitivity vector.
821    #[must_use]
822    #[inline]
823    pub fn scale(&self, input_value: Vec2) -> Vec2 {
824        self.0 * input_value
825    }
826}
827
828impl From<DualAxisSensitivity> for DualAxisProcessor {
829    fn from(value: DualAxisSensitivity) -> Self {
830        Self::Sensitivity(value)
831    }
832}
833
834impl Eq for DualAxisSensitivity {}
835
836impl Hash for DualAxisSensitivity {
837    fn hash<H: Hasher>(&self, state: &mut H) {
838        FloatOrd(self.0.x).hash(state);
839        FloatOrd(self.0.y).hash(state);
840    }
841}
842
843#[cfg(test)]
844mod tests {
845    use super::*;
846
847    #[test]
848    fn test_dual_axis_inverted() {
849        let all = DualAxisInverted::ALL;
850        assert_eq!(all.inverted(), BVec2::TRUE);
851
852        let only_x = DualAxisInverted::ONLY_X;
853        assert_eq!(only_x.inverted(), BVec2::new(true, false));
854
855        let only_y = DualAxisInverted::ONLY_Y;
856        assert_eq!(only_y.inverted(), BVec2::new(false, true));
857
858        for x in -300..300 {
859            let x = x as f32 * 0.01;
860
861            for y in -300..300 {
862                let y = y as f32 * 0.01;
863                let value = Vec2::new(x, y);
864
865                let processor = DualAxisProcessor::Inverted(all);
866                assert_eq!(DualAxisProcessor::from(all), processor);
867                assert_eq!(processor.process(value), all.invert(value));
868                assert_eq!(all.invert(value), -value);
869                assert_eq!(all.invert(-value), value);
870
871                let processor = DualAxisProcessor::Inverted(only_x);
872                assert_eq!(DualAxisProcessor::from(only_x), processor);
873                assert_eq!(processor.process(value), only_x.invert(value));
874                assert_eq!(only_x.invert(value), Vec2::new(-x, y));
875                assert_eq!(only_x.invert(-value), Vec2::new(x, -y));
876
877                let processor = DualAxisProcessor::Inverted(only_y);
878                assert_eq!(DualAxisProcessor::from(only_y), processor);
879                assert_eq!(processor.process(value), only_y.invert(value));
880                assert_eq!(only_y.invert(value), Vec2::new(x, -y));
881                assert_eq!(only_y.invert(-value), Vec2::new(-x, y));
882            }
883        }
884    }
885
886    #[test]
887    fn test_dual_axis_sensitivity() {
888        for x in -300..300 {
889            let x = x as f32 * 0.01;
890
891            for y in -300..300 {
892                let y = y as f32 * 0.01;
893                let value = Vec2::new(x, y);
894
895                let sensitivity = x;
896
897                let all = DualAxisSensitivity::all(sensitivity);
898                let processor = DualAxisProcessor::Sensitivity(all);
899                assert_eq!(DualAxisProcessor::from(all), processor);
900                assert_eq!(processor.process(value), all.scale(value));
901                assert_eq!(all.sensitivities(), Vec2::splat(sensitivity));
902                assert_eq!(all.scale(value), sensitivity * value);
903
904                let only_x = DualAxisSensitivity::only_x(sensitivity);
905                let processor = DualAxisProcessor::Sensitivity(only_x);
906                assert_eq!(DualAxisProcessor::from(only_x), processor);
907                assert_eq!(processor.process(value), only_x.scale(value));
908                assert_eq!(only_x.sensitivities(), Vec2::new(sensitivity, 1.0));
909                assert_eq!(only_x.scale(value).x, x * sensitivity);
910                assert_eq!(only_x.scale(value).y, y);
911
912                let only_y = DualAxisSensitivity::only_y(sensitivity);
913                let processor = DualAxisProcessor::Sensitivity(only_y);
914                assert_eq!(DualAxisProcessor::from(only_y), processor);
915                assert_eq!(processor.process(value), only_y.scale(value));
916                assert_eq!(only_y.sensitivities(), Vec2::new(1.0, sensitivity));
917                assert_eq!(only_y.scale(value).x, x);
918                assert_eq!(only_y.scale(value).y, y * sensitivity);
919
920                let sensitivity2 = y;
921                let separate = DualAxisSensitivity::new(sensitivity, sensitivity2);
922                let processor = DualAxisProcessor::Sensitivity(separate);
923                assert_eq!(DualAxisProcessor::from(separate), processor);
924                assert_eq!(processor.process(value), separate.scale(value));
925                assert_eq!(
926                    separate.sensitivities(),
927                    Vec2::new(sensitivity, sensitivity2)
928                );
929                assert_eq!(separate.scale(value).x, x * sensitivity);
930                assert_eq!(separate.scale(value).y, y * sensitivity2);
931            }
932        }
933    }
934}