use blinc_platform::{InputEvent, TouchEvent};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TouchPhase {
Began,
Moved,
Ended,
Cancelled,
}
#[derive(Clone, Debug)]
pub struct Touch {
pub id: u64,
pub x: f32,
pub y: f32,
pub phase: TouchPhase,
pub force: f32,
}
impl Touch {
pub fn new(id: u64, x: f32, y: f32, phase: TouchPhase) -> Self {
Self {
id,
x,
y,
phase,
force: 0.0,
}
}
pub fn with_force(id: u64, x: f32, y: f32, phase: TouchPhase, force: f32) -> Self {
Self {
id,
x,
y,
phase,
force,
}
}
}
pub fn convert_touch(touch: &Touch) -> InputEvent {
match touch.phase {
TouchPhase::Began => InputEvent::Touch(TouchEvent::Started {
id: touch.id,
x: touch.x,
y: touch.y,
pressure: touch.force,
}),
TouchPhase::Moved => InputEvent::Touch(TouchEvent::Moved {
id: touch.id,
x: touch.x,
y: touch.y,
pressure: touch.force,
}),
TouchPhase::Ended => InputEvent::Touch(TouchEvent::Ended {
id: touch.id,
x: touch.x,
y: touch.y,
}),
TouchPhase::Cancelled => InputEvent::Touch(TouchEvent::Cancelled { id: touch.id }),
}
}
pub fn convert_touches(touches: &[Touch]) -> Vec<InputEvent> {
touches.iter().map(convert_touch).collect()
}
#[derive(Debug, Default)]
pub struct GestureDetector {
active_touches: Vec<Touch>,
tap_in_progress: bool,
tap_start: Option<(f32, f32)>,
pinch_span: Option<f32>,
}
impl GestureDetector {
pub fn new() -> Self {
Self::default()
}
fn pinch_span_and_center(&self) -> Option<(f32, (f32, f32))> {
if self.active_touches.len() < 2 {
return None;
}
let first = &self.active_touches[0];
let second = &self.active_touches[1];
let dx = second.x - first.x;
let dy = second.y - first.y;
let span = (dx * dx + dy * dy).sqrt();
let center = ((first.x + second.x) * 0.5, (first.y + second.y) * 0.5);
Some((span, center))
}
pub fn process(&mut self, touch: &Touch) -> Option<Gesture> {
match touch.phase {
TouchPhase::Began => {
self.active_touches.push(touch.clone());
if self.active_touches.len() == 1 {
self.tap_in_progress = true;
self.tap_start = Some((touch.x, touch.y));
} else {
self.tap_in_progress = false;
self.tap_start = None;
if self.active_touches.len() == 2 {
self.pinch_span = self.pinch_span_and_center().map(|(span, _)| span);
} else {
self.pinch_span = None;
}
}
None
}
TouchPhase::Moved => {
if let Some(existing) = self.active_touches.iter_mut().find(|t| t.id == touch.id) {
if let Some((start_x, start_y)) = self.tap_start {
let dx = touch.x - start_x;
let dy = touch.y - start_y;
if dx * dx + dy * dy > 100.0 {
self.tap_in_progress = false;
}
}
existing.x = touch.x;
existing.y = touch.y;
}
let touch_count = self.active_touches.len();
if touch_count == 2 {
self.tap_in_progress = false;
self.tap_start = None;
if let Some((span, center)) = self.pinch_span_and_center() {
if !span.is_finite() {
self.pinch_span = None;
return None;
}
let scale = match self.pinch_span {
Some(previous) if previous.is_finite() && previous > 0.0 => {
span / previous
}
_ => 1.0,
};
self.pinch_span = Some(span);
if !scale.is_finite() {
return None;
}
return Some(Gesture::Pinch {
scale: scale.clamp(0.90, 1.10),
center,
});
}
} else {
if touch_count > 1 {
self.tap_in_progress = false;
self.tap_start = None;
}
self.pinch_span = None;
}
None
}
TouchPhase::Ended => {
self.active_touches.retain(|t| t.id != touch.id);
if self.active_touches.len() < 2 {
self.pinch_span = None;
}
if self.tap_in_progress {
self.tap_in_progress = false;
self.tap_start = None;
return Some(Gesture::Tap {
x: touch.x,
y: touch.y,
});
}
self.tap_start = None;
None
}
TouchPhase::Cancelled => {
self.active_touches.retain(|t| t.id != touch.id);
self.tap_in_progress = false;
self.tap_start = None;
self.pinch_span = None;
None
}
}
}
pub fn active_touch_count(&self) -> usize {
self.active_touches.len()
}
}
#[derive(Clone, Debug)]
pub enum Gesture {
Tap { x: f32, y: f32 },
LongPress { x: f32, y: f32 },
Pan {
dx: f32,
dy: f32,
velocity: (f32, f32),
},
Pinch { scale: f32, center: (f32, f32) },
Rotation { angle: f32, center: (f32, f32) },
}