cranpose-foundation 0.1.8

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

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DragGestureEvent {
    None,
    Started {
        start: Point,
        current: Point,
    },
    Dragged {
        delta: Point,
        total: Point,
        current: Point,
    },
    Ended,
    Canceled,
}

#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct DragGesture {
    pointer_id: Option<PointerId>,
    start_position: Point,
    last_position: Point,
    dragging: bool,
}

impl DragGesture {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn handle_event(&mut self, event: &PointerEvent) -> DragGestureEvent {
        match event.kind {
            PointerEventKind::Down if !event.is_consumed() => {
                self.pointer_id = Some(event.id);
                self.start_position = event.position;
                self.last_position = event.position;
                self.dragging = false;
                DragGestureEvent::None
            }
            PointerEventKind::Move if self.pointer_id == Some(event.id) => {
                let total = delta(self.start_position, event.position);
                if !self.dragging {
                    if length(total) <= DRAG_THRESHOLD {
                        return DragGestureEvent::None;
                    }
                    self.dragging = true;
                    self.last_position = event.position;
                    event.consume();
                    return DragGestureEvent::Started {
                        start: self.start_position,
                        current: event.position,
                    };
                }

                let event_delta = delta(self.last_position, event.position);
                self.last_position = event.position;
                event.consume();
                DragGestureEvent::Dragged {
                    delta: event_delta,
                    total,
                    current: event.position,
                }
            }
            PointerEventKind::Up if self.pointer_id == Some(event.id) => {
                let was_dragging = self.dragging;
                self.reset();
                if was_dragging {
                    DragGestureEvent::Ended
                } else {
                    DragGestureEvent::None
                }
            }
            PointerEventKind::Cancel if self.pointer_id == Some(event.id) => {
                self.reset();
                DragGestureEvent::Canceled
            }
            _ => DragGestureEvent::None,
        }
    }

    pub fn reset(&mut self) {
        self.pointer_id = None;
        self.start_position = Point { x: 0.0, y: 0.0 };
        self.last_position = Point { x: 0.0, y: 0.0 };
        self.dragging = false;
    }
}

fn delta(from: Point, to: Point) -> Point {
    Point {
        x: to.x - from.x,
        y: to.y - from.y,
    }
}

fn length(delta: Point) -> f32 {
    (delta.x * delta.x + delta.y * delta.y).sqrt()
}

#[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 drag_starts_after_threshold_and_consumes_move() {
        let mut gesture = DragGesture::new();
        let down = pointer(PointerEventKind::Down, 0.0, 0.0);
        let move_inside_slop = pointer(PointerEventKind::Move, DRAG_THRESHOLD - 1.0, 0.0);
        let move_after_slop = pointer(PointerEventKind::Move, DRAG_THRESHOLD + 1.0, 0.0);

        assert_eq!(gesture.handle_event(&down), DragGestureEvent::None);
        assert_eq!(
            gesture.handle_event(&move_inside_slop),
            DragGestureEvent::None
        );
        assert!(!move_inside_slop.is_consumed());
        assert_eq!(
            gesture.handle_event(&move_after_slop),
            DragGestureEvent::Started {
                start: Point { x: 0.0, y: 0.0 },
                current: Point {
                    x: DRAG_THRESHOLD + 1.0,
                    y: 0.0
                },
            }
        );
        assert!(move_after_slop.is_consumed());
    }

    #[test]
    fn drag_reports_delta_after_start() {
        let mut gesture = DragGesture::new();
        gesture.handle_event(&pointer(PointerEventKind::Down, 0.0, 0.0));
        gesture.handle_event(&pointer(PointerEventKind::Move, DRAG_THRESHOLD + 1.0, 0.0));

        assert_eq!(
            gesture.handle_event(&pointer(PointerEventKind::Move, DRAG_THRESHOLD + 3.0, 2.0)),
            DragGestureEvent::Dragged {
                delta: Point { x: 2.0, y: 2.0 },
                total: Point {
                    x: DRAG_THRESHOLD + 3.0,
                    y: 2.0
                },
                current: Point {
                    x: DRAG_THRESHOLD + 3.0,
                    y: 2.0
                },
            }
        );
    }
}