Skip to main content

dioxus_native_dom/
events.rs

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    /// Returns `None` if the document is currently mutably borrowed.
161    /// Use this from background tasks to avoid panicking during event handling.
162    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    /// Scroll the element into view (the backing for both `MountedData::scroll_to` and
239    /// `MountedData::scroll_to_with_options`).
240    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    /// Scroll the element's own content to the given offset (the backing for
273    /// `MountedData::scroll`). The offset is clamped to the element's scrollable range.
274    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            // TODO: queue focus events somehow
303            doc.set_focus_to(self.node_id);
304        } else if doc.get_focussed_node_id() == Some(self.node_id) {
305            // Q: Should this only clear focus if the node is focussed?
306            // TODO: queue blur events somehow
307            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        // todo: actually implement validation here.
334        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    // TODO: implement these fields with real values
471    fn width(&self) -> f64 {
472        1.0
473    }
474    fn height(&self) -> f64 {
475        1.0
476    }
477}
478
479/// Touch event data exposed to Dioxus Native application code.
480///
481/// Blitz tracks input via pointer events, so a touch event is generated from the
482/// pointer event of the finger that triggered it (`touches_changed`). The full
483/// list of concurrent touches is carried on the triggering event's
484/// [`BlitzPointerEvent::active_pointers`] list and reported via `touches`.
485#[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        // All pointers currently active on the surface (multi-touch).
497        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        // Just the touch that triggered this event.
507        vec![TouchPoint::new(NativeTouchPointData(self.0.clone()))]
508    }
509
510    fn target_touches(&self) -> Vec<TouchPoint> {
511        // We don't track a per-touch target, so approximate `targetTouches`
512        // (touches that started on the event target) with all active touches.
513        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        // TODO: expose real touch radius once blitz tracks it
552        ScreenPoint::new(1.0, 1.0)
553    }
554
555    fn rotation(&self) -> f64 {
556        // TODO: expose real touch rotation once blitz tracks it
557        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        // The triggering event (second finger down) carries the list of all
716        // currently-active pointers.
717        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        // `touches` reports every active pointer ...
727        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        // ... while `changed_touches` reports only the triggering touch.
740        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        // e.g. a `touchend` for the last finger: it has been removed from the
749        // active list before dispatch, so `touches` is empty but
750        // `changed_touches` still reports the finger that ended.
751        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}