diff --git a/src/apply_mutations.rs b/src/apply_mutations.rs index dc560f2..c3bd575 100644 --- a/src/apply_mutations.rs +++ b/src/apply_mutations.rs @@ -183,12 +183,23 @@ pub fn apply_mutations( .entity_mut(element_id_to_bevy_ui_entity[&id]) .insert(Text::from_section(value, TextStyle::default())); } - Mutation::NewEventListener { name, id: _ } => { + 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()); + } + } + Mutation::RemoveEventListener { name, id } => { + if name == "mouse_enter" || name == "mouse_exit" { + world + .entity_mut(element_id_to_bevy_ui_entity[&id]) + .remove::(); + } } - Mutation::RemoveEventListener { .. } => {} Mutation::Remove { id } => { let entity = element_id_to_bevy_ui_entity[&id]; DespawnRecursive { entity }.apply(world); diff --git a/src/events.rs b/src/events.rs index b672bde..442f094 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,37 +1,51 @@ -use bevy::ecs::{ - entity::Entity, - event::{Events, ManualEventReader}, - system::Resource, +use bevy::{ + ecs::{ + entity::Entity, + event::{Event, EventWriter, Events, ManualEventReader}, + system::{Local, Query, Resource}, + }, + ui::RelativeCursorPosition, + utils::EntityHashSet, }; use bevy_mod_picking::events::{Click, Out, Over, Pointer}; use dioxus::core::ScopeState; -use std::{any::Any, rc::Rc}; +use std::{any::Any, mem, rc::Rc}; // TODO: Other events #[derive(Resource, Default)] pub struct EventReaders { click: ManualEventReader>, - mouse_enter: ManualEventReader>, - mouse_exit: ManualEventReader>, + mouse_over: ManualEventReader>, + mouse_out: ManualEventReader>, + mouse_enter: ManualEventReader, + mouse_exit: ManualEventReader, } impl EventReaders { pub fn get_dioxus_events( &mut self, click: &Events>, - mouse_enter: &Events>, - mouse_exit: &Events>, - ) -> Vec<(Entity, &'static str, Rc)> { - let mut events: Vec<(Entity, &'static str, Rc)> = Vec::new(); + mouse_over: &Events>, + mouse_out: &Events>, + mouse_enter: &Events, + mouse_exit: &Events, + ) -> Vec<(Entity, &'static str, Rc, bool)> { + let mut events: Vec<(Entity, &'static str, Rc, bool)> = Vec::new(); for event in self.click.read(click) { - events.push((event.target, "click", Rc::new(()))); + events.push((event.target, "click", Rc::new(()), true)); + } + for event in self.mouse_over.read(mouse_over) { + events.push((event.target, "mouse_over", Rc::new(()), false)); + } + for event in self.mouse_out.read(mouse_out) { + events.push((event.target, "mouse_out", Rc::new(()), false)); } for event in self.mouse_enter.read(mouse_enter) { - events.push((event.target, "mouse_enter", Rc::new(()))); + events.push((event.target, "mouse_enter", Rc::new(()), false)); } for event in self.mouse_exit.read(mouse_exit) { - events.push((event.target, "mouse_exit", Rc::new(()))); + events.push((event.target, "mouse_exit", Rc::new(()), false)); } events } @@ -40,6 +54,8 @@ impl EventReaders { pub fn is_supported_event(event: &str) -> bool { match event { "click" => true, + "mouse_over" => true, + "mouse_out" => true, "mouse_enter" => true, "mouse_exit" => true, _ => false, @@ -50,11 +66,57 @@ pub mod events { super::impl_event! [ (); onclick + onmouse_over + onmouse_out onmouse_enter onmouse_exit ]; } +pub fn generate_mouse_enter_leave_events( + entities: Query<(Entity, &RelativeCursorPosition)>, + mut previous_over: Local>, + mut over: Local>, + mut enter: EventWriter, + mut leave: EventWriter, +) { + mem::swap::>(&mut previous_over, &mut over); + + over.clear(); + for (entity, relative_cursor_position) in &entities { + if relative_cursor_position.mouse_over() { + over.insert(entity); + } + } + + enter.send_batch( + over.iter() + .copied() + .filter(|entity| !previous_over.contains(entity)) + .map(|target| MouseEnter { target }), + ); + + leave.send_batch( + previous_over + .iter() + .copied() + .filter(|entity| !over.contains(entity)) + .map(|target| MouseExit { target }), + ); +} + +#[derive(Event)] +pub struct MouseEnter { + target: Entity, +} + +#[derive(Event)] +pub struct MouseExit { + target: Entity, +} + +// ---------------------------------------------------------------------------- + pub trait EventReturn

: Sized { fn spawn(self, _cx: &ScopeState) {} } diff --git a/src/lib.rs b/src/lib.rs index ca3e2e7..4a57d56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,14 +11,17 @@ mod parse_attributes; mod tick; use self::{ - apply_mutations::BevyTemplate, deferred_system::DeferredSystemRegistry, events::EventReaders, - hooks::EcsSubscriptions, tick::tick_dioxus_ui, + apply_mutations::BevyTemplate, + deferred_system::DeferredSystemRegistry, + events::{generate_mouse_enter_leave_events, EventReaders, MouseEnter, MouseExit}, + hooks::EcsSubscriptions, + tick::tick_dioxus_ui, }; use bevy::{ - app::{App, Plugin, Update}, - ecs::{bundle::Bundle, component::Component, entity::Entity}, + app::{App, Last, Plugin, PreUpdate}, + ecs::{bundle::Bundle, component::Component, entity::Entity, schedule::IntoSystemConfigs}, prelude::Deref, - ui::node_bundles::NodeBundle, + ui::{node_bundles::NodeBundle, ui_focus_system}, utils::{EntityHashMap, HashMap}, }; use dioxus::core::{Element, ElementId, Scope, VirtualDom}; @@ -43,7 +46,13 @@ impl Plugin for DioxusUiPlugin { app.init_non_send_resource::() .init_resource::() .init_resource::() - .add_systems(Update, tick_dioxus_ui); + .add_event::() + .add_event::() + .add_systems( + PreUpdate, + generate_mouse_enter_leave_events.after(ui_focus_system), + ) + .add_systems(Last, tick_dioxus_ui); } } diff --git a/src/tick.rs b/src/tick.rs index d4e1a3e..c27fd89 100644 --- a/src/tick.rs +++ b/src/tick.rs @@ -17,7 +17,13 @@ pub fn tick_dioxus_ui(world: &mut World) { run_deferred_systems(world); let ui_events = world.resource_scope(|world, mut event_readers: Mut| { - event_readers.get_dioxus_events(world.resource(), world.resource(), world.resource()) + event_readers.get_dioxus_events( + world.resource(), + world.resource(), + world.resource(), + world.resource(), + world.resource(), + ) }); let root_entities: HashMap = world @@ -62,20 +68,26 @@ fn run_deferred_systems(world: &mut World) { } fn dispatch_ui_events( - events: &Vec<(Entity, &str, Rc)>, + events: &Vec<(Entity, &str, Rc, bool)>, ui_root: &mut UiRoot, world: &World, ) { - for (mut target, name, data) in events { + for (mut target, name, data, bubbles) in events { let mut target_element_id = ui_root.bevy_ui_entity_to_element_id.get(&target).copied(); - while target_element_id.is_none() || world.entity(target).contains::() { - target = world.entity(target).get::().unwrap().get(); - 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::() + { + target = world.entity(target).get::().unwrap().get(); + target_element_id = ui_root.bevy_ui_entity_to_element_id.get(&target).copied(); + } } - ui_root - .virtual_dom - .handle_event(name, Rc::clone(data), target_element_id.unwrap(), true); + if let Some(target_element_id) = target_element_id { + ui_root + .virtual_dom + .handle_event(name, Rc::clone(data), target_element_id, *bubbles); + } } }