use std::{ any::{Any, TypeId}, cell::RefMut, collections::HashSet, rc::{Rc, Weak}, }; use glam::Vec2; use slotmap::{DenseSlotMap, new_key_type}; use crate::{ animation::{self, Animation}, components::{ComponentTrait, ComponentWeak}, globals, i18n::I18n, layout::{LayoutDispatchFunc, LayoutState, LayoutTask, WidgetID}, sound::WguiSoundType, stack::{ScissorStack, Transform, TransformStack}, widget::{EventResult, WidgetData, WidgetObj}, }; #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct DeviceBitmask(pub u8); impl DeviceBitmask { pub fn from_usize(mask: usize) -> Self { // more than 8 input devices? debug_assert!(mask & !0xff == 0); Self(mask as u8) } } #[derive(Debug, Clone, Copy)] pub enum MouseButtonIndex { Left, Right, Middle, } #[derive(Debug, Clone, Copy)] pub struct MouseButtonEvent { pub index: MouseButtonIndex, pub pos: Vec2, pub device: DeviceBitmask, } #[derive(Debug, Clone, Copy)] pub struct MousePosition { pub pos: Vec2, pub device: DeviceBitmask, } pub struct MouseLeaveEvent { pub device: DeviceBitmask, } pub struct MouseMotionEvent { pub pos: Vec2, pub device: DeviceBitmask, } pub struct MouseWheelEvent { pub pos: Vec2, /* mouse position */ pub delta: Vec2, /* wheel delta */ pub device: DeviceBitmask, } #[derive(Clone)] pub struct TextInputEvent { pub text: Option>, } pub struct InternalStateChangeEvent { pub metadata: usize, } pub enum Event { InternalStateChange(InternalStateChangeEvent), MouseDown(MouseButtonEvent), MouseLeave(MouseLeaveEvent), MouseMotion(MouseMotionEvent), MouseUp(MouseButtonEvent), MouseWheel(MouseWheelEvent), MouseCancel, // Called if the user started scrolling by swiping above the button, to cancel all currently pressed buttons (prevent clicks) TextInput(TextInputEvent), } impl Event { fn test_transform_pos(transform: &Transform, pos: Vec2) -> bool { pos.x >= transform.abs_pos.x && pos.x < transform.abs_pos.x + transform.visual_dim.x && pos.y >= transform.abs_pos.y && pos.y < transform.abs_pos.y + transform.visual_dim.y } pub fn test_mouse_within_transform(&self, transform: &Transform) -> bool { match self { Self::MouseDown(evt) => Self::test_transform_pos(transform, evt.pos), Self::MouseMotion(evt) => Self::test_transform_pos(transform, evt.pos), Self::MouseUp(evt) => Self::test_transform_pos(transform, evt.pos), Self::MouseWheel(evt) => Self::test_transform_pos(transform, evt.pos), _ => false, } } } pub enum StyleSetRequest { Display(taffy::Display), Margin(taffy::Rect), Width(taffy::Dimension), Height(taffy::Dimension), Size(taffy::Size), } // alterables which will be dispatched in the next loop iteration phase #[derive(Default)] pub struct EventAlterables { pub dirty_widgets: Vec, pub components_to_refresh_once: Vec, pub style_set_requests: Vec<(WidgetID, StyleSetRequest)>, pub global_events_to_emit: Vec, pub animations: Vec, pub widgets_to_tick: HashSet, // widgets which needs to be ticked in the next `Layout::update()` fn pub transform_stack: TransformStack, pub scissor_stack: ScissorStack, pub tasks: Vec, pub needs_redraw: bool, pub trigger_haptics: bool, } // helper functions impl EventAlterables { pub const fn mark_redraw(&mut self) { self.needs_redraw = true; } pub fn set_style(&mut self, widget_id: WidgetID, request: StyleSetRequest) { self.style_set_requests.push((widget_id, request)); } pub fn set_widget_visible(&mut self, widget_id: WidgetID, visible: bool) { self.style_set_requests.push(( widget_id, StyleSetRequest::Display(if visible { taffy::Display::Flex } else { taffy::Display::None }), )); } pub fn mark_dirty(&mut self, widget_id: WidgetID) { self.dirty_widgets.push(widget_id); } pub fn mark_dirty_and_redraw(&mut self, widget_id: WidgetID) { self.mark_dirty(widget_id); self.mark_redraw(); } pub fn emit_global_event(&mut self, event: Event) { self.global_events_to_emit.push(event); } pub fn mark_tick(&mut self, widget_id: WidgetID) { self.widgets_to_tick.insert(widget_id); } pub const fn trigger_haptics(&mut self) { self.trigger_haptics = true; } pub fn animate(&mut self, animation: Animation) { self.animations.push(animation); } pub fn play_sound(&mut self, sound_type: WguiSoundType) { self.tasks.push(LayoutTask::PlaySound(sound_type)); } pub fn dispatch(&mut self, func: LayoutDispatchFunc) { self.tasks.push(LayoutTask::Dispatch(func)); } pub fn focus(&mut self, component: &Weak) { self.tasks.push(LayoutTask::SetFocus(component.clone())); } pub fn unfocus(&mut self) { self.tasks.push(LayoutTask::Unfocus); } pub fn refresh_component_once(&mut self, component: &Weak) { self.components_to_refresh_once.push(component.clone()); } } pub struct CallbackDataCommon<'a> { pub state: &'a LayoutState, pub alterables: &'a mut EventAlterables, } impl CallbackDataCommon<'_> { pub fn i18n(&self) -> RefMut<'_, I18n> { self.state.globals.i18n() } // helper functions pub fn mark_widget_dirty(&mut self, id: WidgetID) { self.alterables.mark_dirty_and_redraw(id); } pub fn globals(&self) -> RefMut<'_, globals::Globals> { self.state.globals.get() } } pub struct CallbackData<'a> { pub obj: &'a mut dyn WidgetObj, pub widget_data: &'a mut WidgetData, pub widget_id: WidgetID, pub node_id: taffy::NodeId, pub metadata: CallbackMetadata, } pub enum CallbackMetadata { None, MouseButton(MouseButtonEvent), MousePosition(MousePosition), TextInput(TextInputEvent), Custom(usize), } impl CallbackMetadata { // helper function pub const fn get_mouse_pos_absolute(&self) -> Option { match *self { CallbackMetadata::MouseButton(b) => Some(b.pos), CallbackMetadata::MousePosition(b) => Some(b.pos), CallbackMetadata::Custom(_) | CallbackMetadata::None => None, CallbackMetadata::TextInput(_) => None, } } pub fn get_mouse_pos_relative(&self, transform_stack: &TransformStack) -> Option { let mouse_pos_abs = self.get_mouse_pos_absolute()?; Some(mouse_pos_abs - transform_stack.get().abs_pos) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EventListenerKind { MousePress, MouseMotion, MouseRelease, MouseCancel, MouseEnter, MouseLeave, TextInput, InternalStateChange, } pub type EventCallbackInternal = Box< dyn for<'a, 'b, 'c, 'd> Fn( &'a mut CallbackDataCommon<'b>, &'a mut CallbackData<'c>, &'d mut dyn Any, &'d mut dyn Any, ) -> anyhow::Result, >; pub type EventCallback = Box< dyn for<'a, 'b, 'c, 'd> Fn( &'a mut CallbackDataCommon<'b>, &'a mut CallbackData<'c>, &'d mut U1, &'d mut U2, ) -> anyhow::Result, >; new_key_type! { pub struct EventListenerID; } pub struct EventListener { kind: EventListenerKind, callback: EventCallbackInternal, tid1: Option, tid2: Option, } impl EventListener { pub fn call_with( &self, common: &mut CallbackDataCommon, data: &mut CallbackData, user_data: &mut (&mut U1, &mut U2), ) -> anyhow::Result { let a1: &mut (dyn Any + 'static) = if self.tid1.is_none() { &mut () } else { user_data.0 }; let a2: &mut (dyn Any + 'static) = if self.tid2.is_none() { &mut () } else { user_data.1 }; (self.callback)(common, data, a1, a2) } } #[derive(Default)] pub struct EventListenerCollection { inner: DenseSlotMap, } impl EventListenerCollection { /// Iterates over event handlers with a matching U type pub fn iter_filtered( &self, kind: EventListenerKind, ) -> impl Iterator { let tid1 = TypeId::of::(); let tid2 = TypeId::of::(); self .inner .values() .filter(move |p| p.tid1.is_none_or(|a| a == tid1) && p.tid2.is_none_or(|a| a == tid2) && p.kind == kind) } pub fn register( &mut self, kind: EventListenerKind, callback: EventCallback, ) -> EventListenerID { let tid_unit = TypeId::of::<()>(); let tid1 = TypeId::of::(); let tid2 = TypeId::of::(); let callback_inner: EventCallbackInternal = Box::new(move |common, data, u1_any, u2_any| { if let Some(u1) = u1_any.downcast_mut::() && let Some(u2) = u2_any.downcast_mut::() { callback(common, data, u1, u2) } else { Ok(EventResult::Pass) } }); let new_item = EventListener { kind, callback: callback_inner, tid1: (tid1 != tid_unit).then_some(tid1), tid2: (tid2 != tid_unit).then_some(tid2), }; self.inner.insert(new_item) } pub fn remove(&mut self, event_listener_id: EventListenerID) -> Option { self.inner.remove(event_listener_id) } }