use std::fmt::Debug;
use cranpose_foundation::PointerId;
use cranpose_render_common::Renderer;
use crate::{AppShell, PointerSource};
enum Route {
Primary,
Secondary(PointerId),
Untracked,
}
#[derive(Debug)]
pub struct TouchRouter<F> {
primary: Option<F>,
secondaries: Vec<(F, PointerId)>,
next_secondary_id: PointerId,
}
impl<F> Default for TouchRouter<F> {
fn default() -> Self {
Self {
primary: None,
secondaries: Vec::new(),
next_secondary_id: PointerId::PRIMARY,
}
}
}
impl<F: Copy + Eq> TouchRouter<F> {
pub fn press<R>(&mut self, shell: &mut AppShell<R>, finger: Option<F>, x: f32, y: f32) -> bool
where
R: Renderer,
R::Error: Debug,
{
match self.route_press(finger) {
Route::Primary => {
let event_time = shell.realtime_pointer_event_time(None);
shell.set_cursor_at_event_time(x, y, event_time);
shell.pointer_pressed_at_event_time(event_time)
}
Route::Secondary(id) => shell.secondary_pointer_pressed(id, x, y, None),
Route::Untracked => false,
}
}
pub fn moved<R>(&self, shell: &mut AppShell<R>, finger: Option<F>, x: f32, y: f32) -> bool
where
R: Renderer,
R::Error: Debug,
{
match self.route(finger) {
Route::Primary => shell.set_cursor(x, y),
Route::Secondary(id) => shell.secondary_pointer_moved(id, x, y, None),
Route::Untracked => false,
}
}
pub fn release<R>(&mut self, shell: &mut AppShell<R>, finger: Option<F>, x: f32, y: f32) -> bool
where
R: Renderer,
R::Error: Debug,
{
let Some(finger) = finger else {
return release_primary(shell, x, y);
};
if self.primary == Some(finger) {
let mut changed = false;
for (_, id) in self.secondaries.drain(..) {
changed |= shell.secondary_pointer_released(id, x, y, None);
}
self.end_gesture();
return release_primary(shell, x, y) | changed;
}
if let Some(id) = self.take_secondary(finger) {
return shell.secondary_pointer_released(id, x, y, None);
}
self.is_idle() && release_primary(shell, x, y)
}
pub fn cancel<R>(
&mut self,
shell: &mut AppShell<R>,
finger: Option<F>,
position: Option<(f32, f32)>,
) -> bool
where
R: Renderer,
R::Error: Debug,
{
let secondary = finger
.filter(|finger| self.primary != Some(*finger))
.and_then(|finger| self.take_secondary(finger));
if let Some(id) = secondary {
shell.set_pointer_source(PointerSource::Touch);
let (x, y) = position.unwrap_or((0.0, 0.0));
return shell.secondary_pointer_released(id, x, y, None);
}
let ends_gesture = finger.is_none() || self.primary == finger || self.is_idle();
if ends_gesture {
self.end_gesture();
shell.cancel_gesture();
}
ends_gesture
}
fn route_press(&mut self, finger: Option<F>) -> Route {
let Some(finger) = finger else {
return Route::Primary;
};
match self.primary {
None => {
self.primary = Some(finger);
Route::Primary
}
Some(primary) if primary == finger => Route::Primary,
Some(_) => Route::Secondary(self.secondary_id(finger).unwrap_or_else(|| {
self.next_secondary_id = PointerId(self.next_secondary_id.0 + 1);
self.secondaries.push((finger, self.next_secondary_id));
self.next_secondary_id
})),
}
}
fn route(&self, finger: Option<F>) -> Route {
match finger {
None => Route::Primary,
Some(finger) if self.primary == Some(finger) => Route::Primary,
Some(finger) => self
.secondary_id(finger)
.map_or(Route::Untracked, Route::Secondary),
}
}
fn secondary_id(&self, finger: F) -> Option<PointerId> {
self.secondaries
.iter()
.find(|(tracked, _)| *tracked == finger)
.map(|(_, id)| *id)
}
fn take_secondary(&mut self, finger: F) -> Option<PointerId> {
let index = self
.secondaries
.iter()
.position(|(tracked, _)| *tracked == finger)?;
Some(self.secondaries.remove(index).1)
}
fn is_idle(&self) -> bool {
self.primary.is_none() && self.secondaries.is_empty()
}
fn end_gesture(&mut self) {
self.primary = None;
self.secondaries.clear();
self.next_secondary_id = PointerId::PRIMARY;
}
}
fn release_primary<R>(shell: &mut AppShell<R>, x: f32, y: f32) -> bool
where
R: Renderer,
R::Error: Debug,
{
let event_time = shell.realtime_pointer_event_time(None);
shell.pointer_released_at_position_event_time(x, y, event_time)
}