#![cfg_attr(docsrs, feature(doc_cfg))]
use bevy::camera::RenderTarget;
use bevy::ecs::query::Has;
use bevy::prelude::*;
use bevy::window::{PrimaryWindow, WindowRef};
use smallvec::SmallVec;
#[allow(missing_docs)]
pub mod prelude {
pub use crate::{CursorLocation, TrackCursorPlugin, UpdateCursorLocation};
}
pub struct TrackCursorPlugin;
impl Plugin for TrackCursorPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<CursorLocation>().add_systems(
First,
update_cursor_location_res.in_set(UpdateCursorLocation),
);
}
}
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone, Copy)]
pub struct UpdateCursorLocation;
#[derive(Resource, Default)]
pub struct CursorLocation(Option<Location>);
#[derive(Debug, Clone, PartialEq)]
pub struct Location {
pub position: Vec2,
pub window: Entity,
pub camera: Entity,
#[cfg(feature = "2d")]
pub world_position: Vec2,
#[cfg(feature = "3d")]
pub ray: Ray3d,
}
impl CursorLocation {
#[inline]
pub fn get(&self) -> Option<&Location> {
self.0.as_ref()
}
#[inline]
pub fn position(&self) -> Option<Vec2> {
self.get().map(|data| data.position)
}
#[inline]
pub fn window(&self) -> Option<Entity> {
self.get().map(|data| data.window)
}
#[inline]
pub fn camera(&self) -> Option<Entity> {
self.get().map(|data| data.camera)
}
#[cfg(feature = "2d")]
#[inline]
pub fn world_position(&self) -> Option<Vec2> {
self.get().map(|data| data.world_position)
}
#[cfg(feature = "3d")]
#[inline]
pub fn ray(&self) -> Option<Ray3d> {
self.get().map(|data| data.ray)
}
}
fn update_cursor_location_res(
window_q: Query<(Entity, &Window, Has<PrimaryWindow>)>,
camera_q: Query<(Entity, &GlobalTransform, &Camera, &RenderTarget)>,
cursor: ResMut<CursorLocation>,
) {
let mut cursor = cursor.map_unchanged(|cursor| &mut cursor.0);
for (win_ref, window, is_primary) in &window_q {
let Some(cursor_position) = window.cursor_position() else {
continue;
};
let Some(physical_cursor_position) = window.physical_cursor_position() else {
continue;
};
let mut cameras = camera_q
.iter()
.filter(|&(_, _, _, target)| match target {
RenderTarget::Window(WindowRef::Primary) => is_primary,
RenderTarget::Window(WindowRef::Entity(target_ref)) => *target_ref == win_ref,
RenderTarget::Image(_)
| RenderTarget::TextureView(_)
| RenderTarget::None { .. } => false,
})
.collect::<SmallVec<[_; 4]>>();
cameras.sort_unstable_by_key(|(_, _, camera, _)| std::cmp::Reverse(camera.order));
for (camera_ref, cam_t, camera, _) in cameras {
let _ = cam_t;
let contain_cursor = match camera.viewport {
Some(ref viewport) => {
let Vec2 { x, y } = physical_cursor_position;
let Vec2 { x: vx, y: vy } = viewport.physical_position.as_vec2();
let Vec2 { x: vw, y: vh } = viewport.physical_size.as_vec2();
x >= vx && x <= (vx + vw) && y >= vy && y <= (vy + vh)
}
None => true,
};
if !contain_cursor {
continue;
}
#[cfg(feature = "2d")]
let Ok(world_position) = camera.viewport_to_world_2d(cam_t, cursor_position) else {
continue;
};
#[cfg(feature = "3d")]
let Ok(ray) = camera.viewport_to_world(cam_t, cursor_position) else {
continue;
};
cursor.set_if_neq(Some(Location {
position: cursor_position,
window: win_ref,
camera: camera_ref,
#[cfg(feature = "2d")]
world_position,
#[cfg(feature = "3d")]
ray,
}));
return;
}
}
cursor.set_if_neq(None);
}