nightshade 0.13.1

A cross-platform data-oriented game engine.
Documentation
//! Touch screen input state.

use std::collections::HashMap;

/// Individual touch contact point.
#[derive(Default, Debug, Clone, Copy)]
pub struct TouchPoint {
    /// Unique identifier for this touch.
    pub id: u64,
    /// Current position in window coordinates.
    pub position: nalgebra_glm::Vec2,
    /// Position when the touch began.
    pub start_position: nalgebra_glm::Vec2,
    /// Position on the previous frame.
    pub previous_position: nalgebra_glm::Vec2,
    /// Current touch phase.
    pub phase: TouchPhase,
}

/// Touch contact lifecycle phase.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum TouchPhase {
    /// Touch just began this frame.
    #[default]
    Started,
    /// Touch moved since last frame.
    Moved,
    /// Touch was released.
    Ended,
    /// Touch was interrupted by the system.
    Cancelled,
}

/// Multi-touch input state with gesture detection.
#[derive(Default, Debug, Clone)]
pub struct Touch {
    /// All active touch points by ID.
    pub touches: HashMap<u64, TouchPoint>,
    /// ID of the first touch (for single-finger gestures).
    pub primary_touch_id: Option<u64>,
    /// ID of the second touch (for two-finger gestures).
    pub secondary_touch_id: Option<u64>,
    /// Detected gesture this frame.
    pub gesture: TouchGesture,
}

/// Recognized touch gesture type.
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub enum TouchGesture {
    /// No gesture detected.
    #[default]
    None,
    /// Single finger drag.
    SingleDrag { delta: nalgebra_glm::Vec2 },
    /// Two finger pinch (zoom).
    Pinch {
        delta: f32,
        center: nalgebra_glm::Vec2,
    },
    /// Two finger pan.
    TwoFingerDrag { delta: nalgebra_glm::Vec2 },
}

impl Touch {
    /// Returns the number of currently active touches.
    pub fn active_touch_count(&self) -> usize {
        self.touches
            .values()
            .filter(|t| t.phase != TouchPhase::Ended && t.phase != TouchPhase::Cancelled)
            .count()
    }

    /// Returns the primary (first) touch if active.
    pub fn primary_touch(&self) -> Option<&TouchPoint> {
        self.primary_touch_id
            .and_then(|id| self.touches.get(&id))
            .filter(|t| t.phase != TouchPhase::Ended && t.phase != TouchPhase::Cancelled)
    }

    /// Returns the secondary (second) touch if active.
    pub fn secondary_touch(&self) -> Option<&TouchPoint> {
        self.secondary_touch_id
            .and_then(|id| self.touches.get(&id))
            .filter(|t| t.phase != TouchPhase::Ended && t.phase != TouchPhase::Cancelled)
    }

    /// Analyzes current touches and updates the detected gesture.
    pub fn update_gesture(&mut self) {
        let active_count = self.active_touch_count();

        self.gesture = match active_count {
            1 => {
                if let Some(touch) = self.primary_touch() {
                    if touch.phase == TouchPhase::Moved {
                        let delta = touch.position - touch.previous_position;
                        TouchGesture::SingleDrag { delta }
                    } else {
                        TouchGesture::None
                    }
                } else {
                    TouchGesture::None
                }
            }
            2 => {
                if let (Some(primary), Some(secondary)) =
                    (self.primary_touch(), self.secondary_touch())
                {
                    let current_distance = (primary.position - secondary.position).magnitude();
                    let previous_distance =
                        (primary.previous_position - secondary.previous_position).magnitude();
                    let pinch_delta = current_distance - previous_distance;

                    let primary_delta = primary.position - primary.previous_position;
                    let secondary_delta = secondary.position - secondary.previous_position;
                    let avg_delta = (primary_delta + secondary_delta) * 0.5;

                    let center = (primary.position + secondary.position) * 0.5;

                    if pinch_delta.abs() > avg_delta.magnitude() * 0.5 {
                        TouchGesture::Pinch {
                            delta: pinch_delta,
                            center,
                        }
                    } else {
                        TouchGesture::TwoFingerDrag { delta: avg_delta }
                    }
                } else {
                    TouchGesture::None
                }
            }
            _ => TouchGesture::None,
        };
    }

    /// Removes touches that have ended or been cancelled.
    pub fn clear_ended_touches(&mut self) {
        self.touches
            .retain(|_, t| t.phase != TouchPhase::Ended && t.phase != TouchPhase::Cancelled);

        if let Some(id) = self.primary_touch_id
            && !self.touches.contains_key(&id)
        {
            self.primary_touch_id = None;
        }
        if let Some(id) = self.secondary_touch_id
            && !self.touches.contains_key(&id)
        {
            self.secondary_touch_id = None;
        }
    }
}