use crate as leafwing_input_manager;
use crate::axislike::{DualAxisDirection, DualAxisType};
use crate::buttonlike::ButtonValue;
use crate::clashing_inputs::BasicInputs;
use crate::input_processing::*;
use crate::user_input::{InputControlKind, UserInput};
use bevy::ecs::message::Messages;
use bevy::ecs::system::StaticSystemParam;
use bevy::ecs::system::lifetimeless::SRes;
use bevy::input::mouse::{
AccumulatedMouseMotion, AccumulatedMouseScroll, MouseButton, MouseButtonInput, MouseMotion,
MouseWheel,
};
use bevy::input::{ButtonInput, ButtonState};
use bevy::math::FloatOrd;
use bevy::prelude::{Entity, Reflect, ResMut, Vec2, World};
use leafwing_input_manager_macros::serde_typetag;
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
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 = SRes<ButtonInput<MouseButton>>;
fn compute(
mut central_input_store: ResMut<CentralInputStore>,
source_data: StaticSystemParam<Self::SourceData>,
) {
for button in source_data.get_pressed() {
central_input_store.update_buttonlike(*button, ButtonValue::from_pressed(true));
}
for button in source_data.get_just_released() {
central_input_store.update_buttonlike(*button, ButtonValue::from_pressed(false));
}
}
}
#[serde_typetag]
impl Buttonlike for MouseButton {
#[inline]
fn get_pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Option<bool> {
input_store.pressed(self)
}
fn press(&self, world: &mut World) {
let mut messages = world.resource_mut::<Messages<MouseButtonInput>>();
messages.write(MouseButtonInput {
button: *self,
state: ButtonState::Pressed,
window: Entity::PLACEHOLDER,
});
}
fn release(&self, world: &mut World) {
let mut messages = world.resource_mut::<Messages<MouseButtonInput>>();
messages.write(MouseButtonInput {
button: *self,
state: ButtonState::Released,
window: Entity::PLACEHOLDER,
});
}
fn set_value(&self, world: &mut World, value: f32) {
if value > 0.0 {
self.press(world);
} else {
self.release(world);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct MouseMoveDirection {
pub direction: DualAxisDirection,
pub threshold: f32,
}
impl MouseMoveDirection {
#[inline]
pub fn threshold(mut self, threshold: f32) -> Self {
assert!(threshold >= 0.0);
self.threshold = threshold;
self
}
pub const UP: Self = Self {
direction: DualAxisDirection::Up,
threshold: 0.0,
};
pub const DOWN: Self = Self {
direction: DualAxisDirection::Down,
threshold: 0.0,
};
pub const LEFT: Self = Self {
direction: DualAxisDirection::Left,
threshold: 0.0,
};
pub const RIGHT: Self = Self {
direction: DualAxisDirection::Right,
threshold: 0.0,
};
}
impl UserInput for MouseMoveDirection {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new((*self).threshold(0.0)))
}
}
#[serde_typetag]
impl Buttonlike for MouseMoveDirection {
#[inline]
fn get_pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Option<bool> {
let mouse_movement = input_store.pair(&MouseMove::default());
mouse_movement
.map(|mouse_movement| self.direction.is_active(mouse_movement, self.threshold))
}
fn press(&self, world: &mut World) {
world
.resource_mut::<Messages<MouseMotion>>()
.write(MouseMotion {
delta: self.direction.full_active_value(),
});
}
fn release(&self, _world: &mut World) {}
}
impl Eq for MouseMoveDirection {}
impl Hash for MouseMoveDirection {
fn hash<H: Hasher>(&self, state: &mut H) {
self.direction.hash(state);
FloatOrd(self.threshold).hash(state);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct MouseMoveAxis {
pub axis: DualAxisType,
pub 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 {
direction: self.axis.negative(),
threshold: 0.0,
}),
Box::new(MouseMoveDirection {
direction: self.axis.positive(),
threshold: 0.0,
}),
])
}
}
#[serde_typetag]
impl Axislike for MouseMoveAxis {
#[inline]
fn get_value(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Option<f32> {
input_store.pair(&MouseMove::default()).map(|movement| {
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 message = MouseMotion {
delta: match self.axis {
DualAxisType::X => Vec2::new(value, 0.0),
DualAxisType::Y => Vec2::new(0.0, value),
},
};
world.resource_mut::<Messages<MouseMotion>>().write(message);
}
}
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 processors: Vec<DualAxisProcessor>,
}
impl UpdatableInput for MouseMove {
type SourceData = SRes<AccumulatedMouseMotion>;
fn compute(
mut central_input_store: ResMut<CentralInputStore>,
source_data: StaticSystemParam<Self::SourceData>,
) {
central_input_store.update_dualaxislike(Self::default(), source_data.delta);
}
}
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 {
#[inline]
fn get_axis_pair(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Option<Vec2> {
input_store.pair(&MouseMove::default()).map(|movement| {
self.processors
.iter()
.fold(movement, |value, processor| processor.process(value))
})
}
fn set_axis_pair(&self, world: &mut World, value: Vec2) {
world
.resource_mut::<Messages<MouseMotion>>()
.write(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, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct MouseScrollDirection {
pub direction: DualAxisDirection,
pub threshold: f32,
}
impl MouseScrollDirection {
#[inline]
pub fn threshold(mut self, threshold: f32) -> Self {
assert!(threshold >= 0.0);
self.threshold = threshold;
self
}
pub const UP: Self = Self {
direction: DualAxisDirection::Up,
threshold: 0.0,
};
pub const DOWN: Self = Self {
direction: DualAxisDirection::Down,
threshold: 0.0,
};
pub const LEFT: Self = Self {
direction: DualAxisDirection::Left,
threshold: 0.0,
};
pub const RIGHT: Self = Self {
direction: DualAxisDirection::Right,
threshold: 0.0,
};
}
impl UserInput for MouseScrollDirection {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new((*self).threshold(0.0)))
}
}
#[serde_typetag]
impl Buttonlike for MouseScrollDirection {
#[inline]
fn get_pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Option<bool> {
input_store
.pair(&MouseScroll::default())
.map(|movement| self.direction.is_active(movement, self.threshold))
}
fn press(&self, world: &mut World) {
let vec = self.direction.full_active_value();
world
.resource_mut::<Messages<MouseWheel>>()
.write(MouseWheel {
unit: bevy::input::mouse::MouseScrollUnit::Pixel,
x: vec.x,
y: vec.y,
window: Entity::PLACEHOLDER,
});
}
fn release(&self, _world: &mut World) {}
}
impl Eq for MouseScrollDirection {}
impl Hash for MouseScrollDirection {
fn hash<H: Hasher>(&self, state: &mut H) {
self.direction.hash(state);
FloatOrd(self.threshold).hash(state);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct MouseScrollAxis {
pub axis: DualAxisType,
pub 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 {
direction: self.axis.negative(),
threshold: 0.0,
}),
Box::new(MouseScrollDirection {
direction: self.axis.positive(),
threshold: 0.0,
}),
])
}
}
#[serde_typetag]
impl Axislike for MouseScrollAxis {
#[inline]
fn get_value(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Option<f32> {
input_store.pair(&MouseScroll::default()).map(|movement| {
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 message = 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::<Messages<MouseWheel>>().write(message);
}
}
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 processors: Vec<DualAxisProcessor>,
}
impl UpdatableInput for MouseScroll {
type SourceData = SRes<AccumulatedMouseScroll>;
fn compute(
mut central_input_store: ResMut<CentralInputStore>,
source_data: StaticSystemParam<Self::SourceData>,
) {
central_input_store.update_dualaxislike(Self::default(), source_data.delta);
}
}
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 {
#[inline]
fn get_axis_pair(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Option<Vec2> {
input_store.pair(&MouseScroll::default()).map(|movement| {
self.processors
.iter()
.fold(movement, |value, processor| processor.process(value))
})
}
fn set_axis_pair(&self, world: &mut World, value: Vec2) {
world
.resource_mut::<Messages<MouseWheel>>()
.write(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
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::CentralInputStorePlugin;
use bevy::input::InputPlugin;
use bevy::prelude::*;
fn test_app() -> App {
let mut app = App::new();
app.add_plugins(InputPlugin)
.add_plugins(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 gamepad = app.world_mut().spawn(()).id();
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::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 gamepad = app.world_mut().spawn(()).id();
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), 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 gamepad = app.world_mut().spawn(()).id();
let inputs = app.world().resource::<CentralInputStore>();
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_messages = app.world().get_resource::<Messages<MouseMotion>>().unwrap();
for message in mouse_motion_messages.iter_current_update_messages() {
dbg!("Message sent: {:?}", message);
}
let accumulated_mouse_movement = app.world().resource::<AccumulatedMouseMotion>();
assert_eq!(accumulated_mouse_movement.delta, Vec2::new(0.0, 0.0));
app.update();
let accumulated_mouse_movement = app.world().resource::<AccumulatedMouseMotion>();
assert_eq!(accumulated_mouse_movement.delta, Vec2::new(0.0, 5.0));
let inputs = app.world().resource::<CentralInputStore>();
assert_eq!(
inputs.pair(&MouseMove::default()).unwrap(),
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()),
None,
"Initial movement is not None."
);
MouseMoveAxis::Y.set_value(app.world_mut(), 3.0);
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert_eq!(
inputs.pair(&MouseMove::default()).unwrap(),
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()).unwrap(),
Vec2::ZERO,
"No movement was expected. Is the position in the message stream being cleared properly?"
);
}
}