Skip to main content

repame_view2d/
pick.rs

1//! Pointer picks in world space: the event vocabulary and the click rule.
2//!
3//! Reuse this vocabulary for any surface that reports picks; the emitters
4//! (`actors_view_world_with_events`, a game's own sprite viewport) and the
5//! consumers then agree on what the pointer did.
6
7use glam::Vec2;
8
9pub enum PickEvent {
10    Press {
11        world: Vec2,
12        screen: [f32; 2],
13        button: repose_core::input::PointerButton,
14    },
15    Click {
16        world: Vec2,
17        screen: [f32; 2],
18        button: repose_core::input::PointerButton,
19    },
20    /// Pointer released, always. [`PickEvent::Click`] is gated behind
21    /// [`click_within_slop_dp`], so a drag that ends far from where it started
22    /// emits no click at all; a game that drags something has no other way to
23    /// learn that the drag ended. Emitted before [`PickEvent::Click`], and
24    /// whether or not a click follows.
25    Release {
26        world: Vec2,
27        screen: [f32; 2],
28        button: repose_core::input::PointerButton,
29    },
30    Hover {
31        world: Vec2,
32        /// Window-physical px (y-down), same space as `Press.screen`.
33        /// Games stage this raw and unproject through the live camera
34        /// each frame (screen-anchored aim); the `world` point is baked
35        /// through the camera at event time and goes stale as the
36        /// camera moves.
37        screen: [f32; 2],
38    },
39    /// Touch/pen contact began: pointer id + window-physical px.
40    /// Mouse does not emit these; taps still emit `Click` too.
41    TouchDown { id: u64, screen: [f32; 2] },
42    /// Touch/pen contact moved (same coordinate space as `TouchDown`).
43    TouchMove { id: u64, screen: [f32; 2] },
44    /// Touch/pen contact ended (up / leave).
45    TouchUp { id: u64 },
46}
47
48/// Press slop in dp: pointer-up farther than this from pointer-down
49/// cancels the `Click` (drag-off-cancel). Density-independent: compared
50/// after dividing physical-px deltas by the painted density, so a tap
51/// cancels identically on density 1 and density 3.
52pub const CLICK_SLOP_DP: f32 = 12.0;
53
54/// Dp-space slop check: physical-px points divided by `density` (falling
55/// back to 1.0) before comparing against [`CLICK_SLOP_DP`].
56pub fn click_within_slop_dp(a: [f32; 2], b: [f32; 2], density: f32) -> bool {
57    let d = if density.is_finite() && density > 1e-6 {
58        density
59    } else {
60        1.0
61    };
62    let dx = (a[0] - b[0]) / d;
63    let dy = (a[1] - b[1]) / d;
64    dx * dx + dy * dy <= CLICK_SLOP_DP * CLICK_SLOP_DP
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn click_slop_cancels_drags() {
73        assert!(click_within_slop_dp([100.0, 100.0], [105.0, 105.0], 1.0));
74        assert!(click_within_slop_dp([100.0, 100.0], [105.0, 105.0], 1.0));
75        assert!(!click_within_slop_dp(
76            [100.0, 100.0],
77            [100.0 + CLICK_SLOP_DP + 1.0, 100.0],
78            1.0
79        ));
80    }
81
82    #[test]
83    fn click_slop_is_density_independent() {
84        // 12 dp at density 3 is 36 px: same tap cancels on both densities.
85        assert!(click_within_slop_dp([0.0, 0.0], [36.0, 0.0], 3.0));
86        assert!(!click_within_slop_dp([0.0, 0.0], [37.0, 0.0], 3.0));
87        assert!(click_within_slop_dp([0.0, 0.0], [12.0, 0.0], 1.0));
88        assert!(!click_within_slop_dp([0.0, 0.0], [13.0, 0.0], 1.0));
89    }
90}