use bevy::prelude::{Entity, Resource, World}; use crate::{ cursor::{CursorEvent, ScrollEvent}, keyboard_event::KeyboardEvent, prelude::{KayakWidgetContext, OnChange}, }; /// An event type sent to widgets #[derive(Resource, Clone, Debug)] pub struct KEvent { /// The node targeted by this event pub target: Entity, /// The current target of this event pub current_target: Entity, /// The type of event pub event_type: EventType, /// Indicates whether this event should propagate or not pub(crate) should_propagate: bool, /// Indicates whether the default action of this event (if any) has been prevented pub(crate) default_prevented: bool, /// OnChange systems to call afterwards pub(crate) on_change_systems: Vec<OnChange>, } impl PartialEq for KEvent { fn eq(&self, other: &Self) -> bool { self.target == other.target && self.current_target == other.current_target && self.event_type == other.event_type && self.should_propagate == other.should_propagate && self.default_prevented == other.default_prevented } } impl Default for KEvent { fn default() -> Self { Self { target: Entity::from_raw(0), current_target: Entity::from_raw(0), event_type: EventType::Click(CursorEvent::default()), should_propagate: true, default_prevented: false, on_change_systems: Vec::new(), } } } impl KEvent { /// Create a new event /// /// This is the preferred method for creating an event as it automatically sets up /// propagation and other event metadata in a standardized manner pub fn new(target: Entity, event_type: EventType) -> Self { Self { target, current_target: target, should_propagate: event_type.propagates(), event_type, default_prevented: false, on_change_systems: Vec::new(), } } /// Returns whether this event is currently set to propagate pub fn propagates(&self) -> bool { self.should_propagate } /// If called, prevents this event from propagating up the hierarchy pub fn stop_propagation(&mut self) { if matches!( self.event_type, EventType::Click(..) | EventType::MouseIn(..) | EventType::MouseDown(..) | EventType::Scroll(..) | EventType::Focus | EventType::Hover(..) ) { self.should_propagate = false; } } /// Returns whether this event's default action has been prevented or not pub fn is_default_prevented(&self) -> bool { self.default_prevented } /// Prevents this event's default action (if any) from being executed pub fn prevent_default(&mut self) { self.default_prevented = true; } pub fn add_system(&mut self, system: OnChange) { self.on_change_systems.push(system); } pub(crate) fn run_on_change(&mut self, world: &mut World, widget_context: KayakWidgetContext) { for system in self.on_change_systems.drain(..) { system.try_call(self.current_target, world, widget_context.clone()); } } } /// The type of event /// /// __Note:__ This type implements `PartialEq` and `Hash` in a way that only considers the variant itself, /// not the underlying data. If full comparisons are needed, they should be done with the inner data or /// with a custom wrapper. #[derive(Debug, Clone)] pub enum EventType { /// An event that occurs when the user clicks a widget Click(CursorEvent), /// An event that occurs when the user hovers the cursor over a widget Hover(CursorEvent), /// An event that occurs when the user moves the cursor into a widget MouseIn(CursorEvent), /// An event that occurs when the user moves the cursor out of a widget MouseOut(CursorEvent), /// An event that occurs when the user presses down on the cursor over a widget MouseDown(CursorEvent), /// An event that occurs when the user releases the cursor over a widget MouseUp(CursorEvent), /// An event that occurs when the user scrolls over a widget Scroll(ScrollEvent), /// An event that occurs when a widget receives focus Focus, /// An event that occurs when a widget loses focus Blur, /// An event that occurs when the user types in a character within a _focused_ widget CharInput { c: smol_str::SmolStr }, /// An event that occurs when the user releases a key within a _focused_ widget KeyUp(KeyboardEvent), /// An event that occurs when the user presses a key down within a _focused_ widget KeyDown(KeyboardEvent), } impl Eq for EventType {} /// __Note:__ Only checks if the two event types are the same discriminant impl PartialEq for EventType { fn eq(&self, other: &Self) -> bool { std::mem::discriminant(self) == std::mem::discriminant(other) } } /// __Note:__ Only uses the discriminant for hashing impl std::hash::Hash for EventType { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { std::hash::Hash::hash(&std::mem::discriminant(self), state); } } /// The various categories an event can belong to #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum EventCategory { /// A category for events related to the mouse/cursor Mouse, /// A category for events related to the keyboard Keyboard, /// A category for events related to focus Focus, } impl EventType { /// Returns whether this event type should propagate by default /// /// For more details on what should and shouldn't propagate, check out the /// [W3 specifications](https://www.w3.org/TR/uievents/#event-types), upon which this is based. pub fn propagates(&self) -> bool { match self { // Propagates Self::Hover(..) => true, Self::Click(..) => true, Self::MouseDown(..) => true, Self::MouseUp(..) => true, Self::Scroll(..) => true, Self::CharInput { .. } => true, Self::KeyUp(..) => true, Self::KeyDown(..) => true, // Doesn't Propagate Self::MouseIn(..) => false, Self::MouseOut(..) => false, Self::Focus => false, Self::Blur => false, } } /// Get the category of this event pub fn event_category(&self) -> EventCategory { match self { // Mouse Self::Hover(..) => EventCategory::Mouse, Self::Click(..) => EventCategory::Mouse, Self::MouseDown(..) => EventCategory::Mouse, Self::MouseUp(..) => EventCategory::Mouse, Self::MouseIn(..) => EventCategory::Mouse, Self::MouseOut(..) => EventCategory::Mouse, Self::Scroll(..) => EventCategory::Mouse, // Keyboard Self::CharInput { .. } => EventCategory::Keyboard, Self::KeyUp(..) => EventCategory::Keyboard, Self::KeyDown(..) => EventCategory::Keyboard, // Focus Self::Focus => EventCategory::Focus, Self::Blur => EventCategory::Focus, } } }