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.device(),
325 details.triggering_event.as_ref(),
326 cursors,
327 );
328 }
329
330 pub fn mouse_is_connected(self: Handle<Self>, app: &App) -> bool {
332 !app.get(self).mouse_states.is_empty()
333 }
334
335 pub fn update_with_event(
354 self: Handle<Self>,
355 app: &mut App,
356 event: &PointerEvent,
357 hit_test_result: Option<&HitTestResult>,
358 ) {
359 if event.kind() != PointerDeviceKind::Mouse && event.kind() != PointerDeviceKind::Stylus {
360 return;
362 }
363 if is_signal_event(event) {
364 return;
366 }
367 let is_removed = matches!(event, PointerEvent::Removed(_));
368 let owned_result;
369 let result: Option<&HitTestResult> = if is_removed {
370 None
371 } else if let Some(result) = hit_test_result {
372 Some(result)
373 } else {
374 owned_result = self.run_hit_test(app, event.position(), event.view_id());
375 Some(&owned_result)
376 };
377 let device = event.device();
378 if !MouseTracker::should_mark_state_dirty(app.get(self).mouse_states.get(&device), event) {
379 return;
380 }
381
382 self.monitor_mouse_connection(app, |app| {
383 self.device_update_phase(app, |app| {
384 if !app.get(self).mouse_states.contains_key(&device) {
386 if is_removed {
387 return;
388 }
389 app.get_mut(self)
390 .mouse_states
391 .insert(device, MouseState::new(event.clone()));
392 } else {
393 debug_assert!(!matches!(event, PointerEvent::Added(_)));
394 }
395 let next_annotations = match result {
396 Some(result) => {
397 MouseTracker::hit_test_in_view_result_to_annotations(app, result)
398 }
399 None => Annotations::new(),
400 };
401 let target_state = app
402 .get_mut(self)
403 .mouse_states
404 .get_mut(&device)
405 .expect("the state was just found or created");
406 let last_event = target_state.replace_latest_event(event.clone());
407 let retained = MouseTracker::retain_annotations(app, &next_annotations);
408 let target_state = app
409 .get_mut(self)
410 .mouse_states
411 .get_mut(&device)
412 .expect("the state was just found or created");
413 let (last_annotations, last_retained) =
414 target_state.replace_annotations(next_annotations.clone(), retained);
415 if is_removed {
416 app.get_mut(self).mouse_states.shift_remove(&device);
417 }
418 self.handle_device_update(
419 app,
420 MouseTrackerUpdateDetails::by_pointer_event(
421 last_annotations,
422 next_annotations,
423 Some(last_event),
424 event.clone(),
425 ),
426 );
427 MouseTracker::release_annotations(app, last_retained);
428 });
429 });
430 }
431
432 pub fn update_all_devices(self: Handle<Self>, app: &mut App) {
442 self.device_update_phase(app, |app| {
443 let devices: Vec<i64> = app.get(self).mouse_states.keys().copied().collect();
444 for device in devices {
445 let Some(last_event) = app
446 .get(self)
447 .mouse_states
448 .get(&device)
449 .map(|state| state.latest_event.clone())
450 else {
451 continue;
452 };
453 let next_annotations = self.find_annotations(app, &last_event);
454 if !app.get(self).mouse_states.contains_key(&device) {
455 continue;
456 }
457 let retained = MouseTracker::retain_annotations(app, &next_annotations);
458 let Some(dirty_state) = app.get_mut(self).mouse_states.get_mut(&device) else {
459 continue;
460 };
461 let (last_annotations, last_retained) =
462 dirty_state.replace_annotations(next_annotations.clone(), retained);
463 self.handle_device_update(
464 app,
465 MouseTrackerUpdateDetails::by_new_frame(
466 last_annotations,
467 next_annotations,
468 last_event,
469 ),
470 );
471 MouseTracker::release_annotations(app, last_retained);
472 }
473 });
474 }
475
476 pub fn debug_device_active_cursor(
483 self: Handle<Self>,
484 app: &App,
485 device: i64,
486 ) -> Option<MouseCursorRef> {
487 app.get(self)
488 .mouse_cursor_mixin
489 .debug_device_active_cursor(device)
490 }
491
492 fn monitor_mouse_connection(self: Handle<Self>, app: &mut App, task: impl FnOnce(&mut App)) {
497 let mouse_was_connected = self.mouse_is_connected(app);
498 task(app);
499 if mouse_was_connected != self.mouse_is_connected(app) {
500 self.notify_listeners(app);
501 }
502 }
503
504 fn device_update_phase(self: Handle<Self>, app: &mut App, task: impl FnOnce(&mut App)) {
509 debug_assert!(!app.get(self).debug_during_device_update);
510 if cfg!(debug_assertions) {
511 app.get_mut(self).debug_during_device_update = true;
512 }
513 task(app);
514 if cfg!(debug_assertions) {
515 app.get_mut(self).debug_during_device_update = false;
516 }
517 }
518
519 fn handle_device_update_mouse_events(app: &mut App, details: &MouseTrackerUpdateDetails) {
521 let latest_event = details.latest_event();
522 let last_annotations = &details.last_annotations;
523 let next_annotations = &details.next_annotations;
524
525 let base_exit_event = PointerExitEvent::from_mouse_event(latest_event);
533 for (target, transform) in last_annotations {
534 if next_annotations.contains_key(target) {
535 continue;
536 }
537 let Some(annotation) = target.mouse_tracker_annotation(app) else {
538 continue;
539 };
540 if annotation.valid_for_mouse_tracker
541 && let Some(on_exit) = annotation.on_exit
542 {
543 on_exit(app, base_exit_event.transformed(Some(*transform)));
544 }
545 }
546
547 let entering_annotations: Vec<(AnyRenderObject, Matrix4)> = next_annotations
550 .iter()
551 .filter(|(target, _)| !last_annotations.contains_key(*target))
552 .map(|(target, transform)| (*target, *transform))
553 .collect();
554 let base_enter_event = PointerEnterEvent::from_mouse_event(latest_event);
555 for (target, transform) in entering_annotations.into_iter().rev() {
556 let Some(annotation) = target.mouse_tracker_annotation(app) else {
557 continue;
558 };
559 if annotation.valid_for_mouse_tracker
560 && let Some(on_enter) = annotation.on_enter
561 {
562 on_enter(app, base_enter_event.transformed(Some(transform)));
563 }
564 }
565 }
566}
567
568fn is_signal_event(event: &PointerEvent) -> bool {
570 matches!(
571 event,
572 PointerEvent::Scroll(_) | PointerEvent::ScrollInertiaCancel(_) | PointerEvent::Scale(_)
573 )
574}
575
576fn annotation_target(app: &App, entry: &HitTestEntry) -> Option<AnyRenderObject> {
579 let target: &dyn Any = entry.target();
580 let target = target
581 .downcast_ref::<BoxHitTestEntry>()?
582 .target()
583 .as_object();
584 target.mouse_tracker_annotation(app).map(|_| target)
585}
586
587#[cfg(test)]
588mod tests {
589 use inset_foundation::AppCell;
590 use std::cell::{Cell, RefCell};
591
592 use inset_embedder::{
593 InertPlatform, Platform, PlatformRef, Size, SystemMouseCursorKind, TargetPlatform, ViewRef,
594 };
595 use inset_gestures::{
596 PointerAddedEvent, PointerHoverEvent, PointerRemovedEvent, PointerScrollEvent,
597 };
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 struct CursorRecordingPlatform {
611 activated: RefCell<Vec<(i64, SystemMouseCursorKind)>>,
612 }
613
614 impl Platform for CursorRecordingPlatform {
615 fn target_platform(&self) -> TargetPlatform {
616 InertPlatform.target_platform()
617 }
618
619 fn request_frame(&self) {}
620
621 fn now(&self) -> std::time::Instant {
622 InertPlatform.now()
623 }
624
625 fn wake_at(&self, _deadline: std::time::Instant) {}
626
627 fn views(&self) -> Vec<ViewRef> {
628 Vec::new()
629 }
630
631 fn view(&self, _id: ViewId) -> Option<ViewRef> {
632 None
633 }
634
635 fn implicit_view(&self) -> Option<ViewRef> {
636 None
637 }
638
639 fn activate_system_cursor(&self, device: i64, kind: SystemMouseCursorKind) {
640 self.activated.borrow_mut().push((device, kind));
641 }
642 }
643
644 fn app_with_cursor_host() -> (Rc<AppCell>, Rc<CursorRecordingPlatform>) {
645 let platform = Rc::new(CursorRecordingPlatform {
646 activated: RefCell::new(Vec::new()),
647 });
648 let platform_ref: PlatformRef = Rc::clone(&platform) as PlatformRef;
649 (AppCell::with_platform(platform_ref), platform)
650 }
651
652 struct RegionTree {
655 owner: Handle<PipelineOwner>,
656 root: crate::RenderHandle<RenderRepaintBoundary>,
657 padding: crate::RenderHandle<RenderPadding>,
658 region: crate::RenderHandle<RenderMouseRegion>,
659 }
660
661 fn tree_with_region(app: &mut App) -> RegionTree {
662 let region = RenderMouseRegion::new(app, true, None);
663 let padding = RenderPadding::new(
664 app,
665 inset_painting::EdgeInsets::all(50.0).into(),
666 None,
667 Some(region.as_box()),
668 );
669 let root = RenderRepaintBoundary::new(app, Some(padding.as_box()));
670 let owner = PipelineOwner::new(app, None);
671 owner.set_root_node(app, Some(root.as_object()));
672 root.schedule_initial_layout(app);
673 root.layout(app, BoxConstraints::tight(Size::new(200.0, 200.0)), false);
674 RegionTree {
675 owner,
676 root,
677 padding,
678 region,
679 }
680 }
681
682 fn tracker_for(
683 app: &mut App,
684 root: crate::RenderHandle<RenderRepaintBoundary>,
685 ) -> Handle<MouseTracker> {
686 MouseTracker::new(
687 app,
688 Rc::new(move |app: &mut App, position: Offset, _view_id: ViewId| {
689 let mut result = HitTestResult::new();
690 root.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
691 result
692 }),
693 )
694 }
695
696 fn mouse_hover(position: Offset) -> PointerEvent {
697 PointerEvent::Hover(PointerHoverEvent {
698 kind: PointerDeviceKind::Mouse,
699 position,
700 ..PointerHoverEvent::default()
701 })
702 }
703
704 fn mouse_added(position: Offset) -> PointerEvent {
705 PointerEvent::Added(PointerAddedEvent {
706 kind: PointerDeviceKind::Mouse,
707 position,
708 ..PointerAddedEvent::default()
709 })
710 }
711
712 fn mouse_removed(position: Offset) -> PointerEvent {
713 PointerEvent::Removed(PointerRemovedEvent {
714 kind: PointerDeviceKind::Mouse,
715 position,
716 ..PointerRemovedEvent::default()
717 })
718 }
719
720 fn record_events(
722 app: &mut App,
723 region: crate::RenderHandle<RenderMouseRegion>,
724 ) -> Rc<RefCell<Vec<String>>> {
725 let log = Rc::new(RefCell::new(Vec::new()));
726 let enter_log = Rc::clone(&log);
727 region.set_on_enter(
728 app,
729 Some(Rc::new(move |_app: &mut App, event: PointerEnterEvent| {
730 enter_log
731 .borrow_mut()
732 .push(format!("enter {:?}", event.local_position()));
733 })),
734 );
735 let hover_log = Rc::clone(&log);
736 region.set_on_hover(
737 app,
738 Some(Rc::new(move |_app: &mut App, event: PointerHoverEvent| {
739 hover_log
740 .borrow_mut()
741 .push(format!("hover {:?}", event.local_position()));
742 })),
743 );
744 let exit_log = Rc::clone(&log);
745 region.set_on_exit(
746 app,
747 Some(Rc::new(move |_app: &mut App, event: PointerExitEvent| {
748 exit_log
749 .borrow_mut()
750 .push(format!("exit {:?}", event.local_position()));
751 })),
752 );
753 log
754 }
755
756 #[test]
757 fn entering_and_leaving_a_region_sends_enter_then_exit_in_local_coordinates() {
758 let cell = AppCell::new();
759 let mut app = cell.borrow_mut();
760 let RegionTree { root, region, .. } = tree_with_region(&mut app);
761 let tracker = tracker_for(&mut app, root);
762 let log = record_events(&mut app, region);
763
764 tracker.update_with_event(&mut app, &mouse_added(Offset::new(10.0, 10.0)), None);
765 assert!(log.borrow().is_empty(), "outside the region: nothing");
766
767 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(60.0, 70.0)), None);
768 assert_eq!(*log.borrow(), ["enter Offset(10.0, 20.0)"]);
769
770 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(160.0, 70.0)), None);
771 assert_eq!(
772 *log.borrow(),
773 ["enter Offset(10.0, 20.0)", "exit Offset(110.0, 20.0)"]
774 );
775 }
776
777 #[test]
778 fn a_still_mouse_notices_the_tree_moving_under_it_after_a_frame() {
779 let cell = AppCell::new();
780 let mut app = cell.borrow_mut();
781 let RegionTree {
782 owner,
783 root,
784 padding,
785 region,
786 } = tree_with_region(&mut app);
787 let tracker = tracker_for(&mut app, root);
788 let log = record_events(&mut app, region);
789 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
790 assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
791
792 padding.set_padding(
794 &mut app,
795 inset_painting::EdgeInsetsGeometry::only(150.0, 0.0, 0.0, 0.0),
796 );
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)", "exit Offset(50.0, 50.0)"],
802 "the exit is placed with the transform recorded when the region was entered"
803 );
804 }
805
806 #[test]
807 fn a_region_removed_from_the_tree_sends_no_exit() {
808 let cell = AppCell::new();
809 let mut app = cell.borrow_mut();
810 let RegionTree {
811 owner,
812 root,
813 padding,
814 region,
815 } = tree_with_region(&mut app);
816 let tracker = tracker_for(&mut app, root);
817 let log = record_events(&mut app, region);
818 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
819 assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
820
821 padding.set_child(&mut app, None);
822 owner.flush_layout(&mut app);
823 tracker.update_all_devices(&mut app);
824 assert_eq!(
825 *log.borrow(),
826 ["enter Offset(50.0, 50.0)"],
827 "Flutter: a detached region is no longer valid for the tracker, so no exit is sent"
828 );
829 }
830
831 #[test]
832 fn a_detached_region_is_invalid_for_the_tracker() {
833 let cell = AppCell::new();
834 let mut app = cell.borrow_mut();
835 let region = RenderMouseRegion::new(&mut app, true, None);
836 assert!(region.valid_for_mouse_tracker(&app));
837 let owner = PipelineOwner::new(&mut app, None);
838 region.as_object().attach(&mut app, owner);
839 assert!(region.valid_for_mouse_tracker(&app));
840 region.as_object().detach(&mut app);
841 assert!(!region.valid_for_mouse_tracker(&app));
842 assert!(
843 !region
844 .mouse_tracker_annotation(&app)
845 .unwrap()
846 .valid_for_mouse_tracker
847 );
848 }
849
850 #[test]
851 fn a_removed_mouse_exits_and_disconnects() {
852 let cell = AppCell::new();
853 let mut app = cell.borrow_mut();
854 let RegionTree { root, region, .. } = tree_with_region(&mut app);
855 let tracker = tracker_for(&mut app, root);
856 let log = record_events(&mut app, region);
857 let notifications = Rc::new(Cell::new(0));
858 let counted = Rc::clone(¬ifications);
859 tracker.add_listener(
860 &mut app,
861 Listener::new(move |_app| counted.set(counted.get() + 1)),
862 );
863
864 assert!(!tracker.mouse_is_connected(&app));
865 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
866 assert!(tracker.mouse_is_connected(&app));
867 assert_eq!(notifications.get(), 1);
868
869 tracker.update_with_event(&mut app, &mouse_removed(Offset::new(100.0, 100.0)), None);
870 assert!(!tracker.mouse_is_connected(&app));
871 assert_eq!(notifications.get(), 2);
872 assert_eq!(
873 *log.borrow(),
874 ["enter Offset(50.0, 50.0)", "exit Offset(50.0, 50.0)"]
875 );
876 }
877
878 #[test]
879 fn touch_and_signal_events_are_ignored() {
880 let cell = AppCell::new();
881 let mut app = cell.borrow_mut();
882 let RegionTree { root, region, .. } = tree_with_region(&mut app);
883 let tracker = tracker_for(&mut app, root);
884 let log = record_events(&mut app, region);
885
886 tracker.update_with_event(
887 &mut app,
888 &PointerEvent::Hover(PointerHoverEvent {
889 position: Offset::new(100.0, 100.0),
890 ..PointerHoverEvent::default()
891 }),
892 None,
893 );
894 assert!(!tracker.mouse_is_connected(&app), "touch: not tracked");
895
896 tracker.update_with_event(
897 &mut app,
898 &PointerEvent::Scroll(PointerScrollEvent {
899 kind: PointerDeviceKind::Mouse,
900 position: Offset::new(100.0, 100.0),
901 ..PointerScrollEvent::default()
902 }),
903 None,
904 );
905 assert!(!tracker.mouse_is_connected(&app), "a signal: not tracked");
906 assert!(log.borrow().is_empty());
907 }
908
909 #[test]
910 fn the_front_regions_cursor_wins_and_deferring_regions_let_the_one_behind_decide() {
911 let (cell, platform) = app_with_cursor_host();
912 let mut app = cell.borrow_mut();
913 let RegionTree {
914 owner,
915 root,
916 region,
917 ..
918 } = tree_with_region(&mut app);
919 region.set_cursor(&mut app, SystemMouseCursors::TEXT.into());
920 let front = RenderMouseRegion::new(&mut app, true, None);
922 region.set_child(&mut app, Some(front.as_box()));
923 owner.flush_layout(&mut app);
924 let tracker = tracker_for(&mut app, root);
925
926 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
927 assert_eq!(
928 *platform.activated.borrow(),
929 [(0, SystemMouseCursorKind::Text)]
930 );
931
932 front.set_cursor(&mut app, SystemMouseCursors::CLICK.into());
933 tracker.update_all_devices(&mut app);
934 assert_eq!(
935 *platform.activated.borrow(),
936 [
937 (0, SystemMouseCursorKind::Text),
938 (0, SystemMouseCursorKind::Click)
939 ]
940 );
941 let click: MouseCursorRef = SystemMouseCursors::CLICK.into();
942 assert!(*tracker.debug_device_active_cursor(&app, 0).unwrap() == *click);
943
944 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(10.0, 10.0)), None);
945 assert_eq!(
946 platform.activated.borrow().last().unwrap().1,
947 SystemMouseCursorKind::Basic,
948 "over nothing, the fallback cursor"
949 );
950 }
951
952 #[test]
953 fn an_opaque_region_hides_the_region_behind_it_and_a_transparent_one_does_not() {
954 let cell = AppCell::new();
955 let mut app = cell.borrow_mut();
956 let back = RenderMouseRegion::new(&mut app, true, None);
957 let front = RenderMouseRegion::new(&mut app, true, Some(back.as_box()));
958 let root = RenderRepaintBoundary::new(&mut app, Some(front.as_box()));
959 let owner = PipelineOwner::new(&mut app, None);
960 owner.set_root_node(&mut app, Some(root.as_object()));
961 root.schedule_initial_layout(&mut app);
962 root.layout(
963 &mut app,
964 BoxConstraints::tight(Size::new(100.0, 100.0)),
965 false,
966 );
967 let tracker = tracker_for(&mut app, root);
968 let back_log = record_events(&mut app, back);
969 let front_log = record_events(&mut app, front);
970
971 tracker.update_with_event(&mut app, &mouse_added(Offset::new(10.0, 10.0)), None);
972 assert_eq!(*front_log.borrow(), ["enter Offset(10.0, 10.0)"]);
973 assert_eq!(
974 *back_log.borrow(),
975 ["enter Offset(10.0, 10.0)"],
976 "the front region's hit test still visits its child; opacity only stops what is behind the region in the parent's hit test"
977 );
978 }
979
980 #[test]
981 fn hover_reaches_the_region_through_handle_event() {
982 let cell = AppCell::new();
983 let mut app = cell.borrow_mut();
984 let region = RenderMouseRegion::new(&mut app, true, None);
985 let log = record_events(&mut app, region);
986 let event = mouse_hover(Offset::new(3.0, 4.0));
987 let entry = BoxHitTestEntry::new(region.as_box(), Offset::new(3.0, 4.0));
988 region.handle_event(&mut app, &event, &entry);
989 assert_eq!(*log.borrow(), ["hover Offset(3.0, 4.0)"]);
990 }
991
992 #[test]
993 fn an_opaque_region_absorbs_the_hit_test_for_what_is_behind_it() {
994 let cell = AppCell::new();
995 let mut app = cell.borrow_mut();
996 let region = RenderMouseRegion::new(&mut app, true, None);
997 let root = RenderRepaintBoundary::new(&mut app, Some(region.as_box()));
998 let owner = PipelineOwner::new(&mut app, None);
999 owner.set_root_node(&mut app, Some(root.as_object()));
1000 root.schedule_initial_layout(&mut app);
1001 root.layout(
1002 &mut app,
1003 BoxConstraints::tight(Size::new(100.0, 100.0)),
1004 false,
1005 );
1006
1007 let mut result = HitTestResult::new();
1008 assert!(region.hit_test(
1009 &mut app,
1010 &mut BoxHitTestResult::wrap(&mut result),
1011 Offset::new(5.0, 5.0)
1012 ));
1013 region.set_opaque(&mut app, false);
1014 let mut result = HitTestResult::new();
1015 assert!(!region.hit_test(
1016 &mut app,
1017 &mut BoxHitTestResult::wrap(&mut result),
1018 Offset::new(5.0, 5.0)
1019 ));
1020 assert_eq!(
1021 result.path().len(),
1022 1,
1023 "the region is still on the path; it just does not claim the hit"
1024 );
1025 }
1026
1027 #[test]
1028 fn a_region_with_no_annotation_neighbours_still_uses_the_fallback_cursor() {
1029 let (cell, platform) = app_with_cursor_host();
1030 let mut app = cell.borrow_mut();
1031 let child =
1032 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
1033 let root = RenderRepaintBoundary::new(&mut app, Some(child.as_box()));
1034 let owner = PipelineOwner::new(&mut app, None);
1035 owner.set_root_node(&mut app, Some(root.as_object()));
1036 root.schedule_initial_layout(&mut app);
1037 root.layout(
1038 &mut app,
1039 BoxConstraints::tight(Size::new(100.0, 100.0)),
1040 false,
1041 );
1042 let tracker = tracker_for(&mut app, root);
1043 tracker.update_with_event(&mut app, &mouse_added(Offset::new(5.0, 5.0)), None);
1044 assert_eq!(
1045 *platform.activated.borrow(),
1046 [(0, SystemMouseCursorKind::Basic)]
1047 );
1048 }
1049}