use bevy::input::mouse::{MouseButtonInput, MouseMotion, MouseWheel};
use bevy::input::{ButtonInput, ButtonState};
use bevy::prelude::{
Entity, Events, Gamepad, MouseButton, Reflect, Res, ResMut, Resource, Vec2, World,
};
use leafwing_input_manager_macros::serde_typetag;
use serde::{Deserialize, Serialize};
use crate as leafwing_input_manager;
use crate::axislike::{DualAxisDirection, DualAxisType};
use crate::clashing_inputs::BasicInputs;
use crate::input_processing::*;
use crate::user_input::{InputControlKind, UserInput};
use super::updating::{CentralInputStore, UpdatableInput};
use super::{Axislike, Buttonlike, DualAxislike};
impl UserInput for MouseButton {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new(*self))
}
}
impl UpdatableInput for MouseButton {
type SourceData = ButtonInput<MouseButton>;
fn compute(
mut central_input_store: ResMut<CentralInputStore>,
source_data: Res<Self::SourceData>,
) {
for key in source_data.get_pressed() {
central_input_store.update_buttonlike(*key, true);
}
for key in source_data.get_just_released() {
central_input_store.update_buttonlike(*key, false);
}
}
}
#[serde_typetag]
impl Buttonlike for MouseButton {
#[inline]
fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool {
input_store.pressed(self)
}
fn press(&self, world: &mut World) {
let mut events = world.resource_mut::<Events<MouseButtonInput>>();
events.send(MouseButtonInput {
button: *self,
state: ButtonState::Pressed,
window: Entity::PLACEHOLDER,
});
}
fn release(&self, world: &mut World) {
let mut events = world.resource_mut::<Events<MouseButtonInput>>();
events.send(MouseButtonInput {
button: *self,
state: ButtonState::Released,
window: Entity::PLACEHOLDER,
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct MouseMoveDirection(pub DualAxisDirection);
impl MouseMoveDirection {
pub const UP: Self = Self(DualAxisDirection::Up);
pub const DOWN: Self = Self(DualAxisDirection::Down);
pub const LEFT: Self = Self(DualAxisDirection::Left);
pub const RIGHT: Self = Self(DualAxisDirection::Right);
}
impl UserInput for MouseMoveDirection {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new(*self))
}
}
#[serde_typetag]
impl Buttonlike for MouseMoveDirection {
#[must_use]
#[inline]
fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool {
let mouse_movement = input_store.pair(&MouseMove::default());
self.0.is_active(mouse_movement)
}
fn press(&self, world: &mut World) {
world
.resource_mut::<Events<MouseMotion>>()
.send(MouseMotion {
delta: self.0.full_active_value(),
});
}
fn release(&self, _world: &mut World) {}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct MouseMoveAxis {
pub axis: DualAxisType,
pub(crate) processors: Vec<AxisProcessor>,
}
impl MouseMoveAxis {
pub const X: Self = Self {
axis: DualAxisType::X,
processors: Vec::new(),
};
pub const Y: Self = Self {
axis: DualAxisType::Y,
processors: Vec::new(),
};
}
impl UserInput for MouseMoveAxis {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Axis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
Box::new(MouseMoveDirection(self.axis.negative())),
Box::new(MouseMoveDirection(self.axis.positive())),
])
}
}
#[serde_typetag]
impl Axislike for MouseMoveAxis {
#[must_use]
#[inline]
fn value(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> f32 {
let movement = input_store.pair(&MouseMove::default());
let value = self.axis.get_value(movement);
self.processors
.iter()
.fold(value, |value, processor| processor.process(value))
}
fn set_value(&self, world: &mut World, value: f32) {
let event = MouseMotion {
delta: match self.axis {
DualAxisType::X => Vec2::new(value, 0.0),
DualAxisType::Y => Vec2::new(0.0, value),
},
};
world.resource_mut::<Events<MouseMotion>>().send(event);
}
}
impl WithAxisProcessingPipelineExt for MouseMoveAxis {
#[inline]
fn reset_processing_pipeline(mut self) -> Self {
self.processors.clear();
self
}
#[inline]
fn replace_processing_pipeline(
mut self,
processors: impl IntoIterator<Item = AxisProcessor>,
) -> Self {
self.processors = processors.into_iter().collect();
self
}
#[inline]
fn with_processor(mut self, processor: impl Into<AxisProcessor>) -> Self {
self.processors.push(processor.into());
self
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct MouseMove {
pub(crate) processors: Vec<DualAxisProcessor>,
}
impl UpdatableInput for MouseMove {
type SourceData = AccumulatedMouseMovement;
fn compute(
mut central_input_store: ResMut<CentralInputStore>,
source_data: Res<Self::SourceData>,
) {
central_input_store.update_dualaxislike(Self::default(), source_data.0);
}
}
impl UserInput for MouseMove {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::DualAxis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
Box::new(MouseMoveDirection::UP),
Box::new(MouseMoveDirection::DOWN),
Box::new(MouseMoveDirection::LEFT),
Box::new(MouseMoveDirection::RIGHT),
])
}
}
#[serde_typetag]
impl DualAxislike for MouseMove {
#[must_use]
#[inline]
fn axis_pair(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> Vec2 {
let movement = input_store.pair(&MouseMove::default());
self.processors
.iter()
.fold(movement, |value, processor| processor.process(value))
}
fn set_axis_pair(&self, world: &mut World, value: Vec2) {
world
.resource_mut::<Events<MouseMotion>>()
.send(MouseMotion { delta: value });
}
}
impl WithDualAxisProcessingPipelineExt for MouseMove {
#[inline]
fn reset_processing_pipeline(mut self) -> Self {
self.processors.clear();
self
}
#[inline]
fn replace_processing_pipeline(
mut self,
processor: impl IntoIterator<Item = DualAxisProcessor>,
) -> Self {
self.processors = processor.into_iter().collect();
self
}
#[inline]
fn with_processor(mut self, processor: impl Into<DualAxisProcessor>) -> Self {
self.processors.push(processor.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct MouseScrollDirection(pub DualAxisDirection);
impl MouseScrollDirection {
pub const UP: Self = Self(DualAxisDirection::Up);
pub const DOWN: Self = Self(DualAxisDirection::Down);
pub const LEFT: Self = Self(DualAxisDirection::Left);
pub const RIGHT: Self = Self(DualAxisDirection::Right);
}
impl UserInput for MouseScrollDirection {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new(*self))
}
}
#[serde_typetag]
impl Buttonlike for MouseScrollDirection {
#[must_use]
#[inline]
fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool {
let movement = input_store.pair(&MouseScroll::default());
self.0.is_active(movement)
}
fn press(&self, world: &mut World) {
let vec = self.0.full_active_value();
world.resource_mut::<Events<MouseWheel>>().send(MouseWheel {
unit: bevy::input::mouse::MouseScrollUnit::Pixel,
x: vec.x,
y: vec.y,
window: Entity::PLACEHOLDER,
});
}
fn release(&self, _world: &mut World) {}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct MouseScrollAxis {
pub axis: DualAxisType,
pub(crate) processors: Vec<AxisProcessor>,
}
impl MouseScrollAxis {
pub const X: Self = Self {
axis: DualAxisType::X,
processors: Vec::new(),
};
pub const Y: Self = Self {
axis: DualAxisType::Y,
processors: Vec::new(),
};
}
impl UserInput for MouseScrollAxis {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Axis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
Box::new(MouseScrollDirection(self.axis.negative())),
Box::new(MouseScrollDirection(self.axis.positive())),
])
}
}
#[serde_typetag]
impl Axislike for MouseScrollAxis {
#[must_use]
#[inline]
fn value(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> f32 {
let movement = input_store.pair(&MouseScroll::default());
let value = self.axis.get_value(movement);
self.processors
.iter()
.fold(value, |value, processor| processor.process(value))
}
fn set_value(&self, world: &mut World, value: f32) {
let event = MouseWheel {
unit: bevy::input::mouse::MouseScrollUnit::Pixel,
x: if self.axis == DualAxisType::X {
value
} else {
0.0
},
y: if self.axis == DualAxisType::Y {
value
} else {
0.0
},
window: Entity::PLACEHOLDER,
};
world.resource_mut::<Events<MouseWheel>>().send(event);
}
}
impl WithAxisProcessingPipelineExt for MouseScrollAxis {
#[inline]
fn reset_processing_pipeline(mut self) -> Self {
self.processors.clear();
self
}
#[inline]
fn replace_processing_pipeline(
mut self,
processors: impl IntoIterator<Item = AxisProcessor>,
) -> Self {
self.processors = processors.into_iter().collect();
self
}
#[inline]
fn with_processor(mut self, processor: impl Into<AxisProcessor>) -> Self {
self.processors.push(processor.into());
self
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct MouseScroll {
pub(crate) processors: Vec<DualAxisProcessor>,
}
impl UpdatableInput for MouseScroll {
type SourceData = AccumulatedMouseScroll;
fn compute(
mut central_input_store: ResMut<CentralInputStore>,
source_data: Res<Self::SourceData>,
) {
central_input_store.update_dualaxislike(Self::default(), source_data.0);
}
}
impl UserInput for MouseScroll {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::DualAxis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
Box::new(MouseScrollDirection::UP),
Box::new(MouseScrollDirection::DOWN),
Box::new(MouseScrollDirection::LEFT),
Box::new(MouseScrollDirection::RIGHT),
])
}
}
#[serde_typetag]
impl DualAxislike for MouseScroll {
#[must_use]
#[inline]
fn axis_pair(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> Vec2 {
let movement = input_store.pair(&MouseScroll::default());
self.processors
.iter()
.fold(movement, |value, processor| processor.process(value))
}
fn set_axis_pair(&self, world: &mut World, value: Vec2) {
world.resource_mut::<Events<MouseWheel>>().send(MouseWheel {
unit: bevy::input::mouse::MouseScrollUnit::Pixel,
x: value.x,
y: value.y,
window: Entity::PLACEHOLDER,
});
}
}
impl WithDualAxisProcessingPipelineExt for MouseScroll {
#[inline]
fn reset_processing_pipeline(mut self) -> Self {
self.processors.clear();
self
}
#[inline]
fn replace_processing_pipeline(
mut self,
processors: impl IntoIterator<Item = DualAxisProcessor>,
) -> Self {
self.processors = processors.into_iter().collect();
self
}
#[inline]
fn with_processor(mut self, processor: impl Into<DualAxisProcessor>) -> Self {
self.processors.push(processor.into());
self
}
}
#[derive(Debug, Default, Resource, Reflect, Serialize, Deserialize, Clone, PartialEq)]
pub struct AccumulatedMouseMovement(pub Vec2);
impl AccumulatedMouseMovement {
#[inline]
pub fn reset(&mut self) {
self.0 = Vec2::ZERO;
}
#[inline]
pub fn accumulate(&mut self, event: &MouseMotion) {
self.0 += event.delta;
}
}
#[derive(Debug, Default, Resource, Reflect, Serialize, Deserialize, Clone, PartialEq)]
pub struct AccumulatedMouseScroll(pub Vec2);
impl AccumulatedMouseScroll {
#[inline]
pub fn reset(&mut self) {
self.0 = Vec2::ZERO;
}
#[inline]
pub fn accumulate(&mut self, event: &MouseWheel) {
self.0.x += event.x;
self.0.y += event.y;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::{AccumulatorPlugin, CentralInputStorePlugin};
use bevy::input::InputPlugin;
use bevy::prelude::*;
fn test_app() -> App {
let mut app = App::new();
app.add_plugins(InputPlugin)
.add_plugins((AccumulatorPlugin, CentralInputStorePlugin));
app
}
#[test]
fn test_mouse_button() {
let left = MouseButton::Left;
assert_eq!(left.kind(), InputControlKind::Button);
let middle = MouseButton::Middle;
assert_eq!(middle.kind(), InputControlKind::Button);
let right = MouseButton::Right;
assert_eq!(right.kind(), InputControlKind::Button);
let mut app = test_app();
app.update();
let inputs = app.world().resource::<CentralInputStore>();
let gamepad = Gamepad::new(0);
assert!(!left.pressed(inputs, gamepad));
assert!(!middle.pressed(inputs, gamepad));
assert!(!right.pressed(inputs, gamepad));
let mut app = test_app();
MouseButton::Left.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(left.pressed(inputs, gamepad));
assert!(!middle.pressed(inputs, gamepad));
assert!(!right.pressed(inputs, gamepad));
let mut app = test_app();
MouseButton::Middle.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!left.pressed(inputs, gamepad));
assert!(middle.pressed(inputs, gamepad));
assert!(!right.pressed(inputs, gamepad));
let mut app = test_app();
MouseButton::Right.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!left.pressed(inputs, gamepad));
assert!(!middle.pressed(inputs, gamepad));
assert!(right.pressed(inputs, gamepad));
}
#[test]
fn test_mouse_move() {
let mouse_move_up = MouseMoveDirection::UP;
assert_eq!(mouse_move_up.kind(), InputControlKind::Button);
let mouse_move_y = MouseMoveAxis::Y;
assert_eq!(mouse_move_y.kind(), InputControlKind::Axis);
let mouse_move = MouseMove::default();
assert_eq!(mouse_move.kind(), InputControlKind::DualAxis);
let mut app = test_app();
app.update();
let inputs = app.world().resource::<CentralInputStore>();
let gamepad = Gamepad::new(0);
assert!(!mouse_move_up.pressed(inputs, gamepad));
assert_eq!(mouse_move_y.value(inputs, gamepad), 0.0);
assert_eq!(mouse_move.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.0));
let data = Vec2::new(-1.0, 0.0);
let mut app = test_app();
MouseMoveDirection::LEFT.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!mouse_move_up.pressed(inputs, gamepad));
assert_eq!(mouse_move_y.value(inputs, gamepad), 0.0);
assert_eq!(mouse_move.axis_pair(inputs, gamepad), data);
let data = Vec2::new(0.0, 1.0);
let mut app = test_app();
MouseMoveDirection::UP.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(mouse_move_up.pressed(inputs, gamepad));
assert_eq!(mouse_move_y.value(inputs, gamepad), data.y);
assert_eq!(mouse_move.axis_pair(inputs, gamepad), data);
let data = Vec2::new(0.0, -1.0);
let mut app = test_app();
MouseMoveDirection::DOWN.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!mouse_move_up.pressed(inputs, gamepad));
assert_eq!(mouse_move_y.value(inputs, gamepad), data.y);
assert_eq!(mouse_move.axis_pair(inputs, gamepad), data);
let data = Vec2::new(0.0, 3.0);
let mut app = test_app();
MouseMoveAxis::Y.set_value(app.world_mut(), data.y);
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(mouse_move_up.pressed(inputs, gamepad));
assert_eq!(mouse_move_y.value(inputs, gamepad), data.y);
assert_eq!(mouse_move.axis_pair(inputs, gamepad), data);
let data = Vec2::new(2.0, 3.0);
let mut app = test_app();
MouseMove::default().set_axis_pair(app.world_mut(), data);
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(mouse_move_up.pressed(inputs, gamepad));
assert_eq!(mouse_move_y.value(inputs, gamepad), data.y);
assert_eq!(mouse_move.axis_pair(inputs, gamepad), data);
}
#[test]
fn test_mouse_scroll() {
let mouse_scroll_up = MouseScrollDirection::UP;
assert_eq!(mouse_scroll_up.kind(), InputControlKind::Button);
let mouse_scroll_y = MouseScrollAxis::Y;
assert_eq!(mouse_scroll_y.kind(), InputControlKind::Axis);
let mouse_scroll = MouseScroll::default();
assert_eq!(mouse_scroll.kind(), InputControlKind::DualAxis);
let mut app = test_app();
app.update();
let inputs = app.world().resource::<CentralInputStore>();
let gamepad = Gamepad::new(0);
assert!(!mouse_scroll_up.pressed(inputs, gamepad));
assert_eq!(mouse_scroll_y.value(inputs, gamepad), 0.0);
assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.0));
let data = Vec2::new(0.0, 1.0);
let mut app = test_app();
MouseScrollDirection::UP.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(mouse_scroll_up.pressed(inputs, gamepad));
assert_eq!(mouse_scroll_y.value(inputs, gamepad), data.y);
assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), data);
let data = Vec2::new(0.0, -1.0);
let mut app = test_app();
MouseScrollDirection::DOWN.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!mouse_scroll_up.pressed(inputs, gamepad));
assert_eq!(mouse_scroll_y.value(inputs, gamepad), data.y);
assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), data);
let data = Vec2::new(0.0, 3.0);
let mut app = test_app();
MouseScrollAxis::Y.set_value(app.world_mut(), data.y);
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(mouse_scroll_up.pressed(inputs, gamepad));
assert_eq!(mouse_scroll_y.value(inputs, gamepad), data.y);
assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), data);
let data = Vec2::new(2.0, 3.0);
let mut app = test_app();
MouseScroll::default().set_axis_pair(app.world_mut(), data);
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(mouse_scroll_up.pressed(inputs, gamepad));
assert_eq!(mouse_scroll_y.value(inputs, gamepad), data.y);
assert_eq!(mouse_scroll.axis_pair(inputs, gamepad), data);
}
#[test]
fn one_frame_accumulate_mouse_movement() {
let mut app = test_app();
MouseMoveAxis::Y.set_value(app.world_mut(), 3.0);
MouseMoveAxis::Y.set_value(app.world_mut(), 2.0);
let mouse_motion_events = app.world().get_resource::<Events<MouseMotion>>().unwrap();
for event in mouse_motion_events.iter_current_update_events() {
dbg!("Event sent: {:?}", event);
}
let accumulated_mouse_movement = app.world().resource::<AccumulatedMouseMovement>();
assert_eq!(accumulated_mouse_movement.0, Vec2::new(0.0, 0.0));
app.update();
let accumulated_mouse_movement = app.world().resource::<AccumulatedMouseMovement>();
assert_eq!(accumulated_mouse_movement.0, Vec2::new(0.0, 5.0));
let inputs = app.world().resource::<CentralInputStore>();
assert_eq!(inputs.pair(&MouseMove::default()), Vec2::new(0.0, 5.0));
}
#[test]
fn multiple_frames_accumulate_mouse_movement() {
let mut app = test_app();
let inputs = app.world().resource::<CentralInputStore>();
assert_eq!(
inputs.pair(&MouseMove::default()),
Vec2::ZERO,
"Initial movement is not zero."
);
MouseMoveAxis::Y.set_value(app.world_mut(), 3.0);
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert_eq!(
inputs.pair(&MouseMove::default()),
Vec2::new(0.0, 3.0),
"Movement sent was not read correctly."
);
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert_eq!(
inputs.pair(&MouseMove::default()),
Vec2::ZERO,
"No movement was expected. Is the position in the event stream being cleared properly?"
);
}
}