use crate::{
EguiContext, helpers,
input::{EguiContextPointerPosition, HoveredNonWindowEguiContext},
};
use bevy_camera::{Camera, NormalizedRenderTarget, RenderTarget};
use bevy_ecs::{
change_detection::Res,
component::Component,
entity::Entity,
error::Result,
observer::On,
prelude::{Commands, Query, With},
};
use bevy_math::Ray3d;
use bevy_picking::{
Pickable,
events::{Move, Out, Over, Pointer},
mesh_picking::ray_cast::RayMeshHit,
prelude::{MeshRayCast, MeshRayCastSettings, RayCastVisibility},
};
use bevy_transform::components::GlobalTransform;
use bevy_window::PrimaryWindow;
#[derive(Component)]
#[require(Pickable)]
pub struct PickableEguiContext(pub Entity);
pub fn handle_move_system(
event: On<Pointer<Move>>,
mut mesh_ray_cast: MeshRayCast,
mut egui_pointers: Query<&mut EguiContextPointerPosition>,
egui_contexts: Query<(&Camera, &GlobalTransform, &RenderTarget), With<EguiContext>>,
pickable_egui_context_query: Query<&PickableEguiContext>,
primary_window_query: Query<Entity, With<PrimaryWindow>>,
) -> Result {
let NormalizedRenderTarget::Window(_) = event.pointer_location.target else {
return Ok(());
};
let Ok((context_camera, global_transform, render_target)) = egui_contexts.get(event.hit.camera)
else {
return Ok(());
};
let settings = MeshRayCastSettings {
visibility: RayCastVisibility::Any,
filter: &|entity| pickable_egui_context_query.contains(entity),
early_exit_test: &|_| true,
};
let Some(ray) = make_ray(
&primary_window_query,
context_camera,
global_transform,
render_target,
&bevy_picking::pointer::PointerLocation {
location: Some(event.pointer_location.clone()),
},
) else {
return Ok(());
};
let &[(hit_entity, RayMeshHit { uv: Some(uv), .. })] = mesh_ray_cast.cast_ray(ray, &settings)
else {
return Ok(());
};
let &PickableEguiContext(context) = pickable_egui_context_query.get(hit_entity)?;
let (egui_mesh_camera, _, _) = egui_contexts.get(context)?;
let Some(viewport_size) = egui_mesh_camera.logical_target_size() else {
return Ok(());
};
egui_pointers.get_mut(context)?.position = helpers::vec2_into_egui_pos2(viewport_size * uv);
Ok(())
}
pub fn handle_over_system(
event: On<Pointer<Over>>,
pickable_egui_context_query: Query<&PickableEguiContext>,
mut commands: Commands,
) {
if let Ok(&PickableEguiContext(context)) = pickable_egui_context_query.get(event.entity) {
commands.insert_resource(HoveredNonWindowEguiContext(context));
}
}
pub fn handle_out_system(
event: On<Pointer<Out>>,
pickable_egui_context_query: Query<&PickableEguiContext>,
mut commands: Commands,
hovered_non_window_egui_context: Option<Res<HoveredNonWindowEguiContext>>,
) {
if let Ok(&PickableEguiContext(context)) = pickable_egui_context_query.get(event.entity)
&& hovered_non_window_egui_context
.as_deref()
.is_some_and(|&HoveredNonWindowEguiContext(hovered_context)| hovered_context == context)
{
commands.remove_resource::<HoveredNonWindowEguiContext>();
}
}
fn make_ray(
primary_window_entity: &Query<Entity, With<PrimaryWindow>>,
camera: &Camera,
camera_tfm: &GlobalTransform,
render_target: &RenderTarget,
pointer_loc: &bevy_picking::pointer::PointerLocation,
) -> Option<Ray3d> {
let pointer_loc = pointer_loc.location()?;
if !pointer_loc.is_in_viewport(camera, render_target, primary_window_entity) {
return None;
}
let mut viewport_pos = pointer_loc.position;
if let Some(viewport) = &camera.viewport {
let viewport_logical = camera.to_logical(viewport.physical_position)?;
viewport_pos -= viewport_logical;
}
camera.viewport_to_world(camera_tfm, viewport_pos).ok()
}