Skip to main content

dioxus_flow/
press.rs

1//! When a press has become a drag.
2//!
3//! This is one number and one comparison, and it is here on purpose. Whether a
4//! press is still a click decides three separate things — whether a gesture has
5//! started, whether releasing counts as a click, and when the pointer may be
6//! captured — and the last of those is easy to get wrong: capturing on the press
7//! itself makes the browser re-target `click` and `dblclick` at the capturing
8//! element, so every double-click on anything inside the surface is lost. Capture
9//! belongs to a drag, not to a press, and [`Press::is_drag`] is what says which
10//! one is in hand.
11
12use crate::types::Point;
13
14/// How far a mouse or trackpad must travel before a press is a drag, in surface
15/// pixels.
16pub const PRECISE_THRESHOLD: f64 = 4.0;
17/// The same for touch and pen, which are noisier.
18pub const COARSE_THRESHOLD: f64 = 8.0;
19
20/// The press a gesture began with.
21#[derive(Clone, Copy, Debug, PartialEq)]
22pub struct Press {
23    /// Where it landed, in surface pixels.
24    pub at: Point,
25    /// How far it has to travel to become a drag.
26    pub threshold: f64,
27}
28
29impl Press {
30    pub const fn new(at: Point, threshold: f64) -> Self {
31        Self { at, threshold }
32    }
33
34    /// A press from a pointer that reports its own position precisely, or not.
35    pub const fn from_pointer(at: Point, coarse: bool) -> Self {
36        Self::new(
37            at,
38            if coarse {
39                COARSE_THRESHOLD
40            } else {
41                PRECISE_THRESHOLD
42            },
43        )
44    }
45
46    /// Whether the pointer has travelled far enough for this to be a drag.
47    pub fn is_drag(self, now: Point) -> bool {
48        self.at.distance(now) >= self.threshold
49    }
50
51    /// The same question for a pointer tracked in map coordinates, where the
52    /// threshold still means screen pixels however far the view is zoomed.
53    pub fn is_drag_in_world(self, moved: f64, zoom: f64) -> bool {
54        moved * zoom >= self.threshold
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn a_still_pointer_is_never_a_drag_and_a_travelled_one_always_is() {
64        let press = Press::from_pointer(Point::new(100.0, 100.0), false);
65        assert!(!press.is_drag(press.at));
66        assert!(!press.is_drag(Point::new(103.0, 100.0)));
67        assert!(press.is_drag(Point::new(104.0, 100.0)));
68        assert!(press.is_drag(Point::new(97.0, 97.0)));
69    }
70
71    #[test]
72    fn touch_is_allowed_more_wobble_than_a_mouse() {
73        let at = Point::new(0.0, 0.0);
74        let wobble = Point::new(6.0, 0.0);
75        assert!(Press::from_pointer(at, false).is_drag(wobble));
76        assert!(!Press::from_pointer(at, true).is_drag(wobble));
77    }
78
79    /// The threshold is a screen distance, so the further in the view is zoomed
80    /// the less of the map a drag has to cross.
81    #[test]
82    fn the_threshold_means_screen_pixels_at_any_zoom() {
83        let press = Press::from_pointer(Point::new(0.0, 0.0), false);
84        assert!(!press.is_drag_in_world(3.0, 1.0));
85        assert!(press.is_drag_in_world(3.0, 2.0));
86        assert!(!press.is_drag_in_world(3.0, 0.5));
87    }
88}