use crate::*;
use bevy::{input::{gamepad::GamepadButtonChangedEvent, mouse::MouseButtonInput, ButtonState}, render::camera::RenderTarget, utils::HashMap, window::{CursorGrabMode, PrimaryWindow, WindowRef}};
use picking_core::PickSet;
use pointer::{InputMove, InputPress, Location};
#[derive(Component, Debug, Clone, PartialEq)]
pub struct Cursor2d {
cursor_request: CursorIcon,
cursor_request_priority: f32,
cursor_atlas_map: HashMap<CursorIcon, (usize, Vec2)>,
pub location: Vec2,
pub confined: bool,
pub visible: bool,
}
impl Cursor2d {
pub fn new() -> Cursor2d {
Cursor2d {
cursor_request: CursorIcon::Default,
cursor_request_priority: 0.0,
cursor_atlas_map: HashMap::new(),
location: Vec2::ZERO,
confined: false,
visible: true,
}
}
pub fn request_cursor(&mut self, request: CursorIcon, priority: f32) {
if priority > self.cursor_request_priority {
self.cursor_request = request;
self.cursor_request_priority = priority;
}
}
pub fn set_index(mut self, icon: CursorIcon, index: usize, offset: impl Into<Vec2>) -> Self {
self.cursor_atlas_map.insert(icon, (index, offset.into()));
self
}
}
impl Default for Cursor2d {
fn default() -> Self {
Self {
cursor_request: Default::default(),
cursor_request_priority: Default::default(),
cursor_atlas_map: Default::default(),
location: Default::default(),
confined: Default::default(),
visible: true,
}
}
}
#[derive(Component, Debug, Clone, PartialEq)]
pub struct GamepadCursor {
pub id: usize,
pub mode: GamepadCursorMode,
pub speed: f32,
}
impl GamepadCursor {
pub fn new(id: usize) -> Self {
Self { id, ..Default::default() }
}
}
impl Default for GamepadCursor {
fn default() -> Self {
Self { id: 0, mode: Default::default(), speed: 1.0 }
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub enum GamepadCursorMode {
#[default]
Free,
}
#[derive(Bundle)]
pub struct CursorBundle {
pub cursor: Cursor2d,
pub pointer: PointerBundle,
}
impl Default for CursorBundle {
fn default() -> Self {
Self {
cursor: default(),
pointer: PointerBundle::new(PointerId::Custom(pointer::Uuid::new_v4())),
}
}
}
#[derive(Bundle)]
pub struct StyledCursorBundle {
pub cursor: Cursor2d,
pub pointer: PointerBundle,
pub atlas: TextureAtlas,
pub sprite: SpriteBundle,
pub pickable: Pickable,
}
impl Default for StyledCursorBundle {
fn default() -> Self {
Self {
cursor: default(),
pointer: PointerBundle::new(PointerId::Custom(pointer::Uuid::new_v4())),
atlas: default(),
sprite: default(),
pickable: Pickable::IGNORE,
}
}
}
fn cursor_set_visibility(
mut windows: Query<&mut Window, With<PrimaryWindow>>,
mut query: Query<(&Cursor2d, Option<&mut Visibility>, Has<GamepadCursor>, Has<Handle<Image>>)>
) {
if let Ok(mut window) = windows.get_single_mut() {
for (cursor, optional_visibility, has_gamepad, has_image) in &mut query {
if let Some(mut visibility) = optional_visibility {
*visibility = if cursor.visible { Visibility::Visible } else { Visibility::Hidden };
if window.cursor_position().is_none() && !has_gamepad { *visibility = Visibility::Hidden }
}
if !has_gamepad {
window.cursor.visible = if has_image { false } else { cursor.visible };
}
}
}
}
fn cursor_change_native(
mut windows: Query<&mut Window, With<PrimaryWindow>>,
mut query: Query<&Cursor2d, Without<GamepadCursor>>
) {
if let Ok(mut window) = windows.get_single_mut() {
for cursor in &mut query {
if window.cursor.visible { window.cursor.icon = cursor.cursor_request; }
window.cursor.grab_mode = if cursor.confined { CursorGrabMode::Confined } else { CursorGrabMode::None }
}
}
}
fn gamepad_move_cursor(
axis: Res<Axis<GamepadAxis>>,
time: Res<Time>,
windows: Query<&Window, With<PrimaryWindow>>,
mut query: Query<(&mut Cursor2d, &GamepadCursor)>,
) {
if let Ok(window) = windows.get_single() {
for (mut cursor, gamepad) in query.iter_mut() {
let x = axis.get(GamepadAxis { gamepad: Gamepad::new(gamepad.id), axis_type: GamepadAxisType::LeftStickX });
let y = axis.get(GamepadAxis { gamepad: Gamepad::new(gamepad.id), axis_type: GamepadAxisType::LeftStickY });
if let (Some(x), Some(y)) = (x, y) {
cursor.location.x += x * time.delta_seconds() * 500.0 * gamepad.speed;
cursor.location.y += y * time.delta_seconds() * 500.0 * gamepad.speed;
let w = window.width()/2.0;
let h = window.height()/2.0;
cursor.location.x = cursor.location.x.clamp(-w, w);
cursor.location.y = cursor.location.y.clamp(-h, h);
}
}
}
}
fn mouse_move_cursor(
windows: Query<&Window, With<PrimaryWindow>>,
cameras: Query<&OrthographicProjection>,
mut query: Query<(&mut Cursor2d, Option<&Parent>), Without<GamepadCursor>>
) {
if let Ok(window) = windows.get_single() {
for (mut cursor, parent_option) in &mut query {
if let Some(position) = window.cursor_position() {
let scale = if let Some(parent) = parent_option {
if let Ok(projection) = cameras.get(**parent) { projection.scale } else { 1.0 }
} else { 1.0 };
cursor.location.x = (position.x - window.width()*0.5) * scale;
cursor.location.y = -((position.y - window.height()*0.5) * scale);
}
}
}
}
fn cursor_update_transform(
mut query: Query<(&Cursor2d, &mut Transform)>
) {
for (cursor, mut transform) in &mut query {
let sprite_offset = cursor.cursor_atlas_map.get(&cursor.cursor_request).unwrap_or(&(0, Vec2::ZERO)).1;
transform.translation.x = cursor.location.x - sprite_offset.x * transform.scale.x;
transform.translation.y = cursor.location.y + sprite_offset.y * transform.scale.y;
}
}
fn cursor_move_virtual_pointer(
windows: Query<(Entity, &Window), With<PrimaryWindow>>,
mut query: Query<(&mut PointerLocation, &Cursor2d)>,
) {
if let Ok((win_entity, window)) = windows.get_single() {
for (mut pointer, cursor) in query.iter_mut() {
pointer.location = Some(pointer::Location {
target: RenderTarget::Window(WindowRef::Primary).normalize(Some(win_entity)).unwrap(),
position: Vec2 {
x: cursor.location.x + window.width()/2.0,
y: -cursor.location.y + window.height()/2.0,
}.round(),
});
}
}
}
fn cursor_mouse_pick_events(
mut mouse_inputs: EventReader<MouseButtonInput>,
mut cursor_last: Local<HashMap<PointerId, Vec2>>,
pointers: Query<(&PointerId, &PointerLocation), (With<Cursor2d>, Without<GamepadCursor>)>,
mut pointer_move: EventWriter<InputMove>,
mut pointer_presses: EventWriter<InputPress>,
) {
for (pointer, location) in &pointers {
if let Some(location) = &location.location {
let last = cursor_last.get(pointer).unwrap_or(&Vec2::ZERO);
if *last == location.position { continue; }
pointer_move.send(InputMove::new(
*pointer,
Location {
target: location.target.clone(),
position: location.position,
},
location.position - *last,
));
cursor_last.insert(*pointer, location.position);
}
}
for input in mouse_inputs.read() {
let button = match input.button {
MouseButton::Left => PointerButton::Primary,
MouseButton::Right => PointerButton::Secondary,
MouseButton::Middle => PointerButton::Middle,
MouseButton::Other(_) => continue,
MouseButton::Back => continue,
MouseButton::Forward => continue,
};
match input.state {
ButtonState::Pressed => {
for (pointer, _) in &pointers {
pointer_presses.send(InputPress::new_down(*pointer, button));
}
}
ButtonState::Released => {
for (pointer, _) in &pointers {
pointer_presses.send(InputPress::new_up(*pointer, button));
}
}
}
}
}
fn cursor_gamepad_pick_events(
mut gamepad_inputs: EventReader<GamepadButtonChangedEvent>,
mut cursor_last: Local<HashMap<PointerId, Vec2>>,
pointers: Query<(&PointerId, &PointerLocation, &GamepadCursor), With<Cursor2d>>,
mut pointer_move: EventWriter<InputMove>,
mut pointer_presses: EventWriter<InputPress>,
) {
for (pointer, location, _) in &pointers {
if let Some(location) = &location.location {
let last = cursor_last.get(pointer).unwrap_or(&Vec2::ZERO);
if *last == location.position { continue; }
pointer_move.send(InputMove::new(
*pointer,
Location {
target: location.target.clone(),
position: location.position,
},
location.position - *last,
));
cursor_last.insert(*pointer, location.position);
}
}
for input in gamepad_inputs.read() {
let button = match input.button_type {
GamepadButtonType::South => PointerButton::Primary,
GamepadButtonType::East => PointerButton::Secondary,
GamepadButtonType::West => PointerButton::Middle,
_ => continue,
};
match input.value {
1.0 => {
for (pointer, _, gamepad) in &pointers {
if gamepad.id != input.gamepad.id { continue; }
pointer_presses.send(InputPress::new_down(*pointer, button));
}
}
0.0 => {
for (pointer, _, gamepad) in &pointers {
if gamepad.id != input.gamepad.id { continue; }
pointer_presses.send(InputPress::new_up(*pointer, button));
}
},
_ => {}
}
}
}
fn cursor_reset_icon(
mut query: Query<&mut Cursor2d>
) {
for mut cursor in &mut query {
cursor.cursor_request = CursorIcon::Default;
cursor.cursor_request_priority = 0.0;
}
}
fn cursor_update_texture(
mut query: Query<(&Cursor2d, &mut TextureAtlas)>
) {
for (cursor, mut atlas) in &mut query {
atlas.index = cursor.cursor_atlas_map.get(&cursor.cursor_request).unwrap_or(&(0, Vec2::ZERO)).0;
}
}
#[derive(Component, Debug, Clone, PartialEq)]
pub struct OnHoverSetCursor {
pub cursor: CursorIcon,
}
impl OnHoverSetCursor {
pub fn new(cursor: CursorIcon) -> Self {
OnHoverSetCursor {
cursor
}
}
}
fn on_hover_set_cursor(query: Query<(&UiAnimator<Hover>, &OnHoverSetCursor)>, mut cursor: Query<&mut Cursor2d>) {
for (control, hover_cursor) in &query {
if control.is_forward() {
if let Ok(mut cursor) = cursor.get_single_mut(){
cursor.request_cursor(hover_cursor.cursor, 1.0);
}
}
}
}
pub struct CursorPlugin;
impl Plugin for CursorPlugin {
fn build(&self, app: &mut App) {
app
.add_systems(First, (cursor_mouse_pick_events, cursor_gamepad_pick_events, apply_deferred).chain().in_set(PickSet::Input))
.add_systems(PreUpdate, cursor_reset_icon)
.add_systems(PreUpdate, (gamepad_move_cursor, mouse_move_cursor, cursor_update_transform, cursor_move_virtual_pointer).chain())
.add_systems(PostUpdate, cursor_set_visibility)
.add_systems(PostUpdate, cursor_change_native)
.add_systems(PostUpdate, cursor_update_texture)
.add_systems(Update, on_hover_set_cursor);
}
}