From 17f963e4cc356f0175549eafe87aa116d00325d1 Mon Sep 17 00:00:00 2001
From: JMS55 <47158642+JMS55@users.noreply.github.com>
Date: Sat, 23 Dec 2023 12:09:09 -0800
Subject: [PATCH] Fix event dispatch

---
 src/apply_mutations.rs |  32 ++++--------
 src/events.rs          | 116 +++++++++++++++++++++++++++++++++--------
 src/tick.rs            |  27 ++++------
 3 files changed, 112 insertions(+), 63 deletions(-)

diff --git a/src/apply_mutations.rs b/src/apply_mutations.rs
index c3bd575..161395b 100644
--- a/src/apply_mutations.rs
+++ b/src/apply_mutations.rs
@@ -1,4 +1,7 @@
-use crate::{events::is_supported_event, parse_attributes::set_attribute, tick::IntrinsicTextNode};
+use crate::{
+    events::{insert_event_listener, remove_event_listener},
+    parse_attributes::set_attribute,
+};
 use bevy::{
     ecs::{entity::Entity, system::Command, world::World},
     hierarchy::{BuildWorldChildren, Children, DespawnRecursive, Parent},
@@ -75,7 +78,6 @@ pub fn apply_mutations(
                     TextLayoutInfo::default(),
                     TextFlags::default(),
                     ContentSize::default(),
-                    IntrinsicTextNode,
                 ));
                 element_id_to_bevy_ui_entity.insert(id, entity);
                 bevy_ui_entity_to_element_id.insert(entity, id);
@@ -184,21 +186,10 @@ pub fn apply_mutations(
                     .insert(Text::from_section(value, TextStyle::default()));
             }
             Mutation::NewEventListener { name, id } => {
-                if !is_supported_event(name) {
-                    panic!("Encountered unsupported bevy_dioxus event `{name}`.");
-                }
-                if name == "mouse_enter" || name == "mouse_exit" {
-                    world
-                        .entity_mut(element_id_to_bevy_ui_entity[&id])
-                        .insert(RelativeCursorPosition::default());
-                }
+                insert_event_listener(name, world.entity_mut(element_id_to_bevy_ui_entity[&id]));
             }
             Mutation::RemoveEventListener { name, id } => {
-                if name == "mouse_enter" || name == "mouse_exit" {
-                    world
-                        .entity_mut(element_id_to_bevy_ui_entity[&id])
-                        .remove::<RelativeCursorPosition>();
-                }
+                remove_event_listener(name, world.entity_mut(element_id_to_bevy_ui_entity[&id]));
             }
             Mutation::Remove { id } => {
                 let entity = element_id_to_bevy_ui_entity[&id];
@@ -336,13 +327,10 @@ impl BevyTemplateNode {
                     .id()
             }
             Self::IntrinsicTextNode(text) => world
-                .spawn((
-                    TextBundle {
-                        text: text.clone(),
-                        ..default()
-                    },
-                    IntrinsicTextNode,
-                ))
+                .spawn(TextBundle {
+                    text: text.clone(),
+                    ..default()
+                })
                 .id(),
         }
     }
diff --git a/src/events.rs b/src/events.rs
index 0555ccd..c05148c 100644
--- a/src/events.rs
+++ b/src/events.rs
@@ -1,9 +1,13 @@
 use bevy::{
     ecs::{
+        component::Component,
         entity::Entity,
         event::{Event, EventWriter, Events, ManualEventReader},
         system::{Local, Query, Resource},
+        world::World,
     },
+    hierarchy::Parent,
+    prelude::EntityWorldMut,
     ui::RelativeCursorPosition,
     utils::EntityHashSet,
 };
@@ -12,6 +16,18 @@ use dioxus::core::ScopeState;
 use std::{any::Any, mem, rc::Rc};
 
 // TODO: Other events
+pub mod events {
+    super::impl_event! [
+        ();
+        onclick
+        onclick_down
+        onclick_up
+        onmouse_over
+        onmouse_out
+        onmouse_enter
+        onmouse_exit
+    ];
+}
 
 #[derive(Resource, Default)]
 pub struct EventReaders {
@@ -25,7 +41,7 @@ pub struct EventReaders {
 }
 
 impl EventReaders {
-    pub fn get_dioxus_events(
+    pub fn read_events(
         &mut self,
         click: &Events<Pointer<Click>>,
         click_down: &Events<Pointer<Down>>,
@@ -61,31 +77,85 @@ impl EventReaders {
     }
 }
 
-pub fn is_supported_event(event: &str) -> bool {
-    match event {
-        "click" => true,
-        "click_down" => true,
-        "click_up" => true,
-        "mouse_over" => true,
-        "mouse_out" => true,
-        "mouse_enter" => true,
-        "mouse_exit" => true,
-        _ => false,
+pub fn insert_event_listener(name: &str, mut entity: EntityWorldMut<'_>) {
+    match name {
+        "click" => entity.insert(HasClickEventListener),
+        "click_down" => entity.insert(HasClickDownEventListener),
+        "click_up" => entity.insert(HasClickUpEventListener),
+        "mouse_over" => &mut entity,
+        "mouse_out" => &mut entity,
+        "mouse_enter" => entity.insert((
+            HasMouseEnterEventListener,
+            RelativeCursorPosition::default(),
+        )),
+        "mouse_exit" => {
+            entity.insert((HasMouseExitEventListener, RelativeCursorPosition::default()))
+        }
+        _ => panic!("Encountered unsupported bevy_dioxus event `{name}`."),
+    };
+}
+
+pub fn remove_event_listener(name: &str, mut entity: EntityWorldMut<'_>) {
+    match name {
+        "click" => entity.remove::<HasClickEventListener>(),
+        "click_down" => entity.remove::<HasClickDownEventListener>(),
+        "click_up" => entity.remove::<HasClickUpEventListener>(),
+        "mouse_over" => &mut entity,
+        "mouse_out" => &mut entity,
+        "mouse_enter" => {
+            entity.remove::<HasMouseEnterEventListener>();
+            if !entity.contains::<HasMouseExitEventListener>() {
+                entity.remove::<RelativeCursorPosition>();
+            }
+            &mut entity
+        }
+        "mouse_exit" => {
+            entity.remove::<HasMouseExitEventListener>();
+            if !entity.contains::<HasMouseEnterEventListener>() {
+                entity.remove::<RelativeCursorPosition>();
+            }
+            &mut entity
+        }
+        _ => unreachable!(),
+    };
+}
+
+#[derive(Component)]
+pub struct HasClickEventListener;
+
+#[derive(Component)]
+pub struct HasClickDownEventListener;
+
+#[derive(Component)]
+pub struct HasClickUpEventListener;
+
+#[derive(Component)]
+pub struct HasMouseEnterEventListener;
+
+#[derive(Component)]
+pub struct HasMouseExitEventListener;
+
+// ----------------------------------------------------------------------------
+
+pub fn bubble_event(event_name: &str, target_entity: &mut Entity, world: &World) {
+    match event_name {
+        "click" => bubble_event_helper::<HasClickEventListener>(target_entity, world),
+        "click_down" => bubble_event_helper::<HasClickDownEventListener>(target_entity, world),
+        "click_up" => bubble_event_helper::<HasClickUpEventListener>(target_entity, world),
+        _ => unreachable!(),
+    };
+}
+
+fn bubble_event_helper<T: Component>(target_entity: &mut Entity, world: &World) {
+    while !world.entity(*target_entity).contains::<T>() {
+        *target_entity = match world.entity(*target_entity).get::<Parent>() {
+            Some(parent) => parent.get(),
+            None => return,
+        };
     }
 }
 
-pub mod events {
-    super::impl_event! [
-        ();
-        onclick
-        onclick_down
-        onclick_up
-        onmouse_over
-        onmouse_out
-        onmouse_enter
-        onmouse_exit
-    ];
-}
+// ----------------------------------------------------------------------------
 
 pub fn generate_mouse_enter_leave_events(
     entities: Query<(Entity, &RelativeCursorPosition)>,
diff --git a/src/tick.rs b/src/tick.rs
index 04daf49..d971608 100644
--- a/src/tick.rs
+++ b/src/tick.rs
@@ -1,14 +1,15 @@
 use crate::{
-    apply_mutations::apply_mutations, deferred_system::DeferredSystemRegistry,
-    ecs_hooks::EcsContext, events::EventReaders, DioxusUiRoot, UiContext, UiRoot,
+    apply_mutations::apply_mutations,
+    deferred_system::DeferredSystemRegistry,
+    ecs_hooks::EcsContext,
+    events::{bubble_event, EventReaders},
+    DioxusUiRoot, UiContext, UiRoot,
 };
 use bevy::{
     ecs::{
-        component::Component,
         entity::Entity,
         world::{Mut, World},
     },
-    hierarchy::Parent,
     utils::HashMap,
 };
 use std::{any::Any, mem, rc::Rc, sync::Arc};
@@ -17,7 +18,7 @@ pub fn tick_dioxus_ui(world: &mut World) {
     run_deferred_systems(world);
 
     let ui_events = world.resource_scope(|world, mut event_readers: Mut<EventReaders>| {
-        event_readers.get_dioxus_events(
+        event_readers.read_events(
             world.resource(),
             world.resource(),
             world.resource(),
@@ -75,20 +76,13 @@ fn dispatch_ui_events(
     world: &World,
 ) {
     for (mut target, name, data, bubbles) in events {
-        let mut target_element_id = ui_root.bevy_ui_entity_to_element_id.get(&target).copied();
         if *bubbles {
-            while target_element_id.is_none()
-                || world.entity(target).contains::<IntrinsicTextNode>()
-            {
-                target = world.entity(target).get::<Parent>().unwrap().get();
-                target_element_id = ui_root.bevy_ui_entity_to_element_id.get(&target).copied();
-            }
+            bubble_event(name, &mut target, world);
         }
-
-        if let Some(target_element_id) = target_element_id {
+        if let Some(target_element_id) = ui_root.bevy_ui_entity_to_element_id.get(&target) {
             ui_root
                 .virtual_dom
-                .handle_event(name, Rc::clone(data), target_element_id, *bubbles);
+                .handle_event(name, Rc::clone(data), *target_element_id, *bubbles);
         }
     }
 }
@@ -139,6 +133,3 @@ fn render_ui(root_entity: Entity, ui_root: &mut UiRoot, world: &mut World) {
         world,
     );
 }
-
-#[derive(Component)]
-pub struct IntrinsicTextNode;