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::{MouseCursor, Size, SystemMouseCursorKind};
593 use inset_gestures::{
594 PointerAddedEvent, PointerHoverEvent, PointerRemovedEvent, PointerScrollEvent,
595 };
596 use inset_test::TestPlatform;
597
598 use inset_foundation::{Listenable, Listener};
599
600 use super::*;
601 use crate::box_::{BoxConstraints, BoxHitTestResult, RenderBox};
602 use crate::object::RenderObject;
603 use crate::object::RenderObjectWithChildMixin;
604 use crate::pipeline_owner::PipelineOwner;
605 use crate::proxy_box::{RenderConstrainedBox, RenderMouseRegion, RenderRepaintBoundary};
606 use crate::shifted_box::RenderPadding;
607
608 #[derive(Default)]
610 struct RecordingCursor {
611 activated: RefCell<Vec<(i64, SystemMouseCursorKind)>>,
612 }
613
614 impl MouseCursor for RecordingCursor {
615 fn activate_system_cursor(&self, device: i64, kind: SystemMouseCursorKind) {
616 self.activated.borrow_mut().push((device, kind));
617 }
618 }
619
620 fn app_with_cursor_host() -> (Rc<AppCell>, Rc<RecordingCursor>) {
621 let cursor = Rc::new(RecordingCursor::default());
622 let platform = TestPlatform::new().with_mouse_cursor(cursor.clone());
623 (AppCell::with_platform(Rc::new(platform)), cursor)
624 }
625
626 struct RegionTree {
629 owner: Handle<PipelineOwner>,
630 root: crate::RenderHandle<RenderRepaintBoundary>,
631 padding: crate::RenderHandle<RenderPadding>,
632 region: crate::RenderHandle<RenderMouseRegion>,
633 }
634
635 fn tree_with_region(app: &mut App) -> RegionTree {
636 let region = RenderMouseRegion::new(app, true, None);
637 let padding = RenderPadding::new(
638 app,
639 inset_painting::EdgeInsets::all(50.0).into(),
640 None,
641 Some(region.as_box()),
642 );
643 let root = RenderRepaintBoundary::new(app, Some(padding.as_box()));
644 let owner = PipelineOwner::new(app, None);
645 owner.set_root_node(app, Some(root.as_object()));
646 root.schedule_initial_layout(app);
647 root.layout(app, BoxConstraints::tight(Size::new(200.0, 200.0)), false);
648 RegionTree {
649 owner,
650 root,
651 padding,
652 region,
653 }
654 }
655
656 fn tracker_for(
657 app: &mut App,
658 root: crate::RenderHandle<RenderRepaintBoundary>,
659 ) -> Handle<MouseTracker> {
660 MouseTracker::new(
661 app,
662 Rc::new(move |app: &mut App, position: Offset, _view_id: ViewId| {
663 let mut result = HitTestResult::new();
664 root.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
665 result
666 }),
667 )
668 }
669
670 fn mouse_hover(position: Offset) -> PointerEvent {
671 PointerEvent::Hover(PointerHoverEvent {
672 kind: PointerDeviceKind::Mouse,
673 position,
674 ..PointerHoverEvent::default()
675 })
676 }
677
678 fn mouse_added(position: Offset) -> PointerEvent {
679 PointerEvent::Added(PointerAddedEvent {
680 kind: PointerDeviceKind::Mouse,
681 position,
682 ..PointerAddedEvent::default()
683 })
684 }
685
686 fn mouse_removed(position: Offset) -> PointerEvent {
687 PointerEvent::Removed(PointerRemovedEvent {
688 kind: PointerDeviceKind::Mouse,
689 position,
690 ..PointerRemovedEvent::default()
691 })
692 }
693
694 fn record_events(
696 app: &mut App,
697 region: crate::RenderHandle<RenderMouseRegion>,
698 ) -> Rc<RefCell<Vec<String>>> {
699 let log = Rc::new(RefCell::new(Vec::new()));
700 let enter_log = Rc::clone(&log);
701 region.set_on_enter(
702 app,
703 Some(Rc::new(move |_app: &mut App, event: PointerEnterEvent| {
704 enter_log
705 .borrow_mut()
706 .push(format!("enter {:?}", event.local_position()));
707 })),
708 );
709 let hover_log = Rc::clone(&log);
710 region.set_on_hover(
711 app,
712 Some(Rc::new(move |_app: &mut App, event: PointerHoverEvent| {
713 hover_log
714 .borrow_mut()
715 .push(format!("hover {:?}", event.local_position()));
716 })),
717 );
718 let exit_log = Rc::clone(&log);
719 region.set_on_exit(
720 app,
721 Some(Rc::new(move |_app: &mut App, event: PointerExitEvent| {
722 exit_log
723 .borrow_mut()
724 .push(format!("exit {:?}", event.local_position()));
725 })),
726 );
727 log
728 }
729
730 #[test]
731 fn entering_and_leaving_a_region_sends_enter_then_exit_in_local_coordinates() {
732 let cell = AppCell::new();
733 let mut app = cell.borrow_mut();
734 let RegionTree { root, region, .. } = tree_with_region(&mut app);
735 let tracker = tracker_for(&mut app, root);
736 let log = record_events(&mut app, region);
737
738 tracker.update_with_event(&mut app, &mouse_added(Offset::new(10.0, 10.0)), None);
739 assert!(log.borrow().is_empty(), "outside the region: nothing");
740
741 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(60.0, 70.0)), None);
742 assert_eq!(*log.borrow(), ["enter Offset(10.0, 20.0)"]);
743
744 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(160.0, 70.0)), None);
745 assert_eq!(
746 *log.borrow(),
747 ["enter Offset(10.0, 20.0)", "exit Offset(110.0, 20.0)"]
748 );
749 }
750
751 #[test]
752 fn a_still_mouse_notices_the_tree_moving_under_it_after_a_frame() {
753 let cell = AppCell::new();
754 let mut app = cell.borrow_mut();
755 let RegionTree {
756 owner,
757 root,
758 padding,
759 region,
760 } = tree_with_region(&mut app);
761 let tracker = tracker_for(&mut app, root);
762 let log = record_events(&mut app, region);
763 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
764 assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
765
766 padding.set_padding(
768 &mut app,
769 inset_painting::EdgeInsetsGeometry::only(150.0, 0.0, 0.0, 0.0),
770 );
771 owner.flush_layout(&mut app);
772 tracker.update_all_devices(&mut app);
773 assert_eq!(
774 *log.borrow(),
775 ["enter Offset(50.0, 50.0)", "exit Offset(50.0, 50.0)"],
776 "the exit is placed with the transform recorded when the region was entered"
777 );
778 }
779
780 #[test]
781 fn a_region_removed_from_the_tree_sends_no_exit() {
782 let cell = AppCell::new();
783 let mut app = cell.borrow_mut();
784 let RegionTree {
785 owner,
786 root,
787 padding,
788 region,
789 } = tree_with_region(&mut app);
790 let tracker = tracker_for(&mut app, root);
791 let log = record_events(&mut app, region);
792 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
793 assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
794
795 padding.set_child(&mut app, None);
796 owner.flush_layout(&mut app);
797 tracker.update_all_devices(&mut app);
798 assert_eq!(
799 *log.borrow(),
800 ["enter Offset(50.0, 50.0)"],
801 "Flutter: a detached region is no longer valid for the tracker, so no exit is sent"
802 );
803 }
804
805 #[test]
806 fn a_detached_region_is_invalid_for_the_tracker() {
807 let cell = AppCell::new();
808 let mut app = cell.borrow_mut();
809 let region = RenderMouseRegion::new(&mut app, true, None);
810 assert!(region.valid_for_mouse_tracker(&app));
811 let owner = PipelineOwner::new(&mut app, None);
812 region.as_object().attach(&mut app, owner);
813 assert!(region.valid_for_mouse_tracker(&app));
814 region.as_object().detach(&mut app);
815 assert!(!region.valid_for_mouse_tracker(&app));
816 assert!(
817 !region
818 .mouse_tracker_annotation(&app)
819 .unwrap()
820 .valid_for_mouse_tracker
821 );
822 }
823
824 #[test]
825 fn a_removed_mouse_exits_and_disconnects() {
826 let cell = AppCell::new();
827 let mut app = cell.borrow_mut();
828 let RegionTree { root, region, .. } = tree_with_region(&mut app);
829 let tracker = tracker_for(&mut app, root);
830 let log = record_events(&mut app, region);
831 let notifications = Rc::new(Cell::new(0));
832 let counted = Rc::clone(¬ifications);
833 tracker.add_listener(
834 &mut app,
835 Listener::new(move |_app| counted.set(counted.get() + 1)),
836 );
837
838 assert!(!tracker.mouse_is_connected(&app));
839 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
840 assert!(tracker.mouse_is_connected(&app));
841 assert_eq!(notifications.get(), 1);
842
843 tracker.update_with_event(&mut app, &mouse_removed(Offset::new(100.0, 100.0)), None);
844 assert!(!tracker.mouse_is_connected(&app));
845 assert_eq!(notifications.get(), 2);
846 assert_eq!(
847 *log.borrow(),
848 ["enter Offset(50.0, 50.0)", "exit Offset(50.0, 50.0)"]
849 );
850 }
851
852 #[test]
853 fn touch_and_signal_events_are_ignored() {
854 let cell = AppCell::new();
855 let mut app = cell.borrow_mut();
856 let RegionTree { root, region, .. } = tree_with_region(&mut app);
857 let tracker = tracker_for(&mut app, root);
858 let log = record_events(&mut app, region);
859
860 tracker.update_with_event(
861 &mut app,
862 &PointerEvent::Hover(PointerHoverEvent {
863 position: Offset::new(100.0, 100.0),
864 ..PointerHoverEvent::default()
865 }),
866 None,
867 );
868 assert!(!tracker.mouse_is_connected(&app), "touch: not tracked");
869
870 tracker.update_with_event(
871 &mut app,
872 &PointerEvent::Scroll(PointerScrollEvent {
873 kind: PointerDeviceKind::Mouse,
874 position: Offset::new(100.0, 100.0),
875 ..PointerScrollEvent::default()
876 }),
877 None,
878 );
879 assert!(!tracker.mouse_is_connected(&app), "a signal: not tracked");
880 assert!(log.borrow().is_empty());
881 }
882
883 #[test]
884 fn the_front_regions_cursor_wins_and_deferring_regions_let_the_one_behind_decide() {
885 let (cell, cursor) = app_with_cursor_host();
886 let mut app = cell.borrow_mut();
887 let RegionTree {
888 owner,
889 root,
890 region,
891 ..
892 } = tree_with_region(&mut app);
893 region.set_cursor(&mut app, SystemMouseCursors::TEXT.into());
894 let front = RenderMouseRegion::new(&mut app, true, None);
896 region.set_child(&mut app, Some(front.as_box()));
897 owner.flush_layout(&mut app);
898 let tracker = tracker_for(&mut app, root);
899
900 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
901 assert_eq!(
902 *cursor.activated.borrow(),
903 [(0, SystemMouseCursorKind::Text)]
904 );
905
906 front.set_cursor(&mut app, SystemMouseCursors::CLICK.into());
907 tracker.update_all_devices(&mut app);
908 assert_eq!(
909 *cursor.activated.borrow(),
910 [
911 (0, SystemMouseCursorKind::Text),
912 (0, SystemMouseCursorKind::Click)
913 ]
914 );
915 let click: MouseCursorRef = SystemMouseCursors::CLICK.into();
916 assert!(*tracker.debug_device_active_cursor(&app, 0).unwrap() == *click);
917
918 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(10.0, 10.0)), None);
919 assert_eq!(
920 cursor.activated.borrow().last().unwrap().1,
921 SystemMouseCursorKind::Basic,
922 "over nothing, the fallback cursor"
923 );
924 }
925
926 #[test]
927 fn an_opaque_region_hides_the_region_behind_it_and_a_transparent_one_does_not() {
928 let cell = AppCell::new();
929 let mut app = cell.borrow_mut();
930 let back = RenderMouseRegion::new(&mut app, true, None);
931 let front = RenderMouseRegion::new(&mut app, true, Some(back.as_box()));
932 let root = RenderRepaintBoundary::new(&mut app, Some(front.as_box()));
933 let owner = PipelineOwner::new(&mut app, None);
934 owner.set_root_node(&mut app, Some(root.as_object()));
935 root.schedule_initial_layout(&mut app);
936 root.layout(
937 &mut app,
938 BoxConstraints::tight(Size::new(100.0, 100.0)),
939 false,
940 );
941 let tracker = tracker_for(&mut app, root);
942 let back_log = record_events(&mut app, back);
943 let front_log = record_events(&mut app, front);
944
945 tracker.update_with_event(&mut app, &mouse_added(Offset::new(10.0, 10.0)), None);
946 assert_eq!(*front_log.borrow(), ["enter Offset(10.0, 10.0)"]);
947 assert_eq!(
948 *back_log.borrow(),
949 ["enter Offset(10.0, 10.0)"],
950 "the front region's hit test still visits its child; opacity only stops what is behind the region in the parent's hit test"
951 );
952 }
953
954 #[test]
955 fn hover_reaches_the_region_through_handle_event() {
956 let cell = AppCell::new();
957 let mut app = cell.borrow_mut();
958 let region = RenderMouseRegion::new(&mut app, true, None);
959 let log = record_events(&mut app, region);
960 let event = mouse_hover(Offset::new(3.0, 4.0));
961 let entry = BoxHitTestEntry::new(region.as_box(), Offset::new(3.0, 4.0));
962 region.handle_event(&mut app, &event, &entry);
963 assert_eq!(*log.borrow(), ["hover Offset(3.0, 4.0)"]);
964 }
965
966 #[test]
967 fn an_opaque_region_absorbs_the_hit_test_for_what_is_behind_it() {
968 let cell = AppCell::new();
969 let mut app = cell.borrow_mut();
970 let region = RenderMouseRegion::new(&mut app, true, None);
971 let root = RenderRepaintBoundary::new(&mut app, Some(region.as_box()));
972 let owner = PipelineOwner::new(&mut app, None);
973 owner.set_root_node(&mut app, Some(root.as_object()));
974 root.schedule_initial_layout(&mut app);
975 root.layout(
976 &mut app,
977 BoxConstraints::tight(Size::new(100.0, 100.0)),
978 false,
979 );
980
981 let mut result = HitTestResult::new();
982 assert!(region.hit_test(
983 &mut app,
984 &mut BoxHitTestResult::wrap(&mut result),
985 Offset::new(5.0, 5.0)
986 ));
987 region.set_opaque(&mut app, false);
988 let mut result = HitTestResult::new();
989 assert!(!region.hit_test(
990 &mut app,
991 &mut BoxHitTestResult::wrap(&mut result),
992 Offset::new(5.0, 5.0)
993 ));
994 assert_eq!(
995 result.path().len(),
996 1,
997 "the region is still on the path; it just does not claim the hit"
998 );
999 }
1000
1001 #[test]
1002 fn a_region_with_no_annotation_neighbours_still_uses_the_fallback_cursor() {
1003 let (cell, cursor) = app_with_cursor_host();
1004 let mut app = cell.borrow_mut();
1005 let child =
1006 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
1007 let root = RenderRepaintBoundary::new(&mut app, Some(child.as_box()));
1008 let owner = PipelineOwner::new(&mut app, None);
1009 owner.set_root_node(&mut app, Some(root.as_object()));
1010 root.schedule_initial_layout(&mut app);
1011 root.layout(
1012 &mut app,
1013 BoxConstraints::tight(Size::new(100.0, 100.0)),
1014 false,
1015 );
1016 let tracker = tracker_for(&mut app, root);
1017 tracker.update_with_event(&mut app, &mouse_added(Offset::new(5.0, 5.0)), None);
1018 assert_eq!(
1019 *cursor.activated.borrow(),
1020 [(0, SystemMouseCursorKind::Basic)]
1021 );
1022 }
1023}