public inbox for yew-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: yew-devel@lists.proxmox.com
Subject: [PATCH yew-widget-toolkit v2 2/4] touch: gesture detector: unify on_drag{_start,_update,_end} callbacks
Date: Thu, 16 Apr 2026 10:47:28 +0200	[thread overview]
Message-ID: <20260416085849.1062721-2-d.csapak@proxmox.com> (raw)
In-Reply-To: <20260416085849.1062721-1-d.csapak@proxmox.com>

Makes for a simpler interface with less callbacks, and with the
introduction of the GestureDragEvent with an included GesturePhase, we
don't lose anything here.

Adapted the users (Slidable and SideDialog) to use this new interface.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
new in v2

 src/touch/gesture_detector.rs | 100 +++++++++++++++++++---------------
 src/touch/mod.rs              |   4 +-
 src/touch/side_dialog.rs      |  78 +++++++++++++-------------
 src/touch/slidable/mod.rs     |  32 +++++------
 4 files changed, 111 insertions(+), 103 deletions(-)

diff --git a/src/touch/gesture_detector.rs b/src/touch/gesture_detector.rs
index 8cabf55..d3b4452 100644
--- a/src/touch/gesture_detector.rs
+++ b/src/touch/gesture_detector.rs
@@ -133,6 +133,28 @@ impl GesturePinchZoomEvent {
     }
 }
 
+/// An event that can happen when the user uses a drag gesture
+#[derive(Clone, PartialEq)]
+pub struct GestureDragEvent {
+    /// The current phase of the event
+    pub phase: GesturePhase,
+
+    event: InputEvent,
+}
+
+impl GestureDragEvent {
+    fn new(event: InputEvent, phase: GesturePhase) -> Self {
+        Self { event, phase }
+    }
+}
+
+impl Deref for GestureDragEvent {
+    type Target = InputEvent;
+    fn deref(&self) -> &Self::Target {
+        &self.event
+    }
+}
+
 /// Gesture detector.
 ///
 /// You need to set the CSS attribute `touch-action: none;` on children to receive all events.
@@ -189,15 +211,9 @@ pub struct GestureDetector {
     #[prop_or_default]
     pub on_long_press: Option<Callback<()>>,
 
-    /// Callback for drag-start events.
-    #[prop_or_default]
-    pub on_drag_start: Option<Callback<InputEvent>>,
-    /// Callback for drag-start events.
-    #[prop_or_default]
-    pub on_drag_update: Option<Callback<InputEvent>>,
-    /// Callback for drag-start events.
+    /// Callback for drag events.
     #[prop_or_default]
-    pub on_drag_end: Option<Callback<InputEvent>>,
+    pub on_drag: Option<Callback<GestureDragEvent>>,
 
     #[prop_or_default]
     pub on_swipe: Option<Callback<GestureSwipeEvent>>,
@@ -233,21 +249,9 @@ impl GestureDetector {
         self
     }
 
-    /// Builder style method to set the on_drag_start callback
-    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<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<InputEvent>) -> Self {
-        self.on_drag_end = cb.into_event_callback();
+    /// Builder style method to set the on_drag callback
+    pub fn on_drag(mut self, cb: impl IntoEventCallback<GestureDragEvent>) -> Self {
+        self.on_drag = cb.into_event_callback();
         self
     }
 
@@ -687,8 +691,8 @@ impl PwtGestureDetector {
                         //log::info!("DRAG START {} {}", event.x(), event.y());
                         self.state = DetectionState::Drag;
                         self.capture_pointer(event.pointer_id());
-                        if let Some(on_drag_start) = &props.on_drag_start {
-                            on_drag_start.emit(event.into());
+                        if let Some(on_drag) = &props.on_drag {
+                            on_drag.emit(GestureDragEvent::new(event.into(), GesturePhase::Start));
                         }
                     }
                 }
@@ -709,8 +713,9 @@ impl PwtGestureDetector {
                         // Make sure it cannot be a TAP or LONG PRESS event
                         if distance >= props.tap_tolerance {
                             self.state = DetectionState::Drag;
-                            if let Some(on_drag_start) = &props.on_drag_start {
-                                on_drag_start.emit(touch.into());
+                            if let Some(on_drag) = &props.on_drag {
+                                on_drag
+                                    .emit(GestureDragEvent::new(touch.into(), GesturePhase::Start));
                             }
                         }
                     }
@@ -745,8 +750,8 @@ impl PwtGestureDetector {
                 self.register_pointer(ctx, &event);
                 self.state = DetectionState::Double;
                 //log::info!("DRAG END");
-                if let Some(on_drag_end) = &props.on_drag_end {
-                    on_drag_end.emit(event.into());
+                if let Some(on_drag) = &props.on_drag {
+                    on_drag.emit(GestureDragEvent::new(event.into(), GesturePhase::End));
                 }
                 let (point0, point1) = self.get_pinch_points();
                 self.call_on_pinch_zoom(ctx, point0, point1, GesturePhase::Start);
@@ -771,8 +776,8 @@ impl PwtGestureDetector {
                 }
                 for_each_active_touch(&event, |touch| {
                     if self.pointers.contains_key(&touch.identifier()) {
-                        if let Some(on_drag_end) = &props.on_drag_end {
-                            on_drag_end.emit(touch.into());
+                        if let Some(on_drag) = &props.on_drag {
+                            on_drag.emit(GestureDragEvent::new(touch.into(), GesturePhase::End));
                         }
                     }
                 });
@@ -792,8 +797,11 @@ impl PwtGestureDetector {
                     let time_diff = now() - pointer_state.start_ctime;
                     let speed = distance / time_diff;
                     //log::info!("DRAG END {time_diff} {speed}");
-                    if let Some(on_drag_end) = &props.on_drag_end {
-                        on_drag_end.emit(event.clone().into());
+                    if let Some(on_drag) = &props.on_drag {
+                        on_drag.emit(GestureDragEvent::new(
+                            event.clone().into(),
+                            GesturePhase::End,
+                        ));
                     }
 
                     if let Some(on_swipe) = &props.on_swipe {
@@ -828,8 +836,11 @@ impl PwtGestureDetector {
                         let time_diff = now() - pointer_state.start_ctime;
                         let speed = distance / time_diff;
                         //log::info!("DRAG END {time_diff} {speed}");
-                        if let Some(on_drag_end) = &props.on_drag_end {
-                            on_drag_end.emit(touch.clone().into());
+                        if let Some(on_drag) = &props.on_drag {
+                            on_drag.emit(GestureDragEvent::new(
+                                touch.clone().into(),
+                                GesturePhase::End,
+                            ));
                         }
 
                         if let Some(on_swipe) = &props.on_swipe {
@@ -865,8 +876,8 @@ 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 {
-                            on_drag_update.emit(event.into());
+                        if let Some(on_drag) = &props.on_drag {
+                            on_drag.emit(GestureDragEvent::new(event.into(), GesturePhase::Update));
                         }
                     }
                 }
@@ -886,8 +897,11 @@ 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 {
-                                on_drag_update.emit(touch.into());
+                            if let Some(on_drag) = &props.on_drag {
+                                on_drag.emit(GestureDragEvent::new(
+                                    touch.into(),
+                                    GesturePhase::Update,
+                                ));
                             }
                         }
                     }
@@ -899,8 +913,8 @@ impl PwtGestureDetector {
                 if let Some(_pointer_state) = self.unregister_pointer(event.pointer_id()) {
                     self.state = DetectionState::Initial;
                     //log::info!("DRAG END");
-                    if let Some(on_drag_end) = &props.on_drag_end {
-                        on_drag_end.emit(event.into());
+                    if let Some(on_drag) = &props.on_drag {
+                        on_drag.emit(GestureDragEvent::new(event.into(), GesturePhase::End));
                     }
                 }
             }
@@ -909,8 +923,8 @@ impl PwtGestureDetector {
                 assert!(pointer_count == 1);
                 self.unregister_touches(&event, |_id, touch, _pointer_state| {
                     //log::info!("DRAG END");
-                    if let Some(on_drag_end) = &props.on_drag_end {
-                        on_drag_end.emit(touch.into());
+                    if let Some(on_drag) = &props.on_drag {
+                        on_drag.emit(GestureDragEvent::new(touch.into(), GesturePhase::End));
                     }
                 });
                 self.state = DetectionState::Initial;
diff --git a/src/touch/mod.rs b/src/touch/mod.rs
index 6d00ee7..34d5402 100644
--- a/src/touch/mod.rs
+++ b/src/touch/mod.rs
@@ -6,8 +6,8 @@ pub use application_bar::{ApplicationBar, PwtApplicationBar};
 
 mod gesture_detector;
 pub use gesture_detector::{
-    GestureDetector, GesturePhase, GesturePinchZoomEvent, GestureSwipeEvent, InputEvent,
-    PinchPoint, PwtGestureDetector,
+    GestureDetector, GestureDragEvent, GesturePhase, GesturePinchZoomEvent, GestureSwipeEvent,
+    InputEvent, PinchPoint, PwtGestureDetector,
 };
 
 mod fab;
diff --git a/src/touch/side_dialog.rs b/src/touch/side_dialog.rs
index 8760160..9bc23c9 100644
--- a/src/touch/side_dialog.rs
+++ b/src/touch/side_dialog.rs
@@ -10,10 +10,11 @@ use yew::virtual_dom::{Key, VComp, VNode};
 use crate::dom::IntoHtmlElement;
 use crate::props::{AsCssStylesMut, CssStyles};
 use crate::state::{SharedState, SharedStateObserver};
+use crate::touch::GestureDragEvent;
 use crate::widget::Container;
 use crate::{impl_yew_std_props_builder, prelude::*};
 
-use super::{GestureDetector, GestureSwipeEvent, InputEvent};
+use super::{GestureDetector, GesturePhase, GestureSwipeEvent, InputEvent};
 
 // Messages sent from the [SideDialogController].
 pub enum SideDialogControllerMsg {
@@ -125,9 +126,7 @@ pub enum Msg {
     Close,
     Dismiss, // Slide out, then close
     SliderAnimationEnd,
-    DragStart(InputEvent),
-    DragEnd(InputEvent),
-    Drag(InputEvent),
+    Drag(GestureDragEvent),
     Swipe(GestureSwipeEvent),
     Controller,
 }
@@ -262,43 +261,44 @@ impl Component for PwtSideDialog {
                 };
                 true
             }
-            Msg::DragStart(event) => {
+            Msg::Drag(event) => {
                 let x = event.x() as f64;
                 let y = event.y() as f64;
-                if x > 0.0 && y > 0.0 {
-                    // prevent divide by zero
-                    self.drag_start = Some((x, y));
-                    self.drag_delta = Some((0.0, 0.0));
-                }
-                false
-            }
-            Msg::DragEnd(_event) => {
-                let mut dismiss = false;
-                let threshold = 100.0;
-                if let Some((delta_x, delta_y)) = self.drag_delta {
-                    dismiss = match props.location {
-                        SideDialogLocation::Left => delta_x < -threshold,
-                        SideDialogLocation::Right => delta_x > threshold,
-                        SideDialogLocation::Top => delta_y < -threshold,
-                        SideDialogLocation::Bottom => delta_y > threshold,
-                    };
-                }
-                self.drag_start = None;
-                self.drag_delta = None;
-
-                if dismiss {
-                    ctx.link().send_message(Msg::Dismiss);
-                }
-                true
-            }
-            Msg::Drag(event) => {
-                if let Some(start) = &self.drag_start {
-                    let x = event.x() as f64;
-                    let y = event.y() as f64;
+                match event.phase {
+                    GesturePhase::Start => {
+                        if x > 0.0 && y > 0.0 {
+                            // prevent divide by zero
+                            self.drag_start = Some((x, y));
+                            self.drag_delta = Some((0.0, 0.0));
+                        }
+                        false
+                    }
+                    GesturePhase::Update => {
+                        if let Some(start) = &self.drag_start {
+                            self.drag_delta = Some((x - start.0, y - start.1));
+                        }
+                        true
+                    }
+                    GesturePhase::End => {
+                        let mut dismiss = false;
+                        let threshold = 100.0;
+                        if let Some((delta_x, delta_y)) = self.drag_delta {
+                            dismiss = match props.location {
+                                SideDialogLocation::Left => delta_x < -threshold,
+                                SideDialogLocation::Right => delta_x > threshold,
+                                SideDialogLocation::Top => delta_y < -threshold,
+                                SideDialogLocation::Bottom => delta_y > threshold,
+                            };
+                        }
+                        self.drag_start = None;
+                        self.drag_delta = None;
 
-                    self.drag_delta = Some((x - start.0, y - start.1));
+                        if dismiss {
+                            ctx.link().send_message(Msg::Dismiss);
+                        }
+                        true
+                    }
                 }
-                true
             }
             Msg::Swipe(event) => {
                 let angle = event.direction; // -180 to + 180
@@ -421,9 +421,7 @@ impl Component for PwtSideDialog {
                     }
                 }
             })
-            .on_drag_start(ctx.link().callback(Msg::DragStart))
-            .on_drag_end(ctx.link().callback(Msg::DragEnd))
-            .on_drag_update(ctx.link().callback(Msg::Drag))
+            .on_drag(ctx.link().callback(Msg::Drag))
             .on_swipe(ctx.link().callback(Msg::Swipe));
 
         let controller_context = html! {
diff --git a/src/touch/slidable/mod.rs b/src/touch/slidable/mod.rs
index ff77bbd..ae45e29 100644
--- a/src/touch/slidable/mod.rs
+++ b/src/touch/slidable/mod.rs
@@ -15,7 +15,9 @@ use yew::virtual_dom::VNode;
 use crate::dom::DomSizeObserver;
 use crate::prelude::*;
 use crate::props::CssLength;
-use crate::touch::{GestureDetector, GestureSwipeEvent, InputEvent};
+use crate::touch::{
+    GestureDetector, GestureDragEvent, GesturePhase, GestureSwipeEvent, InputEvent,
+};
 use crate::widget::{Container, Row};
 
 use pwt_macros::widget;
@@ -133,9 +135,7 @@ pub struct PwtSlidable {
 
 pub enum Msg {
     StartDismissTransition,
-    Drag(InputEvent),
-    DragStart(InputEvent),
-    DragEnd(InputEvent),
+    Drag(GestureDragEvent),
     Swipe(GestureSwipeEvent),
     LeftResize(f64),
     RightResize(f64),
@@ -245,17 +245,15 @@ impl Component for PwtSlidable {
             Msg::StartDismissTransition => {
                 self.view_state = ViewState::DismissTransition;
             }
-            Msg::Drag(event) => {
-                self.drag_pos = Some(self.drag_start - event.x());
-            }
-            Msg::DragStart(event) => {
-                self.drag_start = event.x();
-            }
-            Msg::DragEnd(_event) => {
-                self.drag_start = 0;
-                self.start_pos -= self.drag_pos.take().unwrap_or(0) as f64;
-                self.finalize_drag();
-            }
+            Msg::Drag(event) => match event.phase {
+                GesturePhase::Start => self.drag_start = event.x(),
+                GesturePhase::Update => self.drag_pos = Some(self.drag_start - event.x()),
+                GesturePhase::End => {
+                    self.drag_start = 0;
+                    self.start_pos -= self.drag_pos.take().unwrap_or(0) as f64;
+                    self.finalize_drag();
+                }
+            },
             Msg::ContentResize(width, height) => {
                 if self.start_pos == 0f64 && self.drag_pos.is_none() && !self.switch_back {
                     self.content_width = width.max(0f64);
@@ -379,9 +377,7 @@ impl Component for PwtSlidable {
                 .with_child(props.content.clone())
                 .into_html_with_ref(self.content_ref.clone()),
         )
-        .on_drag_start(ctx.link().callback(Msg::DragStart))
-        .on_drag_end(ctx.link().callback(Msg::DragEnd))
-        .on_drag_update(ctx.link().callback(Msg::Drag))
+        .on_drag(ctx.link().callback(Msg::Drag))
         .on_tap(ctx.link().callback(Msg::OnTap))
         .on_swipe(ctx.link().callback(Msg::Swipe));
 
-- 
2.47.3





  reply	other threads:[~2026-04-16  8:58 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-16  8:47 [PATCH yew-widget-toolkit v2 1/4] touch: gesture detector: implement pinch zoom gesture Dominik Csapak
2026-04-16  8:47 ` Dominik Csapak [this message]
2026-04-16  8:47 ` [PATCH yew-widget-toolkit v2 3/4] touch: gesture detector: derive Clone and PartialEq for Events Dominik Csapak
2026-04-16  8:47 ` [PATCH yew-widget-toolkit v2 4/4] touch: gesture detector: add missing comment for swipe callback Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260416085849.1062721-2-d.csapak@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=yew-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal