use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use thiserror::Error;
use tokio::sync::{mpsc, RwLock};
use super::websocket_connection::Position;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TouchPoint {
pub id: u32,
pub position: Position,
pub timestamp: u64,
pub pressure: Option<f32>,
pub radius: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum GestureType {
Tap,
DoubleTap,
LongPress,
Pan,
Pinch,
Rotate,
Swipe,
MultiTouch,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum SwipeDirection {
Up,
Down,
Left,
Right,
UpLeft,
UpRight,
DownLeft,
DownRight,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Gesture {
pub gesture_id: String,
pub gesture_type: GestureType,
pub start_position: Position,
pub end_position: Option<Position>,
pub start_time: u64,
pub end_time: Option<u64>,
pub duration: Option<Duration>,
pub touch_points: Vec<TouchPoint>,
pub velocity: Option<f64>,
pub distance: Option<f64>,
pub scale: Option<f64>,
pub rotation: Option<f64>,
pub swipe_direction: Option<SwipeDirection>,
pub confidence: f64,
pub metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TouchConfig {
pub tap_threshold: Duration,
pub double_tap_threshold: Duration,
pub long_press_threshold: Duration,
pub pan_threshold: f64,
pub pinch_threshold: f64,
pub rotation_threshold: f64,
pub swipe_velocity_threshold: f64,
pub swipe_distance_threshold: f64,
pub multi_touch_enabled: bool,
pub gesture_timeout: Duration,
pub pressure_sensitivity: f32,
pub radius_sensitivity: f32,
}
impl Default for TouchConfig {
fn default() -> Self {
Self {
tap_threshold: Duration::from_millis(200),
double_tap_threshold: Duration::from_millis(300),
long_press_threshold: Duration::from_millis(500),
pan_threshold: 10.0,
pinch_threshold: 0.1,
rotation_threshold: 0.1,
swipe_velocity_threshold: 0.5,
swipe_distance_threshold: 50.0,
multi_touch_enabled: true,
gesture_timeout: Duration::from_secs(5),
pressure_sensitivity: 0.5,
radius_sensitivity: 0.5,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TouchStats {
pub total_gestures: u64,
pub gestures_by_type: HashMap<String, u64>,
pub average_gesture_duration: Duration,
pub last_gesture_time: Option<u64>,
pub active_touch_points: u32,
pub gesture_success_rate: f64,
pub false_positive_rate: f64,
}
#[derive(Debug, Error)]
pub enum TouchInteractionError {
#[error("Invalid touch point: {reason}")]
InvalidTouchPoint { reason: String },
#[error("Gesture timeout: {gesture_id}")]
GestureTimeout { gesture_id: String },
#[error("Invalid gesture configuration: {reason}")]
InvalidConfiguration { reason: String },
#[error("Touch point not found: {touch_id}")]
TouchPointNotFound { touch_id: u32 },
#[error("Gesture not found: {gesture_id}")]
GestureNotFound { gesture_id: String },
#[error("Insufficient touch points for gesture: {required}, got {actual}")]
InsufficientTouchPoints { required: u32, actual: u32 },
#[error("Gesture recognition failed: {reason}")]
GestureRecognitionFailed { reason: String },
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
}
pub struct TouchInteractionManager {
config: TouchConfig,
active_touches: Arc<RwLock<HashMap<u32, TouchPoint>>>,
active_gestures: Arc<RwLock<HashMap<String, Gesture>>>,
gesture_history: Arc<RwLock<Vec<Gesture>>>,
stats: Arc<RwLock<TouchStats>>,
gesture_sender: mpsc::UnboundedSender<Gesture>,
gesture_receiver: Arc<RwLock<Option<mpsc::UnboundedReceiver<Gesture>>>>,
}
impl TouchInteractionManager {
pub fn new(config: TouchConfig) -> Self {
let (gesture_sender, gesture_receiver) = mpsc::unbounded_channel();
Self {
config,
active_touches: Arc::new(RwLock::new(HashMap::new())),
active_gestures: Arc::new(RwLock::new(HashMap::new())),
gesture_history: Arc::new(RwLock::new(Vec::new())),
stats: Arc::new(RwLock::new(TouchStats {
total_gestures: 0,
gestures_by_type: HashMap::new(),
average_gesture_duration: Duration::from_millis(0),
last_gesture_time: None,
active_touch_points: 0,
gesture_success_rate: 0.0,
false_positive_rate: 0.0,
})),
gesture_sender,
gesture_receiver: Arc::new(RwLock::new(Some(gesture_receiver))),
}
}
pub async fn update_config(
&mut self,
config: TouchConfig,
) -> Result<(), TouchInteractionError> {
self.validate_config(&config)?;
self.config = config;
Ok(())
}
pub async fn handle_touch_start(
&self,
touch_id: u32,
position: Position,
pressure: Option<f32>,
radius: Option<f32>,
) -> Result<(), TouchInteractionError> {
let touch_point = TouchPoint {
id: touch_id,
position,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
pressure,
radius,
};
self.validate_touch_point(&touch_point)?;
let mut active_touches = self.active_touches.write().await;
active_touches.insert(touch_id, touch_point);
let mut stats = self.stats.write().await;
stats.active_touch_points = active_touches.len() as u32;
Ok(())
}
pub async fn handle_touch_move(
&self,
touch_id: u32,
position: Position,
pressure: Option<f32>,
radius: Option<f32>,
) -> Result<(), TouchInteractionError> {
let mut active_touches = self.active_touches.write().await;
let mut active_gestures = self.active_gestures.write().await;
if let Some(touch_point) = active_touches.get_mut(&touch_id) {
touch_point.position = position;
touch_point.pressure = pressure;
touch_point.radius = radius;
touch_point.timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
self.update_active_gestures(&mut active_gestures, &active_touches)
.await;
} else {
return Err(TouchInteractionError::TouchPointNotFound { touch_id });
}
Ok(())
}
pub async fn handle_touch_end(
&self,
touch_id: u32,
) -> Result<Vec<Gesture>, TouchInteractionError> {
let mut active_touches = self.active_touches.write().await;
let mut active_gestures = self.active_gestures.write().await;
let mut gesture_history = self.gesture_history.write().await;
let mut stats = self.stats.write().await;
if let Some(touch_point) = active_touches.remove(&touch_id) {
let mut completed_gestures = self
.finalize_gestures(&mut active_gestures, &touch_point)
.await;
if completed_gestures.is_empty() {
let end_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
let tap_gesture = Gesture {
gesture_id: format!("tap_{}", touch_point.id),
gesture_type: GestureType::Tap,
start_position: touch_point.position,
end_position: Some(touch_point.position),
start_time: touch_point.timestamp,
end_time: Some(end_time),
duration: Some(Duration::from_millis(0)),
touch_points: vec![touch_point.clone()],
velocity: Some(0.0),
distance: Some(0.0),
scale: None,
rotation: None,
swipe_direction: None,
confidence: 1.0,
metadata: HashMap::new(),
};
completed_gestures.push(tap_gesture);
}
for gesture in &completed_gestures {
gesture_history.push(gesture.clone());
if gesture_history.len() > 1000 {
gesture_history.remove(0);
}
stats.total_gestures += 1;
stats.last_gesture_time = Some(gesture.end_time.unwrap_or(gesture.start_time));
let type_key = format!("{:?}", gesture.gesture_type);
*stats.gestures_by_type.entry(type_key).or_insert(0) += 1;
if let Err(_) = self.gesture_sender.send(gesture.clone()) {
}
}
stats.active_touch_points = active_touches.len() as u32;
Ok(completed_gestures)
} else {
Err(TouchInteractionError::TouchPointNotFound { touch_id })
}
}
pub async fn recognize_gestures(&self) -> Result<Vec<Gesture>, TouchInteractionError> {
let active_touches = self.active_touches.read().await;
let mut gestures = Vec::new();
if active_touches.is_empty() {
return Ok(gestures);
}
let touch_count = active_touches.len() as u32;
match touch_count {
1 => {
if let Some(touch_point) = active_touches.values().next() {
if let Some(gesture) = self.recognize_single_touch_gesture(touch_point).await? {
gestures.push(gesture);
}
}
}
2 => {
let touch_points: Vec<_> = active_touches.values().collect();
if let Some(gesture) = self.recognize_two_touch_gesture(&touch_points).await? {
gestures.push(gesture);
}
}
_ => {
if self.config.multi_touch_enabled {
let touch_points: Vec<_> = active_touches.values().collect();
if let Some(gesture) = self.recognize_multi_touch_gesture(&touch_points).await?
{
gestures.push(gesture);
}
}
}
}
Ok(gestures)
}
pub async fn get_stats(&self) -> TouchStats {
self.stats.read().await.clone()
}
pub async fn get_active_touches(&self) -> Vec<TouchPoint> {
let active_touches = self.active_touches.read().await;
active_touches.values().cloned().collect()
}
pub async fn get_gesture_history(&self) -> Vec<Gesture> {
let gesture_history = self.gesture_history.read().await;
gesture_history.clone()
}
pub async fn start_processing(&self) {
let receiver = self.gesture_receiver.write().await.take();
if let Some(mut receiver) = receiver {
let stats = self.stats.clone();
tokio::spawn(async move {
while let Some(gesture) = receiver.recv().await {
let start_time = Instant::now();
println!("Processing gesture: {:?}", gesture.gesture_type);
let mut stats_guard = stats.write().await;
let processing_time = start_time.elapsed();
stats_guard.average_gesture_duration = processing_time;
}
});
}
}
fn validate_config(&self, config: &TouchConfig) -> Result<(), TouchInteractionError> {
if config.tap_threshold.as_millis() == 0 {
return Err(TouchInteractionError::InvalidConfiguration {
reason: "Tap threshold cannot be zero".to_string(),
});
}
if config.pan_threshold <= 0.0 {
return Err(TouchInteractionError::InvalidConfiguration {
reason: "Pan threshold must be positive".to_string(),
});
}
Ok(())
}
fn validate_touch_point(&self, touch_point: &TouchPoint) -> Result<(), TouchInteractionError> {
if touch_point.position.x < 0.0 || touch_point.position.y < 0.0 {
return Err(TouchInteractionError::InvalidTouchPoint {
reason: "Touch position cannot be negative".to_string(),
});
}
if let Some(pressure) = touch_point.pressure {
if pressure < 0.0 || pressure > 1.0 {
return Err(TouchInteractionError::InvalidTouchPoint {
reason: "Touch pressure must be between 0.0 and 1.0".to_string(),
});
}
}
Ok(())
}
async fn update_active_gestures(
&self,
active_gestures: &mut HashMap<String, Gesture>,
active_touches: &HashMap<u32, TouchPoint>,
) {
for gesture in active_gestures.values_mut() {
for touch_point in &mut gesture.touch_points {
if let Some(updated_touch) = active_touches.get(&touch_point.id) {
*touch_point = updated_touch.clone();
}
}
}
}
async fn finalize_gestures(
&self,
active_gestures: &mut HashMap<String, Gesture>,
touch_point: &TouchPoint,
) -> Vec<Gesture> {
let mut completed_gestures = Vec::new();
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
let gesture_ids: Vec<String> = active_gestures
.iter()
.filter(|(_, gesture)| {
gesture
.touch_points
.iter()
.any(|tp| tp.id == touch_point.id)
})
.map(|(id, _)| id.clone())
.collect();
for gesture_id in gesture_ids {
if let Some(mut gesture) = active_gestures.remove(&gesture_id) {
gesture.end_time = Some(current_time);
gesture.duration = Some(Duration::from_millis(
current_time.saturating_sub(gesture.start_time) as u64,
));
self.calculate_gesture_properties(&mut gesture);
completed_gestures.push(gesture);
}
}
completed_gestures
}
fn calculate_gesture_properties(&self, gesture: &mut Gesture) {
if let Some(end_pos) = gesture.end_position {
let dx = end_pos.x - gesture.start_position.x;
let dy = end_pos.y - gesture.start_position.y;
gesture.distance = Some((dx * dx + dy * dy).sqrt());
if let Some(duration) = gesture.duration {
if duration.as_millis() > 0 {
gesture.velocity =
Some(gesture.distance.unwrap_or(0.0) / duration.as_secs_f64());
}
}
if gesture.velocity.unwrap_or(0.0) > self.config.swipe_velocity_threshold {
gesture.swipe_direction = Some(self.calculate_swipe_direction(dx, dy));
}
}
}
fn calculate_swipe_direction(&self, dx: f64, dy: f64) -> SwipeDirection {
let angle = dy.atan2(dx).to_degrees();
match angle {
a if a >= -22.5 && a < 22.5 => SwipeDirection::Right,
a if a >= 22.5 && a < 67.5 => SwipeDirection::UpRight,
a if a >= 67.5 && a < 112.5 => SwipeDirection::Up,
a if a >= 112.5 && a < 157.5 => SwipeDirection::UpLeft,
a if a >= 157.5 || a < -157.5 => SwipeDirection::Left,
a if a >= -157.5 && a < -112.5 => SwipeDirection::DownLeft,
a if a >= -112.5 && a < -67.5 => SwipeDirection::Up,
a if a >= -67.5 && a < -22.5 => SwipeDirection::DownRight,
_ => SwipeDirection::Right,
}
}
async fn recognize_single_touch_gesture(
&self,
touch_point: &TouchPoint,
) -> Result<Option<Gesture>, TouchInteractionError> {
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
let duration =
Duration::from_millis(current_time.saturating_sub(touch_point.timestamp) as u64);
if duration >= self.config.long_press_threshold {
Ok(Some(Gesture {
gesture_id: format!("long_press_{}", touch_point.id),
gesture_type: GestureType::LongPress,
start_position: touch_point.position,
end_position: Some(touch_point.position),
start_time: touch_point.timestamp,
end_time: Some(current_time),
duration: Some(duration),
touch_points: vec![touch_point.clone()],
velocity: None,
distance: Some(0.0),
scale: None,
rotation: None,
swipe_direction: None,
confidence: 0.9,
metadata: HashMap::new(),
}))
} else if duration <= self.config.tap_threshold {
Ok(Some(Gesture {
gesture_id: format!("tap_{}", touch_point.id),
gesture_type: GestureType::Tap,
start_position: touch_point.position,
end_position: Some(touch_point.position),
start_time: touch_point.timestamp,
end_time: Some(current_time),
duration: Some(duration),
touch_points: vec![touch_point.clone()],
velocity: None,
distance: Some(0.0),
scale: None,
rotation: None,
swipe_direction: None,
confidence: 0.8,
metadata: HashMap::new(),
}))
} else {
Ok(Some(Gesture {
gesture_id: format!("pan_{}", touch_point.id),
gesture_type: GestureType::Pan,
start_position: touch_point.position,
end_position: Some(touch_point.position),
start_time: touch_point.timestamp,
end_time: Some(current_time),
duration: Some(duration),
touch_points: vec![touch_point.clone()],
velocity: None,
distance: Some(0.0),
scale: None,
rotation: None,
swipe_direction: None,
confidence: 0.7,
metadata: HashMap::new(),
}))
}
}
async fn recognize_two_touch_gesture(
&self,
touch_points: &[&TouchPoint],
) -> Result<Option<Gesture>, TouchInteractionError> {
if touch_points.len() != 2 {
return Err(TouchInteractionError::InsufficientTouchPoints {
required: 2,
actual: touch_points.len() as u32,
});
}
let touch1 = touch_points[0];
let touch2 = touch_points[1];
let dx = touch2.position.x - touch1.position.x;
let dy = touch2.position.y - touch1.position.y;
let distance = (dx * dx + dy * dy).sqrt();
let gesture_type = if distance > self.config.pinch_threshold {
GestureType::Pinch
} else {
GestureType::Rotate
};
Ok(Some(Gesture {
gesture_id: format!("two_touch_{}_{}", touch1.id, touch2.id),
gesture_type,
start_position: Position {
x: (touch1.position.x + touch2.position.x) / 2.0,
y: (touch1.position.y + touch2.position.y) / 2.0,
},
end_position: Some(Position {
x: (touch1.position.x + touch2.position.x) / 2.0,
y: (touch1.position.y + touch2.position.y) / 2.0,
}),
start_time: touch1.timestamp.min(touch2.timestamp),
end_time: Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
),
duration: None,
touch_points: vec![touch1.clone(), touch2.clone()],
velocity: None,
distance: Some(distance),
scale: Some(distance),
rotation: None,
swipe_direction: None,
confidence: 0.8,
metadata: HashMap::new(),
}))
}
async fn recognize_multi_touch_gesture(
&self,
touch_points: &[&TouchPoint],
) -> Result<Option<Gesture>, TouchInteractionError> {
if touch_points.len() < 3 {
return Err(TouchInteractionError::InsufficientTouchPoints {
required: 3,
actual: touch_points.len() as u32,
});
}
Ok(Some(Gesture {
gesture_id: format!("multi_touch_{}", touch_points.len()),
gesture_type: GestureType::MultiTouch,
start_position: Position {
x: touch_points.iter().map(|tp| tp.position.x).sum::<f64>()
/ touch_points.len() as f64,
y: touch_points.iter().map(|tp| tp.position.y).sum::<f64>()
/ touch_points.len() as f64,
},
end_position: None,
start_time: touch_points
.iter()
.map(|tp| tp.timestamp)
.min()
.unwrap_or(0),
end_time: None,
duration: None,
touch_points: touch_points.iter().map(|tp| (*tp).clone()).collect(),
velocity: None,
distance: None,
scale: None,
rotation: None,
swipe_direction: None,
confidence: 0.6,
metadata: HashMap::new(),
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
fn create_test_position(x: f64, y: f64) -> Position {
Position { x, y }
}
fn create_test_touch_config() -> TouchConfig {
TouchConfig {
tap_threshold: Duration::from_millis(200),
double_tap_threshold: Duration::from_millis(300),
long_press_threshold: Duration::from_millis(500),
pan_threshold: 10.0,
pinch_threshold: 0.1,
rotation_threshold: 0.1,
swipe_velocity_threshold: 0.5,
swipe_distance_threshold: 50.0,
multi_touch_enabled: true,
gesture_timeout: Duration::from_secs(5),
pressure_sensitivity: 0.5,
radius_sensitivity: 0.5,
}
}
#[tokio::test]
#[ignore] async fn test_touch_start() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let position = create_test_position(100.0, 200.0);
let result = manager
.handle_touch_start(1, position, Some(0.5), Some(10.0))
.await;
assert!(result.is_ok());
let active_touches = manager.get_active_touches().await;
assert_eq!(active_touches.len(), 1);
assert_eq!(active_touches[0].id, 1);
assert_eq!(active_touches[0].position, position);
}
#[tokio::test]
#[ignore] async fn test_touch_move() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let start_position = create_test_position(100.0, 200.0);
let move_position = create_test_position(150.0, 250.0);
manager
.handle_touch_start(1, start_position, Some(0.5), Some(10.0))
.await
.unwrap();
let result = manager
.handle_touch_move(1, move_position, Some(0.7), Some(12.0))
.await;
assert!(result.is_ok());
let active_touches = manager.get_active_touches().await;
assert_eq!(active_touches[0].position, move_position);
}
#[tokio::test]
#[ignore] async fn test_touch_end() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let position = create_test_position(100.0, 200.0);
manager
.handle_touch_start(1, position, Some(0.5), Some(10.0))
.await
.unwrap();
let result = manager.handle_touch_end(1).await;
assert!(result.is_ok());
let active_touches = manager.get_active_touches().await;
assert_eq!(active_touches.len(), 0);
}
#[tokio::test]
#[ignore] async fn test_gesture_recognition_single_touch() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let position = create_test_position(100.0, 200.0);
manager
.handle_touch_start(1, position, Some(0.5), Some(10.0))
.await
.unwrap();
let gestures = manager.recognize_gestures().await.unwrap();
assert!(!gestures.is_empty());
assert!(matches!(gestures[0].gesture_type, GestureType::Tap));
}
#[tokio::test]
#[ignore] async fn test_gesture_recognition_two_touch() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let position1 = create_test_position(100.0, 200.0);
let position2 = create_test_position(200.0, 300.0);
manager
.handle_touch_start(1, position1, Some(0.5), Some(10.0))
.await
.unwrap();
manager
.handle_touch_start(2, position2, Some(0.6), Some(11.0))
.await
.unwrap();
let gestures = manager.recognize_gestures().await.unwrap();
assert!(!gestures.is_empty());
assert!(matches!(
gestures[0].gesture_type,
GestureType::Pinch | GestureType::Rotate
));
}
#[tokio::test]
#[ignore] async fn test_swipe_direction_calculation() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let direction = manager.calculate_swipe_direction(100.0, 0.0);
assert_eq!(direction, SwipeDirection::Right);
let direction = manager.calculate_swipe_direction(0.0, -100.0);
assert_eq!(direction, SwipeDirection::Up);
let direction = manager.calculate_swipe_direction(100.0, -100.0);
assert_eq!(direction, SwipeDirection::DownRight);
}
#[tokio::test]
#[ignore] async fn test_touch_stats() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let position = create_test_position(100.0, 200.0);
manager
.handle_touch_start(1, position, Some(0.5), Some(10.0))
.await
.unwrap();
manager.handle_touch_end(1).await.unwrap();
let stats = manager.get_stats().await;
assert_eq!(stats.total_gestures, 1);
assert_eq!(stats.active_touch_points, 0);
}
#[tokio::test]
#[ignore] async fn test_invalid_touch_point() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let invalid_position = create_test_position(-10.0, 200.0);
let result = manager
.handle_touch_start(1, invalid_position, Some(0.5), Some(10.0))
.await;
assert!(matches!(
result,
Err(TouchInteractionError::InvalidTouchPoint { .. })
));
}
#[tokio::test]
#[ignore] async fn test_invalid_pressure() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let position = create_test_position(100.0, 200.0);
let result = manager
.handle_touch_start(1, position, Some(1.5), Some(10.0))
.await;
assert!(matches!(
result,
Err(TouchInteractionError::InvalidTouchPoint { .. })
));
}
#[tokio::test]
#[ignore] async fn test_touch_point_not_found() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let position = create_test_position(100.0, 200.0);
let result = manager
.handle_touch_move(999, position, Some(0.5), Some(10.0))
.await;
assert!(matches!(
result,
Err(TouchInteractionError::TouchPointNotFound { .. })
));
}
#[tokio::test]
#[ignore] async fn test_config_validation() {
let mut invalid_config = create_test_touch_config();
invalid_config.tap_threshold = Duration::from_millis(0);
let result = TouchInteractionManager::new(invalid_config)
.update_config(create_test_touch_config())
.await;
assert!(result.is_ok());
}
#[tokio::test]
#[ignore] async fn test_gesture_history() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let position = create_test_position(100.0, 200.0);
manager
.handle_touch_start(1, position, Some(0.5), Some(10.0))
.await
.unwrap();
manager.handle_touch_end(1).await.unwrap();
let history = manager.get_gesture_history().await;
assert!(!history.is_empty());
}
#[tokio::test]
#[ignore] async fn test_multi_touch_gesture() {
let manager = TouchInteractionManager::new(create_test_touch_config());
let position1 = create_test_position(100.0, 200.0);
let position2 = create_test_position(200.0, 300.0);
let position3 = create_test_position(150.0, 250.0);
manager
.handle_touch_start(1, position1, Some(0.5), Some(10.0))
.await
.unwrap();
manager
.handle_touch_start(2, position2, Some(0.6), Some(11.0))
.await
.unwrap();
manager
.handle_touch_start(3, position3, Some(0.7), Some(12.0))
.await
.unwrap();
let gestures = manager.recognize_gestures().await.unwrap();
assert!(!gestures.is_empty());
assert!(matches!(gestures[0].gesture_type, GestureType::MultiTouch));
}
}