browsing 0.1.7

Browser automation: navigate, click, extract, screenshot. Standalone browser control via CDP.
Documentation
//! Touch gesture emulation via CDP Input.dispatchTouchEvent

use crate::browser::views::TouchPoint;
use crate::error::Result;
use std::sync::Arc;

/// Manages touch gesture emulation (tap, swipe, pinch, multi-touch)
#[derive(Debug, Clone)]
pub struct TouchManager {
    client: Arc<crate::browser::cdp::CdpClient>,
}

impl TouchManager {
    /// Create a new touch manager
    pub fn new(client: Arc<crate::browser::cdp::CdpClient>) -> Self {
        Self { client }
    }

    /// Dispatch a touch event of the given type with the provided touch points.
    /// `event_type`: "touchStart", "touchMove", "touchEnd", "touchCancel"
    pub async fn dispatch_touch(
        &self,
        event_type: &str,
        touch_points: Vec<TouchPoint>,
    ) -> Result<()> {
        let points: Vec<serde_json::Value> = touch_points
            .into_iter()
            .map(|p| {
                let mut obj = serde_json::json!({
                    "x": p.x,
                    "y": p.y,
                });
                if let Some(id) = p.id {
                    obj["id"] = serde_json::json!(id);
                }
                if let Some(rx) = p.radius_x {
                    obj["radiusX"] = serde_json::json!(rx);
                }
                if let Some(ry) = p.radius_y {
                    obj["radiusY"] = serde_json::json!(ry);
                }
                if let Some(ra) = p.rotation_angle {
                    obj["rotationAngle"] = serde_json::json!(ra);
                }
                if let Some(force) = p.force {
                    obj["force"] = serde_json::json!(force);
                }
                obj
            })
            .collect();

        self.client
            .send_command(
                "Input.dispatchTouchEvent",
                serde_json::json!({
                    "type": event_type,
                    "touchPoints": points,
                }),
            )
            .await?;
        Ok(())
    }

    /// Single tap at the given coordinates
    pub async fn tap(&self, x: f64, y: f64) -> Result<()> {
        let point = TouchPoint::at(x, y);
        self.dispatch_touch("touchStart", vec![point.clone()]).await?;
        self.dispatch_touch("touchEnd", vec![point]).await?;
        Ok(())
    }

    /// Double tap at the given coordinates
    pub async fn double_tap(&self, x: f64, y: f64) -> Result<()> {
        self.tap(x, y).await?;
        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
        self.tap(x, y).await?;
        Ok(())
    }

    /// Swipe from start to end coordinates over a number of steps
    pub async fn swipe(
        &self,
        start_x: f64,
        start_y: f64,
        end_x: f64,
        end_y: f64,
        steps: u32,
    ) -> Result<()> {
        let dx = (end_x - start_x) / steps as f64;
        let dy = (end_y - start_y) / steps as f64;

        // Touch start
        self.dispatch_touch("touchStart", vec![TouchPoint::at(start_x, start_y)])
            .await?;

        // Intermediate moves
        for i in 1..steps {
            self.dispatch_touch(
                "touchMove",
                vec![TouchPoint::at(start_x + dx * i as f64, start_y + dy * i as f64)],
            )
            .await?;
        }

        // Touch end
        self.dispatch_touch("touchEnd", vec![TouchPoint::at(end_x, end_y)])
            .await?;

        Ok(())
    }

    /// Pinch gesture (zoom in/out) centered at the given point.
    /// `scale_factor` > 1.0 zooms in, < 1.0 zooms out.
    pub async fn pinch(&self, center_x: f64, center_y: f64, scale_factor: f64) -> Result<()> {
        let distance = 50.0;
        let start_distance = distance;
        let end_distance = distance * scale_factor;

        // Start with two fingers apart
        self.dispatch_touch(
            "touchStart",
            vec![
                TouchPoint::at(center_x - start_distance, center_y),
                TouchPoint::at(center_x + start_distance, center_y),
            ],
        )
        .await?;

        // Move fingers to new distance
        self.dispatch_touch(
            "touchMove",
            vec![
                TouchPoint::at(center_x - end_distance, center_y),
                TouchPoint::at(center_x + end_distance, center_y),
            ],
        )
        .await?;

        // Release
        self.dispatch_touch(
            "touchEnd",
            vec![
                TouchPoint::at(center_x - end_distance, center_y),
                TouchPoint::at(center_x + end_distance, center_y),
            ],
        )
        .await?;

        Ok(())
    }

    /// Long press (touch and hold) at the given coordinates
    pub async fn long_press(&self, x: f64, y: f64, duration_ms: u64) -> Result<()> {
        let point = TouchPoint::at(x, y);
        self.dispatch_touch("touchStart", vec![point.clone()]).await?;
        tokio::time::sleep(tokio::time::Duration::from_millis(duration_ms)).await;
        self.dispatch_touch("touchEnd", vec![point]).await?;
        Ok(())
    }
}

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

    #[test]
    fn test_touch_point_creation() {
        let point = TouchPoint::at(100.0, 200.0);
        assert_eq!(point.x, 100.0);
        assert_eq!(point.y, 200.0);
        assert_eq!(point.id, None);
    }

    #[test]
    fn test_touch_point_with_id() {
        let point = TouchPoint {
            x: 50.0,
            y: 75.0,
            radius_x: Some(10.0),
            radius_y: Some(10.0),
            rotation_angle: Some(0.0),
            force: Some(0.5),
            id: Some(1),
        };
        assert_eq!(point.id, Some(1));
        assert_eq!(point.force, Some(0.5));
    }
}