use bevy::picking::events::{Click, Drag, Enter, Leave, Pointer, Press, Release};
use bevy::picking::pointer::PointerButton;
use bevy::platform::collections::HashSet;
use bevy::prelude::*;
use bevy::ui::{ComputedNode, UiGlobalTransform};
use super::events::{climb, dom_button, send_ui_event, surface_relative};
use crate::bridge::{JsBridge, PointerHandlers, RNode, StyleVariants};
use crate::protocol::Style;
use crate::surface::SurfaceVirtualPointer;
use crate::ui_map::{apply_style, overlay_style};
pub fn collect_surface_clicks(
bridge: Res<JsBridge>,
pointer: Option<Res<SurfaceVirtualPointer>>,
mut clicks: MessageReader<Pointer<Click>>,
targets: Query<&RNode, With<Interaction>>,
child_of: Query<&ChildOf>,
) {
let Some(pointer) = pointer else { return };
let mut topmost: Option<(f32, Entity)> = None;
for ev in clicks.read() {
if ev.pointer_id != pointer.id || ev.button != PointerButton::Primary {
continue;
}
if let Some(target) = climb(ev.entity, &child_of, |e| targets.contains(e))
&& !topmost.is_some_and(|(depth, _)| depth <= ev.hit.depth)
{
topmost = Some((ev.hit.depth, target));
}
}
if let Some((_, target)) = topmost
&& let Ok(rnode) = targets.get(target)
{
debug!("surface click -> reconciler node {}", rnode.0);
send_ui_event(&bridge, rnode.0, "click", None, None, None);
}
}
#[allow(clippy::too_many_arguments)]
pub fn collect_surface_pointer_events(
bridge: Res<JsBridge>,
pointer: Option<Res<SurfaceVirtualPointer>>,
mut presses: MessageReader<Pointer<Press>>,
mut releases: MessageReader<Pointer<Release>>,
mut drags: MessageReader<Pointer<Drag>>,
nodes: Query<(&RNode, &PointerHandlers, &ComputedNode, &UiGlobalTransform)>,
child_of: Query<&ChildOf>,
) {
let Some(pointer) = pointer else { return };
let mut seen: HashSet<(Entity, PointerButton)> = HashSet::new();
let emit = |entity: Entity,
want: fn(&PointerHandlers) -> bool,
kind: &str,
at: Vec2,
button: PointerButton,
seen: &mut HashSet<(Entity, PointerButton)>| {
if let Some(target) = climb(entity, &child_of, |e| nodes.contains(e))
&& seen.insert((target, button))
&& let Ok((rnode, handlers, node, transform)) = nodes.get(target)
&& want(handlers)
&& let Some((pos, abs)) = surface_relative(node, transform, at)
{
send_ui_event(
&bridge,
rnode.0,
kind,
Some(pos),
Some(abs),
Some(dom_button(button)),
);
}
};
for ev in presses.read() {
if ev.pointer_id == pointer.id {
emit(
ev.entity,
|h| h.down,
"pointerDown",
ev.pointer_location.position,
ev.button,
&mut seen,
);
}
}
seen.clear();
for ev in drags.read() {
if ev.pointer_id == pointer.id {
emit(
ev.entity,
|h| h.moved,
"pointerMove",
ev.pointer_location.position,
ev.button,
&mut seen,
);
}
}
seen.clear();
for ev in releases.read() {
if ev.pointer_id == pointer.id {
emit(
ev.entity,
|h| h.up,
"pointerUp",
ev.pointer_location.position,
ev.button,
&mut seen,
);
}
}
}
pub fn collect_surface_hover_events(
bridge: Res<JsBridge>,
pointer: Option<Res<SurfaceVirtualPointer>>,
mut enters: MessageReader<Pointer<Enter>>,
mut leaves: MessageReader<Pointer<Leave>>,
nodes: Query<(&RNode, &PointerHandlers, &ComputedNode, &UiGlobalTransform)>,
) {
let Some(pointer) = pointer else { return };
let emit = |entity: Entity, want: fn(&PointerHandlers) -> bool, kind: &str, at: Vec2| {
if let Ok((rnode, handlers, node, transform)) = nodes.get(entity)
&& want(handlers)
&& let Some((pos, abs)) = surface_relative(node, transform, at)
{
send_ui_event(&bridge, rnode.0, kind, Some(pos), Some(abs), None);
}
};
for ev in enters.read() {
if ev.pointer_id == pointer.id {
emit(
ev.entity,
|h| h.enter,
"pointerEnter",
ev.pointer_location.position,
);
}
}
for ev in leaves.read() {
if ev.pointer_id == pointer.id {
emit(
ev.entity,
|h| h.leave,
"pointerLeave",
ev.pointer_location.position,
);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn apply_surface_interaction_styles(
mut commands: Commands,
pointer: Option<Res<SurfaceVirtualPointer>>,
mut enters: MessageReader<Pointer<Enter>>,
mut leaves: MessageReader<Pointer<Leave>>,
mut presses: MessageReader<Pointer<Press>>,
mut releases: MessageReader<Pointer<Release>>,
variants: Query<&StyleVariants>,
child_of: Query<&ChildOf>,
rnodes: Query<&RNode>,
) {
let Some(pointer) = pointer else { return };
let mut restyle = |entity: Entity, style: Option<Style>| {
let _diag = rnodes
.get(entity)
.ok()
.map(|r| crate::diag::node_scope(r.0));
apply_style(&mut commands.entity(entity), &style);
};
let target = |entity: Entity| climb(entity, &child_of, |e| variants.contains(e));
for ev in leaves.read() {
if ev.pointer_id == pointer.id
&& let Ok(v) = variants.get(ev.entity)
{
restyle(ev.entity, v.base.clone());
}
}
for ev in enters.read() {
if ev.pointer_id == pointer.id
&& let Ok(v) = variants.get(ev.entity)
{
restyle(ev.entity, overlay_style(&v.base, &v.hover));
}
}
for ev in releases.read() {
if ev.pointer_id == pointer.id
&& ev.button == PointerButton::Primary
&& let Some(t) = target(ev.entity)
&& let Ok(v) = variants.get(t)
{
restyle(t, overlay_style(&v.base, &v.hover));
}
}
for ev in presses.read() {
if ev.pointer_id == pointer.id
&& ev.button == PointerButton::Primary
&& let Some(t) = target(ev.entity)
&& let Ok(v) = variants.get(t)
{
let pressed = overlay_style(&overlay_style(&v.base, &v.hover), &v.press);
restyle(t, pressed);
}
}
}