[yew-devel] [PATCH yew-widget-toolkit 1/7] touch: gesture detector: introduce `InputEvent`

Dominik Csapak d.csapak at proxmox.com
Tue Jun 24 14:19:19 CEST 2025


this is an abstracted event for either a PointerEvent or a Touch.
This prepares the interfaces to deal with touch only input.

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 Cargo.toml                    |  1 +
 src/touch/gesture_detector.rs | 87 ++++++++++++++++++++++-------------
 src/touch/mod.rs              |  4 +-
 src/touch/side_dialog.rs      | 14 +++---
 src/touch/slidable/mod.rs     |  8 ++--
 5 files changed, 67 insertions(+), 47 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 0e919e0..2561436 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,6 +50,7 @@ web-sys = { version = "0.3", features = [
   "IntersectionObserver",
   "IntersectionObserverEntry",
   "KeyboardEventInit",
+  "Touch",
 ] }
 js-sys = "0.3"
 log = "0.4.6"
diff --git a/src/touch/gesture_detector.rs b/src/touch/gesture_detector.rs
index 2e50e54..0929e01 100644
--- a/src/touch/gesture_detector.rs
+++ b/src/touch/gesture_detector.rs
@@ -3,6 +3,7 @@ use std::collections::HashMap;
 use std::rc::Rc;
 
 use gloo_timers::callback::Timeout;
+use web_sys::Touch;
 use yew::html::IntoEventCallback;
 use yew::prelude::*;
 use yew::virtual_dom::{Key, VComp, VNode};
@@ -11,39 +12,64 @@ use crate::impl_to_html;
 use crate::props::{ContainerBuilder, EventSubscriber, WidgetBuilder, WidgetStyleBuilder};
 use crate::widget::Container;
 
-/// Like [PointerEvent](web_sys::PointerEvent) (currently no additional features)
-pub struct GestureDragEvent {
-    event: PointerEvent,
+/// An event that can happen from a [`PointerEvent`] or a [`Touch`]
+///
+/// For convenience, expose the most important values from the underlying events
+pub enum InputEvent {
+    PointerEvent(PointerEvent),
+    Touch(Touch),
 }
 
-impl GestureDragEvent {
-    fn new(event: PointerEvent) -> Self {
-        Self { event }
+impl InputEvent {
+    pub fn x(&self) -> i32 {
+        match self {
+            InputEvent::PointerEvent(pointer_event) => pointer_event.client_x(),
+            InputEvent::Touch(touch) => touch.client_x(),
+        }
+    }
+
+    pub fn y(&self) -> i32 {
+        match self {
+            InputEvent::PointerEvent(pointer_event) => pointer_event.client_y(),
+            InputEvent::Touch(touch) => touch.client_y(),
+        }
+    }
+
+    pub fn id(&self) -> i32 {
+        match self {
+            InputEvent::PointerEvent(pointer_event) => pointer_event.pointer_id(),
+            InputEvent::Touch(touch) => touch.identifier(),
+        }
     }
 }
 
-impl Deref for GestureDragEvent {
-    type Target = PointerEvent;
-    fn deref(&self) -> &Self::Target {
-        &self.event
+impl From<PointerEvent> for InputEvent {
+    fn from(event: PointerEvent) -> Self {
+        Self::PointerEvent(event)
+    }
+}
+
+impl From<Touch> for InputEvent {
+    fn from(touch: Touch) -> Self {
+        Self::Touch(touch)
     }
 }
 
 /// Like [PointerEvent](web_sys::PointerEvent), but includes the swipe direction
 pub struct GestureSwipeEvent {
-    event: PointerEvent,
+    event: InputEvent,
     /// Direction angle (from -180 to +180 degree)
     pub direction: f64,
 }
 
 impl GestureSwipeEvent {
-    fn new(event: PointerEvent, direction: f64) -> Self {
+    fn new(event: InputEvent, direction: f64) -> Self {
         Self { event, direction }
     }
 }
 
 impl Deref for GestureSwipeEvent {
-    type Target = PointerEvent;
+    type Target = InputEvent;
     fn deref(&self) -> &Self::Target {
         &self.event
     }
@@ -98,20 +124,20 @@ pub struct GestureDetector {
 
     /// Callback for tap events.
     #[prop_or_default]
-    pub on_tap: Option<Callback<PointerEvent>>,
+    pub on_tap: Option<Callback<InputEvent>>,
     /// Callback for long-tap events.
     #[prop_or_default]
     pub on_long_press: Option<Callback<()>>,
 
     /// Callback for drag-start events.
     #[prop_or_default]
-    pub on_drag_start: Option<Callback<GestureDragEvent>>,
+    pub on_drag_start: Option<Callback<InputEvent>>,
     /// Callback for drag-start events.
     #[prop_or_default]
-    pub on_drag_update: Option<Callback<GestureDragEvent>>,
+    pub on_drag_update: Option<Callback<InputEvent>>,
     /// Callback for drag-start events.
     #[prop_or_default]
-    pub on_drag_end: Option<Callback<GestureDragEvent>>,
+    pub on_drag_end: Option<Callback<InputEvent>>,
 
     #[prop_or_default]
     pub on_swipe: Option<Callback<GestureSwipeEvent>>,
@@ -132,7 +158,7 @@ impl GestureDetector {
     }
 
     /// Builder style method to set the on_tap callback
-    pub fn on_tap(mut self, cb: impl IntoEventCallback<PointerEvent>) -> Self {
+    pub fn on_tap(mut self, cb: impl IntoEventCallback<InputEvent>) -> Self {
         self.on_tap = cb.into_event_callback();
         self
     }
@@ -144,19 +170,19 @@ impl GestureDetector {
     }
 
     /// Builder style method to set the on_drag_start callback
-    pub fn on_drag_start(mut self, cb: impl IntoEventCallback<GestureDragEvent>) -> Self {
+    pub fn on_drag_start(mut self, cb: impl IntoEventCallback<InputEvent>) -> Self {
         self.on_drag_start = cb.into_event_callback();
         self
     }
 
     /// Builder style method to set the on_drag_update callback
-    pub fn on_drag_update(mut self, cb: impl IntoEventCallback<GestureDragEvent>) -> Self {
+    pub fn on_drag_update(mut self, cb: impl IntoEventCallback<InputEvent>) -> Self {
         self.on_drag_update = cb.into_event_callback();
         self
     }
 
     /// Builder style method to set the on_drag_end callback
-    pub fn on_drag_end(mut self, cb: impl IntoEventCallback<GestureDragEvent>) -> Self {
+    pub fn on_drag_end(mut self, cb: impl IntoEventCallback<InputEvent>) -> Self {
         self.on_drag_end = cb.into_event_callback();
         self
     }
@@ -365,7 +391,7 @@ impl PwtGestureDetector {
                     if !pointer_state.got_tap_timeout && distance < props.tap_tolerance {
                         if let Some(on_tap) = &props.on_tap {
                             //log::info!("tap {} {}", event.x(), event.y());
-                            on_tap.emit(event);
+                            on_tap.emit(event.into());
                         }
                     }
                 }
@@ -387,8 +413,7 @@ impl PwtGestureDetector {
                         self.state = DetectionState::Drag;
                         self.capture_pointer(event.pointer_id());
                         if let Some(on_drag_start) = &props.on_drag_start {
-                            let event = GestureDragEvent::new(event);
-                            on_drag_start.emit(event);
+                            on_drag_start.emit(event.into());
                         }
                     }
                 }
@@ -417,8 +442,7 @@ impl PwtGestureDetector {
                 self.state = DetectionState::Double;
                 //log::info!("DRAG END");
                 if let Some(on_drag_end) = &props.on_drag_end {
-                    let event = GestureDragEvent::new(event);
-                    on_drag_end.emit(event);
+                    on_drag_end.emit(event.into());
                 }
             }
             Msg::PointerUp(event) => {
@@ -437,8 +461,7 @@ impl PwtGestureDetector {
                     let speed = distance / time_diff;
                     //log::info!("DRAG END {time_diff} {speed}");
                     if let Some(on_drag_end) = &props.on_drag_end {
-                        let event = GestureDragEvent::new(event.clone());
-                        on_drag_end.emit(event);
+                        on_drag_end.emit(event.clone().into());
                     }
 
                     if let Some(on_swipe) = &props.on_swipe {
@@ -453,7 +476,7 @@ impl PwtGestureDetector {
                                 event.y(),
                             );
 
-                            let event = GestureSwipeEvent::new(event, direction);
+                            let event = GestureSwipeEvent::new(event.into(), direction);
                             on_swipe.emit(event)
                         }
                     }
@@ -473,8 +496,7 @@ impl PwtGestureDetector {
                     if distance >= props.tap_tolerance || pointer_state.got_tap_timeout {
                         //log::info!("DRAG TO {} {}", event.x(), event.y());
                         if let Some(on_drag_update) = &props.on_drag_update {
-                            let event = GestureDragEvent::new(event);
-                            on_drag_update.emit(event);
+                            on_drag_update.emit(event.into());
                         }
                     }
                 }
@@ -486,8 +508,7 @@ impl PwtGestureDetector {
                     self.state = DetectionState::Initial;
                     //log::info!("DRAG END");
                     if let Some(on_drag_end) = &props.on_drag_end {
-                        let event = GestureDragEvent::new(event);
-                        on_drag_end.emit(event);
+                        on_drag_end.emit(event.into());
                     }
                 }
             }
diff --git a/src/touch/mod.rs b/src/touch/mod.rs
index c33f1f0..87fcc85 100644
--- a/src/touch/mod.rs
+++ b/src/touch/mod.rs
@@ -5,9 +5,7 @@ mod application_bar;
 pub use application_bar::{ApplicationBar, PwtApplicationBar};
 
 mod gesture_detector;
-pub use gesture_detector::{
-    GestureDetector, GestureDragEvent, GestureSwipeEvent, PwtGestureDetector,
-};
+pub use gesture_detector::{GestureDetector, GestureSwipeEvent, InputEvent, PwtGestureDetector};
 
 mod fab;
 pub use fab::{Fab, PwtFab};
diff --git a/src/touch/side_dialog.rs b/src/touch/side_dialog.rs
index 2bd45f8..01abcd9 100644
--- a/src/touch/side_dialog.rs
+++ b/src/touch/side_dialog.rs
@@ -12,7 +12,7 @@ use crate::prelude::*;
 use crate::state::{SharedState, SharedStateObserver};
 use crate::widget::Container;
 
-use super::{GestureDetector, GestureDragEvent, GestureSwipeEvent};
+use super::{GestureDetector, GestureSwipeEvent, InputEvent};
 
 // Messages sent from the [SideDialogController].
 pub enum SideDialogControllerMsg {
@@ -125,9 +125,9 @@ pub enum Msg {
     Close,
     Dismiss, // Slide out, then close
     SliderAnimationEnd,
-    DragStart(GestureDragEvent),
-    DragEnd(GestureDragEvent),
-    Drag(GestureDragEvent),
+    DragStart(InputEvent),
+    DragEnd(InputEvent),
+    Drag(InputEvent),
     Swipe(GestureSwipeEvent),
     Controller,
 }
@@ -390,11 +390,11 @@ impl Component for PwtSideDialog {
             .on_tap({
                 let slider_ref = self.slider_ref.clone();
                 let link = ctx.link().clone();
-                move |event: PointerEvent| {
+                move |event: InputEvent| {
                     if let Some(element) = slider_ref.clone().into_html_element() {
                         let rect = element.get_bounding_client_rect();
-                        let x = event.client_x() as f64;
-                        let y = event.client_y() as f64;
+                        let x = event.x() as f64;
+                        let y = event.y() as f64;
 
                         if (rect.left() < x)
                             && (x < rect.right())
diff --git a/src/touch/slidable/mod.rs b/src/touch/slidable/mod.rs
index c81d2a9..ffcf8c8 100644
--- a/src/touch/slidable/mod.rs
+++ b/src/touch/slidable/mod.rs
@@ -15,7 +15,7 @@ use yew::virtual_dom::VNode;
 use crate::dom::DomSizeObserver;
 use crate::prelude::*;
 use crate::props::CssLength;
-use crate::touch::{GestureDetector, GestureDragEvent, GestureSwipeEvent};
+use crate::touch::{GestureDetector, GestureSwipeEvent, InputEvent};
 use crate::widget::{Container, Row};
 
 use pwt_macros::widget;
@@ -116,9 +116,9 @@ pub struct PwtSlidable {
 
 pub enum Msg {
     StartDismissTransition,
-    Drag(GestureDragEvent),
-    DragStart(GestureDragEvent),
-    DragEnd(GestureDragEvent),
+    Drag(InputEvent),
+    DragStart(InputEvent),
+    DragEnd(InputEvent),
     Swipe(GestureSwipeEvent),
     LeftResize(f64),
     RightResize(f64),
-- 
2.39.5





More information about the yew-devel mailing list