use bevy::camera::NormalizedRenderTarget;
use bevy::ecs::entity::{EntityHashMap, EntityHashSet};
use bevy::ecs::message::MessageMutator;
use bevy::picking::backend::PointerHits;
use bevy::picking::events::PointerState;
use bevy::picking::hover::HoverMap;
use bevy::picking::pointer::{
Location, PointerButton, PointerId, PointerLocation, PointerPressState,
};
use bevy::prelude::*;
use bevy::ui::{ComputedNode, ComputedUiTargetCamera, UiGlobalTransform};
use super::events::climb;
use crate::PointerCapture;
use crate::bridge::{ReactInteraction, ReactNode};
use crate::ext::{EventLocalPos, VirtualPointers};
use crate::layer::pick3d::Transform3dPointer;
pub fn fold_span_hits(
mut hits: MessageMutator<PointerHits>,
spans: Query<(), (With<TextSpan>, With<ReactNode>)>,
blocks: Query<(), With<Text>>,
child_of: Query<&ChildOf>,
) {
for hits in hits.read() {
for (entity, _) in &mut hits.picks {
if spans.contains(*entity)
&& let Some(block) = climb(*entity, &child_of, |e| blocks.contains(e))
{
*entity = block;
}
}
}
}
pub(crate) fn device(pointer: PointerId, transform3d: Option<&Transform3dPointer>) -> PointerId {
if transform3d.is_some_and(|t| t.id == pointer) {
PointerId::Mouse
} else {
pointer
}
}
#[allow(clippy::too_many_arguments)]
pub fn sync_interactions(
hover_map: Option<Res<HoverMap>>,
presses: Option<Res<PointerState>>,
transform3d: Option<Res<Transform3dPointer>>,
virtual_pointers: Option<Res<VirtualPointers>>,
pointers: Query<(&PointerId, &PointerLocation, &PointerPressState)>,
windows: Query<&Window>,
mut nodes: Query<&mut ReactInteraction>,
disabled: Query<(), With<bevy::ui::InteractionDisabled>>,
capture: Option<ResMut<PointerCapture>>,
mut live: Local<EntityHashSet>,
mut next: Local<EntityHashMap<ReactInteraction>>,
) {
next.clear();
let mut over_ui = false;
let cursor_gone = pointers
.iter()
.find(|(id, ..)| **id == PointerId::Mouse)
.and_then(|(_, location, _)| location.location())
.and_then(|location| match &location.target {
NormalizedRenderTarget::Window(window) => windows.get(window.entity()).ok(),
_ => None,
})
.is_some_and(|window| window.cursor_position().is_none());
for (&pointer, _, press) in &pointers {
let on_window = !virtual_pointers
.as_ref()
.is_some_and(|v| v.contains(pointer));
let hovers = match device(pointer, transform3d.as_deref()) {
PointerId::Mouse => !cursor_gone,
PointerId::Touch(_) => press.is_primary_pressed(),
PointerId::Custom(_) => true,
};
if hovers && let Some(hits) = hover_map.as_ref().and_then(|map| map.get(&pointer)) {
for &entity in hits.keys() {
if nodes.contains(entity) {
next.entry(entity).or_insert(ReactInteraction::Hovered);
over_ui |= on_window;
}
}
}
let pressing = presses
.as_ref()
.and_then(|presses| presses.get(pointer, PointerButton::Primary));
for &entity in pressing.iter().flat_map(|state| state.pressing.keys()) {
if nodes.contains(entity) {
if !disabled.contains(entity) {
next.insert(entity, ReactInteraction::Pressed);
}
over_ui |= on_window;
}
}
}
for (&entity, &state) in next.iter() {
if let Ok(mut interaction) = nodes.get_mut(entity) {
interaction.set_if_neq(state);
}
}
for entity in live.drain() {
if !next.contains_key(&entity)
&& let Ok(mut interaction) = nodes.get_mut(entity)
{
interaction.set_if_neq(ReactInteraction::None);
}
}
live.extend(next.keys().copied());
if let Some(mut capture) = capture {
capture.over_ui = over_ui;
}
}
pub(super) struct PointerSample {
pub location: Location,
pub client: Vec2,
}
pub(super) fn sample_pointer(
id: PointerId,
pointers: &Query<(&PointerId, &PointerLocation)>,
transform3d: Option<&Transform3dPointer>,
) -> Option<PointerSample> {
let location = location_of(id, pointers)?.clone();
let client = client_pos(id, &location, pointers, transform3d);
Some(PointerSample { location, client })
}
fn location_of<'a>(
id: PointerId,
pointers: &'a Query<(&PointerId, &PointerLocation)>,
) -> Option<&'a Location> {
pointers
.iter()
.find(|(pointer, _)| **pointer == id)
.and_then(|(_, location)| location.location())
}
pub(super) fn client_pos(
id: PointerId,
location: &Location,
pointers: &Query<(&PointerId, &PointerLocation)>,
transform3d: Option<&Transform3dPointer>,
) -> Vec2 {
let device = device(id, transform3d);
if device == id {
return location.position;
}
location_of(device, pointers).map_or(location.position, |mouse| mouse.position)
}
pub(super) fn pointer_over(entity: Entity, hover_map: &HoverMap) -> Option<PointerId> {
hover_map
.iter()
.find(|(_, hits)| hits.contains_key(&entity))
.map(|(pointer, _)| *pointer)
}
pub(super) type Geometry = Option<(
&'static ComputedNode,
&'static UiGlobalTransform,
Option<&'static ComputedUiTargetCamera>,
)>;
pub(super) fn event_pos(
user: Option<&EventLocalPos>,
geometry: Option<(
&ComputedNode,
&UiGlobalTransform,
Option<&ComputedUiTargetCamera>,
)>,
cameras: &Query<&Camera>,
location: &Location,
) -> Option<Vec2> {
user.and_then(|user| user.0).or_else(|| {
let (node, transform, target) = geometry?;
let camera = target
.and_then(|target| target.get())
.and_then(|camera| cameras.get(camera).ok());
let physical = match camera {
Some(camera) => crate::raster::pointer_physical_position(location, camera),
None => location.position * crate::raster::node_scale_factor(node),
};
node.normalize_point(*transform, physical)
.map(|centered| (centered + 0.5).clamp(Vec2::ZERO, Vec2::ONE))
})
}
#[cfg(test)]
pub(crate) mod test_pointers;
#[cfg(test)]
mod tests;