euv_ui/component/touch/hook/impl.rs
1use super::*;
2
3/// Implementation of touch point extraction from DOM touch events.
4impl NativeTouchPoint {
5 /// Extracts all active touch points from a `TouchEvent`.
6 ///
7 /// Iterates over the `touches` list of the given `TouchEvent` and
8 /// builds a `Vec<NativeTouchPoint>` with each touch point's
9 /// identifier, viewport coordinates, screen coordinates, page
10 /// coordinates, and offset coordinates relative to the target element.
11 ///
12 /// The offset coordinates (`offset_x`, `offset_y`) are computed by
13 /// subtracting the target element's bounding rect from the touch's
14 /// client coordinates, since the browser `Touch` object does not
15 /// provide `offsetX`/`offsetY` directly.
16 ///
17 /// Uses web-sys typed getters (`TouchEvent::touches()`,
18 /// `TouchList::get`, `Touch::client_x()`) instead of
19 /// `Reflect::get(event, "clientX")`. The Reflect path allocates a
20 /// `JsValue::from_str` per field per touch (7 fields × N touches per
21 /// event) on every `touchmove` (60-120Hz); the typed getters skip
22 /// the string lookup and the per-field JS string allocation.
23 ///
24 /// # Arguments
25 ///
26 /// - `&Event` - The native DOM touch event.
27 ///
28 /// # Returns
29 ///
30 /// - `Vec<NativeTouchPoint>` - All currently active touch points.
31 pub fn extract_all(event: &Event) -> Vec<NativeTouchPoint> {
32 let touch_event: &TouchEvent = event.unchecked_ref::<TouchEvent>();
33 let touches: TouchList = touch_event.touches();
34 let target: JsValue = event
35 .target()
36 .map_or(JsValue::NULL, |event_target: EventTarget| {
37 event_target.into()
38 });
39 let element: Element = target.unchecked_into();
40 let rect: DomRect = element.get_bounding_client_rect();
41 let rect_left: f64 = rect.left();
42 let rect_top: f64 = rect.top();
43 let length: u32 = touches.length();
44 (0..length)
45 .filter_map(|index: u32| touches.get(index))
46 .map(|touch: Touch| {
47 let identifier: i32 = touch.identifier();
48 let client_x: i32 = touch.client_x();
49 let client_y: i32 = touch.client_y();
50 let screen_x: i32 = touch.screen_x();
51 let screen_y: i32 = touch.screen_y();
52 let page_x: i32 = touch.page_x();
53 let page_y: i32 = touch.page_y();
54 let offset_x: i32 = (client_x as f64 - rect_left).round() as i32;
55 let offset_y: i32 = (client_y as f64 - rect_top).round() as i32;
56 NativeTouchPoint {
57 identifier,
58 client_x,
59 client_y,
60 screen_x,
61 screen_y,
62 offset_x,
63 offset_y,
64 page_x,
65 page_y,
66 }
67 })
68 .collect()
69 }
70
71 /// Extracts the changed touch points from a `TouchEvent`.
72 ///
73 /// The `changedTouches` list contains touch points that have changed
74 /// since the last touch event:
75 /// - For `touchstart` - newly added touch points.
76 /// - For `touchmove` - touch points that have moved.
77 /// - For `touchend` / `touchcancel` - removed touch points.
78 ///
79 /// This is useful for determining which specific fingers were lifted
80 /// in a `touchend` event, since the `touches` list no longer contains
81 /// them.
82 ///
83 /// Uses web-sys typed getters (`TouchEvent::changed_touches()`,
84 /// `TouchList::get`, `Touch::client_x()`) to avoid the per-field
85 /// `Reflect::get` + `JsValue::from_str` allocation cost on the hot
86 /// `touchmove` path.
87 ///
88 /// # Arguments
89 ///
90 /// - `&Event` - The native DOM touch event.
91 ///
92 /// # Returns
93 ///
94 /// - `Vec<NativeTouchPoint>` - The touch points that changed in this event.
95 pub fn extract_changed(event: &Event) -> Vec<NativeTouchPoint> {
96 let touch_event: &TouchEvent = event.unchecked_ref::<TouchEvent>();
97 let touches: TouchList = touch_event.changed_touches();
98 let target: JsValue = event
99 .target()
100 .map_or(JsValue::NULL, |event_target: EventTarget| {
101 event_target.into()
102 });
103 let element: Element = target.unchecked_into();
104 let rect: DomRect = element.get_bounding_client_rect();
105 let rect_left: f64 = rect.left();
106 let rect_top: f64 = rect.top();
107 let length: u32 = touches.length();
108 (0..length)
109 .filter_map(|index: u32| touches.get(index))
110 .map(|touch: Touch| {
111 let identifier: i32 = touch.identifier();
112 let client_x: i32 = touch.client_x();
113 let client_y: i32 = touch.client_y();
114 let screen_x: i32 = touch.screen_x();
115 let screen_y: i32 = touch.screen_y();
116 let page_x: i32 = touch.page_x();
117 let page_y: i32 = touch.page_y();
118 let offset_x: i32 = (client_x as f64 - rect_left).round() as i32;
119 let offset_y: i32 = (client_y as f64 - rect_top).round() as i32;
120 NativeTouchPoint {
121 identifier,
122 client_x,
123 client_y,
124 screen_x,
125 screen_y,
126 offset_x,
127 offset_y,
128 page_x,
129 page_y,
130 }
131 })
132 .collect()
133 }
134}
135
136/// Implementation of high-precision touch point extraction from DOM touch events.
137impl NativeTouchPointF64 {
138 /// Extracts all active touch points with high-precision `f64` offset coordinates
139 /// from a `TouchEvent`.
140 ///
141 /// Similar to `NativeTouchPoint::extract_all`, but returns `f64` precision for
142 /// offset/client coordinates, which is essential for canvas drawing
143 /// and other pixel-precise interactions.
144 ///
145 /// Uses web-sys typed getters (`TouchEvent::touches()`,
146 /// `TouchList::get`, `Touch::client_x()`) instead of
147 /// `Reflect::get(event, "clientX")`. web-sys `Touch` exposes
148 /// `client_x`/`page_x`/etc. as `i32`; we widen to `f64` to preserve
149 /// the `NativeTouchPointF64` high-precision contract without losing
150 /// sub-pixel information on the offset computation.
151 ///
152 /// # Arguments
153 ///
154 /// - `&Event` - The native DOM touch event.
155 ///
156 /// # Returns
157 ///
158 /// - `Vec<NativeTouchPointF64>` - All currently active touch points with `f64` coordinates.
159 pub fn extract_all(event: &Event) -> Vec<NativeTouchPointF64> {
160 let touch_event: &TouchEvent = event.unchecked_ref::<TouchEvent>();
161 let touches: TouchList = touch_event.touches();
162 let target: JsValue = event
163 .target()
164 .map_or(JsValue::NULL, |event_target: EventTarget| {
165 event_target.into()
166 });
167 let element: Element = target.unchecked_into();
168 let rect: DomRect = element.get_bounding_client_rect();
169 let rect_left: f64 = rect.left();
170 let rect_top: f64 = rect.top();
171 let length: u32 = touches.length();
172 (0..length)
173 .filter_map(|index: u32| touches.get(index))
174 .map(|touch: Touch| {
175 let identifier: i32 = touch.identifier();
176 let client_x: f64 = touch.client_x() as f64;
177 let client_y: f64 = touch.client_y() as f64;
178 let screen_x: f64 = touch.screen_x() as f64;
179 let screen_y: f64 = touch.screen_y() as f64;
180 let page_x: f64 = touch.page_x() as f64;
181 let page_y: f64 = touch.page_y() as f64;
182 let offset_x: f64 = client_x - rect_left;
183 let offset_y: f64 = client_y - rect_top;
184 NativeTouchPointF64 {
185 identifier,
186 client_x,
187 client_y,
188 screen_x,
189 screen_y,
190 offset_x,
191 offset_y,
192 page_x,
193 page_y,
194 }
195 })
196 .collect()
197 }
198}