use bevy_camera::NormalizedRenderTarget;
use bevy_camera::{Camera, RenderTarget};
use bevy_ecs::prelude::*;
use bevy_input::mouse::MouseScrollUnit;
use bevy_input::touch::TouchPhase;
use bevy_math::Vec2;
use bevy_platform::collections::HashMap;
use bevy_reflect::prelude::*;
use bevy_window::PrimaryWindow;
use uuid::Uuid;
use core::{fmt::Debug, ops::Deref};
use crate::backend::HitData;
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, Component, Reflect)]
#[require(PointerLocation, PointerPress, PointerInteraction)]
#[reflect(Component, Default, Debug, Hash, PartialEq, Clone)]
pub enum PointerId {
#[default]
Mouse,
Touch(u64),
#[reflect(ignore, clone)]
Custom(Uuid),
}
impl PointerId {
pub fn is_touch(&self) -> bool {
matches!(self, PointerId::Touch(_))
}
pub fn is_mouse(&self) -> bool {
matches!(self, PointerId::Mouse)
}
pub fn is_custom(&self) -> bool {
matches!(self, PointerId::Custom(_))
}
pub fn get_touch_id(&self) -> Option<u64> {
if let PointerId::Touch(id) = self {
Some(*id)
} else {
None
}
}
}
#[derive(Debug, Default, Clone, Component, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
pub struct PointerInteraction {
pub(crate) sorted_entities: Vec<(Entity, HitData)>,
}
impl PointerInteraction {
pub fn get_nearest_hit(&self) -> Option<&(Entity, HitData)> {
self.sorted_entities.first()
}
}
impl Deref for PointerInteraction {
type Target = Vec<(Entity, HitData)>;
fn deref(&self) -> &Self::Target {
&self.sorted_entities
}
}
#[derive(Debug, Clone, Default, Resource)]
pub struct PointerMap {
inner: HashMap<PointerId, Entity>,
}
impl PointerMap {
pub fn get_entity(&self, pointer_id: PointerId) -> Option<Entity> {
self.inner.get(&pointer_id).copied()
}
}
pub fn update_pointer_map(pointers: Query<(Entity, &PointerId)>, mut map: ResMut<PointerMap>) {
map.inner.clear();
for (entity, id) in &pointers {
map.inner.insert(*id, entity);
}
}
#[derive(Debug, Default, Clone, Component, Reflect, PartialEq, Eq)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub struct PointerPress {
primary: bool,
secondary: bool,
middle: bool,
}
impl PointerPress {
#[inline]
pub fn is_primary_pressed(&self) -> bool {
self.primary
}
#[inline]
pub fn is_secondary_pressed(&self) -> bool {
self.secondary
}
#[inline]
pub fn is_middle_pressed(&self) -> bool {
self.middle
}
#[inline]
pub fn is_any_pressed(&self) -> bool {
self.primary || self.middle || self.secondary
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
#[reflect(Clone, PartialEq)]
pub enum PressDirection {
Pressed,
Released,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
#[reflect(Clone, PartialEq)]
pub enum PointerButton {
Primary,
Secondary,
Middle,
}
impl PointerButton {
pub fn iter() -> impl Iterator<Item = PointerButton> {
[Self::Primary, Self::Secondary, Self::Middle].into_iter()
}
}
#[derive(Debug, Default, Clone, Component, Reflect, PartialEq)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub struct PointerLocation {
#[reflect(ignore, clone)]
pub location: Option<Location>,
}
impl PointerLocation {
pub fn new(location: Location) -> Self {
Self {
location: Some(location),
}
}
pub fn location(&self) -> Option<&Location> {
self.location.as_ref()
}
}
#[derive(Debug, Clone, Reflect, PartialEq)]
#[reflect(Debug, PartialEq, Clone)]
pub struct Location {
pub target: NormalizedRenderTarget,
pub position: Vec2,
}
impl Location {
#[inline]
pub fn is_in_viewport(
&self,
camera: &Camera,
render_target: &RenderTarget,
primary_window: &Query<Entity, With<PrimaryWindow>>,
) -> bool {
if render_target
.normalize(Some(match primary_window.single() {
Ok(w) => w,
Err(_) => return false,
}))
.as_ref()
!= Some(&self.target)
{
return false;
}
camera
.logical_viewport_rect()
.is_some_and(|rect| rect.contains(self.position))
}
}
#[derive(Debug, Clone, Copy, Reflect)]
#[reflect(Clone)]
pub enum PointerAction {
Press(PointerButton),
Release(PointerButton),
Move {
delta: Vec2,
},
Scroll {
unit: MouseScrollUnit,
x: f32,
y: f32,
phase: TouchPhase,
},
Cancel,
}
#[derive(Message, Debug, Clone, Reflect)]
#[reflect(Clone)]
pub struct PointerInput {
pub pointer_id: PointerId,
pub location: Location,
pub action: PointerAction,
}
impl PointerInput {
pub fn new(pointer_id: PointerId, location: Location, action: PointerAction) -> PointerInput {
PointerInput {
pointer_id,
location,
action,
}
}
#[inline]
pub fn button_just_pressed(&self, target_button: PointerButton) -> bool {
if let PointerAction::Press(button) = self.action {
button == target_button
} else {
false
}
}
#[inline]
pub fn button_just_released(&self, target_button: PointerButton) -> bool {
if let PointerAction::Release(button) = self.action {
button == target_button
} else {
false
}
}
pub fn receive(
mut events: MessageReader<PointerInput>,
mut pointers: Query<(&PointerId, &mut PointerLocation, &mut PointerPress)>,
) {
for event in events.read() {
match event.action {
PointerAction::Press(button) => {
pointers
.iter_mut()
.for_each(|(pointer_id, _, mut pointer)| {
if *pointer_id == event.pointer_id {
match button {
PointerButton::Primary => pointer.primary = true,
PointerButton::Secondary => pointer.secondary = true,
PointerButton::Middle => pointer.middle = true,
}
}
});
}
PointerAction::Release(button) => {
pointers
.iter_mut()
.for_each(|(pointer_id, _, mut pointer)| {
if *pointer_id == event.pointer_id {
match button {
PointerButton::Primary => pointer.primary = false,
PointerButton::Secondary => pointer.secondary = false,
PointerButton::Middle => pointer.middle = false,
}
}
});
}
PointerAction::Move { .. } => {
pointers.iter_mut().for_each(|(id, mut pointer, _)| {
if *id == event.pointer_id {
pointer.location = Some(event.location.to_owned());
}
});
}
_ => {}
}
}
}
}