use bevy_ecs::{
entity::Entity,
event::{BufferedEvent, EventReader},
resource::Resource,
system::ResMut,
};
use bevy_math::Vec2;
use bevy_platform::collections::HashMap;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct TouchInput {
pub phase: TouchPhase,
pub position: Vec2,
pub window: Entity,
pub force: Option<ForceTouch>,
pub id: u64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum ForceTouch {
Calibrated {
force: f64,
max_possible_force: f64,
altitude_angle: Option<f64>,
},
Normalized(f64),
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, Hash, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum TouchPhase {
Started,
Moved,
Ended,
Canceled,
}
#[derive(Debug, Clone, Copy)]
pub struct Touch {
id: u64,
start_position: Vec2,
start_force: Option<ForceTouch>,
previous_position: Vec2,
previous_force: Option<ForceTouch>,
position: Vec2,
force: Option<ForceTouch>,
}
impl Touch {
pub fn delta(&self) -> Vec2 {
self.position - self.previous_position
}
pub fn distance(&self) -> Vec2 {
self.position - self.start_position
}
#[inline]
pub fn id(&self) -> u64 {
self.id
}
#[inline]
pub fn start_position(&self) -> Vec2 {
self.start_position
}
#[inline]
pub fn start_force(&self) -> Option<ForceTouch> {
self.start_force
}
#[inline]
pub fn previous_position(&self) -> Vec2 {
self.previous_position
}
#[inline]
pub fn previous_force(&self) -> Option<ForceTouch> {
self.previous_force
}
#[inline]
pub fn position(&self) -> Vec2 {
self.position
}
#[inline]
pub fn force(&self) -> Option<ForceTouch> {
self.force
}
}
impl From<&TouchInput> for Touch {
fn from(input: &TouchInput) -> Touch {
Touch {
id: input.id,
start_position: input.position,
start_force: input.force,
previous_position: input.position,
previous_force: input.force,
position: input.position,
force: input.force,
}
}
}
#[derive(Debug, Clone, Default, Resource)]
pub struct Touches {
pressed: HashMap<u64, Touch>,
just_pressed: HashMap<u64, Touch>,
just_released: HashMap<u64, Touch>,
just_canceled: HashMap<u64, Touch>,
}
impl Touches {
pub fn iter(&self) -> impl Iterator<Item = &Touch> + '_ {
self.pressed.values()
}
pub fn get_pressed(&self, id: u64) -> Option<&Touch> {
self.pressed.get(&id)
}
pub fn any_just_pressed(&self) -> bool {
!self.just_pressed.is_empty()
}
pub fn release(&mut self, id: u64) {
if let Some(touch) = self.pressed.remove(&id) {
self.just_released.insert(id, touch);
}
}
pub fn release_all(&mut self) {
self.just_released.extend(self.pressed.drain());
}
pub fn just_pressed(&self, id: u64) -> bool {
self.just_pressed.contains_key(&id)
}
pub fn clear_just_pressed(&mut self, id: u64) -> bool {
self.just_pressed.remove(&id).is_some()
}
pub fn iter_just_pressed(&self) -> impl Iterator<Item = &Touch> {
self.just_pressed.values()
}
pub fn get_released(&self, id: u64) -> Option<&Touch> {
self.just_released.get(&id)
}
pub fn any_just_released(&self) -> bool {
!self.just_released.is_empty()
}
pub fn just_released(&self, id: u64) -> bool {
self.just_released.contains_key(&id)
}
pub fn clear_just_released(&mut self, id: u64) -> bool {
self.just_released.remove(&id).is_some()
}
pub fn iter_just_released(&self) -> impl Iterator<Item = &Touch> {
self.just_released.values()
}
pub fn any_just_canceled(&self) -> bool {
!self.just_canceled.is_empty()
}
pub fn just_canceled(&self, id: u64) -> bool {
self.just_canceled.contains_key(&id)
}
pub fn clear_just_canceled(&mut self, id: u64) -> bool {
self.just_canceled.remove(&id).is_some()
}
pub fn iter_just_canceled(&self) -> impl Iterator<Item = &Touch> {
self.just_canceled.values()
}
pub fn first_pressed_position(&self) -> Option<Vec2> {
self.pressed
.values()
.next()
.or_else(|| self.just_pressed.values().next())
.map(|t| t.position)
}
pub fn clear(&mut self) {
self.just_pressed.clear();
self.just_released.clear();
self.just_canceled.clear();
}
pub fn reset_all(&mut self) {
self.pressed.clear();
self.just_pressed.clear();
self.just_released.clear();
self.just_canceled.clear();
}
fn process_touch_event(&mut self, event: &TouchInput) {
match event.phase {
TouchPhase::Started => {
self.pressed.insert(event.id, event.into());
self.just_pressed.insert(event.id, event.into());
}
TouchPhase::Moved => {
if let Some(mut new_touch) = self.pressed.get(&event.id).cloned() {
new_touch.position = event.position;
new_touch.force = event.force;
self.pressed.insert(event.id, new_touch);
}
}
TouchPhase::Ended => {
if let Some((_, v)) = self.pressed.remove_entry(&event.id) {
self.just_released.insert(event.id, v);
} else {
self.just_released.insert(event.id, event.into());
}
}
TouchPhase::Canceled => {
if let Some((_, v)) = self.pressed.remove_entry(&event.id) {
self.just_canceled.insert(event.id, v);
} else {
self.just_canceled.insert(event.id, event.into());
}
}
};
}
}
pub fn touch_screen_input_system(
mut touch_state: ResMut<Touches>,
mut touch_input_events: EventReader<TouchInput>,
) {
if !touch_state.just_pressed.is_empty() {
touch_state.just_pressed.clear();
}
if !touch_state.just_released.is_empty() {
touch_state.just_released.clear();
}
if !touch_state.just_canceled.is_empty() {
touch_state.just_canceled.clear();
}
if !touch_input_events.is_empty() {
for touch in touch_state.pressed.values_mut() {
touch.previous_position = touch.position;
touch.previous_force = touch.force;
}
for event in touch_input_events.read() {
touch_state.process_touch_event(event);
}
}
}
#[cfg(test)]
mod test {
use super::Touches;
#[test]
fn touch_update() {
use crate::{touch::Touch, Touches};
use bevy_math::Vec2;
let mut touches = Touches::default();
let touch_event = Touch {
id: 4,
start_position: Vec2::ZERO,
start_force: None,
previous_position: Vec2::ZERO,
previous_force: None,
position: Vec2::ZERO,
force: None,
};
touches.just_pressed.insert(4, touch_event);
touches.just_released.insert(4, touch_event);
touches.just_canceled.insert(4, touch_event);
clear_all(&mut touches);
assert!(touches.just_pressed.is_empty());
assert!(touches.just_released.is_empty());
assert!(touches.just_canceled.is_empty());
}
#[test]
fn touch_process() {
use crate::{touch::TouchPhase, TouchInput, Touches};
use bevy_ecs::entity::Entity;
use bevy_math::Vec2;
let mut touches = Touches::default();
let touch_event = TouchInput {
phase: TouchPhase::Started,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 4,
};
clear_all(&mut touches);
touches.process_touch_event(&touch_event);
assert!(touches.pressed.get(&touch_event.id).is_some());
assert!(touches.just_pressed.get(&touch_event.id).is_some());
let moved_touch_event = TouchInput {
phase: TouchPhase::Moved,
position: Vec2::splat(5.0),
window: Entity::PLACEHOLDER,
force: None,
id: touch_event.id,
};
clear_all(&mut touches);
touches.process_touch_event(&moved_touch_event);
assert_eq!(
touches
.pressed
.get(&moved_touch_event.id)
.expect("Missing from pressed after move.")
.previous_position,
touch_event.position
);
let cancel_touch_event = TouchInput {
phase: TouchPhase::Canceled,
position: Vec2::ONE,
window: Entity::PLACEHOLDER,
force: None,
id: touch_event.id,
};
clear_all(&mut touches);
touches.process_touch_event(&cancel_touch_event);
assert!(touches.just_canceled.get(&touch_event.id).is_some());
assert!(touches.pressed.get(&touch_event.id).is_none());
let end_touch_event = TouchInput {
phase: TouchPhase::Ended,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: touch_event.id,
};
clear_all(&mut touches);
touches.process_touch_event(&touch_event);
touches.process_touch_event(&moved_touch_event);
touches.process_touch_event(&end_touch_event);
assert!(touches.just_released.get(&touch_event.id).is_some());
assert!(touches.pressed.get(&touch_event.id).is_none());
let touch = touches.just_released.get(&touch_event.id).unwrap();
assert_ne!(touch.previous_position, touch.position);
}
#[test]
fn touch_process_multi_event() {
use crate::{touch::TouchPhase, TouchInput, Touches};
use bevy_ecs::entity::Entity;
use bevy_math::Vec2;
let mut touches = Touches::default();
let started_touch_event = TouchInput {
phase: TouchPhase::Started,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 4,
};
let moved_touch_event1 = TouchInput {
phase: TouchPhase::Moved,
position: Vec2::splat(5.0),
window: Entity::PLACEHOLDER,
force: None,
id: started_touch_event.id,
};
let moved_touch_event2 = TouchInput {
phase: TouchPhase::Moved,
position: Vec2::splat(6.0),
window: Entity::PLACEHOLDER,
force: None,
id: started_touch_event.id,
};
for touch in touches.pressed.values_mut() {
touch.previous_position = touch.position;
}
touches.process_touch_event(&started_touch_event);
touches.process_touch_event(&moved_touch_event1);
touches.process_touch_event(&moved_touch_event2);
{
let touch = touches.get_pressed(started_touch_event.id).unwrap();
assert_eq!(touch.previous_position, started_touch_event.position);
assert_eq!(touch.position, moved_touch_event2.position);
}
for touch in touches.pressed.values_mut() {
touch.previous_position = touch.position;
}
touches.process_touch_event(&moved_touch_event1);
touches.process_touch_event(&moved_touch_event2);
touches.process_touch_event(&moved_touch_event1);
{
let touch = touches.get_pressed(started_touch_event.id).unwrap();
assert_eq!(touch.previous_position, moved_touch_event2.position);
assert_eq!(touch.position, moved_touch_event1.position);
}
}
#[test]
fn touch_pressed() {
use crate::{touch::TouchPhase, TouchInput, Touches};
use bevy_ecs::entity::Entity;
use bevy_math::Vec2;
let mut touches = Touches::default();
let touch_event = TouchInput {
phase: TouchPhase::Started,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 4,
};
touches.process_touch_event(&touch_event);
assert!(touches.get_pressed(touch_event.id).is_some());
assert!(touches.just_pressed(touch_event.id));
assert_eq!(touches.iter().count(), 1);
touches.clear_just_pressed(touch_event.id);
assert!(!touches.just_pressed(touch_event.id));
}
#[test]
fn touch_released() {
use crate::{touch::TouchPhase, TouchInput, Touches};
use bevy_ecs::entity::Entity;
use bevy_math::Vec2;
let mut touches = Touches::default();
let touch_event = TouchInput {
phase: TouchPhase::Ended,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 4,
};
touches.process_touch_event(&touch_event);
assert!(touches.get_released(touch_event.id).is_some());
assert!(touches.just_released(touch_event.id));
assert_eq!(touches.iter_just_released().count(), 1);
touches.clear_just_released(touch_event.id);
assert!(!touches.just_released(touch_event.id));
}
#[test]
fn touch_canceled() {
use crate::{touch::TouchPhase, TouchInput, Touches};
use bevy_ecs::entity::Entity;
use bevy_math::Vec2;
let mut touches = Touches::default();
let touch_event = TouchInput {
phase: TouchPhase::Canceled,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 4,
};
touches.process_touch_event(&touch_event);
assert!(touches.just_canceled(touch_event.id));
assert_eq!(touches.iter_just_canceled().count(), 1);
touches.clear_just_canceled(touch_event.id);
assert!(!touches.just_canceled(touch_event.id));
}
#[test]
fn release_touch() {
use crate::{touch::TouchPhase, TouchInput, Touches};
use bevy_ecs::entity::Entity;
use bevy_math::Vec2;
let mut touches = Touches::default();
let touch_event = TouchInput {
phase: TouchPhase::Started,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 4,
};
touches.process_touch_event(&touch_event);
assert!(touches.get_pressed(touch_event.id).is_some());
touches.release(touch_event.id);
assert!(touches.get_pressed(touch_event.id).is_none());
assert!(touches.just_released(touch_event.id));
}
#[test]
fn release_all_touches() {
use crate::{touch::TouchPhase, TouchInput, Touches};
use bevy_ecs::entity::Entity;
use bevy_math::Vec2;
let mut touches = Touches::default();
let touch_pressed_event = TouchInput {
phase: TouchPhase::Started,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 4,
};
let touch_moved_event = TouchInput {
phase: TouchPhase::Moved,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 4,
};
touches.process_touch_event(&touch_pressed_event);
touches.process_touch_event(&touch_moved_event);
assert!(touches.get_pressed(touch_pressed_event.id).is_some());
assert!(touches.get_pressed(touch_moved_event.id).is_some());
touches.release_all();
assert!(touches.get_pressed(touch_pressed_event.id).is_none());
assert!(touches.just_released(touch_pressed_event.id));
assert!(touches.get_pressed(touch_moved_event.id).is_none());
assert!(touches.just_released(touch_moved_event.id));
}
#[test]
fn clear_touches() {
use crate::{touch::TouchPhase, TouchInput, Touches};
use bevy_ecs::entity::Entity;
use bevy_math::Vec2;
let mut touches = Touches::default();
let touch_press_event = TouchInput {
phase: TouchPhase::Started,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 4,
};
let touch_canceled_event = TouchInput {
phase: TouchPhase::Canceled,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 5,
};
let touch_released_event = TouchInput {
phase: TouchPhase::Ended,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 6,
};
touches.process_touch_event(&touch_press_event);
touches.process_touch_event(&touch_canceled_event);
touches.process_touch_event(&touch_released_event);
assert!(touches.get_pressed(touch_press_event.id).is_some());
assert!(touches.just_pressed(touch_press_event.id));
assert!(touches.just_canceled(touch_canceled_event.id));
assert!(touches.just_released(touch_released_event.id));
touches.clear();
assert!(touches.get_pressed(touch_press_event.id).is_some());
assert!(!touches.just_pressed(touch_press_event.id));
assert!(!touches.just_canceled(touch_canceled_event.id));
assert!(!touches.just_released(touch_released_event.id));
}
#[test]
fn reset_all_touches() {
use crate::{touch::TouchPhase, TouchInput, Touches};
use bevy_ecs::entity::Entity;
use bevy_math::Vec2;
let mut touches = Touches::default();
let touch_press_event = TouchInput {
phase: TouchPhase::Started,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 4,
};
let touch_canceled_event = TouchInput {
phase: TouchPhase::Canceled,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 5,
};
let touch_released_event = TouchInput {
phase: TouchPhase::Ended,
position: Vec2::splat(4.0),
window: Entity::PLACEHOLDER,
force: None,
id: 6,
};
touches.process_touch_event(&touch_press_event);
touches.process_touch_event(&touch_canceled_event);
touches.process_touch_event(&touch_released_event);
assert!(touches.get_pressed(touch_press_event.id).is_some());
assert!(touches.just_pressed(touch_press_event.id));
assert!(touches.just_canceled(touch_canceled_event.id));
assert!(touches.just_released(touch_released_event.id));
touches.reset_all();
assert!(touches.get_pressed(touch_press_event.id).is_none());
assert!(!touches.just_pressed(touch_press_event.id));
assert!(!touches.just_canceled(touch_canceled_event.id));
assert!(!touches.just_released(touch_released_event.id));
}
fn clear_all(touch_state: &mut Touches) {
touch_state.just_pressed.clear();
touch_state.just_released.clear();
touch_state.just_canceled.clear();
}
}