use std::{
ops::Not,
sync::{Arc, OnceLock},
time::Duration,
};
use apply::Apply;
use bevy_app::prelude::*;
use bevy_ecs::{component::Mutable, lifecycle::HookContext, prelude::*, system::SystemId, world::DeferredWorld};
use bevy_log::prelude::*;
use bevy_math::Vec2;
use bevy_picking::{
backend::prelude::*,
hover::{HoverMap, PickingInteraction},
pointer::{Location, PointerMap},
prelude::*,
};
use bevy_platform::collections::HashMap;
use bevy_reflect::prelude::*;
use bevy_time::{Time, Timer, TimerMode};
use bevy_ui::Pressed;
use bevy_window::*;
use jonmo::prelude::*;
use super::{
element::UiRoot,
global_event_aware::{GlobalEventAware, GlobalEventData},
utils::{HaalkaObserver, clone, observe, register_system, remove_system_holder_on_despawn},
};
trait PointerDataInternal {
fn update_from_move(&mut self, hit: HitData, pointer_location: Location);
}
fn create_move_observer<D: PointerDataInternal + Component<Mutability = Mutable>>(world: &mut World, entity: Entity) {
world.spawn((
Observer::new(|move_event: On<Pointer<Move>>, mut datas: Query<&mut D>| {
let entity = move_event.entity;
if let Ok(mut data) = datas.get_mut(entity) {
data.update_from_move(move_event.hit.clone(), move_event.pointer_location.clone());
}
})
.with_entity(entity),
HaalkaObserver,
));
}
#[derive(Component, Default)]
pub(crate) struct DisableableSignalState {
pub(crate) flags: Vec<bool>,
}
#[derive(Component, Default)]
struct ThrottleTimers {
timers: HashMap<usize, Timer>,
was_active: HashMap<usize, bool>,
next_id: usize,
}
impl ThrottleTimers {
fn add_timer(&mut self, duration: Duration) -> usize {
let id = self.next_id;
self.next_id += 1;
self.timers.insert(id, Timer::new(duration, TimerMode::Once));
self.was_active.insert(id, false);
id
}
fn add_timer_ready(&mut self, duration: Duration) -> usize {
let id = self.next_id;
self.next_id += 1;
let mut timer = Timer::new(duration, TimerMode::Once);
timer.tick(duration); self.timers.insert(id, timer);
id
}
fn tick_and_check(&mut self, id: usize, delta: std::time::Duration, is_active: bool) -> bool {
let was_active = self.was_active.get(&id).copied().unwrap_or(false);
self.was_active.insert(id, is_active);
if !is_active {
if let Some(timer) = self.timers.get_mut(&id) {
timer.reset();
timer.tick(timer.duration());
}
return false;
}
if !was_active {
if let Some(timer) = self.timers.get_mut(&id) {
timer.reset();
}
return true;
}
if let Some(timer) = self.timers.get_mut(&id)
&& timer.tick(delta).is_finished()
{
timer.reset();
return true;
}
false
}
fn check_discrete(&mut self, id: usize, delta: std::time::Duration) -> bool {
if let Some(timer) = self.timers.get_mut(&id) {
timer.tick(delta);
if timer.is_finished() {
timer.reset();
return true;
}
}
false
}
}
fn add_throttle_timer(world: &mut World, entity: Entity, duration: Duration) -> usize {
let mut entity = world.entity_mut(entity);
if let Some(mut collection) = entity.get_mut::<ThrottleTimers>() {
collection.add_timer(duration)
} else {
let mut collection = ThrottleTimers::default();
let id = collection.add_timer(duration);
entity.insert(collection);
id
}
}
fn add_throttle_timer_ready(world: &mut World, entity: Entity, duration: Duration) -> usize {
let mut entity = world.entity_mut(entity);
if let Some(mut collection) = entity.get_mut::<ThrottleTimers>() {
collection.add_timer_ready(duration)
} else {
let mut collection = ThrottleTimers::default();
let id = collection.add_timer_ready(duration);
entity.insert(collection);
id
}
}
#[allow(clippy::type_complexity)]
fn change_setup<D: 'static, Marker>(
handler: impl IntoSystem<In<(Entity, D)>, (), Marker> + Send + Sync + 'static,
system_holder: Arc<OnceLock<SystemId<In<(Entity, D)>, ()>>>,
) -> impl FnOnce(jonmo::Builder) -> jonmo::Builder {
move |builder| {
builder
.on_spawn(clone!((system_holder) move |world, _| {
let _ = system_holder.set(register_system(world, handler));
}))
.apply(remove_system_holder_on_despawn(system_holder))
}
}
#[allow(clippy::type_complexity)]
fn change_system<D: Clone + Send + Sync + 'static>(
system_holder: Arc<OnceLock<SystemId<In<(Entity, D)>, ()>>>,
get_field: fn(&D) -> bool,
) -> impl FnMut(In<(Entity, D)>, Local<Option<bool>>, Commands) + Send + Sync + 'static {
move |In((entity, data)): In<(Entity, D)>, mut prev: Local<Option<bool>>, mut commands: Commands| {
let field = get_field(&data);
if prev.is_none_or(|prev| prev != field) {
*prev = Some(field);
commands.run_system_with(system_holder.get().copied().unwrap(), (entity, data));
}
}
}
#[allow(clippy::type_complexity)]
fn throttle_setup<D: 'static, Marker>(
handler: impl IntoSystem<In<(Entity, D)>, (), Marker> + Send + Sync + 'static,
duration: Duration,
system_holder: Arc<OnceLock<SystemId<In<(Entity, D)>, ()>>>,
timer_id: Arc<OnceLock<usize>>,
) -> impl FnOnce(jonmo::Builder) -> jonmo::Builder {
move |builder| {
builder
.on_spawn(clone!((system_holder, timer_id) move |world, entity| {
let _ = system_holder.set(register_system(world, handler));
let _ = timer_id.set(add_throttle_timer(world, entity, duration));
}))
.apply(remove_system_holder_on_despawn(system_holder))
}
}
#[allow(clippy::type_complexity)]
fn throttle_system<D: Clone + Send + Sync + 'static>(
system_holder: Arc<OnceLock<SystemId<In<(Entity, D)>, ()>>>,
timer_id: Arc<OnceLock<usize>>,
get_field: fn(&D) -> bool,
) -> impl FnMut(In<(Entity, D)>, Commands, Res<Time>, Query<&mut ThrottleTimers>) + Send + Sync + 'static {
move |In((entity, data)): In<(Entity, D)>,
mut commands: Commands,
time: Res<Time>,
mut collections: Query<&mut ThrottleTimers>| {
let is_active = get_field(&data);
if let (Ok(mut collection), Some(&id)) = (collections.get_mut(entity), timer_id.get())
&& collection.tick_and_check(id, time.delta(), is_active)
{
commands.run_system_with(system_holder.get().copied().unwrap(), (entity, data));
}
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn disableable_signal_setup<D: 'static, Marker>(
handler: impl IntoSystem<In<(Entity, D)>, (), Marker> + Send + Sync + 'static,
disabled: impl Signal<Item = bool> + 'static,
system_holder: Arc<OnceLock<SystemId<In<(Entity, D)>, ()>>>,
state_index: Arc<OnceLock<usize>>,
) -> impl FnOnce(jonmo::Builder) -> jonmo::Builder {
move |builder| {
builder
.on_spawn(clone!((system_holder, state_index) move |world, entity| {
let _ = system_holder.set(register_system(world, handler));
let mut entity = world.entity_mut(entity);
let index = if let Some(mut state) = entity.get_mut::<DisableableSignalState>() {
let index = state.flags.len();
state.flags.push(false);
index
} else {
entity.insert(DisableableSignalState { flags: vec![false] });
0
};
let _ = state_index.set(index);
}))
.on_signal_with_entity(
disabled,
clone!((state_index) move |mut entity, disabled| {
let Some(index) = state_index.get().copied() else {
return;
};
if let Some(mut state) = entity.get_mut::<DisableableSignalState>()
&& let Some(flag) = state.flags.get_mut(index) {
*flag = disabled;
}
}),
)
.apply(remove_system_holder_on_despawn(system_holder))
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn disableable_signal_system<D: Send + Sync + 'static>(
system_holder: Arc<OnceLock<SystemId<In<(Entity, D)>, ()>>>,
state_index: Arc<OnceLock<usize>>,
) -> impl FnMut(In<(Entity, D)>, Query<&DisableableSignalState>, Commands) + Send + Sync + 'static {
move |In((entity, data)): In<(Entity, D)>, states: Query<&DisableableSignalState>, mut commands: Commands| {
if let Some(index) = state_index.get().copied()
&& let Ok(state) = states.get(entity)
&& *state.flags.get(index).unwrap_or(&false)
{
return;
}
commands.run_system_with(system_holder.get().copied().unwrap(), (entity, data));
}
}
#[derive(Clone)]
pub struct HoverData {
pub hovered: bool,
pub hit: HitData,
pub pointer_id: PointerId,
pub pointer_location: Location,
}
#[derive(Clone)]
pub struct PressData {
pub pressed: bool,
pub button: PointerButton,
pub hit: HitData,
pub pointer_id: PointerId,
pub pointer_location: Location,
}
#[derive(Clone)]
pub struct DragData {
pub dragged: bool,
pub button: PointerButton,
pub pointer_id: PointerId,
pub pointer_location: Location,
pub hit: HitData,
pub delta: Vec2,
}
pub trait PointerEventAware: GlobalEventAware {
fn on_hovered_disableable<Disabled: Component, Marker>(
self,
handler: impl IntoSystem<In<(Entity, HoverData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.with_builder(|builder| {
let hover_handler_holder = Arc::new(OnceLock::new());
let hovering_handler_holder = Arc::new(OnceLock::new());
builder
.insert(Hoverable)
.on_spawn(
clone!((hover_handler_holder, hovering_handler_holder) move |world, entity| {
let hover_handler_system = register_system(world, handler);
let _ = hover_handler_holder.set(hover_handler_system);
let hovering_handler_system = register_system(
world,
move |In(entity): In<Entity>,
hover_datas: Query<&HoverDataInternal>,
disabled: Query<&Disabled>,
mut commands: Commands| {
if disabled.contains(entity) {
return;
}
if let Ok(hover_data) = hover_datas.get(entity) {
commands.run_system_with(
hover_handler_system,
(entity, HoverData {
hovered: true,
hit: hover_data.hit.clone(),
pointer_id: hover_data.pointer_id,
pointer_location: hover_data.pointer_location.clone(),
}),
);
}
},
);
let _ = hovering_handler_holder.set(hovering_handler_system);
if let Some(mut systems) = world.get_mut::<HoveredSystems>(entity) {
systems.0.push(hovering_handler_system);
} else {
world.entity_mut(entity).insert(HoveredSystems(vec![hovering_handler_system]));
}
if world.get::<HoverMoveObserver>(entity).is_none() {
create_move_observer::<HoverDataInternal>(world, entity);
world.entity_mut(entity).insert(HoverMoveObserver);
}
observe(
world,
entity,
move |enter: On<Pointer<Enter>>,
disabled: Query<&Disabled>,
mut commands: Commands| {
let entity = enter.entity;
if disabled.contains(entity) {
return;
}
let hit = enter.hit.clone();
let pointer_id = enter.pointer_id;
let pointer_location = enter.pointer_location.clone();
if let Ok(mut entity) = commands.get_entity(entity) {
entity.insert(HoverDataInternal {
hit: hit.clone(),
pointer_id,
pointer_location: pointer_location.clone(),
});
entity.insert(Hovered);
}
commands.run_system_with(
hover_handler_system,
(entity, HoverData {
hovered: true,
hit,
pointer_id,
pointer_location,
}),
);
},
);
observe(
world,
entity,
move |leave: On<Pointer<Leave>>,
disabled: Query<&Disabled>,
mut commands: Commands| {
let entity = leave.entity;
let hit = leave.hit.clone();
let pointer_id = leave.pointer_id;
let pointer_location = leave.pointer_location.clone();
if !disabled.contains(entity) {
commands.run_system_with(hover_handler_system, (entity, HoverData {
hovered: false,
hit,
pointer_id,
pointer_location,
}));
}
if let Ok(mut entity) = commands.get_entity(entity) {
entity.remove::<HoverDataInternal>();
}
},
);
}),
)
.apply(remove_system_holder_on_despawn(hover_handler_holder))
.apply(remove_system_holder_on_despawn(hovering_handler_holder))
})
}
fn on_hovered_disableable_signal<Marker>(
self,
handler: impl IntoSystem<In<(Entity, HoverData)>, (), Marker> + Send + Sync + 'static,
disabled: impl Signal<Item = bool> + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let state_index = Arc::new(OnceLock::new());
self.with_builder(disableable_signal_setup(
handler,
disabled,
system_holder.clone(),
state_index.clone(),
))
.on_hovered_disableable::<HoverHandlingDisabled, _>(disableable_signal_system(system_holder, state_index))
}
fn on_hovered<Marker>(
self,
handler: impl IntoSystem<In<(Entity, HoverData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.on_hovered_disableable::<HoverHandlingDisabled, _>(handler)
}
fn on_hovered_change_disableable<Disabled: Component, Marker>(
self,
handler: impl IntoSystem<In<(Entity, HoverData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
self.with_builder(change_setup(handler, system_holder.clone()))
.on_hovered_disableable::<Disabled, _>(change_system(system_holder, |d| d.hovered))
}
fn on_hovered_change_disableable_signal<Marker>(
self,
handler: impl IntoSystem<In<(Entity, HoverData)>, (), Marker> + Send + Sync + 'static,
disabled: impl Signal<Item = bool> + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let state_index = Arc::new(OnceLock::new());
self.with_builder(disableable_signal_setup(
handler,
disabled,
system_holder.clone(),
state_index.clone(),
))
.on_hovered_change_disableable::<HoverHandlingDisabled, _>(disableable_signal_system(
system_holder,
state_index,
))
}
fn on_hovered_change<Marker>(
self,
handler: impl IntoSystem<In<(Entity, HoverData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.on_hovered_change_disableable::<HoverHandlingDisabled, _>(handler)
}
fn on_hovered_throttled<Marker>(
self,
handler: impl IntoSystem<In<(Entity, HoverData)>, (), Marker> + Send + Sync + 'static,
duration: Duration,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let timer_id = Arc::new(OnceLock::new());
self.with_builder(throttle_setup(
handler,
duration,
system_holder.clone(),
timer_id.clone(),
))
.on_hovered_disableable::<HoverHandlingDisabled, _>(throttle_system(system_holder, timer_id, |d| d.hovered))
}
fn on_click_disableable<Disabled: Component, Marker>(
self,
handler: impl IntoSystem<In<(Entity, Pointer<Click>)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.with_builder(|builder| {
let system_holder = Arc::new(OnceLock::new());
builder
.on_spawn(clone!((system_holder) move |world, entity| {
let system = register_system(world, handler);
let _ = system_holder.set(system);
observe(world, entity, move |click: On<Pointer<Click>>, disabled: Query<&Disabled>, mut commands: Commands| {
if disabled.contains(click.entity) {
return;
}
commands.run_system_with(system, (click.entity, (*click).clone()));
});
}))
.apply(remove_system_holder_on_despawn(system_holder))
})
}
fn on_click<Marker>(
self,
handler: impl IntoSystem<In<(Entity, Pointer<Click>)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.on_click_disableable::<ClickHandlingDisabled, _>(handler)
}
fn on_click_disableable_signal<Marker>(
self,
handler: impl IntoSystem<In<(Entity, Pointer<Click>)>, (), Marker> + Send + Sync + 'static,
disabled: impl Signal<Item = bool> + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let state_index = Arc::new(OnceLock::new());
self.with_builder(disableable_signal_setup(
handler,
disabled,
system_holder.clone(),
state_index.clone(),
))
.on_click_disableable::<ClickHandlingDisabled, _>(disableable_signal_system(system_holder, state_index))
}
fn on_click_throttled<Marker>(
self,
handler: impl IntoSystem<In<(Entity, Pointer<Click>)>, (), Marker> + Send + Sync + 'static,
duration: Duration,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let timer_id = Arc::new(OnceLock::new());
self.with_builder(|builder| {
builder
.on_spawn(clone!((system_holder, timer_id) move |world, entity| {
let system = register_system(world, handler);
let _ = system_holder.set(system);
let _ = timer_id.set(add_throttle_timer_ready(world, entity, duration));
observe(world, entity, move |click: On<Pointer<Click>>,
time: Res<Time>,
mut collections: Query<&mut ThrottleTimers>,
mut commands: Commands| {
if let (Ok(mut collection), Some(&id)) = (collections.get_mut(click.entity), timer_id.get())
&& collection.check_discrete(id, time.delta()) {
commands.run_system_with(system, (click.entity, (*click).clone()));
}
});
}))
.apply(remove_system_holder_on_despawn(system_holder))
})
}
fn on_click_outside_disableable<Disabled: Component, Marker>(
self,
handler: impl IntoSystem<In<(Entity, GlobalEventData<Pointer<Click>>)>, (), Marker> + Send + Sync + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
self.with_builder(|builder| {
builder
.on_spawn(clone!((system_holder) move |world, _| {
let _ = system_holder.set(register_system(world, handler));
}))
.apply(remove_system_holder_on_despawn(system_holder.clone()))
})
.on_global_event::<Pointer<Click>, _, _, _>(
move |In((entity, click)): In<(Entity, GlobalEventData<Pointer<Click>>)>,
childrens: Query<&Children>,
child_ofs: Query<&ChildOf>,
ui_roots: Query<&UiRoot>,
disabled: Query<&Disabled>,
mut commands: Commands| {
if disabled.contains(entity) {
return;
}
for ancestor in child_ofs.iter_ancestors(entity) {
if ui_roots.contains(ancestor) {
if !is_inside_or_removed_from_dom(entity, &click, ancestor, &childrens) {
commands.run_system_with(system_holder.get().copied().unwrap(), (entity, click));
}
break;
}
}
},
)
}
fn on_click_outside<Marker>(
self,
handler: impl IntoSystem<In<(Entity, GlobalEventData<Pointer<Click>>)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.on_click_outside_disableable::<ClickOutsideHandlingDisabled, _>(handler)
}
fn on_click_outside_disableable_signal<Marker>(
self,
handler: impl IntoSystem<In<(Entity, GlobalEventData<Pointer<Click>>)>, (), Marker> + Send + Sync + 'static,
disabled: impl Signal<Item = bool> + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let state_index = Arc::new(OnceLock::new());
self.with_builder(disableable_signal_setup(
handler,
disabled,
system_holder.clone(),
state_index.clone(),
))
.on_click_outside_disableable::<ClickOutsideHandlingDisabled, _>(disableable_signal_system(
system_holder,
state_index,
))
}
fn on_click_outside_throttled<Marker>(
self,
handler: impl IntoSystem<In<(Entity, GlobalEventData<Pointer<Click>>)>, (), Marker> + Send + Sync + 'static,
duration: Duration,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let timer_id = Arc::new(OnceLock::new());
self.with_builder(|builder| {
builder
.on_spawn(clone!((system_holder, timer_id) move |world, entity| {
let _ = system_holder.set(register_system(world, handler));
let _ = timer_id.set(add_throttle_timer_ready(world, entity, duration));
}))
.apply(remove_system_holder_on_despawn(system_holder.clone()))
})
.on_global_event::<Pointer<Click>, _, _, _>(
move |In((entity, click)): In<(Entity, GlobalEventData<Pointer<Click>>)>,
childrens: Query<&Children>,
child_ofs: Query<&ChildOf>,
ui_roots: Query<&UiRoot>,
time: Res<Time>,
mut collections: Query<&mut ThrottleTimers>,
mut commands: Commands| {
for ancestor in child_ofs.iter_ancestors(entity) {
if ui_roots.contains(ancestor) {
if !is_inside_or_removed_from_dom(entity, &click, ancestor, &childrens)
&& let (Ok(mut collection), Some(&id)) = (collections.get_mut(entity), timer_id.get())
&& collection.check_discrete(id, time.delta())
{
commands.run_system_with(system_holder.get().copied().unwrap(), (entity, click));
}
break;
}
}
},
)
}
fn on_pressed_disableable<Disabled: Component, Marker>(
self,
handler: impl IntoSystem<In<(Entity, PressData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.with_builder(|builder| {
let press_handler_holder = Arc::new(OnceLock::new());
let pressing_handler_holder = Arc::new(OnceLock::new());
builder
.insert(Pressable)
.on_spawn(
clone!((press_handler_holder, pressing_handler_holder) move |world, entity| {
let press_handler_system = register_system(world, handler);
let _ = press_handler_holder.set(press_handler_system);
let pressing_handler_system = register_system(
world,
move |In(entity): In<Entity>,
press_datas: Query<&PressDataInternal>,
disabled: Query<&Disabled>,
mut commands: Commands| {
if disabled.contains(entity) {
return;
}
if let Ok(press_data) = press_datas.get(entity) {
commands.run_system_with(
press_handler_system,
(entity, PressData {
pressed: true,
button: press_data.button,
hit: press_data.hit.clone(),
pointer_id: press_data.pointer_id,
pointer_location: press_data.pointer_location.clone(),
}),
);
}
},
);
let _ = pressing_handler_holder.set(pressing_handler_system);
if let Some(mut systems) = world.get_mut::<PressedSystems>(entity) {
systems.0.push(pressing_handler_system);
} else {
world.entity_mut(entity).insert(PressedSystems(vec![pressing_handler_system]));
}
if world.get::<PressMoveObserver>(entity).is_none() {
create_move_observer::<PressDataInternal>(world, entity);
world.entity_mut(entity).insert(PressMoveObserver);
}
observe(
world,
entity,
move |press: On<Pointer<Press>>,
disabled: Query<&Disabled>,
mut commands: Commands| {
let entity = press.entity;
if disabled.contains(entity) {
return;
}
let button = press.button;
let hit = press.hit.clone();
let pointer_id = press.pointer_id;
let pointer_location = press.pointer_location.clone();
if let Ok(mut entity) = commands.get_entity(entity) {
entity.insert(PressDataInternal {
button,
hit: hit.clone(),
pointer_id,
pointer_location: pointer_location.clone(),
});
}
commands.run_system_with(
press_handler_system,
(entity, PressData {
pressed: true,
button,
hit,
pointer_id,
pointer_location,
}),
);
},
);
observe(
world,
entity,
move |release: On<Pointer<Release>>,
disabled: Query<&Disabled>,
press_datas: Query<&PressDataInternal>,
mut commands: Commands| {
let entity = release.entity;
if let Ok(press_data) = press_datas.get(entity) {
if press_data.button != release.button {
return;
}
} else {
return;
}
let button = release.button;
let hit = release.hit.clone();
let pointer_id = release.pointer_id;
let pointer_location = release.pointer_location.clone();
if !disabled.contains(entity) {
commands.run_system_with(press_handler_system, (entity, PressData {
pressed: false,
button,
hit,
pointer_id,
pointer_location,
}));
}
if let Ok(mut entity) = commands.get_entity(entity) {
entity.remove::<PressDataInternal>();
}
},
);
}),
)
.apply(remove_system_holder_on_despawn(press_handler_holder))
.apply(remove_system_holder_on_despawn(pressing_handler_holder))
})
.on_hovered_change(|In((entity, data)): In<(Entity, HoverData)>, mut commands: Commands| {
if !data.hovered
&& let Ok(mut entity) = commands.get_entity(entity)
{
entity.remove::<PressDataInternal>();
}
})
}
fn on_pressed_disableable_signal<Marker>(
self,
handler: impl IntoSystem<In<(Entity, PressData)>, (), Marker> + Send + Sync + 'static,
disabled: impl Signal<Item = bool> + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let state_index = Arc::new(OnceLock::new());
self.with_builder(disableable_signal_setup(
handler,
disabled,
system_holder.clone(),
state_index.clone(),
))
.on_pressed_disableable::<PressHandlingDisabled, _>(disableable_signal_system(system_holder, state_index))
}
fn on_pressed<Marker>(
self,
handler: impl IntoSystem<In<(Entity, PressData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.on_pressed_disableable::<PressHandlingDisabled, _>(handler)
}
fn on_pressed_change_disableable<Disabled: Component, Marker>(
self,
handler: impl IntoSystem<In<(Entity, PressData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
self.with_builder(change_setup(handler, system_holder.clone()))
.on_pressed_disableable::<Disabled, _>(change_system(system_holder, |d| d.pressed))
}
fn on_pressed_change_disableable_signal<Marker>(
self,
handler: impl IntoSystem<In<(Entity, PressData)>, (), Marker> + Send + Sync + 'static,
disabled: impl Signal<Item = bool> + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let state_index = Arc::new(OnceLock::new());
self.with_builder(disableable_signal_setup(
handler,
disabled,
system_holder.clone(),
state_index.clone(),
))
.on_pressed_change_disableable::<PressHandlingDisabled, _>(disableable_signal_system(
system_holder,
state_index,
))
}
fn on_pressed_change<Marker>(
self,
handler: impl IntoSystem<In<(Entity, PressData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.on_pressed_change_disableable::<PressHandlingDisabled, _>(handler)
}
fn on_pressed_throttled<Marker>(
self,
handler: impl IntoSystem<In<(Entity, PressData)>, (), Marker> + Send + Sync + 'static,
duration: Duration,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let timer_id = Arc::new(OnceLock::new());
self.with_builder(throttle_setup(
handler,
duration,
system_holder.clone(),
timer_id.clone(),
))
.on_pressed_disableable::<PressHandlingDisabled, _>(throttle_system(system_holder, timer_id, |d| d.pressed))
}
fn on_dragged_disableable<Disabled: Component, Marker>(
self,
handler: impl IntoSystem<In<(Entity, DragData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.with_builder(|builder| {
let drag_handler_holder = Arc::new(OnceLock::new());
let dragging_handler_holder = Arc::new(OnceLock::new());
builder
.insert(Draggable)
.on_spawn(
clone!((drag_handler_holder, dragging_handler_holder) move |world, entity| {
let drag_handler_system = register_system(world, handler);
let _ = drag_handler_holder.set(drag_handler_system);
let dragging_handler_system = register_system(
world,
move |In(entity): In<Entity>,
disabled: Query<&Disabled>,
drag_datas: Query<&DragDataInternal>,
mut commands: Commands| {
if disabled.contains(entity) {
return;
}
if let Ok(drag_data) = drag_datas.get(entity) {
if !drag_data.has_new_delta {
return;
}
let delta = drag_data.delta;
commands.run_system_with(
drag_handler_system,
(
entity,
DragData {
dragged: true,
button: drag_data.button,
pointer_id: drag_data.pointer_id,
pointer_location: drag_data.pointer_location.clone(),
hit: drag_data.hit.clone(),
delta,
},
),
);
}
},
);
let _ = dragging_handler_holder.set(dragging_handler_system);
if let Some(mut systems) = world.get_mut::<DraggedSystems>(entity) {
systems.0.push(dragging_handler_system);
} else {
world.entity_mut(entity).insert(DraggedSystems(vec![dragging_handler_system]));
}
if world.get::<DragMoveObserver>(entity).is_none() {
world.spawn((
Observer::new(
move |drag_event: On<Pointer<Drag>>,
mut drag_datas: Query<&mut DragDataInternal>| {
let entity = drag_event.entity;
if let Ok(mut drag_data) = drag_datas.get_mut(entity)
&& drag_data.button == drag_event.button
{
drag_data.pointer_location =
drag_event.pointer_location.clone();
drag_data.delta += drag_event.delta;
drag_data.has_new_delta = true;
}
},
)
.with_entity(entity),
HaalkaObserver,
));
world.entity_mut(entity).insert(DragMoveObserver);
}
observe(
world,
entity,
move |drag_start: On<Pointer<DragStart>>,
disabled: Query<&Disabled>,
mut commands: Commands| {
let entity = drag_start.entity;
if disabled.contains(entity) {
return;
}
let button = drag_start.button;
let hit = drag_start.hit.clone();
let pointer_id = drag_start.pointer_id;
let pointer_location = drag_start.pointer_location.clone();
if let Ok(mut entity) = commands.get_entity(entity) {
entity.insert(DragDataInternal {
button,
hit: hit.clone(),
pointer_id,
pointer_location: pointer_location.clone(),
delta: Vec2::ZERO,
has_new_delta: false,
});
entity.insert(Dragged);
}
commands.run_system_with(
drag_handler_system,
(entity, DragData {
dragged: true,
button,
hit,
pointer_id,
pointer_location,
delta: Vec2::ZERO,
}),
);
},
);
observe(
world,
entity,
move |drag_end: On<Pointer<DragEnd>>,
disabled: Query<&Disabled>,
drag_datas: Query<&DragDataInternal>,
mut commands: Commands| {
let entity = drag_end.entity;
let stored_hit = if let Ok(drag_data) = drag_datas.get(entity) {
if drag_data.button != drag_end.button {
return;
}
drag_data.hit.clone()
} else {
return;
};
let button = drag_end.button;
let pointer_id = drag_end.pointer_id;
let pointer_location = drag_end.pointer_location.clone();
if !disabled.contains(entity) {
commands.run_system_with(drag_handler_system, (entity, DragData {
dragged: false,
button,
hit: stored_hit,
pointer_id,
pointer_location,
delta: Vec2::ZERO,
}));
}
if let Ok(mut entity) = commands.get_entity(entity) {
entity.remove::<DragDataInternal>();
entity.remove::<Dragged>();
}
},
);
}),
)
.apply(remove_system_holder_on_despawn(drag_handler_holder))
.apply(remove_system_holder_on_despawn(dragging_handler_holder))
})
}
fn on_dragged_disableable_signal<Marker>(
self,
handler: impl IntoSystem<In<(Entity, DragData)>, (), Marker> + Send + Sync + 'static,
disabled: impl Signal<Item = bool> + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let state_index = Arc::new(OnceLock::new());
self.with_builder(disableable_signal_setup(
handler,
disabled,
system_holder.clone(),
state_index.clone(),
))
.on_dragged_disableable::<DragHandlingDisabled, _>(disableable_signal_system(system_holder, state_index))
}
fn on_dragged<Marker>(
self,
handler: impl IntoSystem<In<(Entity, DragData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.on_dragged_disableable::<DragHandlingDisabled, _>(handler)
}
fn on_dragged_change_disableable<Disabled: Component, Marker>(
self,
handler: impl IntoSystem<In<(Entity, DragData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
self.with_builder(change_setup(handler, system_holder.clone()))
.on_dragged_disableable::<Disabled, _>(change_system(system_holder, |d| d.dragged))
}
fn on_dragged_change_disableable_signal<Marker>(
self,
handler: impl IntoSystem<In<(Entity, DragData)>, (), Marker> + Send + Sync + 'static,
disabled: impl Signal<Item = bool> + 'static,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let state_index = Arc::new(OnceLock::new());
self.with_builder(disableable_signal_setup(
handler,
disabled,
system_holder.clone(),
state_index.clone(),
))
.on_dragged_change_disableable::<DragHandlingDisabled, _>(disableable_signal_system(system_holder, state_index))
}
fn on_dragged_change<Marker>(
self,
handler: impl IntoSystem<In<(Entity, DragData)>, (), Marker> + Send + Sync + 'static,
) -> Self {
self.on_dragged_change_disableable::<DragHandlingDisabled, _>(handler)
}
fn on_dragged_throttled<Marker>(
self,
handler: impl IntoSystem<In<(Entity, DragData)>, (), Marker> + Send + Sync + 'static,
duration: Duration,
) -> Self {
let system_holder = Arc::new(OnceLock::new());
let timer_id = Arc::new(OnceLock::new());
self.with_builder(throttle_setup(
handler,
duration,
system_holder.clone(),
timer_id.clone(),
))
.on_dragged_disableable::<DragHandlingDisabled, _>(throttle_system(system_holder, timer_id, |d| d.dragged))
}
}
#[derive(Component, Clone)]
pub struct Hovered;
#[derive(Component, Clone)]
pub struct Dragged;
#[derive(Component, Default, Clone)]
struct PressHandlingDisabled;
#[derive(Component, Default, Clone)]
struct HoverHandlingDisabled;
#[derive(Component, Default, Clone)]
struct DragHandlingDisabled;
#[derive(Component, Default, Clone)]
struct ClickHandlingDisabled;
#[derive(Component, Default, Clone)]
struct ClickOutsideHandlingDisabled;
#[derive(Clone, PartialEq, Debug, Reflect, Event)]
pub struct Enter {
pub hit: HitData,
}
#[derive(Clone, PartialEq, Debug, Reflect, Event)]
pub struct Leave {
pub hit: HitData,
}
#[derive(Component, Clone)]
struct LastSubtreeHoverHit(HitData);
#[allow(clippy::type_complexity)]
fn update_hover_states(
pointer_map: Res<PointerMap>,
pointers: Query<&PointerLocation>,
hover_map: Res<HoverMap>,
mut hovereds: Query<(Entity, Option<&Hovered>, Option<&LastSubtreeHoverHit>), Or<(With<Hoverable>, With<Hovered>)>>,
child_ofs: Query<&ChildOf>,
mut commands: Commands,
) {
let pointer_id = PointerId::Mouse;
let hover_set = hover_map.get(&pointer_id);
for (entity, hovered, last_subtree_hit) in hovereds.iter_mut() {
let hit_data_option = hover_set.and_then(|map| {
if let Some(hit) = map.get(&entity) {
Some(hit)
} else {
map.iter()
.find(|(hit_entity, _)| child_ofs.iter_ancestors(**hit_entity).any(|e| e == entity))
.map(|(_, hit_data)| hit_data)
}
});
let was_hovered = hovered.is_some();
if let Some(hit) = hit_data_option {
let should_update_cache = last_subtree_hit.is_none_or(|cached| cached.0 != *hit);
if should_update_cache && let Ok(mut entity_commands) = commands.get_entity(entity) {
entity_commands.try_insert(LastSubtreeHoverHit(hit.clone()));
}
}
let is_hovered = hit_data_option.is_some();
if was_hovered != is_hovered {
let Some(location) = pointer_map
.get_entity(pointer_id)
.and_then(|entity| pointers.get(entity).ok())
.and_then(|pointer| pointer.location.clone())
else {
debug!(
"Unable to get location for pointer {:?} during pointer {}",
pointer_id,
if is_hovered { "enter" } else { "leave" }
);
continue;
};
if let Some(hit) = hit_data_option.cloned() {
commands.trigger(Pointer::new(pointer_id, location, Enter { hit }, entity));
if let Ok(mut entity_commands) = commands.get_entity(entity) {
entity_commands.try_insert(Hovered);
}
continue;
}
if let Some(hit) = last_subtree_hit.map(|h| h.0.clone()) {
commands.trigger(Pointer::new(pointer_id, location, Leave { hit }, entity));
} else {
debug!(
"Unable to get cached subtree hit for pointer {:?} leave on {:?}",
pointer_id, entity
);
}
if let Ok(mut entity_commands) = commands.get_entity(entity) {
entity_commands.remove::<Hovered>();
entity_commands.remove::<LastSubtreeHoverHit>();
}
}
}
}
#[derive(Component, Clone)]
struct PressDataInternal {
button: PointerButton,
hit: HitData,
pointer_id: PointerId,
pointer_location: Location,
}
impl PointerDataInternal for PressDataInternal {
fn update_from_move(&mut self, hit: HitData, pointer_location: Location) {
self.hit = hit;
self.pointer_location = pointer_location;
}
}
#[derive(Component, Clone, Copy)]
struct PressMoveObserver;
#[derive(Component, Clone)]
struct HoverDataInternal {
hit: HitData,
pointer_id: PointerId,
pointer_location: Location,
}
impl PointerDataInternal for HoverDataInternal {
fn update_from_move(&mut self, hit: HitData, pointer_location: Location) {
self.hit = hit;
self.pointer_location = pointer_location;
}
}
#[derive(Component, Clone, Copy)]
struct HoverMoveObserver;
#[derive(Component, Clone, Default)]
struct HoveredSystems(Vec<SystemId<In<Entity>, ()>>);
#[derive(Component)]
#[component(on_add = on_hoverable_add, on_remove = on_hoverable_remove)]
pub struct Hoverable;
#[derive(Component)]
struct HoverablePropagationStoppers {
enter: Entity,
leave: Entity,
}
fn on_hoverable_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
world.commands().queue(move |world: &mut World| {
let enter = world
.entity_mut(entity)
.observe(|mut event: On<Pointer<Enter>>| {
event.propagate(false);
})
.id();
let leave = world
.entity_mut(entity)
.observe(|mut event: On<Pointer<Leave>>| {
event.propagate(false);
})
.id();
world
.entity_mut(entity)
.insert(HoverablePropagationStoppers { enter, leave });
});
}
fn on_hoverable_remove(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
world.commands().queue(move |world: &mut World| {
if let Some(&HoverablePropagationStoppers { enter, leave }) = world.get::<HoverablePropagationStoppers>(entity)
{
let _ = world.try_despawn(enter);
let _ = world.try_despawn(leave);
}
if let Ok(mut entity) = world.get_entity_mut(entity) {
entity.remove::<HoverablePropagationStoppers>();
}
});
}
#[derive(Component)]
pub struct Pressable;
#[derive(Component)]
#[component(on_add = on_draggable_add, on_remove = on_draggable_remove)]
pub struct Draggable;
#[derive(Component)]
struct DraggableObservers {
drag_start: Entity,
drag_end: Entity,
}
fn on_draggable_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
world.commands().queue(move |world: &mut World| {
let drag_start = world
.entity_mut(entity)
.observe(|drag_start: On<Pointer<DragStart>>, mut commands: Commands| {
if let Ok(mut entity) = commands.get_entity(drag_start.entity) {
entity.insert(Dragged);
}
})
.id();
let drag_end = world
.entity_mut(entity)
.observe(|drag_end: On<Pointer<DragEnd>>, mut commands: Commands| {
if let Ok(mut entity) = commands.get_entity(drag_end.entity) {
entity.remove::<Dragged>();
}
})
.id();
world
.entity_mut(entity)
.insert(DraggableObservers { drag_start, drag_end });
});
}
fn on_draggable_remove(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
if let Some(observers) = world.get::<DraggableObservers>(entity) {
let drag_start = observers.drag_start;
let drag_end = observers.drag_end;
world.commands().queue(move |world: &mut World| {
let _ = world.try_despawn(drag_start);
let _ = world.try_despawn(drag_end);
});
}
world.commands().queue(move |world: &mut World| {
if let Ok(mut entity) = world.get_entity_mut(entity) {
entity.remove::<DraggableObservers>();
entity.remove::<Dragged>();
}
});
}
#[derive(Component, Clone, Default)]
struct PressedSystems(Vec<SystemId<In<Entity>, ()>>);
#[derive(Component, Clone)]
struct DragDataInternal {
button: PointerButton,
hit: HitData,
pointer_id: PointerId,
pointer_location: Location,
delta: Vec2,
has_new_delta: bool,
}
impl PointerDataInternal for DragDataInternal {
fn update_from_move(&mut self, hit: HitData, pointer_location: Location) {
self.hit = hit;
self.pointer_location = pointer_location;
}
}
#[derive(Component, Clone, Copy)]
struct DragMoveObserver;
#[derive(Component, Clone, Default)]
struct DraggedSystems(Vec<SystemId<In<Entity>, ()>>);
#[allow(private_interfaces)]
pub fn pressed_system(mut interaction_query: Query<(Entity, &PressedSystems), With<Pressed>>, mut commands: Commands) {
for (entity, systems) in &mut interaction_query {
for &system in &systems.0 {
commands.run_system_with(system, entity);
}
}
}
#[allow(private_interfaces)]
pub fn dragged_system(mut interaction_query: Query<(Entity, &DraggedSystems), With<Dragged>>, mut commands: Commands) {
for (entity, systems) in &mut interaction_query {
for &system in &systems.0 {
commands.run_system_with(system, entity);
}
}
}
fn reset_drag_data(mut drag_datas: Query<&mut DragDataInternal, With<Dragged>>) {
for mut drag_data in &mut drag_datas {
if drag_data.has_new_delta {
drag_data.has_new_delta = false;
drag_data.delta = Vec2::ZERO;
}
}
}
#[allow(clippy::type_complexity)]
fn pressable_system(
mut interaction_query: Query<
(Entity, &PickingInteraction, Option<&Pressed>),
(With<Pressable>, Changed<PickingInteraction>),
>,
mut commands: Commands,
) {
for (entity, interaction, pressed_option) in &mut interaction_query {
let is_pressed = matches!(interaction, PickingInteraction::Pressed);
if is_pressed != pressed_option.is_some()
&& let Ok(mut entity) = commands.get_entity(entity)
{
if is_pressed {
entity.insert(Pressed);
} else {
entity.remove::<Pressed>();
}
}
}
}
#[allow(private_interfaces)]
pub fn hovered_system(mut hovering_query: Query<(Entity, &HoveredSystems), With<Hovered>>, mut commands: Commands) {
for (entity, systems) in &mut hovering_query {
for &system in &systems.0 {
commands.run_system_with(system, entity);
}
}
}
fn contains(left: Entity, right: Entity, childrens: &Query<&Children>) -> bool {
left == right || childrens.iter_descendants(left).any(|e| e == right)
}
fn is_inside_or_removed_from_dom(
element: Entity,
event: &GlobalEventData<Pointer<Click>>,
ui_root: Entity,
childrens: &Query<&Children>,
) -> bool {
if contains(element, event.original_event_target, childrens) {
return true;
}
if !contains(ui_root, event.original_event_target, childrens) {
return true;
}
false
}
#[derive(Component)]
struct CursorOver;
#[derive(Component, Default, Clone)]
struct CursorDisabled;
#[derive(Resource)]
pub struct CursorableDisabled;
#[derive(Component, Clone)]
pub struct Cursor(Option<CursorIcon>);
pub trait Cursorable: PointerEventAware {
fn cursor_disableable<Disabled: Component>(self, cursor_option: impl Into<Option<CursorIcon>>) -> Self {
let cursor_option = cursor_option.into();
self.with_builder(|builder| {
builder
.insert(CursorOverPropagationStopped)
.observe(
|event: On<Insert, CursorOver>,
cursor_on_hovers: Query<&Cursor>,
disabled: Query<&Disabled>,
cursor_over_disabled_option: Option<Res<CursorableDisabled>>,
mut commands: Commands| {
let entity = event.entity;
if let Ok(Cursor(cursor_option)) = cursor_on_hovers.get(entity).cloned() {
if cursor_over_disabled_option.is_none() {
if disabled.contains(entity).not() {
commands.trigger(SetCursor(cursor_option));
}
} else {
commands.insert_resource(QueuedCursor(cursor_option));
}
}
},
)
.observe(
|event: On<Insert, Cursor>, cursor_overs: Query<&CursorOver>, mut commands: Commands| {
let entity = event.entity;
if cursor_overs.contains(entity)
&& let Ok(mut entity) = commands.get_entity(entity)
{
entity.try_insert(CursorOver);
}
},
)
.insert(Cursor(cursor_option))
.observe(
move |event: On<Insert, Disabled>,
cursor_over: Query<&CursorOver>,
pointer_map: Res<PointerMap>,
pointers: Query<&PointerLocation>,
hover_map: Res<HoverMap>,
child_ofs: Query<&ChildOf>,
mut commands: Commands| {
let entity = event.event().entity;
if let Ok(mut entity) = commands.get_entity(entity) {
entity.remove::<CursorOverPropagationStopped>();
}
if cursor_over.get(entity).is_ok()
&& let Some(((hover_map, location), &ChildOf(parent))) = hover_map
.get(&PointerId::Mouse)
.zip(
pointer_map
.get_entity(PointerId::Mouse)
.and_then(|entity| pointers.get(entity).ok())
.and_then(|pointer| pointer.location.clone()),
)
.zip(child_ofs.get(entity).ok())
&& let Some(hit) = hover_map.get(&entity).cloned()
{
commands.trigger(Pointer::new(PointerId::Mouse, location, Over { hit }, parent));
}
},
)
.observe(
move |event: On<Remove, Disabled>, cursor_over: Query<&CursorOver>, mut commands: Commands| {
let entity = event.event().entity;
if let Ok(mut entity) = commands.get_entity(entity) {
entity.try_insert(CursorOverPropagationStopped);
if cursor_over.get(entity.id()).is_ok() {
entity.try_insert(CursorOver);
}
}
},
)
.observe(
|mut over: On<Pointer<Over>>,
propagation_stopped: Query<&CursorOverPropagationStopped>,
mut commands: Commands| {
let entity = over.entity;
if propagation_stopped.contains(entity) {
over.propagate(false);
}
if let Ok(mut entity) = commands.get_entity(entity) {
entity.try_insert(CursorOver);
}
},
)
.observe(|mut out: On<Pointer<Out>>, mut commands: Commands| {
out.propagate(false);
if let Ok(mut entity) = commands.get_entity(out.entity) {
entity.remove::<CursorOver>();
}
})
})
}
fn cursor(self, cursor_option: impl Into<Option<CursorIcon>>) -> Self {
self.cursor_disableable::<CursorDisabled>(cursor_option)
}
fn cursor_signal_disableable<Disabled: Component>(
self,
cursor_option_signal: impl Signal<Item = impl Into<Option<CursorIcon>> + 'static> + 'static,
) -> Self {
self.with_builder(|builder| {
builder.component_signal::<Cursor>(
cursor_option_signal.map_in(|into_option_cursor| Some(Cursor(into_option_cursor.into()))),
)
})
.cursor_disableable::<Disabled>(None)
}
fn cursor_signal_disableable_signal(
self,
cursor_option_signal: impl Signal<Item = impl Into<Option<CursorIcon>> + 'static> + 'static,
disabled: impl Signal<Item = bool> + 'static,
) -> Self {
self.with_builder(|builder| builder.component_signal(disabled.map_true_in(|| CursorDisabled)))
.cursor_signal_disableable::<CursorDisabled>(cursor_option_signal)
}
fn cursor_signal<S: Signal<Item = impl Into<Option<CursorIcon>> + 'static> + Send + Sync + 'static>(
mut self,
cursor_option_signal_option: impl Into<Option<S>>,
) -> Self {
if let Some(cursor_option_signal) = cursor_option_signal_option.into() {
self = self.cursor_signal_disableable::<CursorDisabled>(cursor_option_signal);
}
self
}
fn cursor_disableable_signal(
self,
cursor_option: impl Into<Option<CursorIcon>>,
disabled: impl Signal<Item = bool> + 'static,
) -> Self {
self.cursor_signal_disableable_signal(signal::once(cursor_option.into()), disabled)
}
}
#[derive(Event)]
pub struct SetCursor(pub Option<CursorIcon>);
#[derive(Component)]
struct CursorOverPropagationStopped;
#[derive(Resource)]
struct QueuedCursor(Option<CursorIcon>);
fn consume_queued_cursor(queued_cursor: Option<Res<QueuedCursor>>, mut commands: Commands) {
if let Some(cursor) = queued_cursor {
commands.trigger(SetCursor(cursor.0.clone()));
commands.remove_resource::<QueuedCursor>();
}
}
fn on_set_cursor(
event: On<SetCursor>,
mut cursor_options: Query<(Entity, &mut CursorOptions), With<PrimaryWindow>>,
mut commands: Commands,
) {
if let Ok((entity, mut cursor_options)) = cursor_options.single_mut() {
let SetCursor(icon_option) = event.event();
if let Some(icon) = icon_option.clone() {
if let Ok(mut window) = commands.get_entity(entity) {
window.try_insert(icon);
}
cursor_options.visible = true;
} else {
cursor_options.visible = false;
}
}
}
#[derive(Resource)]
pub struct UpdateHoverStatesDisabled;
pub(super) fn plugin(app: &mut App) {
app.add_observer(on_set_cursor).add_systems(
PreUpdate,
(
(
pressable_system.run_if(any_with_component::<Pressable>),
pressed_system.run_if(any_with_component::<PressedSystems>),
)
.chain(),
(dragged_system, reset_drag_data)
.chain()
.run_if(any_with_component::<DraggedSystems>),
(
update_hover_states.run_if(
any_with_component::<Hoverable>
.and(resource_exists_and_changed::<HoverMap>)
.and(not(resource_exists::<UpdateHoverStatesDisabled>)),
),
hovered_system.run_if(any_with_component::<HoveredSystems>),
)
.chain(),
consume_queued_cursor.run_if(resource_removed::<CursorableDisabled>),
)
.after(PickingSystems::Last),
);
}