use std::collections::HashMap;
use std::rc::Rc;
use incular_core::{Offset, PointerPhase};
use crate::details::PointerEvent;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScaleStartDetails {
pub focal_point: Offset,
pub pointer_count: usize,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScaleUpdateDetails {
pub focal_point: Offset,
pub scale: f32,
pub pointer_count: usize,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScaleEndDetails {
pub pointer_count: usize,
}
impl ScaleUpdateDetails {
#[must_use]
pub const fn new(focal_point: Offset, scale: f32) -> Self {
Self {
focal_point,
scale,
pointer_count: 2,
}
}
}
pub struct ScaleGestureDetector {
pointers: HashMap<u64, Offset>,
initial_distance: Option<f32>,
on_update: Option<Rc<dyn Fn(ScaleUpdateDetails)>>,
on_start: Option<Rc<dyn Fn(ScaleStartDetails)>>,
on_end: Option<Rc<dyn Fn(ScaleEndDetails)>>,
started: bool,
}
impl ScaleGestureDetector {
#[must_use]
pub fn new(on_update: impl Fn(ScaleUpdateDetails) + 'static) -> Self {
Self::with_callbacks(Some(Rc::new(on_update)), None, None)
}
#[must_use]
pub fn with_callbacks(
on_update: Option<Rc<dyn Fn(ScaleUpdateDetails)>>,
on_start: Option<Rc<dyn Fn(ScaleStartDetails)>>,
on_end: Option<Rc<dyn Fn(ScaleEndDetails)>>,
) -> Self {
Self {
pointers: HashMap::new(),
initial_distance: None,
on_update,
on_start,
on_end,
started: false,
}
}
pub fn handle(&mut self, event: PointerEvent) -> bool {
let handled = match event.phase {
PointerPhase::Enter | PointerPhase::Exit => false,
PointerPhase::Down => true,
PointerPhase::Move => self.pointers.contains_key(&event.pointer),
PointerPhase::Up | PointerPhase::Cancel => self.pointers.contains_key(&event.pointer),
};
if let Some(details) = self.observe(event) {
self.dispatch(details);
}
handled
}
pub fn observe(&mut self, event: PointerEvent) -> Option<ScaleUpdateDetails> {
match event.phase {
PointerPhase::Enter | PointerPhase::Exit => None,
PointerPhase::Down => {
self.pointers.insert(event.pointer, event.position);
self.reset_initial_distance();
None
}
PointerPhase::Move => {
let position = self.pointers.get_mut(&event.pointer)?;
*position = event.position;
let (first, second) = self.first_two()?;
let distance = distance(first, second);
let initial = self
.initial_distance
.get_or_insert(distance.max(f32::EPSILON));
let details = ScaleUpdateDetails::new(
Offset::new((first.x + second.x) * 0.5, (first.y + second.y) * 0.5),
distance / *initial,
);
if !self.started {
self.started = true;
if let Some(callback) = &self.on_start {
callback(ScaleStartDetails {
focal_point: details.focal_point,
pointer_count: details.pointer_count,
});
}
}
Some(details)
}
PointerPhase::Up | PointerPhase::Cancel => {
if self.started {
self.started = false;
if let Some(callback) = &self.on_end {
callback(ScaleEndDetails {
pointer_count: self.pointers.len(),
});
}
}
self.pointers.remove(&event.pointer);
self.reset_initial_distance();
None
}
}
}
pub fn dispatch(&self, details: ScaleUpdateDetails) {
if let Some(callback) = &self.on_update {
callback(details);
}
}
fn first_two(&self) -> Option<(Offset, Offset)> {
let mut pointers = self.pointers.values().copied();
Some((pointers.next()?, pointers.next()?))
}
fn reset_initial_distance(&mut self) {
self.initial_distance = self
.first_two()
.map(|(first, second)| distance(first, second));
}
}
fn distance(first: Offset, second: Offset) -> f32 {
(first.x - second.x).hypot(first.y - second.y)
}