Skip to main content

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