From 4dec9831d87248371b833e1a494a9937a050f2c8 Mon Sep 17 00:00:00 2001
From: MrGVSV <gino.valente.code@gmail.com>
Date: Fri, 21 Jan 2022 17:11:03 -0800
Subject: [PATCH] Added CursorEvent

Added the CursorEvent struct to the cursor-related enum variants on
EventType

Also changed how PartialEq and Hash work for EventType. They no longer
consider the inner variant data
---
 README.md                          |   2 +-
 examples/counter.rs                |   2 +-
 examples/fold.rs                   |   4 +-
 examples/full_ui.rs                |   4 +-
 examples/hooks.rs                  |   4 +-
 examples/if.rs                     |   2 +-
 examples/provider.rs               |   2 +-
 examples/tabs/tab.rs               |   4 +-
 examples/tabs/tab_bar.rs           |   2 +-
 examples/todo/add_button.rs        |   4 +-
 examples/todo/card.rs              |   2 +-
 examples/todo/delete_button.rs     |   4 +-
 examples/todo/todo.rs              |   2 +-
 examples/world_interaction.rs      |   2 +-
 kayak_core/src/cursor.rs           |   8 +++
 kayak_core/src/event.rs            |  79 ++++++++++++++++------
 kayak_core/src/event_dispatcher.rs | 105 ++++++++++++++++++++---------
 kayak_render_macros/src/lib.rs     |   4 +-
 src/widgets/fold.rs                |   2 +-
 src/widgets/inspector.rs           |   2 +-
 src/widgets/tooltip.rs             |   6 +-
 21 files changed, 164 insertions(+), 82 deletions(-)

diff --git a/README.md b/README.md
index 40f7e9f..013c40d 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,7 @@ Widget's can create their own state and will re-render when that state changes.
 fn Counter(context: &mut KayakContext) {
     let (count, set_count, ..) = use_state!(0i32);
     let on_event = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => set_count(count + 1),
+        EventType::Click(..) => set_count(count + 1),
         _ => {}
     });
 
diff --git a/examples/counter.rs b/examples/counter.rs
index f86de9d..efa529a 100644
--- a/examples/counter.rs
+++ b/examples/counter.rs
@@ -31,7 +31,7 @@ fn Counter(context: &mut KayakContext) {
 
     let (count, set_count, ..) = use_state!(0i32);
     let on_event = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => set_count(count + 1),
+        EventType::Click(..) =>  set_count(count + 1),
         _ => {}
     });
 
diff --git a/examples/fold.rs b/examples/fold.rs
index 07463e9..245a421 100644
--- a/examples/fold.rs
+++ b/examples/fold.rs
@@ -63,12 +63,12 @@ fn FolderTree(context: &mut KayakContext) {
     let (is_b_open, set_b_open, ..) = use_state!(false);
     let set_close_b = set_b_open.clone();
     let close_b = Some(OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => set_close_b(false),
+        EventType::Click(..) =>  set_close_b(false),
         _ => {}
     }));
     let set_open_b = set_b_open.clone();
     let open_b = Some(OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => set_open_b(true),
+        EventType::Click(..) =>  set_open_b(true),
         _ => {}
     }));
 
diff --git a/examples/full_ui.rs b/examples/full_ui.rs
index dd419b6..f40962a 100644
--- a/examples/full_ui.rs
+++ b/examples/full_ui.rs
@@ -53,10 +53,10 @@ fn BlueButton(context: KayakContext, children: Children, styles: Option<Style>)
 
     let cloned_current_button_handle = current_button_handle.clone();
     let on_event = OnEvent::new(move |_, event| match event.event_type {
-        EventType::MouseIn => {
+        EventType::MouseIn(..) => {
             cloned_current_button_handle.set(blue_button_hover_handle);
         }
-        EventType::MouseOut => {
+        EventType::MouseOut(..) => {
             cloned_current_button_handle.set(blue_button_handle);
         }
         _ => (),
diff --git a/examples/hooks.rs b/examples/hooks.rs
index 3f10b78..9896dbf 100644
--- a/examples/hooks.rs
+++ b/examples/hooks.rs
@@ -39,7 +39,7 @@ fn StateCounter() {
     // both implement `Copy`. For other types, you may have to clone the state to pass it into a closure like this.
     // (You can also clone the setter as well if you need to use it in multiple places.)
     let on_event = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => set_count(count + 1),
+        EventType::Click(..) =>  set_count(count + 1),
         _ => {}
     });
 
@@ -64,7 +64,7 @@ fn EffectCounter() {
     // the third field in the tuple returned from the `use_state` macro.
     let (count, set_count, raw_count) = use_state!(0);
     let on_event = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => set_count(count + 1),
+        EventType::Click(..) =>  set_count(count + 1),
         _ => {}
     });
 
diff --git a/examples/if.rs b/examples/if.rs
index f6d1e5b..88b0cc9 100644
--- a/examples/if.rs
+++ b/examples/if.rs
@@ -24,7 +24,7 @@ fn Removal(context: &mut KayakContext) {
     let is_visible = context.create_state(true).unwrap();
     let cloned_is_visible = is_visible.clone();
     let on_event = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => {
+        EventType::Click(..) =>  {
             cloned_is_visible.set(!cloned_is_visible.get());
         }
         _ => {}
diff --git a/examples/provider.rs b/examples/provider.rs
index ea7e475..d451ddc 100644
--- a/examples/provider.rs
+++ b/examples/provider.rs
@@ -89,7 +89,7 @@ fn ThemeButton(context: &mut KayakContext, theme: Theme) {
 
     let theme_clone = Arc::new(theme);
     let on_event = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => {
+        EventType::Click(..) =>  {
             // Update the shared state
             // This will cause the ThemeProvider to re-render along with all of the other consumers
             consumer.set((*theme_clone).clone());
diff --git a/examples/tabs/tab.rs b/examples/tabs/tab.rs
index dbf598e..1d0eb9a 100644
--- a/examples/tabs/tab.rs
+++ b/examples/tabs/tab.rs
@@ -30,14 +30,14 @@ pub fn Tab(context: &mut KayakContext, content: String, selected: bool) {
     };
 
     let event_handler = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Hover => {
+        EventType::Hover(..) => {
             if selected {
                 set_hover_state(TabHoverState::Active);
             } else {
                 set_hover_state(TabHoverState::Inactive);
             }
         }
-        EventType::MouseOut => {
+        EventType::MouseOut(..) => {
             set_hover_state(TabHoverState::None);
         }
         EventType::Focus => {
diff --git a/examples/tabs/tab_bar.rs b/examples/tabs/tab_bar.rs
index 6671b3d..6c32a16 100644
--- a/examples/tabs/tab_bar.rs
+++ b/examples/tabs/tab_bar.rs
@@ -24,7 +24,7 @@ pub fn TabBar(
         let on_select = on_select_tab.clone();
         let tab_event_handler = OnEvent::new(move |_, event| {
             match event.event_type {
-                EventType::Click => {
+                EventType::Click(..) =>  {
                     on_select.call(index);
                 }
                 EventType::KeyDown(evt) => {
diff --git a/examples/todo/add_button.rs b/examples/todo/add_button.rs
index f32fd32..a3dbdbe 100644
--- a/examples/todo/add_button.rs
+++ b/examples/todo/add_button.rs
@@ -29,10 +29,10 @@ pub fn AddButton(children: Children, styles: Option<Style>) {
     });
 
     let on_event = OnEvent::new(move |_, event| match event.event_type {
-        EventType::MouseIn => {
+        EventType::MouseIn(..) => {
             set_color(Color::new(0.0791, 0.0998, 0.201, 1.0));
         }
-        EventType::MouseOut => {
+        EventType::MouseOut(..) => {
             set_color(Color::new(0.0781, 0.0898, 0.101, 1.0));
         }
         _ => {}
diff --git a/examples/todo/card.rs b/examples/todo/card.rs
index 78ea8f1..7196752 100644
--- a/examples/todo/card.rs
+++ b/examples/todo/card.rs
@@ -24,7 +24,7 @@ pub fn Card(card_id: usize, name: String, on_delete: Handler<usize>) {
 
     let on_delete = on_delete.clone();
     let on_event = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => {
+        EventType::Click(..) => {
             on_delete.call(card_id);
         }
         _ => (),
diff --git a/examples/todo/delete_button.rs b/examples/todo/delete_button.rs
index ac01130..bbe6735 100644
--- a/examples/todo/delete_button.rs
+++ b/examples/todo/delete_button.rs
@@ -29,10 +29,10 @@ pub fn DeleteButton(children: Children, styles: Option<Style>) {
     });
 
     let on_event = OnEvent::new(move |_, event| match event.event_type {
-        EventType::MouseIn => {
+        EventType::MouseIn(..) => {
             set_color(Color::new(0.0791, 0.0998, 0.201, 1.0));
         }
-        EventType::MouseOut => {
+        EventType::MouseOut(..) => {
             set_color(Color::new(0.0781, 0.0898, 0.101, 1.0));
         }
         _ => {}
diff --git a/examples/todo/todo.rs b/examples/todo/todo.rs
index 19221d9..fea3f1e 100644
--- a/examples/todo/todo.rs
+++ b/examples/todo/todo.rs
@@ -61,7 +61,7 @@ fn TodoApp() {
     let mut todos_cloned = todos.clone();
     let cloned_set_todos = set_todos.clone();
     let add_events = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => {
+        EventType::Click(..) => {
             if !new_todo_value_cloned.is_empty() {
                 todos_cloned.push(Todo {
                     name: new_todo_value_cloned.clone(),
diff --git a/examples/world_interaction.rs b/examples/world_interaction.rs
index cf87293..916d727 100644
--- a/examples/world_interaction.rs
+++ b/examples/world_interaction.rs
@@ -97,7 +97,7 @@ fn ControlPanel() {
     }
 
     let on_change_color = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => {
+        EventType::Click(..) => {
             // Cycle the color
             set_color_index((color_index + 1) % COLORS.len());
         }
diff --git a/kayak_core/src/cursor.rs b/kayak_core/src/cursor.rs
index 0fe983d..44ef8ed 100644
--- a/kayak_core/src/cursor.rs
+++ b/kayak_core/src/cursor.rs
@@ -16,3 +16,11 @@ impl Default for PointerEvents {
         Self::All
     }
 }
+
+#[derive(Default, Debug, Copy, Clone, PartialEq)]
+pub struct CursorEvent {
+    pub(crate) pressed: bool,
+    pub(crate) just_pressed: bool,
+    pub(crate) just_released: bool,
+    pub(crate) position: (f32, f32)
+}
\ No newline at end of file
diff --git a/kayak_core/src/event.rs b/kayak_core/src/event.rs
index b42882b..fd32b4b 100644
--- a/kayak_core/src/event.rs
+++ b/kayak_core/src/event.rs
@@ -1,4 +1,6 @@
+use derivative::Derivative;
 use crate::{Index, KeyboardEvent};
+use crate::cursor::CursorEvent;
 
 #[derive(Debug, Clone, Copy, PartialEq)]
 pub struct Event {
@@ -19,7 +21,7 @@ impl Default for Event {
         Self {
             target: Default::default(),
             current_target: Default::default(),
-            event_type: EventType::Click,
+            event_type: EventType::Click(CursorEvent::default()),
             should_propagate: true,
             default_prevented: false,
         }
@@ -62,19 +64,52 @@ impl Event {
     }
 }
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, Derivative)]
+#[derivative(PartialEq, Hash, Eq)]
 pub enum EventType {
-    Click,
-    Hover,
-    MouseIn,
-    MouseOut,
-    MouseDown,
-    MouseUp,
+    Click(
+        #[derivative(PartialEq = "ignore")]
+        #[derivative(Hash = "ignore")]
+        CursorEvent
+    ),
+    Hover(
+        #[derivative(PartialEq = "ignore")]
+        #[derivative(Hash = "ignore")]
+        CursorEvent
+    ),
+    MouseIn(
+        #[derivative(PartialEq = "ignore")]
+        #[derivative(Hash = "ignore")]
+        CursorEvent
+    ),
+    MouseOut(
+        #[derivative(PartialEq = "ignore")]
+        #[derivative(Hash = "ignore")]
+        CursorEvent
+    ),
+    MouseDown(
+        #[derivative(PartialEq = "ignore")]
+        #[derivative(Hash = "ignore")]
+        CursorEvent
+    ),
+    MouseUp(
+        #[derivative(PartialEq = "ignore")]
+        #[derivative(Hash = "ignore")]
+        CursorEvent
+    ),
     Focus,
     Blur,
     CharInput { c: char },
-    KeyUp(KeyboardEvent),
-    KeyDown(KeyboardEvent),
+    KeyUp(
+        #[derivative(PartialEq = "ignore")]
+        #[derivative(Hash = "ignore")]
+        KeyboardEvent
+    ),
+    KeyDown(
+        #[derivative(PartialEq = "ignore")]
+        #[derivative(Hash = "ignore")]
+        KeyboardEvent
+    ),
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -92,16 +127,16 @@ impl EventType {
     pub fn propagates(&self) -> bool {
         match self {
             // Propagates
-            Self::Hover => true,
-            Self::Click => true,
-            Self::MouseDown => true,
-            Self::MouseUp => true,
+            Self::Hover(..) => true,
+            Self::Click(..) => true,
+            Self::MouseDown(..) => true,
+            Self::MouseUp(..) => true,
             Self::CharInput { .. } => true,
             Self::KeyUp(..) => true,
             Self::KeyDown(..) => true,
             // Doesn't Propagate
-            Self::MouseIn => false,
-            Self::MouseOut => false,
+            Self::MouseIn(..) => false,
+            Self::MouseOut(..) => false,
             Self::Focus => false,
             Self::Blur => false,
         }
@@ -111,12 +146,12 @@ impl EventType {
     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::Hover(..) => EventCategory::Mouse,
+            Self::Click(..) => EventCategory::Mouse,
+            Self::MouseDown(..) => EventCategory::Mouse,
+            Self::MouseUp(..) => EventCategory::Mouse,
+            Self::MouseIn(..) => EventCategory::Mouse,
+            Self::MouseOut(..) => EventCategory::Mouse,
             // Keyboard
             Self::CharInput { .. } => EventCategory::Keyboard,
             Self::KeyUp(..) => EventCategory::Keyboard,
diff --git a/kayak_core/src/event_dispatcher.rs b/kayak_core/src/event_dispatcher.rs
index 8abc012..d5b3637 100644
--- a/kayak_core/src/event_dispatcher.rs
+++ b/kayak_core/src/event_dispatcher.rs
@@ -8,6 +8,7 @@ use crate::{
     KeyboardModifiers, PointerEvents, Widget,
 };
 use std::collections::{HashMap, HashSet};
+use crate::cursor::CursorEvent;
 
 type EventMap = HashMap<Index, HashSet<EventType>>;
 type TreeNode = (
@@ -37,6 +38,7 @@ impl Default for EventState {
 #[derive(Debug, Clone)]
 pub(crate) struct EventDispatcher {
     is_mouse_pressed: bool,
+    next_mouse_pressed: bool,
     current_mouse_position: (f32, f32),
     next_mouse_position: (f32, f32),
     previous_events: EventMap,
@@ -52,6 +54,7 @@ impl EventDispatcher {
         Self {
             last_clicked: Binding::new(Index::default()),
             is_mouse_pressed: Default::default(),
+            next_mouse_pressed: Default::default(),
             current_mouse_position: Default::default(),
             next_mouse_position: Default::default(),
             previous_events: Default::default(),
@@ -163,17 +166,17 @@ impl EventDispatcher {
         // Events that need to be maintained without re-firing between event updates should be managed here
         for (index, events) in &self.previous_events {
             // Mouse is currently pressed for this node
-            if self.is_mouse_pressed && events.contains(&EventType::MouseDown) {
+            if self.is_mouse_pressed && events.contains(&EventType::MouseDown(Default::default())) {
                 // Make sure this event isn't removed while mouse is still held down
-                Self::insert_event(&mut next_events, index, EventType::MouseDown);
+                Self::insert_event(&mut next_events, index, EventType::MouseDown(Default::default()));
             }
 
             // Mouse is currently within this node
-            if events.contains(&EventType::MouseIn)
-                && !Self::contains_event(&next_events, index, &EventType::MouseOut)
+            if events.contains(&EventType::MouseIn(Default::default()))
+                && !Self::contains_event(&next_events, index, &EventType::MouseOut(Default::default()))
             {
                 // Make sure this event isn't removed while mouse is still within node
-                Self::insert_event(&mut next_events, index, EventType::MouseIn);
+                Self::insert_event(&mut next_events, index, EventType::MouseIn(Default::default()));
             }
         }
 
@@ -201,6 +204,30 @@ impl EventDispatcher {
         let old_wants_cursor = self.wants_cursor;
         self.contains_cursor = None;
         self.wants_cursor = None;
+        self.next_mouse_position = self.current_mouse_position;
+        self.next_mouse_pressed = self.is_mouse_pressed;
+
+        // --- Pre-Process --- //
+        // We pre-process some events so that we can provide accurate event data (such as if the mouse is pressed)
+        // This is faster than resolving data after the fact since `input_events` is generally very small
+        for input_event in input_events {
+            if let InputEvent::MouseMoved(point) = input_event {
+                // Reset next global mouse position
+                self.next_mouse_position = *point;
+            }
+
+            if matches!(input_event, InputEvent::MouseLeftPress) {
+                // Reset next global mouse pressed
+                self.next_mouse_pressed = true;
+                break;
+            } else if matches!(input_event, InputEvent::MouseLeftRelease) {
+                // Reset next global mouse pressed
+                self.next_mouse_pressed = false;
+                // Reset global cursor container
+                self.has_cursor = None;
+                break;
+            }
+        }
 
         // === Mouse Events === //
         let mut stack: Vec<TreeNode> = vec![(root, 0)];
@@ -212,12 +239,7 @@ impl EventDispatcher {
                 // --- Process Event --- //
                 if matches!(input_event.category(), InputEventCategory::Mouse) {
                     // A widget's PointerEvents style will determine how it and its children are processed
-                    let mut pointer_events = PointerEvents::default();
-                    if let Some(widget) = widget_manager.current_widgets.get(current).unwrap() {
-                        if let Some(styles) = widget.get_styles() {
-                            pointer_events = styles.pointer_events.resolve();
-                        }
-                    }
+                    let pointer_events = Self::resolve_pointer_events(current, widget_manager);
 
                     match pointer_events {
                         PointerEvents::All | PointerEvents::SelfOnly => {
@@ -226,6 +248,7 @@ impl EventDispatcher {
                                 (current, depth),
                                 &mut states,
                                 widget_manager,
+                                false,
                             );
                             event_stream.extend(events);
 
@@ -290,6 +313,7 @@ impl EventDispatcher {
 
         // === Process Cursor States === //
         self.current_mouse_position = self.next_mouse_position;
+        self.is_mouse_pressed = self.next_mouse_pressed;
 
         if self.contains_cursor.is_none() {
             // No change -> revert
@@ -309,6 +333,7 @@ impl EventDispatcher {
         tree_node: TreeNode,
         states: &mut HashMap<EventType, EventState>,
         widget_manager: &WidgetManager,
+        ignore_layout: bool
     ) -> Vec<Event> {
         let mut event_stream = Vec::<Event>::new();
         let (node, depth) = tree_node;
@@ -316,19 +341,20 @@ impl EventDispatcher {
         match input_event {
             InputEvent::MouseMoved(point) => {
                 if let Some(layout) = widget_manager.get_layout(&node) {
+                    let cursor_event = self.get_cursor_event(*point);
                     let was_contained = layout.contains(&self.current_mouse_position);
                     let is_contained = layout.contains(point);
-                    if was_contained != is_contained {
+                    if !ignore_layout && was_contained != is_contained {
                         if was_contained {
-                            event_stream.push(Event::new(node, EventType::MouseOut));
+                            event_stream.push(Event::new(node, EventType::MouseOut(cursor_event)));
                         } else {
-                            event_stream.push(Event::new(node, EventType::MouseIn));
+                            event_stream.push(Event::new(node, EventType::MouseIn(cursor_event)));
                         }
                     }
                     if self.contains_cursor.is_none() || !self.contains_cursor.unwrap_or_default() {
                         if let Some(widget) = widget_manager.current_widgets.get(node).unwrap() {
                             // Check if the cursor moved onto a widget that qualifies as one that can contain it
-                            if Self::can_contain_cursor(widget) {
+                            if ignore_layout || Self::can_contain_cursor(widget) {
                                 self.contains_cursor = Some(is_contained);
                             }
                         }
@@ -343,21 +369,16 @@ impl EventDispatcher {
                     }
 
                     // Check for hover eligibility
-                    if is_contained {
-                        Self::update_state(states, (node, depth), layout, EventType::Hover);
+                    if ignore_layout || is_contained {
+                        Self::update_state(states, (node, depth), layout, EventType::Hover(cursor_event));
                     }
                 }
-
-                // Reset global mouse position
-                self.next_mouse_position = *point;
             }
             InputEvent::MouseLeftPress => {
-                // Reset global mouse pressed
-                self.is_mouse_pressed = true;
-
                 if let Some(layout) = widget_manager.get_layout(&node) {
-                    if layout.contains(&self.current_mouse_position) {
-                        event_stream.push(Event::new(node, EventType::MouseDown));
+                    if ignore_layout || layout.contains(&self.current_mouse_position) {
+                        let cursor_event = self.get_cursor_event(self.current_mouse_position);
+                        event_stream.push(Event::new(node, EventType::MouseDown(cursor_event)));
 
                         if let Some(focusable) = widget_manager.get_focusable(node) {
                             if focusable {
@@ -378,18 +399,15 @@ impl EventDispatcher {
                 }
             }
             InputEvent::MouseLeftRelease => {
-                // Reset global mouse pressed
-                self.is_mouse_pressed = false;
-                self.has_cursor = None;
-
                 if let Some(layout) = widget_manager.get_layout(&node) {
-                    if layout.contains(&self.current_mouse_position) {
-                        event_stream.push(Event::new(node, EventType::MouseUp));
+                    if ignore_layout || layout.contains(&self.current_mouse_position) {
+                        let cursor_event = self.get_cursor_event(self.current_mouse_position);
+                        event_stream.push(Event::new(node, EventType::MouseUp(cursor_event)));
                         self.last_clicked.set(node);
 
-                        if Self::contains_event(&self.previous_events, &node, &EventType::MouseDown)
+                        if Self::contains_event(&self.previous_events, &node, &EventType::MouseDown(cursor_event))
                         {
-                            Self::update_state(states, (node, depth), layout, EventType::Click);
+                            Self::update_state(states, (node, depth), layout, EventType::Click(cursor_event));
                         }
                     }
                 }
@@ -400,6 +418,27 @@ impl EventDispatcher {
         event_stream
     }
 
+    fn resolve_pointer_events(index: Index, widget_manager: &WidgetManager) -> PointerEvents {
+        let mut pointer_events = PointerEvents::default();
+        if let Some(widget) = widget_manager.current_widgets.get(index).unwrap() {
+            if let Some(styles) = widget.get_styles() {
+                pointer_events = styles.pointer_events.resolve();
+            }
+        }
+        pointer_events
+    }
+
+    fn get_cursor_event(&self, position: (f32, f32)) -> CursorEvent {
+        let change = self.next_mouse_pressed != self.is_mouse_pressed;
+        let pressed = self.next_mouse_pressed;
+        CursorEvent {
+            position,
+            pressed,
+            just_pressed: change && pressed,
+            just_released: change && !pressed,
+        }
+    }
+
     fn process_keyboard_events(
         &mut self,
         input_event: &InputEvent,
diff --git a/kayak_render_macros/src/lib.rs b/kayak_render_macros/src/lib.rs
index f04c848..3459ab2 100644
--- a/kayak_render_macros/src/lib.rs
+++ b/kayak_render_macros/src/lib.rs
@@ -137,7 +137,7 @@ pub fn dyn_partial_eq(_: TokenStream, input: TokenStream) -> TokenStream {
 /// let (count, set_count, ..) = use_state!(0);
 ///
 /// let on_event = OnEvent::new(move |_, event| match event.event_type {
-///         EventType::Click => {
+///         EventType::Click(..) => {
 ///             set_count(foo + 1);
 ///         }
 ///         _ => {}
@@ -215,7 +215,7 @@ pub fn use_state(initial_state: TokenStream) -> TokenStream {
 /// }, [count_state]);
 ///
 /// let on_event = OnEvent::new(move |_, event| match event.event_type {
-///         EventType::Click => {
+///         EventType::Click(..) => {
 ///             set_count(foo + 1);
 ///         }
 ///         _ => {}
diff --git a/src/widgets/fold.rs b/src/widgets/fold.rs
index cee8106..be10357 100644
--- a/src/widgets/fold.rs
+++ b/src/widgets/fold.rs
@@ -54,7 +54,7 @@ pub fn Fold(
     }
 
     let handler = OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => {
+        EventType::Click(..) =>  {
             if open.is_none() {
                 // This is an internally-managed state
                 set_is_open(!is_open);
diff --git a/src/widgets/inspector.rs b/src/widgets/inspector.rs
index 9c63447..8f7b69f 100644
--- a/src/widgets/inspector.rs
+++ b/src/widgets/inspector.rs
@@ -72,7 +72,7 @@ pub fn Inspector() {
     }
 
     let handle_button_events = Some(OnEvent::new(move |_, event| match event.event_type {
-        EventType::Click => last_clicked.set(parent_id_move.unwrap()),
+        EventType::Click(..) => last_clicked.set(parent_id_move.unwrap()),
         _ => {}
     }));
 
diff --git a/src/widgets/tooltip.rs b/src/widgets/tooltip.rs
index 49f5b3e..9c11089 100644
--- a/src/widgets/tooltip.rs
+++ b/src/widgets/tooltip.rs
@@ -185,19 +185,19 @@ pub fn TooltipConsumer(
 
     let text = Arc::new(text);
     self.on_event = Some(OnEvent::new(move |ctx, event| match event.event_type {
-        EventType::MouseIn => {
+        EventType::MouseIn(..) => {
             let mut state = data.get();
             state.visible = true;
             state.text = (*text).clone();
             state.size = size;
             data.set(state);
         }
-        EventType::Hover => {
+        EventType::Hover(..) => {
             let mut state = data.get();
             state.anchor = anchor.unwrap_or(ctx.last_mouse_position());
             data.set(state);
         }
-        EventType::MouseOut => {
+        EventType::MouseOut(..) => {
             let mut state = data.get();
             // Set hidden only if the tooltip's text matches this consumer's
             // Otherwise, it likely got picked up by another widget and should be kept visible
-- 
GitLab