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