1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! smix-host-coord-resolver — pure pipeline from `(A11yNode, Selector)`
//! to normalized `(nx, ny)` tap coordinate, with no I/O.
//!
//! This is the host-side resolve step decoupled from any specific
//! injector (HID / Apple native event chain / runner-side mode=resolve
//! / etc.). Higher-level orchestrators (smix-driver, user code) fetch
//! the tree, call this stone to compute the tap coord, then hand it to
//! their preferred injector.
//!
//! # Stone scope
//!
//! Pure function, no async, no I/O, no awareness of any specific
//! injector implementation. Any iOS-automation project that has an
//! [`A11yNode`] (or equivalent that maps to one) and a structured
//! selector can adopt this crate to get host-side resolve.
//!
//! # Pipeline (mirrors smix-driver's `tap` middle section)
//!
//! 1. `resolve_selector(tree, selector)` — DFS + spatial filter +
//! visibility check.
//! 2. Sanity-check matched node frame (w*h > 0).
//! 3. Compute centroid in app coords.
//! 4. Normalize by `tree.bounds` (app frame).
//! 5. Reject (nx, ny) outside (0, 1) as offscreen.
//!
//! # Example
//!
//! ```
//! use smix_host_coord_resolver::{resolve_to_norm_coord, HostResolveError};
//! use smix_screen::{A11yNode, Rect};
//! use smix_selector::{Modifiers, Pattern, Selector};
//!
//! fn mk(label: &str, bounds: Rect) -> A11yNode {
//! A11yNode {
//! raw_type: "other".into(),
//! element_type_raw: 1,
//! role: None, identifier: None,
//! label: Some(label.into()),
//! title: None, placeholder_value: None, value: None, text: None,
//! bounds, enabled: true, selected: false, has_focus: false,
//! visible: true, children: vec![],
//! }
//! }
//!
//! let mut root = mk("root", Rect { x: 0.0, y: 0.0, w: 390.0, h: 844.0 });
//! root.children.push(mk("Login", Rect { x: 50.0, y: 100.0, w: 200.0, h: 40.0 }));
//!
//! let sel = Selector::Text {
//! text: Pattern::text("Login"),
//! modifiers: Modifiers::default(),
//! };
//! let (nx, ny) = resolve_to_norm_coord(&root, &sel).unwrap();
//! assert!((nx - 150.0 / 390.0).abs() < 1e-9);
//! assert!((ny - 120.0 / 844.0).abs() < 1e-9);
//! ```
use A11yNode;
use Selector;
use resolve_selector;
use Error;
/// Errors returned by [`resolve_to_norm_coord`].
/// Resolve `selector` against `tree` and produce a normalized tap
/// coordinate `(nx, ny)` in `(0, 1)`.
///
/// `tree.bounds` is treated as the app frame (origin top-left, +y down).
/// The matched node's centroid in app coords is divided by app frame
/// width/height to produce a value in `(0, 1)`.
///
/// Returns `Err(HostResolveError)` for any of: no match, matched node
/// has empty frame, unknown app frame, computed centroid offscreen.