Skip to main content

cranpose_app_shell/
touch_router.rs

1//! Fingers of a platform that tells them apart, routed to the shell's
2//! pointers: the first finger down drives the primary pointer, and each
3//! finger that joins it a secondary pointer of its own, until the gesture
4//! ends.
5
6use std::fmt::Debug;
7
8use cranpose_render_common::Renderer;
9
10use crate::{AppShell, PointerSource};
11
12/// Which shell pointer a finger drives.
13enum Route {
14    Primary,
15    Secondary(u64),
16    Untracked,
17}
18
19/// Routes the fingers on one surface to the shell's primary and secondary
20/// pointers. `F` is the platform's identity of a finger. `None` stands for
21/// a mouse, which always drives the primary pointer.
22///
23/// Each method returns whether the shell changed and needs a frame.
24#[derive(Debug)]
25pub struct TouchRouter<F> {
26    primary: Option<F>,
27    /// The fingers that joined the primary one, in the order they came,
28    /// with the secondary pointer each drives.
29    secondaries: Vec<(F, u64)>,
30    next_secondary_id: u64,
31}
32
33impl<F> Default for TouchRouter<F> {
34    fn default() -> Self {
35        Self {
36            primary: None,
37            secondaries: Vec::new(),
38            next_secondary_id: 0,
39        }
40    }
41}
42
43impl<F: Copy + Eq> TouchRouter<F> {
44    /// A finger, or the mouse, went down at `(x, y)` in logical pixels.
45    pub fn press<R>(&mut self, shell: &mut AppShell<R>, finger: Option<F>, x: f32, y: f32) -> bool
46    where
47        R: Renderer,
48        R::Error: Debug,
49    {
50        match self.route_press(finger) {
51            Route::Primary => {
52                let event_time = shell.realtime_pointer_event_time(None);
53                shell.set_cursor_at_event_time(x, y, event_time);
54                shell.pointer_pressed_at_event_time(event_time)
55            }
56            Route::Secondary(id) => shell.secondary_pointer_pressed(id, x, y, None),
57            Route::Untracked => false,
58        }
59    }
60
61    /// A finger, or the mouse, moved to `(x, y)`. A finger that is not down
62    /// moves nothing.
63    pub fn moved<R>(&self, shell: &mut AppShell<R>, finger: Option<F>, x: f32, y: f32) -> bool
64    where
65        R: Renderer,
66        R::Error: Debug,
67    {
68        match self.route(finger) {
69            Route::Primary => shell.set_cursor(x, y),
70            Route::Secondary(id) => shell.secondary_pointer_moved(id, x, y, None),
71            Route::Untracked => false,
72        }
73    }
74
75    /// A finger, or the mouse, lifted at `(x, y)`. The primary finger's
76    /// lift ends the gesture: the secondary fingers are released first,
77    /// there, and the next finger down drives the primary pointer.
78    pub fn release<R>(&mut self, shell: &mut AppShell<R>, finger: Option<F>, x: f32, y: f32) -> bool
79    where
80        R: Renderer,
81        R::Error: Debug,
82    {
83        let Some(finger) = finger else {
84            return release_primary(shell, x, y);
85        };
86        if self.primary == Some(finger) {
87            let mut changed = false;
88            for (_, id) in self.secondaries.drain(..) {
89                changed |= shell.secondary_pointer_released(id, x, y, None);
90            }
91            self.end_gesture();
92            return release_primary(shell, x, y) | changed;
93        }
94        if let Some(id) = self.take_secondary(finger) {
95            return shell.secondary_pointer_released(id, x, y, None);
96        }
97        // A lift after the gesture ended, as some platforms send one per
98        // finger: only a lift with no gesture running reaches the shell.
99        self.is_idle() && release_primary(shell, x, y)
100    }
101
102    /// A finger, or the mouse, left without lifting: the system took the
103    /// touch, or it left the surface. The primary finger's leaving cancels
104    /// the gesture. A secondary finger is released at `position`, or at the
105    /// origin when the platform gives none.
106    pub fn cancel<R>(
107        &mut self,
108        shell: &mut AppShell<R>,
109        finger: Option<F>,
110        position: Option<(f32, f32)>,
111    ) -> bool
112    where
113        R: Renderer,
114        R::Error: Debug,
115    {
116        let secondary = finger
117            .filter(|finger| self.primary != Some(*finger))
118            .and_then(|finger| self.take_secondary(finger));
119        if let Some(id) = secondary {
120            shell.set_pointer_source(PointerSource::Touch);
121            let (x, y) = position.unwrap_or((0.0, 0.0));
122            return shell.secondary_pointer_released(id, x, y, None);
123        }
124        let ends_gesture = finger.is_none() || self.primary == finger || self.is_idle();
125        if ends_gesture {
126            self.end_gesture();
127            shell.cancel_gesture();
128        }
129        ends_gesture
130    }
131
132    fn route_press(&mut self, finger: Option<F>) -> Route {
133        let Some(finger) = finger else {
134            return Route::Primary;
135        };
136        match self.primary {
137            None => {
138                self.primary = Some(finger);
139                Route::Primary
140            }
141            Some(primary) if primary == finger => Route::Primary,
142            Some(_) => Route::Secondary(self.secondary_id(finger).unwrap_or_else(|| {
143                self.next_secondary_id += 1;
144                self.secondaries.push((finger, self.next_secondary_id));
145                self.next_secondary_id
146            })),
147        }
148    }
149
150    fn route(&self, finger: Option<F>) -> Route {
151        match finger {
152            None => Route::Primary,
153            Some(finger) if self.primary == Some(finger) => Route::Primary,
154            Some(finger) => self
155                .secondary_id(finger)
156                .map_or(Route::Untracked, Route::Secondary),
157        }
158    }
159
160    fn secondary_id(&self, finger: F) -> Option<u64> {
161        self.secondaries
162            .iter()
163            .find(|(tracked, _)| *tracked == finger)
164            .map(|(_, id)| *id)
165    }
166
167    fn take_secondary(&mut self, finger: F) -> Option<u64> {
168        let index = self
169            .secondaries
170            .iter()
171            .position(|(tracked, _)| *tracked == finger)?;
172        Some(self.secondaries.remove(index).1)
173    }
174
175    fn is_idle(&self) -> bool {
176        self.primary.is_none() && self.secondaries.is_empty()
177    }
178
179    fn end_gesture(&mut self) {
180        self.primary = None;
181        self.secondaries.clear();
182        self.next_secondary_id = 0;
183    }
184}
185
186fn release_primary<R>(shell: &mut AppShell<R>, x: f32, y: f32) -> bool
187where
188    R: Renderer,
189    R::Error: Debug,
190{
191    let event_time = shell.realtime_pointer_event_time(None);
192    shell.pointer_released_at_position_event_time(x, y, event_time)
193}