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