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}