#[derive(Debug, Clone, Copy)]
pub struct TouchPointer {
pub id: i32,
pub x: f32,
pub y: f32,
pub pressure: f32,
pub size: f32,
}
#[derive(Debug, Clone)]
pub enum TouchEvent {
Down {
pointer: TouchPointer,
pointers: Vec<TouchPointer>,
},
Move { pointers: Vec<TouchPointer> },
Up {
pointer: TouchPointer,
pointers: Vec<TouchPointer>,
},
Cancel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PinchPhase {
Started,
Moved,
Ended,
}
#[derive(Debug, Clone, Copy)]
pub struct PinchGesture {
pub scale: f32,
pub center: (f32, f32),
pub phase: PinchPhase,
}
#[derive(Debug, Default)]
pub struct PinchState {
prev_span: Option<f32>,
last_center: Option<(f32, f32)>,
active: bool,
}
impl PinchState {
pub fn reset(&mut self) {
self.prev_span = None;
self.last_center = None;
self.active = false;
}
}
pub fn detect_pinch(pointers: &[TouchPointer], state: &mut PinchState) -> Option<PinchGesture> {
if pointers.len() < 2 {
if state.active {
let center = state.last_center.unwrap_or((0.0, 0.0));
state.reset();
return Some(PinchGesture {
scale: 1.0,
center,
phase: PinchPhase::Ended,
});
}
state.reset();
return None;
}
let p0 = pointers[0];
let p1 = pointers[1];
let center = ((p0.x + p1.x) * 0.5, (p0.y + p1.y) * 0.5);
let dx = p1.x - p0.x;
let dy = p1.y - p0.y;
let span = (dx * dx + dy * dy).sqrt();
let gesture = match state.prev_span {
None => PinchGesture {
scale: 1.0,
center,
phase: PinchPhase::Started,
},
Some(prev_span) if prev_span > 0.0 => {
let mut scale = span / prev_span;
scale = scale.clamp(0.90, 1.10);
PinchGesture {
scale,
center,
phase: PinchPhase::Moved,
}
}
Some(_) => PinchGesture {
scale: 1.0,
center,
phase: PinchPhase::Moved,
},
};
state.prev_span = Some(span);
state.last_center = Some(center);
state.active = true;
Some(gesture)
}
#[cfg(target_os = "android")]
pub fn convert_motion_event(event: &ndk::event::MotionEvent) -> Option<TouchEvent> {
use ndk::event::MotionAction;
let action = event.action();
let pointer_count = event.pointer_count();
let pointers: Vec<TouchPointer> = (0..pointer_count)
.map(|i| {
let p = event.pointer_at_index(i);
TouchPointer {
id: p.pointer_id(),
x: p.x(),
y: p.y(),
pressure: p.pressure(),
size: p.size(),
}
})
.collect();
let action_pointer_index = event.pointer_index();
match action {
MotionAction::Down | MotionAction::PointerDown => {
let pointer = pointers.get(action_pointer_index).copied()?;
Some(TouchEvent::Down { pointer, pointers })
}
MotionAction::Move => Some(TouchEvent::Move { pointers }),
MotionAction::Up | MotionAction::PointerUp => {
let pointer = pointers.get(action_pointer_index).copied()?;
Some(TouchEvent::Up { pointer, pointers })
}
MotionAction::Cancel => Some(TouchEvent::Cancel),
_ => None,
}
}
pub mod fsm_events {
pub const POINTER_DOWN: u32 = 1;
pub const POINTER_UP: u32 = 2;
pub const POINTER_MOVE: u32 = 3;
pub const POINTER_CANCEL: u32 = 4;
pub const LONG_PRESS: u32 = 5;
pub const SWIPE_LEFT: u32 = 6;
pub const SWIPE_RIGHT: u32 = 7;
pub const SWIPE_UP: u32 = 8;
pub const SWIPE_DOWN: u32 = 9;
pub const PINCH_START: u32 = 10;
pub const PINCH_END: u32 = 11;
}
pub struct GestureDetector {
start_position: Option<(f32, f32)>,
start_time: Option<std::time::Instant>,
swipe_threshold: f32,
long_press_duration: std::time::Duration,
}
impl GestureDetector {
pub fn new() -> Self {
Self {
start_position: None,
start_time: None,
swipe_threshold: 50.0,
long_press_duration: std::time::Duration::from_millis(500),
}
}
pub fn process(&mut self, event: &TouchEvent) -> Option<u32> {
match event {
TouchEvent::Down { pointer, .. } => {
self.start_position = Some((pointer.x, pointer.y));
self.start_time = Some(std::time::Instant::now());
Some(fsm_events::POINTER_DOWN)
}
TouchEvent::Move { pointers } => {
if let (Some((start_x, start_y)), Some(pointer)) =
(self.start_position, pointers.first())
{
let dx = pointer.x - start_x;
let dy = pointer.y - start_y;
if dx.abs() > self.swipe_threshold || dy.abs() > self.swipe_threshold {
if dx.abs() > dy.abs() {
self.start_position = None;
return Some(if dx > 0.0 {
fsm_events::SWIPE_RIGHT
} else {
fsm_events::SWIPE_LEFT
});
} else {
self.start_position = None;
return Some(if dy > 0.0 {
fsm_events::SWIPE_DOWN
} else {
fsm_events::SWIPE_UP
});
}
}
}
Some(fsm_events::POINTER_MOVE)
}
TouchEvent::Up { .. } => {
if let Some(start) = self.start_time {
if start.elapsed() >= self.long_press_duration {
self.start_position = None;
self.start_time = None;
return Some(fsm_events::LONG_PRESS);
}
}
self.start_position = None;
self.start_time = None;
Some(fsm_events::POINTER_UP)
}
TouchEvent::Cancel => {
self.start_position = None;
self.start_time = None;
Some(fsm_events::POINTER_CANCEL)
}
}
}
}
impl Default for GestureDetector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gesture_detector_tap() {
let mut detector = GestureDetector::new();
let down = TouchEvent::Down {
pointer: TouchPointer {
id: 0,
x: 100.0,
y: 100.0,
pressure: 1.0,
size: 0.0,
},
pointers: vec![],
};
let up = TouchEvent::Up {
pointer: TouchPointer {
id: 0,
x: 100.0,
y: 100.0,
pressure: 1.0,
size: 0.0,
},
pointers: vec![],
};
assert_eq!(detector.process(&down), Some(fsm_events::POINTER_DOWN));
assert_eq!(detector.process(&up), Some(fsm_events::POINTER_UP));
}
}