cranpose-foundation 0.1.9

Modifiers, nodes, and foundation elements for Cranpose
Documentation
use crate::gesture_constants::MAX_FLING_VELOCITY;
use crate::nodes::input::{PointerEvent, PointerEventKind, PointerId};
use crate::VelocityTracker1D;
use cranpose_ui_graphics::Point;

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FlingGestureEvent {
    None,
    Fling(Point),
    Canceled,
}

#[derive(Clone)]
pub struct FlingGesture {
    pointer_id: Option<PointerId>,
    x_tracker: VelocityTracker1D,
    y_tracker: VelocityTracker1D,
}

impl Default for FlingGesture {
    fn default() -> Self {
        Self::new()
    }
}

impl std::fmt::Debug for FlingGesture {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FlingGesture")
            .field("pointer_id", &self.pointer_id)
            .finish_non_exhaustive()
    }
}

impl FlingGesture {
    pub fn new() -> Self {
        Self {
            pointer_id: None,
            x_tracker: VelocityTracker1D::new(),
            y_tracker: VelocityTracker1D::new(),
        }
    }

    pub fn handle_event(&mut self, event: &PointerEvent, time_ms: i64) -> FlingGestureEvent {
        match event.kind {
            PointerEventKind::Down if !event.is_consumed() => {
                self.pointer_id = Some(event.id);
                self.x_tracker.reset();
                self.y_tracker.reset();
                self.add_position(time_ms, event.position);
                FlingGestureEvent::None
            }
            PointerEventKind::Move if self.pointer_id == Some(event.id) => {
                self.add_position(time_ms, event.position);
                FlingGestureEvent::None
            }
            PointerEventKind::Up if self.pointer_id == Some(event.id) => {
                self.add_position(time_ms, event.position);
                let velocity = self.velocity();
                self.reset();
                if velocity.x == 0.0 && velocity.y == 0.0 {
                    FlingGestureEvent::None
                } else {
                    FlingGestureEvent::Fling(velocity)
                }
            }
            PointerEventKind::Cancel if self.pointer_id == Some(event.id) => {
                self.reset();
                FlingGestureEvent::Canceled
            }
            _ => FlingGestureEvent::None,
        }
    }

    pub fn velocity(&self) -> Point {
        Point {
            x: self
                .x_tracker
                .calculate_velocity_with_max(MAX_FLING_VELOCITY),
            y: self
                .y_tracker
                .calculate_velocity_with_max(MAX_FLING_VELOCITY),
        }
    }

    pub fn reset(&mut self) {
        self.pointer_id = None;
        self.x_tracker.reset();
        self.y_tracker.reset();
    }

    fn add_position(&mut self, time_ms: i64, position: Point) {
        self.x_tracker.add_data_point(time_ms, position.x);
        self.y_tracker.add_data_point(time_ms, position.y);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn pointer(kind: PointerEventKind, x: f32, y: f32) -> PointerEvent {
        PointerEvent::new(kind, Point { x, y }, Point { x, y })
    }

    #[test]
    fn fling_reports_velocity_on_release() {
        let mut gesture = FlingGesture::new();
        gesture.handle_event(&pointer(PointerEventKind::Down, 0.0, 0.0), 0);
        gesture.handle_event(&pointer(PointerEventKind::Move, 0.0, 40.0), 10);

        match gesture.handle_event(&pointer(PointerEventKind::Up, 0.0, 80.0), 20) {
            FlingGestureEvent::Fling(velocity) => {
                assert_eq!(velocity.x, 0.0);
                assert!(velocity.y > 0.0);
            }
            event => panic!("expected fling event, got {event:?}"),
        }
    }

    #[test]
    fn fling_resets_on_cancel() {
        let mut gesture = FlingGesture::new();
        gesture.handle_event(&pointer(PointerEventKind::Down, 0.0, 0.0), 0);
        assert_eq!(
            gesture.handle_event(&pointer(PointerEventKind::Cancel, 0.0, 0.0), 1),
            FlingGestureEvent::Canceled
        );
        assert_eq!(gesture.velocity(), Point { x: 0.0, y: 0.0 });
    }
}