From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 2FCB81FF13C for ; Thu, 16 Apr 2026 10:58:55 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 15782258D3; Thu, 16 Apr 2026 10:58:55 +0200 (CEST) From: Dominik Csapak 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 Message-ID: <20260416085849.1062721-2-d.csapak@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260416085849.1062721-1-d.csapak@proxmox.com> References: <20260416085849.1062721-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.049 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [mod.rs] Message-ID-Hash: WB54ASRZHRBROFUHLDX5AIM72HY5KAM5 X-Message-ID-Hash: WB54ASRZHRBROFUHLDX5AIM72HY5KAM5 X-MailFrom: d.csapak@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Yew framework devel list at Proxmox List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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 --- 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 for drag-start events. - #[prop_or_default] - pub on_drag_start: Option>, - /// Callback for drag-start events. - #[prop_or_default] - pub on_drag_update: Option>, - /// Callback for drag-start events. + /// Callback for drag events. #[prop_or_default] - pub on_drag_end: Option>, + pub on_drag: Option>, #[prop_or_default] pub on_swipe: Option>, @@ -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) -> 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) -> 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) -> 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) -> 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