leafwing_input_manager/user_input/
mouse.rs

1//! Mouse inputs
2
3use crate as leafwing_input_manager;
4use crate::axislike::{DualAxisDirection, DualAxisType};
5use crate::buttonlike::ButtonValue;
6use crate::clashing_inputs::BasicInputs;
7use crate::input_processing::*;
8use crate::user_input::{InputControlKind, UserInput};
9use bevy::ecs::system::lifetimeless::SRes;
10use bevy::ecs::system::StaticSystemParam;
11use bevy::input::mouse::{
12    AccumulatedMouseMotion, AccumulatedMouseScroll, MouseButton, MouseButtonInput, MouseMotion,
13    MouseWheel,
14};
15use bevy::input::{ButtonInput, ButtonState};
16use bevy::math::FloatOrd;
17use bevy::prelude::{Entity, Events, Reflect, ResMut, Vec2, World};
18use leafwing_input_manager_macros::serde_typetag;
19use serde::{Deserialize, Serialize};
20use std::hash::{Hash, Hasher};
21
22use super::updating::{CentralInputStore, UpdatableInput};
23use super::{Axislike, Buttonlike, DualAxislike};
24
25// Built-in support for Bevy's MouseButton
26impl UserInput for MouseButton {
27    /// [`MouseButton`] acts as a button.
28    #[inline]
29    fn kind(&self) -> InputControlKind {
30        InputControlKind::Button
31    }
32
33    /// Returns a [`BasicInputs`] that only contains the [`MouseButton`] itself,
34    /// as it represents a simple physical button.
35    #[inline]
36    fn decompose(&self) -> BasicInputs {
37        BasicInputs::Simple(Box::new(*self))
38    }
39}
40
41impl UpdatableInput for MouseButton {
42    type SourceData = SRes<ButtonInput<MouseButton>>;
43
44    fn compute(
45        mut central_input_store: ResMut<CentralInputStore>,
46        source_data: StaticSystemParam<Self::SourceData>,
47    ) {
48        for button in source_data.get_pressed() {
49            central_input_store.update_buttonlike(*button, ButtonValue::from_pressed(true));
50        }
51
52        for button in source_data.get_just_released() {
53            central_input_store.update_buttonlike(*button, ButtonValue::from_pressed(false));
54        }
55    }
56}
57
58#[serde_typetag]
59impl Buttonlike for MouseButton {
60    /// Checks if the specified button is currently pressed down.
61    #[inline]
62    fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool {
63        input_store.pressed(self)
64    }
65
66    /// Sends a fake [`MouseButtonInput`] event to the world with [`ButtonState::Pressed`].
67    ///
68    /// # Note
69    ///
70    /// The `window` field will be filled with a placeholder value.
71    fn press(&self, world: &mut World) {
72        let mut events = world.resource_mut::<Events<MouseButtonInput>>();
73        events.send(MouseButtonInput {
74            button: *self,
75            state: ButtonState::Pressed,
76            window: Entity::PLACEHOLDER,
77        });
78    }
79
80    /// Sends a fake [`MouseButtonInput`] event to the world with [`ButtonState::Released`].
81    ///
82    /// # Note
83    ///
84    /// The `window` field will be filled with a placeholder value.
85    fn release(&self, world: &mut World) {
86        let mut events = world.resource_mut::<Events<MouseButtonInput>>();
87        events.send(MouseButtonInput {
88            button: *self,
89            state: ButtonState::Released,
90            window: Entity::PLACEHOLDER,
91        });
92    }
93
94    /// If the value is greater than `0.0`, press the key; otherwise release it.
95    fn set_value(&self, world: &mut World, value: f32) {
96        if value > 0.0 {
97            self.press(world);
98        } else {
99            self.release(world);
100        }
101    }
102}
103
104/// Provides button-like behavior for mouse movement in cardinal directions.
105///
106/// ```rust
107/// use bevy::prelude::*;
108/// use bevy::input::InputPlugin;
109/// use leafwing_input_manager::plugin::CentralInputStorePlugin;
110/// use leafwing_input_manager::prelude::*;
111/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput;
112///
113/// let mut app = App::new();
114/// app.add_plugins((InputPlugin, CentralInputStorePlugin));
115///
116/// // Positive Y-axis movement
117/// let input = MouseMoveDirection::UP;
118///
119/// // Movement in the opposite direction doesn't activate the input
120/// MouseMoveAxis::Y.set_value(app.world_mut(), -5.0);
121/// app.update();
122/// assert!(!app.read_pressed(input));
123///
124/// // Movement in the chosen direction activates the input
125/// MouseMoveAxis::Y.set_value(app.world_mut(), 5.0);
126/// app.update();
127/// assert!(app.read_pressed(input));
128/// ```
129#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
130#[must_use]
131pub struct MouseMoveDirection {
132    /// The direction to monitor (up, down, left, or right).
133    pub direction: DualAxisDirection,
134
135    /// The threshold value for the direction to be considered pressed.
136    /// Must be non-negative.
137    pub threshold: f32,
138}
139
140impl MouseMoveDirection {
141    /// Sets the `threshold` value.
142    ///
143    /// # Requirements
144    ///
145    /// - `threshold` >= `0.0`.
146    ///
147    /// # Panics
148    ///
149    /// Panics if the requirement isn't met.
150    #[inline]
151    pub fn threshold(mut self, threshold: f32) -> Self {
152        assert!(threshold >= 0.0);
153        self.threshold = threshold;
154        self
155    }
156
157    /// Movement in the upward direction.
158    pub const UP: Self = Self {
159        direction: DualAxisDirection::Up,
160        threshold: 0.0,
161    };
162
163    /// Movement in the downward direction.
164    pub const DOWN: Self = Self {
165        direction: DualAxisDirection::Down,
166        threshold: 0.0,
167    };
168
169    /// Movement in the leftward direction.
170    pub const LEFT: Self = Self {
171        direction: DualAxisDirection::Left,
172        threshold: 0.0,
173    };
174
175    /// Movement in the rightward direction.
176    pub const RIGHT: Self = Self {
177        direction: DualAxisDirection::Right,
178        threshold: 0.0,
179    };
180}
181
182impl UserInput for MouseMoveDirection {
183    /// [`MouseMoveDirection`] acts as a virtual button.
184    #[inline]
185    fn kind(&self) -> InputControlKind {
186        InputControlKind::Button
187    }
188
189    /// [`MouseMoveDirection`] represents a simple virtual button.
190    #[inline]
191    fn decompose(&self) -> BasicInputs {
192        BasicInputs::Simple(Box::new((*self).threshold(0.0)))
193    }
194}
195
196#[serde_typetag]
197impl Buttonlike for MouseMoveDirection {
198    /// Checks if there is any recent mouse movement along the specified direction.
199    #[inline]
200    fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool {
201        let mouse_movement = input_store.pair(&MouseMove::default());
202        self.direction.is_active(mouse_movement, self.threshold)
203    }
204
205    /// Sends a [`MouseMotion`] event with a magnitude of 1.0 in the direction defined by `self`.
206    fn press(&self, world: &mut World) {
207        world
208            .resource_mut::<Events<MouseMotion>>()
209            .send(MouseMotion {
210                delta: self.direction.full_active_value(),
211            });
212    }
213
214    /// This method has no effect.
215    ///
216    /// As mouse movement directions are determined based on the recent change in mouse position,
217    /// no action other than waiting for the next frame is necessary to release the input.
218    fn release(&self, _world: &mut World) {}
219}
220
221impl Eq for MouseMoveDirection {}
222
223impl Hash for MouseMoveDirection {
224    fn hash<H: Hasher>(&self, state: &mut H) {
225        self.direction.hash(state);
226        FloatOrd(self.threshold).hash(state);
227    }
228}
229
230/// Relative changes in position of mouse movement on a single axis (X or Y).
231///
232/// # Value Processing
233///
234/// You can customize how the values are processed using a pipeline of processors.
235/// See [`WithAxisProcessingPipelineExt`] for details.
236///
237/// ```rust
238/// use bevy::prelude::*;
239/// use bevy::input::InputPlugin;
240/// use leafwing_input_manager::plugin::CentralInputStorePlugin;
241/// use leafwing_input_manager::prelude::*;
242/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput;
243///
244/// let mut app = App::new();
245/// app.add_plugins((InputPlugin, CentralInputStorePlugin));
246///
247/// // Y-axis movement
248/// let input = MouseMoveAxis::Y;
249///
250/// // Movement on the chosen axis activates the input
251/// MouseMoveAxis::Y.set_value(app.world_mut(), 1.0);
252/// app.update();
253/// assert_eq!(app.read_axis_value(input), 1.0);
254///
255/// // You can configure a processing pipeline (e.g., doubling the value)
256/// let doubled = MouseMoveAxis::Y.sensitivity(2.0);
257/// assert_eq!(app.read_axis_value(doubled), 2.0);
258/// ```
259#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
260#[must_use]
261pub struct MouseMoveAxis {
262    /// The specified axis that this input tracks.
263    pub axis: DualAxisType,
264
265    /// A processing pipeline that handles input values.
266    pub processors: Vec<AxisProcessor>,
267}
268
269impl MouseMoveAxis {
270    /// Movement on the X-axis. No processing is applied to raw data from the mouse.
271    pub const X: Self = Self {
272        axis: DualAxisType::X,
273        processors: Vec::new(),
274    };
275
276    /// Movement on the Y-axis. No processing is applied to raw data from the mouse.
277    pub const Y: Self = Self {
278        axis: DualAxisType::Y,
279        processors: Vec::new(),
280    };
281}
282
283impl UserInput for MouseMoveAxis {
284    /// [`MouseMoveAxis`] acts as an axis input.
285    #[inline]
286    fn kind(&self) -> InputControlKind {
287        InputControlKind::Axis
288    }
289
290    /// [`MouseMoveAxis`] represents a composition of two [`MouseMoveDirection`]s.
291    #[inline]
292    fn decompose(&self) -> BasicInputs {
293        BasicInputs::Composite(vec![
294            Box::new(MouseMoveDirection {
295                direction: self.axis.negative(),
296                threshold: 0.0,
297            }),
298            Box::new(MouseMoveDirection {
299                direction: self.axis.positive(),
300                threshold: 0.0,
301            }),
302        ])
303    }
304}
305
306#[serde_typetag]
307impl Axislike for MouseMoveAxis {
308    /// Retrieves the amount of the mouse movement along the specified axis
309    /// after processing by the associated processors.
310    #[inline]
311    fn value(&self, input_store: &CentralInputStore, _gamepad: Entity) -> f32 {
312        let movement = input_store.pair(&MouseMove::default());
313        let value = self.axis.get_value(movement);
314        self.processors
315            .iter()
316            .fold(value, |value, processor| processor.process(value))
317    }
318
319    /// Sends a [`MouseMotion`] event along the appropriate axis with the specified value.
320    fn set_value(&self, world: &mut World, value: f32) {
321        let event = MouseMotion {
322            delta: match self.axis {
323                DualAxisType::X => Vec2::new(value, 0.0),
324                DualAxisType::Y => Vec2::new(0.0, value),
325            },
326        };
327        world.resource_mut::<Events<MouseMotion>>().send(event);
328    }
329}
330
331impl WithAxisProcessingPipelineExt for MouseMoveAxis {
332    #[inline]
333    fn reset_processing_pipeline(mut self) -> Self {
334        self.processors.clear();
335        self
336    }
337
338    #[inline]
339    fn replace_processing_pipeline(
340        mut self,
341        processors: impl IntoIterator<Item = AxisProcessor>,
342    ) -> Self {
343        self.processors = processors.into_iter().collect();
344        self
345    }
346
347    #[inline]
348    fn with_processor(mut self, processor: impl Into<AxisProcessor>) -> Self {
349        self.processors.push(processor.into());
350        self
351    }
352}
353
354/// Relative changes in position of mouse movement on both axes.
355///
356/// # Value Processing
357///
358/// You can customize how the values are processed using a pipeline of processors.
359/// See [`WithDualAxisProcessingPipelineExt`] for details.
360///
361/// ```rust
362/// use bevy::prelude::*;
363/// use bevy::input::InputPlugin;
364/// use leafwing_input_manager::plugin::CentralInputStorePlugin;
365/// use leafwing_input_manager::prelude::*;
366/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput;
367///
368/// let mut app = App::new();
369/// app.add_plugins((InputPlugin, CentralInputStorePlugin));
370///
371/// let input = MouseMove::default();
372///
373/// // Movement on either axis activates the input
374/// MouseMoveAxis::Y.set_value(app.world_mut(), 3.0);
375/// app.update();
376/// assert_eq!(app.read_dual_axis_values(input), Vec2::new(0.0, 3.0));
377///
378/// // You can configure a processing pipeline (e.g., doubling the Y value)
379/// let doubled = MouseMove::default().sensitivity_y(2.0);
380/// assert_eq!(app.read_dual_axis_values(doubled), Vec2::new(0.0, 6.0));
381/// ```
382#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
383#[must_use]
384pub struct MouseMove {
385    /// A processing pipeline that handles input values.
386    pub processors: Vec<DualAxisProcessor>,
387}
388
389impl UpdatableInput for MouseMove {
390    type SourceData = SRes<AccumulatedMouseMotion>;
391
392    fn compute(
393        mut central_input_store: ResMut<CentralInputStore>,
394        source_data: StaticSystemParam<Self::SourceData>,
395    ) {
396        central_input_store.update_dualaxislike(Self::default(), source_data.delta);
397    }
398}
399
400impl UserInput for MouseMove {
401    /// [`MouseMove`] acts as a dual-axis input.
402    #[inline]
403    fn kind(&self) -> InputControlKind {
404        InputControlKind::DualAxis
405    }
406
407    /// [`MouseMove`] represents a composition of four [`MouseMoveDirection`]s.
408    #[inline]
409    fn decompose(&self) -> BasicInputs {
410        BasicInputs::Composite(vec![
411            Box::new(MouseMoveDirection::UP),
412            Box::new(MouseMoveDirection::DOWN),
413            Box::new(MouseMoveDirection::LEFT),
414            Box::new(MouseMoveDirection::RIGHT),
415        ])
416    }
417}
418
419#[serde_typetag]
420impl DualAxislike for MouseMove {
421    /// Retrieves the mouse displacement after processing by the associated processors.
422    #[inline]
423    fn axis_pair(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Vec2 {
424        let movement = input_store.pair(&MouseMove::default());
425        self.processors
426            .iter()
427            .fold(movement, |value, processor| processor.process(value))
428    }
429
430    /// Sends a [`MouseMotion`] event with the specified displacement.
431    fn set_axis_pair(&self, world: &mut World, value: Vec2) {
432        world
433            .resource_mut::<Events<MouseMotion>>()
434            .send(MouseMotion { delta: value });
435    }
436}
437
438impl WithDualAxisProcessingPipelineExt for MouseMove {
439    #[inline]
440    fn reset_processing_pipeline(mut self) -> Self {
441        self.processors.clear();
442        self
443    }
444
445    #[inline]
446    fn replace_processing_pipeline(
447        mut self,
448        processor: impl IntoIterator<Item = DualAxisProcessor>,
449    ) -> Self {
450        self.processors = processor.into_iter().collect();
451        self
452    }
453
454    #[inline]
455    fn with_processor(mut self, processor: impl Into<DualAxisProcessor>) -> Self {
456        self.processors.push(processor.into());
457        self
458    }
459}
460
461/// Provides button-like behavior for mouse wheel scrolling in cardinal directions.
462///
463/// ```rust
464/// use bevy::prelude::*;
465/// use bevy::input::InputPlugin;
466/// use leafwing_input_manager::plugin::CentralInputStorePlugin;
467/// use leafwing_input_manager::prelude::*;
468/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput;
469///
470/// let mut app = App::new();
471/// app.add_plugins((InputPlugin, CentralInputStorePlugin));
472///
473/// // Positive Y-axis scrolling
474/// let input = MouseScrollDirection::UP;
475///
476/// // Scrolling in the opposite direction doesn't activate the input
477/// MouseScrollAxis::Y.set_value(app.world_mut(), -5.0);
478/// app.update();
479/// assert!(!app.read_pressed(input));
480///
481/// // Scrolling in the chosen direction activates the input
482/// MouseScrollAxis::Y.set_value(app.world_mut(), 5.0);
483/// app.update();
484/// assert!(app.read_pressed(input));
485/// ```
486#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
487#[must_use]
488pub struct MouseScrollDirection {
489    /// The direction to monitor (up, down, left, or right).
490    pub direction: DualAxisDirection,
491
492    /// The threshold value for the direction to be considered pressed.
493    /// Must be non-negative.
494    pub threshold: f32,
495}
496
497impl MouseScrollDirection {
498    /// Sets the `threshold` value.
499    ///
500    /// # Requirements
501    ///
502    /// - `threshold` >= `0.0`.
503    ///
504    /// # Panics
505    ///
506    /// Panics if the requirement isn't met.
507    #[inline]
508    pub fn threshold(mut self, threshold: f32) -> Self {
509        assert!(threshold >= 0.0);
510        self.threshold = threshold;
511        self
512    }
513
514    /// Movement in the upward direction.
515    pub const UP: Self = Self {
516        direction: DualAxisDirection::Up,
517        threshold: 0.0,
518    };
519
520    /// Movement in the downward direction.
521    pub const DOWN: Self = Self {
522        direction: DualAxisDirection::Down,
523        threshold: 0.0,
524    };
525
526    /// Movement in the leftward direction.
527    pub const LEFT: Self = Self {
528        direction: DualAxisDirection::Left,
529        threshold: 0.0,
530    };
531
532    /// Movement in the rightward direction.
533    pub const RIGHT: Self = Self {
534        direction: DualAxisDirection::Right,
535        threshold: 0.0,
536    };
537}
538
539impl UserInput for MouseScrollDirection {
540    /// [`MouseScrollDirection`] acts as a virtual button.
541    #[inline]
542    fn kind(&self) -> InputControlKind {
543        InputControlKind::Button
544    }
545
546    /// [`MouseScrollDirection`] represents a simple virtual button.
547    #[inline]
548    fn decompose(&self) -> BasicInputs {
549        BasicInputs::Simple(Box::new((*self).threshold(0.0)))
550    }
551}
552
553#[serde_typetag]
554impl Buttonlike for MouseScrollDirection {
555    /// Checks if there is any recent mouse wheel movement along the specified direction.
556    #[inline]
557    fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool {
558        let movement = input_store.pair(&MouseScroll::default());
559        self.direction.is_active(movement, self.threshold)
560    }
561
562    /// Sends a [`MouseWheel`] event with a magnitude of 1.0 px in the direction defined by `self`.
563    ///
564    /// # Note
565    ///
566    /// The `window` field will be filled with a placeholder value.
567    fn press(&self, world: &mut World) {
568        let vec = self.direction.full_active_value();
569
570        world.resource_mut::<Events<MouseWheel>>().send(MouseWheel {
571            unit: bevy::input::mouse::MouseScrollUnit::Pixel,
572            x: vec.x,
573            y: vec.y,
574            window: Entity::PLACEHOLDER,
575        });
576    }
577
578    /// This method has no effect.
579    ///
580    /// As mouse scroll directions are determined based on the recent change in mouse scrolling,
581    /// no action other than waiting for the next frame is necessary to release the input.
582    fn release(&self, _world: &mut World) {}
583}
584
585impl Eq for MouseScrollDirection {}
586
587impl Hash for MouseScrollDirection {
588    fn hash<H: Hasher>(&self, state: &mut H) {
589        self.direction.hash(state);
590        FloatOrd(self.threshold).hash(state);
591    }
592}
593
594/// Amount of mouse wheel scrolling on a single axis (X or Y).
595///
596/// # Value Processing
597///
598/// You can customize how the values are processed using a pipeline of processors.
599/// See [`WithAxisProcessingPipelineExt`] for details.
600///
601/// ```rust
602/// use bevy::prelude::*;
603/// use bevy::input::InputPlugin;
604/// use leafwing_input_manager::plugin::CentralInputStorePlugin;
605/// use leafwing_input_manager::prelude::*;
606/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput;
607///
608/// let mut app = App::new();
609/// app.add_plugins((InputPlugin, CentralInputStorePlugin));
610///
611/// // Y-axis movement
612/// let input = MouseScrollAxis::Y;
613///
614/// // Scrolling on the chosen axis activates the input
615/// MouseScrollAxis::Y.set_value(app.world_mut(), 1.0);
616/// app.update();
617/// assert_eq!(app.read_axis_value(input), 1.0);
618///
619/// // You can configure a processing pipeline (e.g., doubling the value)
620/// let doubled = MouseScrollAxis::Y.sensitivity(2.0);
621/// assert_eq!(app.read_axis_value(doubled), 2.0);
622/// ```
623#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
624#[must_use]
625pub struct MouseScrollAxis {
626    /// The axis that this input tracks.
627    pub axis: DualAxisType,
628
629    /// A processing pipeline that handles input values.
630    pub processors: Vec<AxisProcessor>,
631}
632
633impl MouseScrollAxis {
634    /// Horizontal scrolling of the mouse wheel. No processing is applied to raw data from the mouse.
635    pub const X: Self = Self {
636        axis: DualAxisType::X,
637        processors: Vec::new(),
638    };
639
640    /// Vertical scrolling of the mouse wheel. No processing is applied to raw data from the mouse.
641    pub const Y: Self = Self {
642        axis: DualAxisType::Y,
643        processors: Vec::new(),
644    };
645}
646
647impl UserInput for MouseScrollAxis {
648    /// [`MouseScrollAxis`] acts as an axis input.
649    #[inline]
650    fn kind(&self) -> InputControlKind {
651        InputControlKind::Axis
652    }
653
654    /// [`MouseScrollAxis`] represents a composition of two [`MouseScrollDirection`]s.
655    #[inline]
656    fn decompose(&self) -> BasicInputs {
657        BasicInputs::Composite(vec![
658            Box::new(MouseScrollDirection {
659                direction: self.axis.negative(),
660                threshold: 0.0,
661            }),
662            Box::new(MouseScrollDirection {
663                direction: self.axis.positive(),
664                threshold: 0.0,
665            }),
666        ])
667    }
668}
669
670#[serde_typetag]
671impl Axislike for MouseScrollAxis {
672    /// Retrieves the amount of the mouse wheel movement along the specified axis
673    /// after processing by the associated processors.
674    #[inline]
675    fn value(&self, input_store: &CentralInputStore, _gamepad: Entity) -> f32 {
676        let movement = input_store.pair(&MouseScroll::default());
677        let value = self.axis.get_value(movement);
678        self.processors
679            .iter()
680            .fold(value, |value, processor| processor.process(value))
681    }
682
683    /// Sends a [`MouseWheel`] event along the appropriate axis with the specified value in pixels.
684    ///
685    /// # Note
686    ///
687    /// The `window` field will be filled with a placeholder value.
688    fn set_value(&self, world: &mut World, value: f32) {
689        let event = MouseWheel {
690            unit: bevy::input::mouse::MouseScrollUnit::Pixel,
691            x: if self.axis == DualAxisType::X {
692                value
693            } else {
694                0.0
695            },
696            y: if self.axis == DualAxisType::Y {
697                value
698            } else {
699                0.0
700            },
701            window: Entity::PLACEHOLDER,
702        };
703        world.resource_mut::<Events<MouseWheel>>().send(event);
704    }
705}
706
707impl WithAxisProcessingPipelineExt for MouseScrollAxis {
708    #[inline]
709    fn reset_processing_pipeline(mut self) -> Self {
710        self.processors.clear();
711        self
712    }
713
714    #[inline]
715    fn replace_processing_pipeline(
716        mut self,
717        processors: impl IntoIterator<Item = AxisProcessor>,
718    ) -> Self {
719        self.processors = processors.into_iter().collect();
720        self
721    }
722
723    #[inline]
724    fn with_processor(mut self, processor: impl Into<AxisProcessor>) -> Self {
725        self.processors.push(processor.into());
726        self
727    }
728}
729
730/// Amount of mouse wheel scrolling on both axes.
731///
732/// # Value Processing
733///
734/// You can customize how the values are processed using a pipeline of processors.
735/// See [`WithDualAxisProcessingPipelineExt`] for details.
736///
737/// ```rust
738/// use bevy::prelude::*;
739/// use bevy::input::InputPlugin;
740/// use leafwing_input_manager::plugin::CentralInputStorePlugin;
741/// use leafwing_input_manager::prelude::*;
742/// use leafwing_input_manager::user_input::testing_utils::FetchUserInput;
743///
744/// let mut app = App::new();
745/// app.add_plugins((InputPlugin, CentralInputStorePlugin));
746///
747/// let input = MouseScroll::default();
748///
749/// // Scrolling on either axis activates the input
750/// MouseScrollAxis::Y.set_value(app.world_mut(), 3.0);
751/// app.update();
752/// assert_eq!(app.read_dual_axis_values(input), Vec2::new(0.0, 3.0));
753///
754/// // You can configure a processing pipeline (e.g., doubling the Y value)
755/// let doubled = MouseScroll::default().sensitivity_y(2.0);
756/// assert_eq!(app.read_dual_axis_values(doubled), Vec2::new(0.0, 6.0));
757/// ```
758#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
759#[must_use]
760pub struct MouseScroll {
761    /// A processing pipeline that handles input values.
762    pub processors: Vec<DualAxisProcessor>,
763}
764
765impl UpdatableInput for MouseScroll {
766    type SourceData = SRes<AccumulatedMouseScroll>;
767
768    fn compute(
769        mut central_input_store: ResMut<CentralInputStore>,
770        source_data: StaticSystemParam<Self::SourceData>,
771    ) {
772        central_input_store.update_dualaxislike(Self::default(), source_data.delta);
773    }
774}
775
776impl UserInput for MouseScroll {
777    /// [`MouseScroll`] acts as an axis input.
778    #[inline]
779    fn kind(&self) -> InputControlKind {
780        InputControlKind::DualAxis
781    }
782
783    /// [`MouseScroll`] represents a composition of four [`MouseScrollDirection`]s.
784    #[inline]
785    fn decompose(&self) -> BasicInputs {
786        BasicInputs::Composite(vec![
787            Box::new(MouseScrollDirection::UP),
788            Box::new(MouseScrollDirection::DOWN),
789            Box::new(MouseScrollDirection::LEFT),
790            Box::new(MouseScrollDirection::RIGHT),
791        ])
792    }
793}
794
795#[serde_typetag]
796impl DualAxislike for MouseScroll {
797    /// Retrieves the mouse scroll movement on both axes after processing by the associated processors.
798    #[inline]
799    fn axis_pair(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Vec2 {
800        let movement = input_store.pair(&MouseScroll::default());
801        self.processors
802            .iter()
803            .fold(movement, |value, processor| processor.process(value))
804    }
805
806    /// Sends a [`MouseWheel`] event with the specified displacement in pixels.
807    ///
808    /// # Note
809    /// The `window` field will be filled with a placeholder value.
810    fn set_axis_pair(&self, world: &mut World, value: Vec2) {
811        world.resource_mut::<Events<MouseWheel>>().send(MouseWheel {
812            unit: bevy::input::mouse::MouseScrollUnit::Pixel,
813            x: value.x,
814            y: value.y,
815            window: Entity::PLACEHOLDER,
816        });
817    }
818}
819
820impl WithDualAxisProcessingPipelineExt for MouseScroll {
821    #[inline]
822    fn reset_processing_pipeline(mut self) -> Self {
823        self.processors.clear();
824        self
825    }
826
827    #[inline]
828    fn replace_processing_pipeline(
829        mut self,
830        processors: impl IntoIterator<Item = DualAxisProcessor>,
831    ) -> Self {
832        self.processors = processors.into_iter().collect();
833        self
834    }
835
836    #[inline]
837    fn with_processor(mut self, processor: impl Into<DualAxisProcessor>) -> Self {
838        self.processors.push(processor.into());
839        self
840    }
841}
842
843#[cfg(test)]
844mod tests {
845    use super::*;
846    use crate::plugin::CentralInputStorePlugin;
847    use bevy::input::InputPlugin;
848    use bevy::prelude::*;
849
850    fn test_app() -> App {
851        let mut app = App::new();
852        app.add_plugins(InputPlugin)
853            .add_plugins(CentralInputStorePlugin);
854        app
855    }
856
857    #[test]
858    fn test_mouse_button() {
859        let left = MouseButton::Left;
860        assert_eq!(left.kind(), InputControlKind::Button);
861
862        let middle = MouseButton::Middle;
863        assert_eq!(middle.kind(), InputControlKind::Button);
864
865        let right = MouseButton::Right;
866        assert_eq!(right.kind(), InputControlKind::Button);
867
868        // No inputs
869        let mut app = test_app();
870        app.update();
871        let gamepad = app.world_mut().spawn(()).id();
872        let inputs = app.world().resource::<CentralInputStore>();
873
874        assert!(!left.pressed(inputs, gamepad));
875        assert!(!middle.pressed(inputs, gamepad));
876        assert!(!right.pressed(inputs, gamepad));
877
878        // Press left
879        let mut app = test_app();
880        MouseButton::Left.press(app.world_mut());
881        app.update();
882        let inputs = app.world().resource::<CentralInputStore>();
883
884        assert!(left.pressed(inputs, gamepad));
885        assert!(!middle.pressed(inputs, gamepad));
886        assert!(!right.pressed(inputs, gamepad));
887
888        // Press middle
889        let mut app = test_app();
890        MouseButton::Middle.press(app.world_mut());
891        app.update();
892        let inputs = app.world().resource::<CentralInputStore>();
893
894        assert!(!left.pressed(inputs, gamepad));
895        assert!(middle.pressed(inputs, gamepad));
896        assert!(!right.pressed(inputs, gamepad));
897
898        // Press right
899        let mut app = test_app();
900        MouseButton::Right.press(app.world_mut());
901        app.update();
902        let inputs = app.world().resource::<CentralInputStore>();
903
904        assert!(!left.pressed(inputs, gamepad));
905        assert!(!middle.pressed(inputs, gamepad));
906        assert!(right.pressed(inputs, gamepad));
907    }
908
909    #[test]
910    fn test_mouse_move() {
911        let mouse_move_up = MouseMoveDirection::UP;
912        assert_eq!(mouse_move_up.kind(), InputControlKind::Button);
913
914        let mouse_move_y = MouseMoveAxis::Y;
915        assert_eq!(mouse_move_y.kind(), InputControlKind::Axis);
916
917        let mouse_move = MouseMove::default();
918        assert_eq!(mouse_move.kind(), InputControlKind::DualAxis);
919
920        // No inputs
921        let mut app = test_app();
922        app.update();
923        let gamepad = app.world_mut().spawn(()).id();
924        let inputs = app.world().resource::<CentralInputStore>();
925
926        assert!(!mouse_move_up.pressed(inputs, gamepad));
927        assert_eq!(mouse_move_y.value(inputs, gamepad), 0.0);
928        assert_eq!(mouse_move.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.0));
929
930        // Move left
931        let data = Vec2::new(-1.0, 0.0);
932        let mut app = test_app();
933        MouseMoveDirection::LEFT.press(app.world_mut());
934        app.update();
935        let inputs = app.world().resource::<CentralInputStore>();
936
937        assert!(!mouse_move_up.pressed(inputs, gamepad));
938        assert_eq!(mouse_move_y.value(inputs, gamepad), 0.0);
939        assert_eq!(mouse_move.axis_pair(inputs, gamepad), data);
940
941        // Move up
942        let data = Vec2::new(0.0, 1.0);
943        let mut app = test_app();
944        MouseMoveDirection::UP.press(app.world_mut());
945        app.update();
946        let inputs = app.world().resource::<CentralInputStore>();
947
948        assert!(mouse_move_up.pressed(inputs, gamepad));
949        assert_eq!(mouse_move_y.value(inputs, gamepad), data.y);
950        assert_eq!(mouse_move.axis_pair(inputs, gamepad), data);
951
952        // Move down
953        let data = Vec2::new(0.0, -1.0);
954        let mut app = test_app();
955        MouseMoveDirection::DOWN.press(app.world_mut());
956        app.update();
957        let inputs = app.world().resource::<CentralInputStore>();
958
959        assert!(!mouse_move_up.pressed(inputs, gamepad));
960        assert_eq!(mouse_move_y.value(inputs, gamepad), data.y);
961        assert_eq!(mouse_move.axis_pair(inputs, gamepad), data);
962
963        // Set changes in movement on the Y-axis to 3.0
964        let data = Vec2::new(0.0, 3.0);
965        let mut app = test_app();
966        MouseMoveAxis::Y.set_value(app.world_mut(), data.y);
967        app.update();
968        let inputs = app.world().resource::<CentralInputStore>();
969
970        assert!(mouse_move_up.pressed(inputs, gamepad));
971        assert_eq!(mouse_move_y.value(inputs, gamepad), data.y);
972        assert_eq!(mouse_move.axis_pair(inputs, gamepad), data);
973
974        // Set changes in movement to (2.0, 3.0)
975        let data = Vec2::new(2.0, 3.0);
976        let mut app = test_app();
977        MouseMove::default().set_axis_pair(app.world_mut(), data);
978        app.update();
979        let inputs = app.world().resource::<CentralInputStore>();
980
981        assert!(mouse_move_up.pressed(inputs, gamepad));
982        assert_eq!(mouse_move_y.value(inputs, gamepad), data.y);
983        assert_eq!(mouse_move.axis_pair(inputs, gamepad), data);
984    }
985
986    #[test]
987    fn test_mouse_scroll() {
988        let mouse_scroll_up = MouseScrollDirection::UP;
989        assert_eq!(mouse_scroll_up.kind(), InputControlKind::Button);
990
991        let mouse_scroll_y = MouseScrollAxis::Y;
992        assert_eq!(mouse_scroll_y.kind(), InputControlKind::Axis);
993        let mouse_scroll = MouseScroll::default();
994        assert_eq!(mouse_scroll.kind(), InputControlKind::DualAxis);
995
996        // No inputs
997        let mut app = test_app();
998        app.update();
999        let gamepad = app.world_mut().spawn(()).id();
1000        let inputs = app.world().resource::<CentralInputStore>();
1001
1002        assert!(!mouse_scroll_up.pressed(inputs, gamepad));
1003        assert_eq!(mouse_scroll_y.value(inputs, gamepad), 0.0);
1004        assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.0));
1005
1006        // Move up
1007        let data = Vec2::new(0.0, 1.0);
1008        let mut app = test_app();
1009        MouseScrollDirection::UP.press(app.world_mut());
1010        app.update();
1011        let inputs = app.world().resource::<CentralInputStore>();
1012
1013        assert!(mouse_scroll_up.pressed(inputs, gamepad));
1014        assert_eq!(mouse_scroll_y.value(inputs, gamepad), data.y);
1015        assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), data);
1016
1017        // Scroll down
1018        let data = Vec2::new(0.0, -1.0);
1019        let mut app = test_app();
1020        MouseScrollDirection::DOWN.press(app.world_mut());
1021        app.update();
1022        let inputs = app.world().resource::<CentralInputStore>();
1023
1024        assert!(!mouse_scroll_up.pressed(inputs, gamepad));
1025        assert_eq!(mouse_scroll_y.value(inputs, gamepad), data.y);
1026        assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), data);
1027
1028        // Set changes in scrolling on the Y-axis to 3.0
1029        let data = Vec2::new(0.0, 3.0);
1030        let mut app = test_app();
1031        MouseScrollAxis::Y.set_value(app.world_mut(), data.y);
1032        app.update();
1033        let inputs = app.world().resource::<CentralInputStore>();
1034
1035        assert!(mouse_scroll_up.pressed(inputs, gamepad));
1036        assert_eq!(mouse_scroll_y.value(inputs, gamepad), data.y);
1037        assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), data);
1038
1039        // Set changes in scrolling to (2.0, 3.0)
1040        let data = Vec2::new(2.0, 3.0);
1041        let mut app = test_app();
1042        MouseScroll::default().set_axis_pair(app.world_mut(), data);
1043        app.update();
1044        let inputs = app.world().resource::<CentralInputStore>();
1045
1046        assert!(mouse_scroll_up.pressed(inputs, gamepad));
1047        assert_eq!(mouse_scroll_y.value(inputs, gamepad), data.y);
1048        assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), data);
1049    }
1050
1051    #[test]
1052    fn one_frame_accumulate_mouse_movement() {
1053        let mut app = test_app();
1054        MouseMoveAxis::Y.set_value(app.world_mut(), 3.0);
1055        MouseMoveAxis::Y.set_value(app.world_mut(), 2.0);
1056
1057        let mouse_motion_events = app.world().get_resource::<Events<MouseMotion>>().unwrap();
1058        for event in mouse_motion_events.iter_current_update_events() {
1059            dbg!("Event sent: {:?}", event);
1060        }
1061
1062        // The haven't been processed yet
1063        let accumulated_mouse_movement = app.world().resource::<AccumulatedMouseMotion>();
1064        assert_eq!(accumulated_mouse_movement.delta, Vec2::new(0.0, 0.0));
1065
1066        app.update();
1067
1068        // Now the events should be processed
1069        let accumulated_mouse_movement = app.world().resource::<AccumulatedMouseMotion>();
1070        assert_eq!(accumulated_mouse_movement.delta, Vec2::new(0.0, 5.0));
1071
1072        let inputs = app.world().resource::<CentralInputStore>();
1073        assert_eq!(inputs.pair(&MouseMove::default()), Vec2::new(0.0, 5.0));
1074    }
1075
1076    #[test]
1077    fn multiple_frames_accumulate_mouse_movement() {
1078        let mut app = test_app();
1079        let inputs = app.world().resource::<CentralInputStore>();
1080        // Starts at 0
1081        assert_eq!(
1082            inputs.pair(&MouseMove::default()),
1083            Vec2::ZERO,
1084            "Initial movement is not zero."
1085        );
1086
1087        // Send some data
1088        MouseMoveAxis::Y.set_value(app.world_mut(), 3.0);
1089        // Wait for the events to be processed
1090        app.update();
1091
1092        let inputs = app.world().resource::<CentralInputStore>();
1093        // Data is read
1094        assert_eq!(
1095            inputs.pair(&MouseMove::default()),
1096            Vec2::new(0.0, 3.0),
1097            "Movement sent was not read correctly."
1098        );
1099
1100        // Do nothing
1101        app.update();
1102        let inputs = app.world().resource::<CentralInputStore>();
1103        // Back to 0 for this frame
1104        assert_eq!(
1105            inputs.pair(&MouseMove::default()),
1106            Vec2::ZERO,
1107            "No movement was expected. Is the position in the event stream being cleared properly?"
1108        );
1109    }
1110}