1use std::any::Any;
4use std::rc::Rc;
5
6use indexmap::IndexMap;
7use inset_embedder::{Matrix4, Offset, PointerDeviceKind, ViewId};
8use inset_foundation::{App, ChangeNotifier, ChangeNotifierData, Handle, RetainedHandle};
9use inset_gestures::{
10 HitTestEntry, HitTestResult, PointerEnterEvent, PointerEvent, PointerExitEvent,
11};
12use inset_services::{MouseCursorManager, MouseCursorRef, SystemMouseCursors};
13
14use crate::box_::BoxHitTestEntry;
15use crate::object::AnyRenderObject;
16
17pub type MouseTrackerHitTest = Rc<dyn Fn(&mut App, Offset, ViewId) -> HitTestResult>;
22
23type Annotations = IndexMap<AnyRenderObject, Matrix4>;
30
31struct MouseState {
33 retained: Vec<RetainedHandle<AnyRenderObject>>,
35 annotations: Annotations,
39 latest_event: PointerEvent,
41}
42
43impl MouseState {
44 fn new(initial_event: PointerEvent) -> MouseState {
45 MouseState {
46 retained: Vec::new(),
47 annotations: Annotations::new(),
48 latest_event: initial_event,
49 }
50 }
51
52 fn replace_annotations(
53 &mut self,
54 value: Annotations,
55 retained: Vec<RetainedHandle<AnyRenderObject>>,
56 ) -> (Annotations, Vec<RetainedHandle<AnyRenderObject>>) {
57 (
58 std::mem::replace(&mut self.annotations, value),
59 std::mem::replace(&mut self.retained, retained),
60 )
61 }
62
63 fn replace_latest_event(&mut self, value: PointerEvent) -> PointerEvent {
64 debug_assert_eq!(value.device(), self.latest_event.device());
65 std::mem::replace(&mut self.latest_event, value)
66 }
67}
68
69struct MouseTrackerUpdateDetails {
75 last_annotations: Annotations,
79 next_annotations: Annotations,
83 previous_event: Option<PointerEvent>,
92 triggering_event: Option<PointerEvent>,
96}
97
98impl MouseTrackerUpdateDetails {
99 fn by_new_frame(
103 last_annotations: Annotations,
104 next_annotations: Annotations,
105 previous_event: PointerEvent,
106 ) -> MouseTrackerUpdateDetails {
107 MouseTrackerUpdateDetails {
108 last_annotations,
109 next_annotations,
110 previous_event: Some(previous_event),
111 triggering_event: None,
112 }
113 }
114
115 fn by_pointer_event(
120 last_annotations: Annotations,
121 next_annotations: Annotations,
122 previous_event: Option<PointerEvent>,
123 triggering_event: PointerEvent,
124 ) -> MouseTrackerUpdateDetails {
125 MouseTrackerUpdateDetails {
126 last_annotations,
127 next_annotations,
128 previous_event,
129 triggering_event: Some(triggering_event),
130 }
131 }
132
133 fn device(&self) -> i64 {
135 self.previous_event
136 .as_ref()
137 .or(self.triggering_event.as_ref())
138 .expect("an update has an event")
139 .device()
140 }
141
142 fn latest_event(&self) -> &PointerEvent {
146 self.triggering_event
147 .as_ref()
148 .or(self.previous_event.as_ref())
149 .expect("an update has an event")
150 }
151}
152
153pub struct MouseTracker {
175 change_notifier: ChangeNotifierData,
176 hit_test_in_view: MouseTrackerHitTest,
177 mouse_cursor_mixin: MouseCursorManager,
178 mouse_states: IndexMap<i64, MouseState>,
179 debug_during_device_update: bool,
180}
181
182impl ChangeNotifier for MouseTracker {
183 fn change_notifier_data(&self) -> &ChangeNotifierData {
184 &self.change_notifier
185 }
186
187 fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData {
188 &mut self.change_notifier
189 }
190}
191
192impl MouseTracker {
193 pub fn new(app: &mut App, hit_test_in_view: MouseTrackerHitTest) -> Handle<MouseTracker> {
199 app.create(MouseTracker {
200 change_notifier: ChangeNotifierData::new(),
201 hit_test_in_view,
202 mouse_cursor_mixin: MouseCursorManager::new(SystemMouseCursors::BASIC.into()),
203 mouse_states: IndexMap::new(),
204 debug_during_device_update: false,
205 })
206 }
207
208 pub fn dispose(self: Handle<Self>, app: &mut App) {
211 let states = std::mem::take(&mut app.get_mut(self).mouse_states);
212 for state in states.into_values() {
213 MouseTracker::release_annotations(app, state.retained);
214 }
215 app.get_mut(self).change_notifier.dispose();
216 }
217
218 fn retain_annotations(
221 app: &mut App,
222 annotations: &Annotations,
223 ) -> Vec<RetainedHandle<AnyRenderObject>> {
224 annotations
225 .keys()
226 .copied()
227 .map(|target| app.retain(target))
228 .collect()
229 }
230
231 fn release_annotations(app: &mut App, retained: Vec<RetainedHandle<AnyRenderObject>>) {
232 for handle in retained {
233 app.release(handle);
234 }
235 }
236
237 fn should_mark_state_dirty(state: Option<&MouseState>, event: &PointerEvent) -> bool {
242 let Some(state) = state else {
243 return true;
244 };
245 let last_event = &state.latest_event;
246 debug_assert_eq!(event.device(), last_event.device());
247 debug_assert_eq!(
249 matches!(event, PointerEvent::Added(_)),
250 matches!(last_event, PointerEvent::Removed(_))
251 );
252 if is_signal_event(event) {
254 return false;
255 }
256 matches!(last_event, PointerEvent::Added(_))
257 || matches!(event, PointerEvent::Removed(_))
258 || last_event.position() != event.position()
259 }
260
261 fn hit_test_in_view_result_to_annotations(app: &App, result: &HitTestResult) -> Annotations {
262 let mut annotations = Annotations::new();
263 for entry in result.path() {
264 if let Some(target) = annotation_target(app, entry) {
265 annotations.insert(target, entry.transform().expect("an added entry is placed"));
266 }
267 }
268 annotations
269 }
270
271 fn find_annotations(
276 self: Handle<Self>,
277 app: &mut App,
278 latest_event: &PointerEvent,
279 ) -> Annotations {
280 let global_position = latest_event.position();
281 let device = latest_event.device();
282 let view_id = latest_event.view_id();
283 if !app.get(self).mouse_states.contains_key(&device) {
284 return Annotations::new();
285 }
286 let result = self.run_hit_test(app, global_position, view_id);
287 MouseTracker::hit_test_in_view_result_to_annotations(app, &result)
288 }
289
290 fn run_hit_test(
291 self: Handle<Self>,
292 app: &mut App,
293 position: Offset,
294 view_id: ViewId,
295 ) -> HitTestResult {
296 let hit_test_in_view = Rc::clone(&app.get(self).hit_test_in_view);
297 hit_test_in_view(app, position, view_id)
298 }
299
300 fn handle_device_update(self: Handle<Self>, app: &mut App, details: MouseTrackerUpdateDetails) {
311 debug_assert!(app.get(self).debug_during_device_update);
312 MouseTracker::handle_device_update_mouse_events(app, &details);
313 let cursors: Vec<MouseCursorRef> = details
314 .next_annotations
315 .keys()
316 .filter_map(|target| target.mouse_tracker_annotation(app))
317 .map(|annotation| annotation.cursor)
318 .collect();
319 let platform = app.platform();
320 app.get_mut(self)
321 .mouse_cursor_mixin
322 .handle_device_cursor_update(
323 &*platform,
324 details.latest_event().view_id(),
325 details.device(),
326 details.triggering_event.as_ref(),
327 cursors,
328 );
329 }
330
331 pub fn mouse_is_connected(self: Handle<Self>, app: &App) -> bool {
333 !app.get(self).mouse_states.is_empty()
334 }
335
336 pub fn update_with_event(
355 self: Handle<Self>,
356 app: &mut App,
357 event: &PointerEvent,
358 hit_test_result: Option<&HitTestResult>,
359 ) {
360 if event.kind() != PointerDeviceKind::Mouse && event.kind() != PointerDeviceKind::Stylus {
361 return;
363 }
364 if is_signal_event(event) {
365 return;
367 }
368 let is_removed = matches!(event, PointerEvent::Removed(_));
369 let owned_result;
370 let result: Option<&HitTestResult> = if is_removed {
371 None
372 } else if let Some(result) = hit_test_result {
373 Some(result)
374 } else {
375 owned_result = self.run_hit_test(app, event.position(), event.view_id());
376 Some(&owned_result)
377 };
378 let device = event.device();
379 if !MouseTracker::should_mark_state_dirty(app.get(self).mouse_states.get(&device), event) {
380 return;
381 }
382
383 self.monitor_mouse_connection(app, |app| {
384 self.device_update_phase(app, |app| {
385 if !app.get(self).mouse_states.contains_key(&device) {
387 if is_removed {
388 return;
389 }
390 app.get_mut(self)
391 .mouse_states
392 .insert(device, MouseState::new(event.clone()));
393 } else {
394 debug_assert!(!matches!(event, PointerEvent::Added(_)));
395 }
396 let next_annotations = match result {
397 Some(result) => {
398 MouseTracker::hit_test_in_view_result_to_annotations(app, result)
399 }
400 None => Annotations::new(),
401 };
402 let target_state = app
403 .get_mut(self)
404 .mouse_states
405 .get_mut(&device)
406 .expect("the state was just found or created");
407 let last_event = target_state.replace_latest_event(event.clone());
408 let retained = MouseTracker::retain_annotations(app, &next_annotations);
409 let target_state = app
410 .get_mut(self)
411 .mouse_states
412 .get_mut(&device)
413 .expect("the state was just found or created");
414 let (last_annotations, last_retained) =
415 target_state.replace_annotations(next_annotations.clone(), retained);
416 if is_removed {
417 app.get_mut(self).mouse_states.shift_remove(&device);
418 }
419 self.handle_device_update(
420 app,
421 MouseTrackerUpdateDetails::by_pointer_event(
422 last_annotations,
423 next_annotations,
424 Some(last_event),
425 event.clone(),
426 ),
427 );
428 MouseTracker::release_annotations(app, last_retained);
429 });
430 });
431 }
432
433 pub fn update_all_devices(self: Handle<Self>, app: &mut App) {
443 self.device_update_phase(app, |app| {
444 let devices: Vec<i64> = app.get(self).mouse_states.keys().copied().collect();
445 for device in devices {
446 let Some(last_event) = app
447 .get(self)
448 .mouse_states
449 .get(&device)
450 .map(|state| state.latest_event.clone())
451 else {
452 continue;
453 };
454 let next_annotations = self.find_annotations(app, &last_event);
455 if !app.get(self).mouse_states.contains_key(&device) {
456 continue;
457 }
458 let retained = MouseTracker::retain_annotations(app, &next_annotations);
459 let Some(dirty_state) = app.get_mut(self).mouse_states.get_mut(&device) else {
460 continue;
461 };
462 let (last_annotations, last_retained) =
463 dirty_state.replace_annotations(next_annotations.clone(), retained);
464 self.handle_device_update(
465 app,
466 MouseTrackerUpdateDetails::by_new_frame(
467 last_annotations,
468 next_annotations,
469 last_event,
470 ),
471 );
472 MouseTracker::release_annotations(app, last_retained);
473 }
474 });
475 }
476
477 pub fn debug_device_active_cursor(
484 self: Handle<Self>,
485 app: &App,
486 device: i64,
487 ) -> Option<MouseCursorRef> {
488 app.get(self)
489 .mouse_cursor_mixin
490 .debug_device_active_cursor(device)
491 }
492
493 fn monitor_mouse_connection(self: Handle<Self>, app: &mut App, task: impl FnOnce(&mut App)) {
498 let mouse_was_connected = self.mouse_is_connected(app);
499 task(app);
500 if mouse_was_connected != self.mouse_is_connected(app) {
501 self.notify_listeners(app);
502 }
503 }
504
505 fn device_update_phase(self: Handle<Self>, app: &mut App, task: impl FnOnce(&mut App)) {
510 debug_assert!(!app.get(self).debug_during_device_update);
511 if cfg!(debug_assertions) {
512 app.get_mut(self).debug_during_device_update = true;
513 }
514 task(app);
515 if cfg!(debug_assertions) {
516 app.get_mut(self).debug_during_device_update = false;
517 }
518 }
519
520 fn handle_device_update_mouse_events(app: &mut App, details: &MouseTrackerUpdateDetails) {
522 let latest_event = details.latest_event();
523 let last_annotations = &details.last_annotations;
524 let next_annotations = &details.next_annotations;
525
526 let base_exit_event = PointerExitEvent::from_mouse_event(latest_event);
534 for (target, transform) in last_annotations {
535 if next_annotations.contains_key(target) {
536 continue;
537 }
538 let Some(annotation) = target.mouse_tracker_annotation(app) else {
539 continue;
540 };
541 if annotation.valid_for_mouse_tracker
542 && let Some(on_exit) = annotation.on_exit
543 {
544 on_exit(app, base_exit_event.transformed(Some(*transform)));
545 }
546 }
547
548 let entering_annotations: Vec<(AnyRenderObject, Matrix4)> = next_annotations
551 .iter()
552 .filter(|(target, _)| !last_annotations.contains_key(*target))
553 .map(|(target, transform)| (*target, *transform))
554 .collect();
555 let base_enter_event = PointerEnterEvent::from_mouse_event(latest_event);
556 for (target, transform) in entering_annotations.into_iter().rev() {
557 let Some(annotation) = target.mouse_tracker_annotation(app) else {
558 continue;
559 };
560 if annotation.valid_for_mouse_tracker
561 && let Some(on_enter) = annotation.on_enter
562 {
563 on_enter(app, base_enter_event.transformed(Some(transform)));
564 }
565 }
566 }
567}
568
569fn is_signal_event(event: &PointerEvent) -> bool {
571 matches!(
572 event,
573 PointerEvent::Scroll(_) | PointerEvent::ScrollInertiaCancel(_) | PointerEvent::Scale(_)
574 )
575}
576
577fn annotation_target(app: &App, entry: &HitTestEntry) -> Option<AnyRenderObject> {
580 let target: &dyn Any = entry.target();
581 let target = target
582 .downcast_ref::<BoxHitTestEntry>()?
583 .target()
584 .as_object();
585 target.mouse_tracker_annotation(app).map(|_| target)
586}
587
588#[cfg(test)]
589mod tests {
590 use inset_foundation::AppCell;
591 use std::cell::{Cell, RefCell};
592
593 use inset_embedder::{MouseCursor, Size, SystemMouseCursorKind};
594 use inset_gestures::{
595 PointerAddedEvent, PointerHoverEvent, PointerRemovedEvent, PointerScrollEvent,
596 };
597 use inset_test::TestPlatform;
598
599 use inset_foundation::{Listenable, Listener};
600
601 use super::*;
602 use crate::box_::{BoxConstraints, BoxHitTestResult, RenderBox};
603 use crate::object::RenderObject;
604 use crate::object::RenderObjectWithChildMixin;
605 use crate::pipeline_owner::PipelineOwner;
606 use crate::proxy_box::{RenderConstrainedBox, RenderMouseRegion, RenderRepaintBoundary};
607 use crate::shifted_box::RenderPadding;
608
609 #[derive(Default)]
611 struct RecordingCursor {
612 activated: RefCell<Vec<(ViewId, i64, SystemMouseCursorKind)>>,
613 }
614
615 impl MouseCursor for RecordingCursor {
616 fn activate_system_cursor(&self, view: ViewId, device: i64, kind: SystemMouseCursorKind) {
617 self.activated.borrow_mut().push((view, device, kind));
618 }
619 }
620
621 fn app_with_cursor_host() -> (Rc<AppCell>, Rc<RecordingCursor>) {
622 let cursor = Rc::new(RecordingCursor::default());
623 let platform = TestPlatform::new().with_mouse_cursor(cursor.clone());
624 (AppCell::with_platform(Rc::new(platform)), cursor)
625 }
626
627 struct RegionTree {
630 owner: Handle<PipelineOwner>,
631 root: crate::RenderHandle<RenderRepaintBoundary>,
632 padding: crate::RenderHandle<RenderPadding>,
633 region: crate::RenderHandle<RenderMouseRegion>,
634 }
635
636 fn tree_with_region(app: &mut App) -> RegionTree {
637 let region = RenderMouseRegion::new(app, true, None);
638 let padding = RenderPadding::new(
639 app,
640 inset_painting::EdgeInsets::all(50.0).into(),
641 None,
642 Some(region.as_box()),
643 );
644 let root = RenderRepaintBoundary::new(app, Some(padding.as_box()));
645 let owner = PipelineOwner::new(app, None, None);
646 owner.set_root_node(app, Some(root.as_object()));
647 root.schedule_initial_layout(app);
648 root.layout(app, BoxConstraints::tight(Size::new(200.0, 200.0)), false);
649 RegionTree {
650 owner,
651 root,
652 padding,
653 region,
654 }
655 }
656
657 fn tracker_for(
658 app: &mut App,
659 root: crate::RenderHandle<RenderRepaintBoundary>,
660 ) -> Handle<MouseTracker> {
661 MouseTracker::new(
662 app,
663 Rc::new(move |app: &mut App, position: Offset, _view_id: ViewId| {
664 let mut result = HitTestResult::new();
665 root.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
666 result
667 }),
668 )
669 }
670
671 fn mouse_hover(position: Offset) -> PointerEvent {
672 PointerEvent::Hover(PointerHoverEvent {
673 kind: PointerDeviceKind::Mouse,
674 position,
675 ..PointerHoverEvent::default()
676 })
677 }
678
679 fn mouse_added(position: Offset) -> PointerEvent {
680 PointerEvent::Added(PointerAddedEvent {
681 kind: PointerDeviceKind::Mouse,
682 position,
683 ..PointerAddedEvent::default()
684 })
685 }
686
687 fn mouse_removed(position: Offset) -> PointerEvent {
688 PointerEvent::Removed(PointerRemovedEvent {
689 kind: PointerDeviceKind::Mouse,
690 position,
691 ..PointerRemovedEvent::default()
692 })
693 }
694
695 fn record_events(
697 app: &mut App,
698 region: crate::RenderHandle<RenderMouseRegion>,
699 ) -> Rc<RefCell<Vec<String>>> {
700 let log = Rc::new(RefCell::new(Vec::new()));
701 let enter_log = Rc::clone(&log);
702 region.set_on_enter(
703 app,
704 Some(Rc::new(move |_app: &mut App, event: PointerEnterEvent| {
705 enter_log
706 .borrow_mut()
707 .push(format!("enter {:?}", event.local_position()));
708 })),
709 );
710 let hover_log = Rc::clone(&log);
711 region.set_on_hover(
712 app,
713 Some(Rc::new(move |_app: &mut App, event: PointerHoverEvent| {
714 hover_log
715 .borrow_mut()
716 .push(format!("hover {:?}", event.local_position()));
717 })),
718 );
719 let exit_log = Rc::clone(&log);
720 region.set_on_exit(
721 app,
722 Some(Rc::new(move |_app: &mut App, event: PointerExitEvent| {
723 exit_log
724 .borrow_mut()
725 .push(format!("exit {:?}", event.local_position()));
726 })),
727 );
728 log
729 }
730
731 #[test]
732 fn entering_and_leaving_a_region_sends_enter_then_exit_in_local_coordinates() {
733 let cell = AppCell::new();
734 let mut app = cell.borrow_mut();
735 let RegionTree { root, region, .. } = tree_with_region(&mut app);
736 let tracker = tracker_for(&mut app, root);
737 let log = record_events(&mut app, region);
738
739 tracker.update_with_event(&mut app, &mouse_added(Offset::new(10.0, 10.0)), None);
740 assert!(log.borrow().is_empty(), "outside the region: nothing");
741
742 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(60.0, 70.0)), None);
743 assert_eq!(*log.borrow(), ["enter Offset(10.0, 20.0)"]);
744
745 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(160.0, 70.0)), None);
746 assert_eq!(
747 *log.borrow(),
748 ["enter Offset(10.0, 20.0)", "exit Offset(110.0, 20.0)"]
749 );
750 }
751
752 #[test]
753 fn a_still_mouse_notices_the_tree_moving_under_it_after_a_frame() {
754 let cell = AppCell::new();
755 let mut app = cell.borrow_mut();
756 let RegionTree {
757 owner,
758 root,
759 padding,
760 region,
761 } = tree_with_region(&mut app);
762 let tracker = tracker_for(&mut app, root);
763 let log = record_events(&mut app, region);
764 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
765 assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
766
767 padding.set_padding(
769 &mut app,
770 inset_painting::EdgeInsetsGeometry::only(150.0, 0.0, 0.0, 0.0),
771 );
772 owner.flush_layout(&mut app);
773 tracker.update_all_devices(&mut app);
774 assert_eq!(
775 *log.borrow(),
776 ["enter Offset(50.0, 50.0)", "exit Offset(50.0, 50.0)"],
777 "the exit is placed with the transform recorded when the region was entered"
778 );
779 }
780
781 #[test]
782 fn a_region_removed_from_the_tree_sends_no_exit() {
783 let cell = AppCell::new();
784 let mut app = cell.borrow_mut();
785 let RegionTree {
786 owner,
787 root,
788 padding,
789 region,
790 } = tree_with_region(&mut app);
791 let tracker = tracker_for(&mut app, root);
792 let log = record_events(&mut app, region);
793 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
794 assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
795
796 padding.set_child(&mut app, None);
797 owner.flush_layout(&mut app);
798 tracker.update_all_devices(&mut app);
799 assert_eq!(
800 *log.borrow(),
801 ["enter Offset(50.0, 50.0)"],
802 "Flutter: a detached region is no longer valid for the tracker, so no exit is sent"
803 );
804 }
805
806 #[test]
807 fn a_detached_region_is_invalid_for_the_tracker() {
808 let cell = AppCell::new();
809 let mut app = cell.borrow_mut();
810 let region = RenderMouseRegion::new(&mut app, true, None);
811 assert!(region.valid_for_mouse_tracker(&app));
812 let owner = PipelineOwner::new(&mut app, None, None);
813 region.as_object().attach(&mut app, owner);
814 assert!(region.valid_for_mouse_tracker(&app));
815 region.as_object().detach(&mut app);
816 assert!(!region.valid_for_mouse_tracker(&app));
817 assert!(
818 !region
819 .mouse_tracker_annotation(&app)
820 .unwrap()
821 .valid_for_mouse_tracker
822 );
823 }
824
825 #[test]
826 fn a_removed_mouse_exits_and_disconnects() {
827 let cell = AppCell::new();
828 let mut app = cell.borrow_mut();
829 let RegionTree { root, region, .. } = tree_with_region(&mut app);
830 let tracker = tracker_for(&mut app, root);
831 let log = record_events(&mut app, region);
832 let notifications = Rc::new(Cell::new(0));
833 let counted = Rc::clone(¬ifications);
834 tracker.add_listener(
835 &mut app,
836 Listener::new(move |_app| counted.set(counted.get() + 1)),
837 );
838
839 assert!(!tracker.mouse_is_connected(&app));
840 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
841 assert!(tracker.mouse_is_connected(&app));
842 assert_eq!(notifications.get(), 1);
843
844 tracker.update_with_event(&mut app, &mouse_removed(Offset::new(100.0, 100.0)), None);
845 assert!(!tracker.mouse_is_connected(&app));
846 assert_eq!(notifications.get(), 2);
847 assert_eq!(
848 *log.borrow(),
849 ["enter Offset(50.0, 50.0)", "exit Offset(50.0, 50.0)"]
850 );
851 }
852
853 #[test]
854 fn touch_and_signal_events_are_ignored() {
855 let cell = AppCell::new();
856 let mut app = cell.borrow_mut();
857 let RegionTree { root, region, .. } = tree_with_region(&mut app);
858 let tracker = tracker_for(&mut app, root);
859 let log = record_events(&mut app, region);
860
861 tracker.update_with_event(
862 &mut app,
863 &PointerEvent::Hover(PointerHoverEvent {
864 position: Offset::new(100.0, 100.0),
865 ..PointerHoverEvent::default()
866 }),
867 None,
868 );
869 assert!(!tracker.mouse_is_connected(&app), "touch: not tracked");
870
871 tracker.update_with_event(
872 &mut app,
873 &PointerEvent::Scroll(PointerScrollEvent {
874 kind: PointerDeviceKind::Mouse,
875 position: Offset::new(100.0, 100.0),
876 ..PointerScrollEvent::default()
877 }),
878 None,
879 );
880 assert!(!tracker.mouse_is_connected(&app), "a signal: not tracked");
881 assert!(log.borrow().is_empty());
882 }
883
884 #[test]
885 fn the_front_regions_cursor_wins_and_deferring_regions_let_the_one_behind_decide() {
886 let (cell, cursor) = app_with_cursor_host();
887 let mut app = cell.borrow_mut();
888 let RegionTree {
889 owner,
890 root,
891 region,
892 ..
893 } = tree_with_region(&mut app);
894 region.set_cursor(&mut app, SystemMouseCursors::TEXT.into());
895 let front = RenderMouseRegion::new(&mut app, true, None);
897 region.set_child(&mut app, Some(front.as_box()));
898 owner.flush_layout(&mut app);
899 let tracker = tracker_for(&mut app, root);
900
901 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
902 assert_eq!(
903 *cursor.activated.borrow(),
904 [(ViewId(0), 0, SystemMouseCursorKind::Text)]
905 );
906
907 front.set_cursor(&mut app, SystemMouseCursors::CLICK.into());
908 tracker.update_all_devices(&mut app);
909 assert_eq!(
910 *cursor.activated.borrow(),
911 [
912 (ViewId(0), 0, SystemMouseCursorKind::Text),
913 (ViewId(0), 0, SystemMouseCursorKind::Click)
914 ]
915 );
916 let click: MouseCursorRef = SystemMouseCursors::CLICK.into();
917 assert!(*tracker.debug_device_active_cursor(&app, 0).unwrap() == *click);
918
919 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(10.0, 10.0)), None);
920 assert_eq!(
921 cursor.activated.borrow().last().unwrap().2,
922 SystemMouseCursorKind::Basic,
923 "over nothing, the fallback cursor"
924 );
925 }
926
927 #[test]
928 fn the_cursor_goes_to_the_view_the_pointer_was_last_seen_on() {
929 let (cell, cursor) = app_with_cursor_host();
930 let mut app = cell.borrow_mut();
931 let RegionTree { root, region, .. } = tree_with_region(&mut app);
932 region.set_cursor(&mut app, SystemMouseCursors::TEXT.into());
933 let tracker = tracker_for(&mut app, root);
934
935 let added = PointerEvent::Added(PointerAddedEvent {
936 kind: PointerDeviceKind::Mouse,
937 position: Offset::new(100.0, 100.0),
938 view_id: ViewId(2),
939 ..PointerAddedEvent::default()
940 });
941 tracker.update_with_event(&mut app, &added, None);
942 assert_eq!(
943 *cursor.activated.borrow(),
944 [(ViewId(2), 0, SystemMouseCursorKind::Text)]
945 );
946
947 region.set_cursor(&mut app, SystemMouseCursors::CLICK.into());
948 tracker.update_all_devices(&mut app);
949 assert_eq!(
950 cursor.activated.borrow().last(),
951 Some(&(ViewId(2), 0, SystemMouseCursorKind::Click)),
952 "a frame-triggered update has no event of its own; the device's last one names the view"
953 );
954 }
955
956 #[test]
957 fn an_opaque_region_hides_the_region_behind_it_and_a_transparent_one_does_not() {
958 let cell = AppCell::new();
959 let mut app = cell.borrow_mut();
960 let back = RenderMouseRegion::new(&mut app, true, None);
961 let front = RenderMouseRegion::new(&mut app, true, Some(back.as_box()));
962 let root = RenderRepaintBoundary::new(&mut app, Some(front.as_box()));
963 let owner = PipelineOwner::new(&mut app, None, None);
964 owner.set_root_node(&mut app, Some(root.as_object()));
965 root.schedule_initial_layout(&mut app);
966 root.layout(
967 &mut app,
968 BoxConstraints::tight(Size::new(100.0, 100.0)),
969 false,
970 );
971 let tracker = tracker_for(&mut app, root);
972 let back_log = record_events(&mut app, back);
973 let front_log = record_events(&mut app, front);
974
975 tracker.update_with_event(&mut app, &mouse_added(Offset::new(10.0, 10.0)), None);
976 assert_eq!(*front_log.borrow(), ["enter Offset(10.0, 10.0)"]);
977 assert_eq!(
978 *back_log.borrow(),
979 ["enter Offset(10.0, 10.0)"],
980 "the front region's hit test still visits its child; opacity only stops what is behind the region in the parent's hit test"
981 );
982 }
983
984 #[test]
985 fn hover_reaches_the_region_through_handle_event() {
986 let cell = AppCell::new();
987 let mut app = cell.borrow_mut();
988 let region = RenderMouseRegion::new(&mut app, true, None);
989 let log = record_events(&mut app, region);
990 let event = mouse_hover(Offset::new(3.0, 4.0));
991 let entry = BoxHitTestEntry::new(region.as_box(), Offset::new(3.0, 4.0));
992 region.handle_event(&mut app, &event, &entry);
993 assert_eq!(*log.borrow(), ["hover Offset(3.0, 4.0)"]);
994 }
995
996 #[test]
997 fn an_opaque_region_absorbs_the_hit_test_for_what_is_behind_it() {
998 let cell = AppCell::new();
999 let mut app = cell.borrow_mut();
1000 let region = RenderMouseRegion::new(&mut app, true, None);
1001 let root = RenderRepaintBoundary::new(&mut app, Some(region.as_box()));
1002 let owner = PipelineOwner::new(&mut app, None, None);
1003 owner.set_root_node(&mut app, Some(root.as_object()));
1004 root.schedule_initial_layout(&mut app);
1005 root.layout(
1006 &mut app,
1007 BoxConstraints::tight(Size::new(100.0, 100.0)),
1008 false,
1009 );
1010
1011 let mut result = HitTestResult::new();
1012 assert!(region.hit_test(
1013 &mut app,
1014 &mut BoxHitTestResult::wrap(&mut result),
1015 Offset::new(5.0, 5.0)
1016 ));
1017 region.set_opaque(&mut app, false);
1018 let mut result = HitTestResult::new();
1019 assert!(!region.hit_test(
1020 &mut app,
1021 &mut BoxHitTestResult::wrap(&mut result),
1022 Offset::new(5.0, 5.0)
1023 ));
1024 assert_eq!(
1025 result.path().len(),
1026 1,
1027 "the region is still on the path; it just does not claim the hit"
1028 );
1029 }
1030
1031 #[test]
1032 fn a_region_with_no_annotation_neighbours_still_uses_the_fallback_cursor() {
1033 let (cell, cursor) = app_with_cursor_host();
1034 let mut app = cell.borrow_mut();
1035 let child =
1036 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
1037 let root = RenderRepaintBoundary::new(&mut app, Some(child.as_box()));
1038 let owner = PipelineOwner::new(&mut app, None, None);
1039 owner.set_root_node(&mut app, Some(root.as_object()));
1040 root.schedule_initial_layout(&mut app);
1041 root.layout(
1042 &mut app,
1043 BoxConstraints::tight(Size::new(100.0, 100.0)),
1044 false,
1045 );
1046 let tracker = tracker_for(&mut app, root);
1047 tracker.update_with_event(&mut app, &mouse_added(Offset::new(5.0, 5.0)), None);
1048 assert_eq!(
1049 *cursor.activated.borrow(),
1050 [(ViewId(0), 0, SystemMouseCursorKind::Basic)]
1051 );
1052 }
1053}