1use blitz_dom::{
2 BaseDocument, Node, ScrollBehavior as BlitzScrollBehavior,
3 ScrollLogicalPosition as BlitzScrollLogicalPosition,
4};
5use blitz_traits::events::{
6 BlitzKeyEvent, BlitzPointerEvent, BlitzPointerId, BlitzScrollEvent, BlitzWheelDelta,
7 BlitzWheelEvent, MouseEventButton,
8};
9use dioxus_html::{
10 AnimationData, BeforeInputData, CancelData, ClipboardData, CompositionData, DragData,
11 FocusData, FormData, FormValue, HasFileData, HasFocusData, HasFormData, HasKeyboardData,
12 HasMouseData, HasPointerData, HasScrollData, HasTouchData, HasTouchPointData, HasWheelData,
13 HtmlEventConverter, ImageData, KeyboardData, MediaData, MountedData, MountedError,
14 MountedResult, MouseData, PlatformEventData, PointerData, RenderedElementBacking, ResizeData,
15 ScrollBehavior, ScrollData, ScrollLogicalPosition, ScrollToOptions, SelectionData, ToggleData,
16 TouchData, TouchPoint, TransitionData, VisibleData, WheelData,
17 geometry::{
18 ClientPoint, ElementPoint, PagePoint, PixelsRect, PixelsSize, PixelsVector2D, ScreenPoint,
19 WheelDelta,
20 euclid::{Point2D, Size2D, Vector3D},
21 },
22 input_data::{MouseButton, MouseButtonSet},
23 point_interaction::{
24 InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,
25 },
26};
27use keyboard_types::{Code, Key, Location, Modifiers};
28use std::{
29 any::Any,
30 cell::{Ref, RefCell, RefMut},
31 fmt::Display,
32 future::Future,
33 pin::Pin,
34 rc::Rc,
35};
36
37use crate::NodeId;
38
39pub struct NativeConverter {}
40
41impl HtmlEventConverter for NativeConverter {
42 fn convert_cancel_data(&self, _event: &PlatformEventData) -> CancelData {
43 unimplemented!("todo: convert_cancel_data in dioxus-native. requires support in blitz")
44 }
45
46 fn convert_form_data(&self, event: &PlatformEventData) -> FormData {
47 event.downcast::<NativeFormData>().unwrap().clone().into()
48 }
49
50 fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData {
51 event
52 .downcast::<NativePointerData>()
53 .unwrap()
54 .clone()
55 .into()
56 }
57
58 fn convert_keyboard_data(&self, event: &PlatformEventData) -> KeyboardData {
59 event
60 .downcast::<BlitzKeyboardData>()
61 .unwrap()
62 .clone()
63 .into()
64 }
65
66 fn convert_focus_data(&self, _event: &PlatformEventData) -> FocusData {
67 NativeFocusData {}.into()
68 }
69
70 fn convert_animation_data(&self, _event: &PlatformEventData) -> AnimationData {
71 unimplemented!("todo: convert_animation_data in dioxus-native. requires support in blitz")
72 }
73
74 fn convert_before_input_data(&self, _event: &PlatformEventData) -> BeforeInputData {
75 unimplemented!(
76 "todo: convert_before_input_data in dioxus-native. requires support in blitz"
77 )
78 }
79
80 fn convert_clipboard_data(&self, _event: &PlatformEventData) -> ClipboardData {
81 unimplemented!("todo: convert_clipboard_data in dioxus-native. requires support in blitz")
82 }
83
84 fn convert_composition_data(&self, _event: &PlatformEventData) -> CompositionData {
85 unimplemented!("todo: convert_composition_data in dioxus-native. requires support in blitz")
86 }
87
88 fn convert_drag_data(&self, _event: &PlatformEventData) -> DragData {
89 unimplemented!("todo: convert_drag_data in dioxus-native. requires support in blitz")
90 }
91
92 fn convert_image_data(&self, _event: &PlatformEventData) -> ImageData {
93 unimplemented!("todo: convert_image_data in dioxus-native. requires support in blitz")
94 }
95
96 fn convert_media_data(&self, _event: &PlatformEventData) -> MediaData {
97 unimplemented!("todo: convert_media_data in dioxus-native. requires support in blitz")
98 }
99
100 fn convert_mounted_data(&self, event: &PlatformEventData) -> MountedData {
101 event.downcast::<NodeHandle>().unwrap().clone().into()
102 }
103
104 fn convert_pointer_data(&self, event: &PlatformEventData) -> PointerData {
105 event
106 .downcast::<NativePointerData>()
107 .unwrap()
108 .clone()
109 .into()
110 }
111
112 fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData {
113 event.downcast::<NativeScrollData>().unwrap().clone().into()
114 }
115
116 fn convert_selection_data(&self, _event: &PlatformEventData) -> SelectionData {
117 unimplemented!("todo: convert_selection_data in dioxus-native. requires support in blitz")
118 }
119
120 fn convert_toggle_data(&self, _event: &PlatformEventData) -> ToggleData {
121 unimplemented!("todo: convert_toggle_data in dioxus-native. requires support in blitz")
122 }
123
124 fn convert_touch_data(&self, event: &PlatformEventData) -> TouchData {
125 event.downcast::<NativeTouchData>().unwrap().clone().into()
126 }
127
128 fn convert_transition_data(&self, _event: &PlatformEventData) -> TransitionData {
129 unimplemented!("todo: convert_transition_data in dioxus-native. requires support in blitz")
130 }
131
132 fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData {
133 event.downcast::<NativeWheelData>().unwrap().clone().into()
134 }
135
136 fn convert_resize_data(&self, _event: &PlatformEventData) -> ResizeData {
137 unimplemented!("todo: convert_resize_data in dioxus-native. requires support in blitz")
138 }
139
140 fn convert_visible_data(&self, _event: &PlatformEventData) -> VisibleData {
141 unimplemented!("todo: convert_visible_data in dioxus-native. requires support in blitz")
142 }
143}
144
145#[derive(Clone)]
146pub struct NodeHandle {
147 pub(crate) doc: Rc<RefCell<BaseDocument>>,
148 pub(crate) node_id: NodeId,
149}
150
151impl NodeHandle {
152 pub fn node_id(&self) -> NodeId {
153 self.node_id
154 }
155
156 pub fn doc(&self) -> Ref<'_, BaseDocument> {
157 self.doc.borrow()
158 }
159
160 pub fn try_doc(&self) -> Option<Ref<'_, BaseDocument>> {
163 self.doc.try_borrow().ok()
164 }
165
166 pub fn doc_mut(&self) -> RefMut<'_, BaseDocument> {
167 self.doc.borrow_mut()
168 }
169
170 pub fn node(&self) -> Ref<'_, Node> {
171 Ref::map(self.doc.borrow(), |doc| {
172 doc.get_node(self.node_id)
173 .expect("Node does not exist in the Document")
174 })
175 }
176
177 pub fn node_mut(&self) -> RefMut<'_, Node> {
178 RefMut::map(self.doc.borrow_mut(), |doc| {
179 doc.get_node_mut(self.node_id)
180 .expect("Node does not exist in the Document")
181 })
182 }
183
184 fn node_not_exist_err<T>(&self) -> Pin<Box<dyn Future<Output = MountedResult<T>>>> {
185 let node_id = self.node_id;
186 let err = MountedError::OperationFailed(Box::new(NodeNotExistErr(node_id)));
187 Box::pin(async move { Err(err) })
188 }
189}
190
191#[derive(Debug)]
192struct NodeNotExistErr(NodeId);
193impl Display for NodeNotExistErr {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 write!(f, "The node {} does not exist", self.0)
196 }
197}
198impl std::error::Error for NodeNotExistErr {}
199
200impl RenderedElementBacking for NodeHandle {
201 fn as_any(&self) -> &dyn std::any::Any {
202 self
203 }
204
205 fn get_scroll_offset(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsVector2D>>>> {
206 let Some(scroll_offset) = self
207 .doc()
208 .get_node(self.node_id)
209 .map(|node| *node.scroll_offset())
210 else {
211 return self.node_not_exist_err();
212 };
213 Box::pin(async move { Ok(PixelsVector2D::new(scroll_offset.x, scroll_offset.y)) })
214 }
215
216 fn get_scroll_size(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsSize>>>> {
217 let doc = self.doc();
218 let Some(node) = doc.get_node(self.node_id) else {
219 drop(doc);
220 return self.node_not_exist_err();
221 };
222 let scroll_width = node.final_layout().scroll_width() as f64;
223 let scroll_height = node.final_layout().scroll_height() as f64;
224 Box::pin(async move { Ok(PixelsSize::new(scroll_width, scroll_height)) })
225 }
226
227 fn get_client_rect(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsRect>>>> {
228 let Some(bounding_rect) = self.doc_mut().get_client_bounding_rect(self.node_id) else {
229 return self.node_not_exist_err();
230 };
231 let pixels_rect = PixelsRect::new(
232 Point2D::new(bounding_rect.x, bounding_rect.y),
233 Size2D::new(bounding_rect.width, bounding_rect.height),
234 );
235 Box::pin(async move { Ok(pixels_rect) })
236 }
237
238 fn scroll_to(
241 &self,
242 options: ScrollToOptions,
243 ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
244 let node_id = self.node_id;
245 let mut doc = self.doc_mut();
246 if doc.get_node(node_id).is_none() {
247 drop(doc);
248 return self.node_not_exist_err();
249 }
250
251 let behavior = match options.behavior {
252 ScrollBehavior::Smooth => BlitzScrollBehavior::Smooth,
253 ScrollBehavior::Instant => BlitzScrollBehavior::Instant,
254 };
255 let vertical = match options.vertical {
256 ScrollLogicalPosition::Start => BlitzScrollLogicalPosition::Start,
257 ScrollLogicalPosition::Center => BlitzScrollLogicalPosition::Center,
258 ScrollLogicalPosition::End => BlitzScrollLogicalPosition::End,
259 ScrollLogicalPosition::Nearest => BlitzScrollLogicalPosition::Nearest,
260 };
261 let horizontal = match options.horizontal {
262 ScrollLogicalPosition::Start => BlitzScrollLogicalPosition::Start,
263 ScrollLogicalPosition::Center => BlitzScrollLogicalPosition::Center,
264 ScrollLogicalPosition::End => BlitzScrollLogicalPosition::End,
265 ScrollLogicalPosition::Nearest => BlitzScrollLogicalPosition::Nearest,
266 };
267 doc.scroll_into_view(node_id, behavior, vertical, horizontal);
268
269 Box::pin(async { Ok(()) })
270 }
271
272 fn scroll(
275 &self,
276 coordinates: PixelsVector2D,
277 behavior: ScrollBehavior,
278 ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
279 let node_id = self.node_id;
280 let mut doc = self.doc_mut();
281 if doc.get_node(node_id).is_none() {
282 drop(doc);
283 return self.node_not_exist_err();
284 }
285
286 doc.scroll_to(
287 node_id,
288 coordinates.x,
289 coordinates.y,
290 match behavior {
291 ScrollBehavior::Smooth => BlitzScrollBehavior::Smooth,
292 ScrollBehavior::Instant => BlitzScrollBehavior::Instant,
293 },
294 );
295
296 Box::pin(async { Ok(()) })
297 }
298
299 fn set_focus(&self, focus: bool) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
300 let mut doc = self.doc_mut();
301 if focus {
302 doc.set_focus_to(self.node_id);
304 } else if doc.get_focussed_node_id() == Some(self.node_id) {
305 doc.clear_focus();
308 }
309
310 Box::pin(async { Ok(()) })
311 }
312}
313
314#[derive(Clone, Debug)]
315pub struct NativeFormData {
316 pub value: String,
317 pub values: Vec<(String, FormValue)>,
318}
319
320impl HasFormData for NativeFormData {
321 fn as_any(&self) -> &dyn Any {
322 self as &dyn Any
323 }
324
325 fn value(&self) -> String {
326 self.value.clone()
327 }
328
329 fn values(&self) -> Vec<(String, FormValue)> {
330 self.values.clone()
331 }
332 fn valid(&self) -> bool {
333 true
335 }
336}
337
338impl HasFileData for NativeFormData {
339 fn files(&self) -> Vec<dioxus_html::FileData> {
340 vec![]
341 }
342}
343
344#[derive(Clone, Debug)]
345pub(crate) struct BlitzKeyboardData(pub(crate) BlitzKeyEvent);
346
347impl ModifiersInteraction for BlitzKeyboardData {
348 fn modifiers(&self) -> Modifiers {
349 self.0.modifiers
350 }
351}
352
353impl HasKeyboardData for BlitzKeyboardData {
354 fn key(&self) -> Key {
355 self.0.key.clone()
356 }
357
358 fn code(&self) -> Code {
359 self.0.code
360 }
361
362 fn location(&self) -> Location {
363 self.0.location
364 }
365
366 fn is_auto_repeating(&self) -> bool {
367 self.0.is_auto_repeating
368 }
369
370 fn is_composing(&self) -> bool {
371 self.0.is_composing
372 }
373
374 fn as_any(&self) -> &dyn Any {
375 self as &dyn Any
376 }
377}
378
379#[derive(Clone)]
380pub struct NativePointerData(pub(crate) BlitzPointerEvent);
381
382impl InteractionLocation for NativePointerData {
383 fn client_coordinates(&self) -> ClientPoint {
384 ClientPoint::new(self.0.client_x() as f64, self.0.client_y() as f64)
385 }
386
387 fn screen_coordinates(&self) -> ScreenPoint {
388 ScreenPoint::new(self.0.screen_x() as f64, self.0.screen_y() as f64)
389 }
390
391 fn page_coordinates(&self) -> PagePoint {
392 PagePoint::new(self.0.page_x() as f64, self.0.page_y() as f64)
393 }
394}
395
396impl InteractionElementOffset for NativePointerData {
397 fn element_coordinates(&self) -> ElementPoint {
398 ElementPoint::new(self.0.element_x() as f64, self.0.element_y() as f64)
399 }
400}
401
402impl ModifiersInteraction for NativePointerData {
403 fn modifiers(&self) -> Modifiers {
404 self.0.mods
405 }
406}
407
408impl PointerInteraction for NativePointerData {
409 fn trigger_button(&self) -> Option<MouseButton> {
410 Some(match self.0.button {
411 MouseEventButton::Main => MouseButton::Primary,
412 MouseEventButton::Auxiliary => MouseButton::Auxiliary,
413 MouseEventButton::Secondary => MouseButton::Secondary,
414 MouseEventButton::Fourth => MouseButton::Fourth,
415 MouseEventButton::Fifth => MouseButton::Fifth,
416 })
417 }
418
419 fn held_buttons(&self) -> MouseButtonSet {
420 dioxus_html::input_data::decode_mouse_button_set(self.0.buttons.bits() as u16)
421 }
422}
423impl HasMouseData for NativePointerData {
424 fn as_any(&self) -> &dyn Any {
425 self as &dyn Any
426 }
427}
428
429impl HasPointerData for NativePointerData {
430 fn as_any(&self) -> &dyn Any {
431 self as &dyn Any
432 }
433
434 fn is_primary(&self) -> bool {
435 self.0.is_primary
436 }
437
438 fn pointer_id(&self) -> i32 {
439 match self.0.id {
440 BlitzPointerId::Mouse => 0,
441 BlitzPointerId::Pen => 0,
442 BlitzPointerId::Finger(id) => id as i32,
443 }
444 }
445
446 fn pointer_type(&self) -> String {
447 match self.0.id {
448 BlitzPointerId::Mouse => String::from("mouse"),
449 BlitzPointerId::Pen => String::from("pen"),
450 BlitzPointerId::Finger(_) => String::from("touch"),
451 }
452 }
453
454 fn pressure(&self) -> f32 {
455 self.0.details.pressure as f32
456 }
457 fn tangential_pressure(&self) -> f32 {
458 self.0.details.tangential_pressure
459 }
460 fn tilt_x(&self) -> i32 {
461 self.0.details.tilt_x as i32
462 }
463 fn tilt_y(&self) -> i32 {
464 self.0.details.tilt_y as i32
465 }
466 fn twist(&self) -> i32 {
467 self.0.details.twist as i32
468 }
469
470 fn width(&self) -> f64 {
472 1.0
473 }
474 fn height(&self) -> f64 {
475 1.0
476 }
477}
478
479#[derive(Clone)]
486pub struct NativeTouchData(pub(crate) BlitzPointerEvent);
487
488impl ModifiersInteraction for NativeTouchData {
489 fn modifiers(&self) -> Modifiers {
490 self.0.mods
491 }
492}
493
494impl HasTouchData for NativeTouchData {
495 fn touches(&self) -> Vec<TouchPoint> {
496 self.0
498 .active_pointers
499 .borrow()
500 .iter()
501 .map(|event| TouchPoint::new(NativeTouchPointData(event.clone())))
502 .collect()
503 }
504
505 fn touches_changed(&self) -> Vec<TouchPoint> {
506 vec![TouchPoint::new(NativeTouchPointData(self.0.clone()))]
508 }
509
510 fn target_touches(&self) -> Vec<TouchPoint> {
511 self.touches()
514 }
515
516 fn as_any(&self) -> &dyn Any {
517 self as &dyn Any
518 }
519}
520
521#[derive(Clone)]
522pub struct NativeTouchPointData(BlitzPointerEvent);
523
524impl InteractionLocation for NativeTouchPointData {
525 fn client_coordinates(&self) -> ClientPoint {
526 ClientPoint::new(self.0.client_x() as f64, self.0.client_y() as f64)
527 }
528
529 fn screen_coordinates(&self) -> ScreenPoint {
530 ScreenPoint::new(self.0.screen_x() as f64, self.0.screen_y() as f64)
531 }
532
533 fn page_coordinates(&self) -> PagePoint {
534 PagePoint::new(self.0.page_x() as f64, self.0.page_y() as f64)
535 }
536}
537
538impl HasTouchPointData for NativeTouchPointData {
539 fn identifier(&self) -> i32 {
540 match self.0.id {
541 BlitzPointerId::Finger(id) => id as i32,
542 BlitzPointerId::Mouse | BlitzPointerId::Pen => 0,
543 }
544 }
545
546 fn force(&self) -> f64 {
547 self.0.details.pressure
548 }
549
550 fn radius(&self) -> ScreenPoint {
551 ScreenPoint::new(1.0, 1.0)
553 }
554
555 fn rotation(&self) -> f64 {
556 0.0
558 }
559
560 fn as_any(&self) -> &dyn Any {
561 self as &dyn Any
562 }
563}
564
565#[derive(Clone)]
566pub struct NativeFocusData;
567impl HasFocusData for NativeFocusData {
568 fn as_any(&self) -> &dyn Any {
569 self as &dyn Any
570 }
571}
572
573#[derive(Clone)]
574pub struct NativeScrollData(pub(crate) BlitzScrollEvent);
575impl HasScrollData for NativeScrollData {
576 fn as_any(&self) -> &dyn Any {
577 self as &dyn Any
578 }
579
580 fn scroll_top(&self) -> f64 {
581 self.0.scroll_top
582 }
583
584 fn scroll_left(&self) -> f64 {
585 self.0.scroll_left
586 }
587
588 fn scroll_width(&self) -> i32 {
589 self.0.scroll_width
590 }
591
592 fn scroll_height(&self) -> i32 {
593 self.0.scroll_height
594 }
595
596 fn client_width(&self) -> i32 {
597 self.0.client_width
598 }
599
600 fn client_height(&self) -> i32 {
601 self.0.client_height
602 }
603}
604
605#[derive(Clone)]
606pub struct NativeWheelData(pub(crate) BlitzWheelEvent);
607impl HasWheelData for NativeWheelData {
608 fn as_any(&self) -> &dyn Any {
609 self as &dyn Any
610 }
611
612 fn delta(&self) -> WheelDelta {
613 match self.0.delta {
614 BlitzWheelDelta::Lines(x, y) => {
615 dioxus_html::geometry::WheelDelta::Lines(Vector3D::new(x, y, 0.0))
616 }
617 BlitzWheelDelta::Pixels(x, y) => {
618 dioxus_html::geometry::WheelDelta::Pixels(Vector3D::new(x, y, 0.0))
619 }
620 }
621 }
622}
623
624impl HasMouseData for NativeWheelData {
625 fn as_any(&self) -> &dyn Any {
626 self as &dyn Any
627 }
628}
629
630impl PointerInteraction for NativeWheelData {
631 fn trigger_button(&self) -> Option<MouseButton> {
632 None
633 }
634
635 fn held_buttons(&self) -> MouseButtonSet {
636 dioxus_html::input_data::decode_mouse_button_set(self.0.buttons.bits() as u16)
637 }
638}
639
640impl ModifiersInteraction for NativeWheelData {
641 fn modifiers(&self) -> Modifiers {
642 self.0.mods
643 }
644}
645
646impl InteractionElementOffset for NativeWheelData {
647 fn element_coordinates(&self) -> ElementPoint {
648 ElementPoint::new(self.0.element_x() as f64, self.0.element_y() as f64)
649 }
650}
651
652impl InteractionLocation for NativeWheelData {
653 fn client_coordinates(&self) -> ClientPoint {
654 ClientPoint::new(self.0.client_x() as f64, self.0.client_y() as f64)
655 }
656
657 fn screen_coordinates(&self) -> ScreenPoint {
658 ScreenPoint::new(self.0.screen_x() as f64, self.0.screen_y() as f64)
659 }
660
661 fn page_coordinates(&self) -> PagePoint {
662 PagePoint::new(self.0.page_x() as f64, self.0.page_y() as f64)
663 }
664}
665
666pub fn synthetic_click_event(node: &Node, modifiers: Modifiers) -> Box<dyn Any> {
667 Box::new(NativePointerData(
668 node.synthetic_click_event_data(modifiers),
669 ))
670}
671
672pub fn synthetic_form_event(
673 value: impl Into<String>,
674 values: impl Into<Vec<(String, FormValue)>>,
675) -> Box<dyn Any> {
676 Box::new(NativeFormData {
677 value: value.into(),
678 values: values.into(),
679 })
680}
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685 use blitz_traits::events::{
686 BlitzPointerId, MouseEventButton, MouseEventButtons, Point, PointerCoords, PointerDetails,
687 };
688
689 fn finger_event(id: u64, x: f32, y: f32) -> BlitzPointerEvent {
690 BlitzPointerEvent {
691 id: BlitzPointerId::Finger(id),
692 is_primary: id == 0,
693 coords: PointerCoords {
694 page_x: x,
695 page_y: y,
696 screen_x: x,
697 screen_y: y,
698 client_x: x,
699 client_y: y,
700 },
701 button: MouseEventButton::Main,
702 buttons: MouseEventButtons::from(MouseEventButton::Main),
703 mods: Default::default(),
704 details: PointerDetails::default(),
705 element: Point::default(),
706 active_pointers: Default::default(),
707 }
708 }
709
710 #[test]
711 fn touches_reports_all_active_pointers() {
712 let f0 = finger_event(0, 10.0, 20.0);
713 let f1 = finger_event(1, 30.0, 40.0);
714
715 let trigger = f1.clone();
718 {
719 let mut list = trigger.active_pointers.borrow_mut();
720 list.push(f0.clone());
721 list.push(f1.clone());
722 }
723
724 let data = NativeTouchData(trigger);
725
726 let touches = data.touches();
728 assert_eq!(touches.len(), 2);
729 let coords: Vec<(f64, f64)> = touches
730 .iter()
731 .map(|t| {
732 let c = t.client_coordinates();
733 (c.x, c.y)
734 })
735 .collect();
736 assert!(coords.contains(&(10.0, 20.0)));
737 assert!(coords.contains(&(30.0, 40.0)));
738
739 assert_eq!(data.touches_changed().len(), 1);
741 let changed = data.touches_changed();
742 let changed_coords = changed[0].client_coordinates();
743 assert_eq!((changed_coords.x, changed_coords.y), (30.0, 40.0));
744 }
745
746 #[test]
747 fn touches_is_empty_when_no_pointers_are_active() {
748 let data = NativeTouchData(finger_event(0, 1.0, 2.0));
752 assert!(data.touches().is_empty());
753 assert_eq!(data.touches_changed().len(), 1);
754 }
755}