diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index e5d4765a57..4607ad9b71 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -43,7 +43,11 @@ use bevy_ecs::prelude::*; use bevy_reflect::Reflect; use gestures::*; use keyboard::{keyboard_input_system, KeyCode, KeyboardFocusLost, KeyboardInput}; -use mouse::{mouse_button_input_system, MouseButton, MouseButtonInput, MouseMotion, MouseWheel}; +use mouse::{ + accumulate_mouse_motion_system, accumulate_mouse_scroll_system, mouse_button_input_system, + AccumulatedMouseMotion, AccumulatedMouseScroll, MouseButton, MouseButtonInput, MouseMotion, + MouseWheel, +}; use touch::{touch_screen_input_system, TouchInput, Touches}; use gamepad::{ @@ -77,7 +81,15 @@ impl Plugin for InputPlugin { .add_event::() .add_event::() .init_resource::>() - .add_systems(PreUpdate, mouse_button_input_system.in_set(InputSystem)) + .add_systems( + PreUpdate, + ( + mouse_button_input_system, + accumulate_mouse_motion_system, + accumulate_mouse_scroll_system, + ) + .in_set(InputSystem), + ) .add_event::() .add_event::() .add_event::() @@ -94,6 +106,8 @@ impl Plugin for InputPlugin { .init_resource::>() .init_resource::>() .init_resource::>() + .init_resource::() + .init_resource::() .add_systems( PreUpdate, ( @@ -124,7 +138,9 @@ impl Plugin for InputPlugin { .register_type::() .register_type::() .register_type::() - .register_type::(); + .register_type::() + .register_type::() + .register_type::(); } } diff --git a/crates/bevy_input/src/mouse.rs b/crates/bevy_input/src/mouse.rs index 2e9b98602c..22464396f7 100644 --- a/crates/bevy_input/src/mouse.rs +++ b/crates/bevy_input/src/mouse.rs @@ -2,13 +2,15 @@ use crate::{ButtonInput, ButtonState}; use bevy_ecs::entity::Entity; +use bevy_ecs::reflect::ReflectResource; +use bevy_ecs::system::Resource; use bevy_ecs::{ change_detection::DetectChangesMut, event::{Event, EventReader}, system::ResMut, }; use bevy_math::Vec2; -use bevy_reflect::Reflect; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; #[cfg(feature = "serialize")] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; @@ -155,3 +157,81 @@ pub fn mouse_button_input_system( } } } + +/// Tracks how much the mouse has moved every frame. +/// +/// This resource is reset to zero every frame. +/// +/// This resource sums the total [`MouseMotion`] events received this frame. +#[derive(Resource, Debug, Clone, Copy, PartialEq, Reflect, Default)] +#[reflect(Debug, Default, Resource, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct AccumulatedMouseMotion { + /// The change in mouse position. + pub delta: Vec2, +} + +/// Tracks how much the mouse has scrolled every frame. +/// +/// This resource is reset to zero every frame. +/// +/// This resource sums the total [`MouseWheel`] events received this frame. +#[derive(Resource, Debug, Clone, Copy, PartialEq, Reflect)] +#[reflect(Debug, Default, Resource, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct AccumulatedMouseScroll { + /// The mouse scroll unit. + /// If this value changes while scrolling, then the + /// result of the accumulation could be incorrect + pub unit: MouseScrollUnit, + /// The change in scroll position. + pub delta: Vec2, +} + +impl Default for AccumulatedMouseScroll { + fn default() -> Self { + Self { + unit: MouseScrollUnit::Line, + delta: Vec2::ZERO, + } + } +} + +/// Updates the [`AccumulatedMouseMotion`] resource using the [`MouseMotion`] event. +/// The value of [`AccumulatedMouseMotion`] is reset to zero every frame +pub fn accumulate_mouse_motion_system( + mut mouse_motion_event: EventReader, + mut accumulated_mouse_motion: ResMut, +) { + let mut delta = Vec2::ZERO; + for event in mouse_motion_event.read() { + delta += event.delta; + } + accumulated_mouse_motion.delta = delta; +} + +/// Updates the [`AccumulatedMouseScroll`] resource using the [`MouseWheel`] event. +/// The value of [`AccumulatedMouseScroll`] is reset to zero every frame +pub fn accumulate_mouse_scroll_system( + mut mouse_scroll_event: EventReader, + mut accumulated_mouse_scroll: ResMut, +) { + let mut delta = Vec2::ZERO; + let mut unit = MouseScrollUnit::Line; + for event in mouse_scroll_event.read() { + if event.unit != unit { + unit = event.unit; + } + delta += Vec2::new(event.x, event.y); + } + accumulated_mouse_scroll.delta = delta; + accumulated_mouse_scroll.unit = unit; +} diff --git a/examples/input/mouse_input.rs b/examples/input/mouse_input.rs index 6dce16eda1..a57b85c67a 100644 --- a/examples/input/mouse_input.rs +++ b/examples/input/mouse_input.rs @@ -1,11 +1,14 @@ //! Prints mouse button events. -use bevy::prelude::*; +use bevy::{ + input::mouse::{AccumulatedMouseMotion, AccumulatedMouseScroll}, + prelude::*, +}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_systems(Update, mouse_click_system) + .add_systems(Update, (mouse_click_system, mouse_move_system)) .run(); } @@ -23,3 +26,18 @@ fn mouse_click_system(mouse_button_input: Res>) { info!("left mouse just released"); } } + +// This system prints messages when you finish dragging or scrolling with your mouse +fn mouse_move_system( + accumulated_mouse_motion: Res, + accumulated_mouse_scroll: Res, +) { + if accumulated_mouse_motion.delta != Vec2::ZERO { + let delta = accumulated_mouse_motion.delta; + info!("mouse moved ({}, {})", delta.x, delta.y); + } + if accumulated_mouse_scroll.delta != Vec2::ZERO { + let delta = accumulated_mouse_scroll.delta; + info!("mouse scrolled ({}, {})", delta.x, delta.y); + } +}