Skip to main content

repose_ui/
gestures.rs

1use repose_core::Vec2;
2
3use crate::input::*;
4use std::rc::Rc;
5use web_time::{Duration, Instant};
6
7pub struct GestureDetector {
8    on_tap: Option<Rc<dyn Fn(Vec2)>>,
9    on_double_tap: Option<Rc<dyn Fn(Vec2)>>,
10    on_long_press: Option<Rc<dyn Fn(Vec2)>>,
11    on_drag: Option<Rc<dyn Fn(DragEvent)>>,
12    on_swipe: Option<Rc<dyn Fn(SwipeDirection)>>,
13
14    // Internal state
15    last_tap: Option<Instant>,
16    press_start: Option<(Instant, Vec2)>,
17    drag_start: Option<Vec2>,
18}
19
20pub struct DragEvent {
21    pub start: Vec2,
22    pub current: Vec2,
23    pub delta: Vec2,
24    pub velocity: Vec2,
25}
26
27pub enum SwipeDirection {
28    Up,
29    Down,
30    Left,
31    Right,
32}
33
34impl Default for GestureDetector {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl GestureDetector {
41    pub fn new() -> Self {
42        Self {
43            on_tap: None,
44            on_double_tap: None,
45            on_long_press: None,
46            on_drag: None,
47            on_swipe: None,
48            last_tap: None,
49            press_start: None,
50            drag_start: None,
51        }
52    }
53
54    pub fn handle_pointer(&mut self, event: &PointerEvent) {
55        match event.event {
56            PointerEventKind::Down(_) => {
57                self.press_start = Some((Instant::now(), event.position));
58                self.drag_start = Some(event.position);
59
60                // Check for double tap
61                if let Some(last) = self.last_tap
62                    && (Instant::now() - last) < Duration::from_millis(300)
63                {
64                    if let Some(cb) = &self.on_double_tap {
65                        cb(event.position);
66                    }
67                    self.last_tap = None;
68                }
69            }
70            PointerEventKind::Up(_) => {
71                if let Some((start_time, start_pos)) = self.press_start {
72                    let elapsed = Instant::now() - start_time;
73                    let distance = ((event.position.x - start_pos.x).powi(2)
74                        + (event.position.y - start_pos.y).powi(2))
75                    .sqrt();
76
77                    if elapsed < Duration::from_millis(200) && distance < 10.0 {
78                        // Tap
79                        if let Some(cb) = &self.on_tap {
80                            cb(event.position);
81                        }
82                        self.last_tap = Some(Instant::now());
83                    } else if distance > 50.0 {
84                        // Swipe detection
85                        let dx = event.position.x - start_pos.x;
86                        let dy = event.position.y - start_pos.y;
87
88                        if let Some(cb) = &self.on_swipe {
89                            let dir = if dx.abs() > dy.abs() {
90                                if dx > 0.0 {
91                                    SwipeDirection::Right
92                                } else {
93                                    SwipeDirection::Left
94                                }
95                            } else if dy > 0.0 {
96                                SwipeDirection::Down
97                            } else {
98                                SwipeDirection::Up
99                            };
100                            cb(dir);
101                        }
102                    }
103                }
104                self.press_start = None;
105                self.drag_start = None;
106            }
107            PointerEventKind::Move => {
108                if let Some(start) = self.drag_start
109                    && let Some(cb) = &self.on_drag
110                {
111                    cb(DragEvent {
112                        start,
113                        current: event.position,
114                        delta: Vec2 {
115                            x: event.position.x - start.x,
116                            y: event.position.y - start.y,
117                        },
118                        velocity: Vec2::default(), // TODO: calculate from history
119                    });
120                }
121
122                // Long press detection
123                if let Some((start_time, pos)) = self.press_start
124                    && (Instant::now() - start_time) > Duration::from_millis(500)
125                {
126                    if let Some(cb) = &self.on_long_press {
127                        cb(pos);
128                    }
129                    self.press_start = None; // Fire once
130                }
131            }
132            _ => {}
133        }
134    }
135}