use std::cell::RefCell;
use std::collections::BTreeMap;
use crate::geometry::Point;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TouchDeviceId(pub u64);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TouchId(pub u64);
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TouchPhase {
Start,
Move,
End,
Cancel,
}
#[derive(Copy, Clone, Debug)]
pub struct MultiTouchInfo {
pub device_id: TouchDeviceId,
pub num_touches: usize,
pub zoom_delta: f32,
pub rotation_delta: f32,
pub translation_delta: Point,
pub force: f32,
pub center_pos: Point,
}
#[derive(Copy, Clone, Debug)]
struct ActiveTouch {
pos: Point,
prev_pos: Point,
force: f32,
}
#[derive(Default)]
pub struct TouchState {
active: BTreeMap<(TouchDeviceId, TouchId), ActiveTouch>,
last: Option<MultiTouchInfo>,
topology_changed: bool,
}
impl TouchState {
pub fn new() -> Self { Self::default() }
pub fn on_start(
&mut self,
device: TouchDeviceId,
id: TouchId,
pos: Point,
force: Option<f32>,
) {
self.active.insert(
(device, id),
ActiveTouch { pos, prev_pos: pos, force: force.unwrap_or(0.0) },
);
self.topology_changed = true;
}
pub fn on_move(
&mut self,
device: TouchDeviceId,
id: TouchId,
pos: Point,
force: Option<f32>,
) {
if let Some(t) = self.active.get_mut(&(device, id)) {
t.pos = pos;
if let Some(f) = force { t.force = f; }
}
}
pub fn on_end_or_cancel(&mut self, device: TouchDeviceId, id: TouchId) {
if self.active.remove(&(device, id)).is_some() {
self.topology_changed = true;
}
if self.active.len() < 2 {
self.last = None;
}
}
pub fn update_gesture(&mut self) {
let device = self.active.keys().next().map(|(d, _)| *d);
let Some(device) = device else { self.last = None; return; };
let touches: Vec<ActiveTouch> = self.active.iter()
.filter(|((d, _), _)| *d == device)
.map(|(_, t)| *t)
.collect();
if touches.len() < 2 {
self.last = None;
return;
}
let n = touches.len() as f64;
let (mut cx, mut cy) = (0.0, 0.0);
let (mut pcx, mut pcy) = (0.0, 0.0);
for t in &touches {
cx += t.pos.x; cy += t.pos.y;
pcx += t.prev_pos.x; pcy += t.prev_pos.y;
}
cx /= n; cy /= n; pcx /= n; pcy /= n;
let mut zoom_sum = 0.0_f32;
let mut rotation_sum = 0.0_f32;
let mut force_sum = 0.0_f32;
let mut zoom_count = 0;
for t in &touches {
force_sum += t.force;
let dx = (t.pos.x - cx) as f32;
let dy = (t.pos.y - cy) as f32;
let pdx = (t.prev_pos.x - pcx) as f32;
let pdy = (t.prev_pos.y - pcy) as f32;
let r = (dx * dx + dy * dy ).sqrt();
let pr = (pdx * pdx + pdy * pdy).sqrt();
if pr > 1.0 && r > 1.0 {
zoom_sum += r / pr;
rotation_sum += dy.atan2(dx) - pdy.atan2(pdx);
zoom_count += 1;
}
}
let (zoom_delta, rotation_delta) = if self.topology_changed || zoom_count == 0 {
(1.0, 0.0)
} else {
let mut rot = rotation_sum / zoom_count as f32;
use std::f32::consts::PI;
while rot > PI { rot -= 2.0 * PI; }
while rot < -PI { rot += 2.0 * PI; }
(zoom_sum / zoom_count as f32, rot)
};
let translation_delta = if self.topology_changed {
Point::new(0.0, 0.0)
} else {
Point::new(cx - pcx, cy - pcy)
};
self.last = Some(MultiTouchInfo {
device_id: device,
num_touches: touches.len(),
zoom_delta,
rotation_delta,
translation_delta,
force: force_sum / n as f32,
center_pos: Point::new(cx, cy),
});
for t in self.active.values_mut() {
t.prev_pos = t.pos;
}
self.topology_changed = false;
}
pub fn current(&self) -> Option<MultiTouchInfo> { self.last }
pub fn active_count(&self) -> usize { self.active.len() }
}
thread_local! {
static CURRENT: RefCell<Option<MultiTouchInfo>> = RefCell::new(None);
}
pub fn set_current(info: Option<MultiTouchInfo>) {
CURRENT.with(|c| *c.borrow_mut() = info);
}
pub fn current_multi_touch() -> Option<MultiTouchInfo> {
CURRENT.with(|c| *c.borrow())
}