use bevy_app::prelude::*;
use bevy_camera::RenderTarget;
use bevy_ecs::prelude::*;
use bevy_input::{
mouse::MouseWheel,
prelude::*,
touch::{TouchInput, TouchPhase},
ButtonState,
};
use bevy_math::Vec2;
use bevy_platform::collections::{HashMap, HashSet};
use bevy_reflect::prelude::*;
use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
use tracing::debug;
use crate::pointer::{
Location, PointerAction, PointerButton, PointerId, PointerInput, PointerLocation,
};
use crate::PickingSystems;
pub mod prelude {
pub use crate::input::PointerInputPlugin;
}
#[derive(Copy, Clone, Resource, Debug, Reflect)]
#[reflect(Resource, Default, Clone)]
pub struct PointerInputSettings {
pub is_touch_enabled: bool,
pub is_mouse_enabled: bool,
}
impl PointerInputSettings {
fn is_mouse_enabled(state: Res<Self>) -> bool {
state.is_mouse_enabled
}
fn is_touch_enabled(state: Res<Self>) -> bool {
state.is_touch_enabled
}
}
impl Default for PointerInputSettings {
fn default() -> Self {
Self {
is_touch_enabled: true,
is_mouse_enabled: true,
}
}
}
pub struct PointerInputPlugin;
impl Plugin for PointerInputPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<PointerInputSettings>()
.add_systems(Startup, spawn_mouse_pointer)
.add_systems(
First,
(
mouse_pick_events.run_if(PointerInputSettings::is_mouse_enabled),
touch_pick_events.run_if(PointerInputSettings::is_touch_enabled),
)
.chain()
.in_set(PickingSystems::Input),
)
.add_systems(
Last,
deactivate_touch_pointers.run_if(PointerInputSettings::is_touch_enabled),
);
}
}
pub fn spawn_mouse_pointer(mut commands: Commands) {
commands.spawn(PointerId::Mouse);
}
pub fn mouse_pick_events(
mut window_events: EventReader<WindowEvent>,
primary_window: Query<Entity, With<PrimaryWindow>>,
mut cursor_last: Local<Vec2>,
mut pointer_events: EventWriter<PointerInput>,
) {
for window_event in window_events.read() {
match window_event {
WindowEvent::CursorMoved(event) => {
let location = Location {
target: match RenderTarget::Window(WindowRef::Entity(event.window))
.normalize(primary_window.single().ok())
{
Some(target) => target,
None => continue,
},
position: event.position,
};
pointer_events.write(PointerInput::new(
PointerId::Mouse,
location,
PointerAction::Move {
delta: event.position - *cursor_last,
},
));
*cursor_last = event.position;
}
WindowEvent::MouseButtonInput(input) => {
let location = Location {
target: match RenderTarget::Window(WindowRef::Entity(input.window))
.normalize(primary_window.single().ok())
{
Some(target) => target,
None => continue,
},
position: *cursor_last,
};
let button = match input.button {
MouseButton::Left => PointerButton::Primary,
MouseButton::Right => PointerButton::Secondary,
MouseButton::Middle => PointerButton::Middle,
MouseButton::Other(_) | MouseButton::Back | MouseButton::Forward => continue,
};
let action = match input.state {
ButtonState::Pressed => PointerAction::Press(button),
ButtonState::Released => PointerAction::Release(button),
};
pointer_events.write(PointerInput::new(PointerId::Mouse, location, action));
}
WindowEvent::MouseWheel(event) => {
let MouseWheel { unit, x, y, window } = *event;
let location = Location {
target: match RenderTarget::Window(WindowRef::Entity(window))
.normalize(primary_window.single().ok())
{
Some(target) => target,
None => continue,
},
position: *cursor_last,
};
let action = PointerAction::Scroll { x, y, unit };
pointer_events.write(PointerInput::new(PointerId::Mouse, location, action));
}
_ => {}
}
}
}
pub fn touch_pick_events(
mut window_events: EventReader<WindowEvent>,
primary_window: Query<Entity, With<PrimaryWindow>>,
mut touch_cache: Local<HashMap<u64, TouchInput>>,
mut commands: Commands,
mut pointer_events: EventWriter<PointerInput>,
) {
for window_event in window_events.read() {
if let WindowEvent::TouchInput(touch) = window_event {
let pointer = PointerId::Touch(touch.id);
let location = Location {
target: match RenderTarget::Window(WindowRef::Entity(touch.window))
.normalize(primary_window.single().ok())
{
Some(target) => target,
None => continue,
},
position: touch.position,
};
match touch.phase {
TouchPhase::Started => {
debug!("Spawning pointer {:?}", pointer);
commands.spawn((pointer, PointerLocation::new(location.clone())));
pointer_events.write(PointerInput::new(
pointer,
location,
PointerAction::Press(PointerButton::Primary),
));
touch_cache.insert(touch.id, *touch);
}
TouchPhase::Moved => {
if let Some(last_touch) = touch_cache.get(&touch.id) {
if last_touch == touch {
continue;
}
pointer_events.write(PointerInput::new(
pointer,
location,
PointerAction::Move {
delta: touch.position - last_touch.position,
},
));
}
touch_cache.insert(touch.id, *touch);
}
TouchPhase::Ended => {
pointer_events.write(PointerInput::new(
pointer,
location,
PointerAction::Release(PointerButton::Primary),
));
touch_cache.remove(&touch.id);
}
TouchPhase::Canceled => {
pointer_events.write(PointerInput::new(
pointer,
location,
PointerAction::Cancel,
));
touch_cache.remove(&touch.id);
}
}
}
}
}
pub fn deactivate_touch_pointers(
mut commands: Commands,
mut despawn_list: Local<HashSet<(Entity, PointerId)>>,
pointers: Query<(Entity, &PointerId)>,
mut touches: EventReader<TouchInput>,
) {
for touch in touches.read() {
if let TouchPhase::Ended | TouchPhase::Canceled = touch.phase {
for (entity, pointer) in &pointers {
if pointer.get_touch_id() == Some(touch.id) {
despawn_list.insert((entity, *pointer));
}
}
}
}
for (entity, pointer) in despawn_list.drain() {
debug!("Despawning pointer {:?}", pointer);
commands.entity(entity).despawn();
}
}