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