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, ReactNode, StyleVariants};
use crate::ext::VirtualPointers;
use crate::style::Style;
use crate::ui_map::{apply_style, overlay_style};
pub fn collect_virtual_clicks(
bridge: Res<JsBridge>,
pointers: Res<VirtualPointers>,
mut clicks: MessageReader<Pointer<Click>>,
targets: Query<&ReactNode, super::events::ClickOwners>,
child_of: Query<&ChildOf>,
) {
if pointers.is_empty() {
return;
}
let mut topmost: Option<(f32, Entity)> = None;
for ev in clicks.read() {
if !pointers.contains(ev.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)
{
tracing::debug!("virtual-pointer click -> reconciler node {}", rnode.0);
send_ui_event(&bridge, rnode.0, "click", None, None, None);
}
}
#[allow(clippy::too_many_arguments)]
pub fn collect_virtual_pointer_events(
bridge: Res<JsBridge>,
pointers: Res<VirtualPointers>,
mut presses: MessageReader<Pointer<Press>>,
mut releases: MessageReader<Pointer<Release>>,
mut drags: MessageReader<Pointer<Drag>>,
nodes: Query<(
&ReactNode,
&PointerHandlers,
&ComputedNode,
&UiGlobalTransform,
)>,
child_of: Query<&ChildOf>,
) {
if pointers.is_empty() {
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 pointers.contains(ev.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 pointers.contains(ev.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 pointers.contains(ev.pointer_id) {
emit(
ev.entity,
|h| h.up,
"pointerUp",
ev.pointer_location.position,
ev.button,
&mut seen,
);
}
}
}
pub fn collect_virtual_hover_events(
bridge: Res<JsBridge>,
pointers: Res<VirtualPointers>,
mut enters: MessageReader<Pointer<Enter>>,
mut leaves: MessageReader<Pointer<Leave>>,
nodes: Query<(
&ReactNode,
&PointerHandlers,
&ComputedNode,
&UiGlobalTransform,
)>,
) {
if pointers.is_empty() {
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 pointers.contains(ev.pointer_id) {
emit(
ev.entity,
|h| h.enter,
"pointerEnter",
ev.pointer_location.position,
);
}
}
for ev in leaves.read() {
if pointers.contains(ev.pointer_id) {
emit(
ev.entity,
|h| h.leave,
"pointerLeave",
ev.pointer_location.position,
);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn apply_virtual_interaction_styles(
mut commands: Commands,
pointers: Res<VirtualPointers>,
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<&ReactNode>,
flags: Query<&crate::ext::ElementFlags>,
assets: Res<AssetServer>,
bridge: Option<Res<crate::bridge::JsBridge>>,
fonts: Option<Res<crate::plugin::Fonts>>,
) {
if pointers.is_empty() {
return;
}
let default_fonts = crate::plugin::Fonts::default();
let fonts = fonts.as_deref().unwrap_or(&default_fonts);
let styles = bridge
.as_ref()
.map_or(crate::ext::core_registry(), |b| &*b.ext)
.styles();
let mut restyle = |entity: Entity, style: Option<Style>| {
let rnode = rnodes.get(entity).ok();
let _diag = rnode.map(|r| crate::diag::node_scope(r.0));
let kind = match (bridge.as_ref(), rnode) {
(Some(b), Some(r)) => b.shared_tags.kind_cow(r.0),
_ => std::borrow::Cow::Borrowed("node"),
};
let info = bridge
.as_ref()
.map_or(crate::ext::core_registry(), |b| &*b.ext)
.element_or_fallback(&kind);
let attrs = match (bridge.as_ref(), rnode) {
(Some(b), Some(r)) => b.props_cache.get(&r.0).map(|p| &p.attrs),
_ => None,
}
.unwrap_or(crate::element::Attrs::empty());
let wctx = crate::style::WriterCtx {
promoted: false,
fresh: false,
kind: &kind,
flags: flags.get(entity).copied().unwrap_or(info.decl.flags),
assets: &assets,
fonts,
styles,
element: info,
attrs,
events: crate::element::Attrs::empty(),
id: rnode.map_or(0, |r| r.0),
};
apply_style(&mut commands.entity(entity), &style, &wctx);
};
let target = |entity: Entity| climb(entity, &child_of, |e| variants.contains(e));
for ev in leaves.read() {
if pointers.contains(ev.pointer_id)
&& let Ok(v) = variants.get(ev.entity)
{
restyle(ev.entity, v.base.as_ref().cloned());
}
}
for ev in enters.read() {
if pointers.contains(ev.pointer_id)
&& let Ok(v) = variants.get(ev.entity)
{
restyle(ev.entity, overlay_style(v.base.as_ref(), v.hover.as_ref()));
}
}
for ev in releases.read() {
if pointers.contains(ev.pointer_id)
&& ev.button == PointerButton::Primary
&& let Some(t) = target(ev.entity)
&& let Ok(v) = variants.get(t)
{
restyle(t, overlay_style(v.base.as_ref(), v.hover.as_ref()));
}
}
for ev in presses.read() {
if pointers.contains(ev.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.as_ref(), v.hover.as_ref()).as_ref(),
v.press.as_ref(),
);
restyle(t, pressed);
}
}
}