viewpoint_core/page/locator/builders/
tap.rs

1//! Tap builder for locator actions.
2
3use tracing::{debug, instrument};
4
5use super::super::Locator;
6use crate::error::LocatorError;
7
8/// Builder for tap operations with configurable options.
9///
10/// Created via [`Locator::tap`].
11#[derive(Debug)]
12pub struct TapBuilder<'l, 'a> {
13    locator: &'l Locator<'a>,
14    position: Option<(f64, f64)>,
15    force: bool,
16    modifiers: i32,
17}
18
19impl<'l, 'a> TapBuilder<'l, 'a> {
20    pub(crate) fn new(locator: &'l Locator<'a>) -> Self {
21        Self {
22            locator,
23            position: None,
24            force: false,
25            modifiers: 0,
26        }
27    }
28
29    /// Set the position offset from the element's top-left corner.
30    #[must_use]
31    pub fn position(mut self, x: f64, y: f64) -> Self {
32        self.position = Some((x, y));
33        self
34    }
35
36    /// Whether to bypass actionability checks.
37    #[must_use]
38    pub fn force(mut self, force: bool) -> Self {
39        self.force = force;
40        self
41    }
42
43    /// Set modifier keys to hold during the tap.
44    #[must_use]
45    pub fn modifiers(mut self, modifiers: i32) -> Self {
46        self.modifiers = modifiers;
47        self
48    }
49
50    /// Execute the tap operation.
51    #[instrument(level = "debug", skip(self), fields(selector = ?self.locator.selector))]
52    pub async fn send(self) -> Result<(), LocatorError> {
53        let (x, y) = if self.force {
54            let info = self.locator.query_element_info().await?;
55            if !info.found {
56                return Err(LocatorError::NotFound(format!(
57                    "{:?}",
58                    self.locator.selector
59                )));
60            }
61
62            if let Some((offset_x, offset_y)) = self.position {
63                (
64                    info.x.unwrap_or(0.0) + offset_x,
65                    info.y.unwrap_or(0.0) + offset_y,
66                )
67            } else {
68                (
69                    info.x.unwrap_or(0.0) + info.width.unwrap_or(0.0) / 2.0,
70                    info.y.unwrap_or(0.0) + info.height.unwrap_or(0.0) / 2.0,
71                )
72            }
73        } else {
74            let info = self.locator.wait_for_actionable().await?;
75
76            if let Some((offset_x, offset_y)) = self.position {
77                (
78                    info.x.expect("visible element has x") + offset_x,
79                    info.y.expect("visible element has y") + offset_y,
80                )
81            } else {
82                (
83                    info.x.expect("visible element has x")
84                        + info.width.expect("visible element has width") / 2.0,
85                    info.y.expect("visible element has y")
86                        + info.height.expect("visible element has height") / 2.0,
87                )
88            }
89        };
90
91        debug!(x, y, modifiers = self.modifiers, "Tapping element");
92
93        if self.modifiers != 0 {
94            self.locator
95                .page
96                .touchscreen()
97                .tap_with_modifiers(x, y, self.modifiers)
98                .await
99        } else {
100            self.locator.page.touchscreen().tap(x, y).await
101        }
102    }
103}