1use std::any::Any;
4use std::hash::{Hash, Hasher};
5use std::rc::Rc;
6
7use indexmap::IndexMap;
8use inset_embedder::{Matrix4, Offset, PointerDeviceKind, ViewId};
9use inset_foundation::{App, ChangeNotifier, ChangeNotifierData, Handle, RetainedHandle};
10use inset_gestures::{
11 HitTestEntry, HitTestResult, PointerEnterEvent, PointerEvent, PointerExitEvent,
12};
13use inset_painting::{InlineSpan, TextSpan};
14use inset_services::{
15 MouseCursorManager, MouseCursorRef, MouseTrackerAnnotation, SystemMouseCursors,
16};
17
18use crate::box_::BoxHitTestEntry;
19use crate::object::AnyRenderObject;
20
21pub type MouseTrackerHitTest = Rc<dyn Fn(&mut App, Offset, ViewId) -> HitTestResult>;
26
27type Annotations = IndexMap<TrackedAnnotation, Matrix4>;
33
34#[derive(Clone, Debug)]
36enum TrackedAnnotation {
37 RenderObject(AnyRenderObject),
39 Span(Rc<TextSpan>),
42}
43
44impl TrackedAnnotation {
45 fn annotation(&self, app: &App) -> Option<MouseTrackerAnnotation> {
48 match self {
49 TrackedAnnotation::RenderObject(target) => target.mouse_tracker_annotation(app),
50 TrackedAnnotation::Span(span) => Some(span.mouse_tracker_annotation()),
51 }
52 }
53
54 fn render_object(&self) -> Option<AnyRenderObject> {
57 match self {
58 TrackedAnnotation::RenderObject(target) => Some(*target),
59 TrackedAnnotation::Span(_) => None,
60 }
61 }
62}
63
64impl PartialEq for TrackedAnnotation {
65 fn eq(&self, other: &TrackedAnnotation) -> bool {
66 match (self, other) {
67 (TrackedAnnotation::RenderObject(a), TrackedAnnotation::RenderObject(b)) => a == b,
68 (TrackedAnnotation::Span(a), TrackedAnnotation::Span(b)) => a.eq_span(&**b),
69 _ => false,
70 }
71 }
72}
73
74impl Eq for TrackedAnnotation {}
75
76impl Hash for TrackedAnnotation {
77 fn hash<H: Hasher>(&self, state: &mut H) {
78 match self {
79 TrackedAnnotation::RenderObject(target) => target.hash(state),
80 TrackedAnnotation::Span(span) => span.text.hash(state),
82 }
83 }
84}
85
86struct MouseState {
88 retained: Vec<RetainedHandle<AnyRenderObject>>,
90 annotations: Annotations,
94 latest_event: PointerEvent,
96}
97
98impl MouseState {
99 fn new(initial_event: PointerEvent) -> MouseState {
100 MouseState {
101 retained: Vec::new(),
102 annotations: Annotations::new(),
103 latest_event: initial_event,
104 }
105 }
106
107 fn replace_annotations(
108 &mut self,
109 value: Annotations,
110 retained: Vec<RetainedHandle<AnyRenderObject>>,
111 ) -> (Annotations, Vec<RetainedHandle<AnyRenderObject>>) {
112 (
113 std::mem::replace(&mut self.annotations, value),
114 std::mem::replace(&mut self.retained, retained),
115 )
116 }
117
118 fn replace_latest_event(&mut self, value: PointerEvent) -> PointerEvent {
119 debug_assert_eq!(value.device(), self.latest_event.device());
120 std::mem::replace(&mut self.latest_event, value)
121 }
122}
123
124struct MouseTrackerUpdateDetails {
130 last_annotations: Annotations,
134 next_annotations: Annotations,
138 previous_event: Option<PointerEvent>,
147 triggering_event: Option<PointerEvent>,
151}
152
153impl MouseTrackerUpdateDetails {
154 fn by_new_frame(
158 last_annotations: Annotations,
159 next_annotations: Annotations,
160 previous_event: PointerEvent,
161 ) -> MouseTrackerUpdateDetails {
162 MouseTrackerUpdateDetails {
163 last_annotations,
164 next_annotations,
165 previous_event: Some(previous_event),
166 triggering_event: None,
167 }
168 }
169
170 fn by_pointer_event(
175 last_annotations: Annotations,
176 next_annotations: Annotations,
177 previous_event: Option<PointerEvent>,
178 triggering_event: PointerEvent,
179 ) -> MouseTrackerUpdateDetails {
180 MouseTrackerUpdateDetails {
181 last_annotations,
182 next_annotations,
183 previous_event,
184 triggering_event: Some(triggering_event),
185 }
186 }
187
188 fn device(&self) -> i64 {
190 self.previous_event
191 .as_ref()
192 .or(self.triggering_event.as_ref())
193 .expect("an update has an event")
194 .device()
195 }
196
197 fn latest_event(&self) -> &PointerEvent {
201 self.triggering_event
202 .as_ref()
203 .or(self.previous_event.as_ref())
204 .expect("an update has an event")
205 }
206}
207
208pub struct MouseTracker {
230 change_notifier: ChangeNotifierData,
231 hit_test_in_view: MouseTrackerHitTest,
232 mouse_cursor_mixin: MouseCursorManager,
233 mouse_states: IndexMap<i64, MouseState>,
234 debug_during_device_update: bool,
235}
236
237impl ChangeNotifier for MouseTracker {
238 fn change_notifier_data(&self) -> &ChangeNotifierData {
239 &self.change_notifier
240 }
241
242 fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData {
243 &mut self.change_notifier
244 }
245}
246
247impl MouseTracker {
248 pub fn new(app: &mut App, hit_test_in_view: MouseTrackerHitTest) -> Handle<MouseTracker> {
254 app.create(MouseTracker {
255 change_notifier: ChangeNotifierData::new(),
256 hit_test_in_view,
257 mouse_cursor_mixin: MouseCursorManager::new(SystemMouseCursors::BASIC.into()),
258 mouse_states: IndexMap::new(),
259 debug_during_device_update: false,
260 })
261 }
262
263 pub fn dispose(self: Handle<Self>, app: &mut App) {
266 let states = std::mem::take(&mut app.get_mut(self).mouse_states);
267 for state in states.into_values() {
268 MouseTracker::release_annotations(app, state.retained);
269 }
270 app.get_mut(self).change_notifier.dispose();
271 }
272
273 fn retain_annotations(
276 app: &mut App,
277 annotations: &Annotations,
278 ) -> Vec<RetainedHandle<AnyRenderObject>> {
279 annotations
280 .keys()
281 .filter_map(TrackedAnnotation::render_object)
282 .map(|target| app.retain(target))
283 .collect()
284 }
285
286 fn release_annotations(app: &mut App, retained: Vec<RetainedHandle<AnyRenderObject>>) {
287 for handle in retained {
288 app.release(handle);
289 }
290 }
291
292 fn should_mark_state_dirty(state: Option<&MouseState>, event: &PointerEvent) -> bool {
297 let Some(state) = state else {
298 return true;
299 };
300 let last_event = &state.latest_event;
301 debug_assert_eq!(event.device(), last_event.device());
302 debug_assert_eq!(
304 matches!(event, PointerEvent::Added(_)),
305 matches!(last_event, PointerEvent::Removed(_))
306 );
307 if is_signal_event(event) {
309 return false;
310 }
311 matches!(last_event, PointerEvent::Added(_))
312 || matches!(event, PointerEvent::Removed(_))
313 || last_event.position() != event.position()
314 }
315
316 fn hit_test_in_view_result_to_annotations(app: &App, result: &HitTestResult) -> Annotations {
317 let mut annotations = Annotations::new();
318 for entry in result.path() {
319 if let Some(target) = annotation_target(app, entry) {
320 annotations.insert(target, entry.transform().expect("an added entry is placed"));
321 }
322 }
323 annotations
324 }
325
326 fn find_annotations(
331 self: Handle<Self>,
332 app: &mut App,
333 latest_event: &PointerEvent,
334 ) -> Annotations {
335 let global_position = latest_event.position();
336 let device = latest_event.device();
337 let view_id = latest_event.view_id();
338 if !app.get(self).mouse_states.contains_key(&device) {
339 return Annotations::new();
340 }
341 let result = self.run_hit_test(app, global_position, view_id);
342 MouseTracker::hit_test_in_view_result_to_annotations(app, &result)
343 }
344
345 fn run_hit_test(
346 self: Handle<Self>,
347 app: &mut App,
348 position: Offset,
349 view_id: ViewId,
350 ) -> HitTestResult {
351 let hit_test_in_view = Rc::clone(&app.get(self).hit_test_in_view);
352 hit_test_in_view(app, position, view_id)
353 }
354
355 fn handle_device_update(self: Handle<Self>, app: &mut App, details: MouseTrackerUpdateDetails) {
366 debug_assert!(app.get(self).debug_during_device_update);
367 MouseTracker::handle_device_update_mouse_events(app, &details);
368 let cursors: Vec<MouseCursorRef> = details
369 .next_annotations
370 .keys()
371 .filter_map(|target| target.annotation(app))
372 .map(|annotation| annotation.cursor)
373 .collect();
374 let platform = app.platform();
375 app.get_mut(self)
376 .mouse_cursor_mixin
377 .handle_device_cursor_update(
378 &*platform,
379 details.latest_event().view_id(),
380 details.device(),
381 details.triggering_event.as_ref(),
382 cursors,
383 );
384 }
385
386 pub fn mouse_is_connected(self: Handle<Self>, app: &App) -> bool {
388 !app.get(self).mouse_states.is_empty()
389 }
390
391 pub fn update_with_event(
410 self: Handle<Self>,
411 app: &mut App,
412 event: &PointerEvent,
413 hit_test_result: Option<&HitTestResult>,
414 ) {
415 if event.kind() != PointerDeviceKind::Mouse && event.kind() != PointerDeviceKind::Stylus {
416 return;
418 }
419 if is_signal_event(event) {
420 return;
422 }
423 let is_removed = matches!(event, PointerEvent::Removed(_));
424 let owned_result;
425 let result: Option<&HitTestResult> = if is_removed {
426 None
427 } else if let Some(result) = hit_test_result {
428 Some(result)
429 } else {
430 owned_result = self.run_hit_test(app, event.position(), event.view_id());
431 Some(&owned_result)
432 };
433 let device = event.device();
434 if !MouseTracker::should_mark_state_dirty(app.get(self).mouse_states.get(&device), event) {
435 return;
436 }
437
438 self.monitor_mouse_connection(app, |app| {
439 self.device_update_phase(app, |app| {
440 if !app.get(self).mouse_states.contains_key(&device) {
442 if is_removed {
443 return;
444 }
445 app.get_mut(self)
446 .mouse_states
447 .insert(device, MouseState::new(event.clone()));
448 } else {
449 debug_assert!(!matches!(event, PointerEvent::Added(_)));
450 }
451 let next_annotations = match result {
452 Some(result) => {
453 MouseTracker::hit_test_in_view_result_to_annotations(app, result)
454 }
455 None => Annotations::new(),
456 };
457 let target_state = app
458 .get_mut(self)
459 .mouse_states
460 .get_mut(&device)
461 .expect("the state was just found or created");
462 let last_event = target_state.replace_latest_event(event.clone());
463 let retained = MouseTracker::retain_annotations(app, &next_annotations);
464 let target_state = app
465 .get_mut(self)
466 .mouse_states
467 .get_mut(&device)
468 .expect("the state was just found or created");
469 let (last_annotations, last_retained) =
470 target_state.replace_annotations(next_annotations.clone(), retained);
471 if is_removed {
472 app.get_mut(self).mouse_states.shift_remove(&device);
473 }
474 self.handle_device_update(
475 app,
476 MouseTrackerUpdateDetails::by_pointer_event(
477 last_annotations,
478 next_annotations,
479 Some(last_event),
480 event.clone(),
481 ),
482 );
483 MouseTracker::release_annotations(app, last_retained);
484 });
485 });
486 }
487
488 pub fn update_all_devices(self: Handle<Self>, app: &mut App) {
498 self.device_update_phase(app, |app| {
499 let devices: Vec<i64> = app.get(self).mouse_states.keys().copied().collect();
500 for device in devices {
501 let Some(last_event) = app
502 .get(self)
503 .mouse_states
504 .get(&device)
505 .map(|state| state.latest_event.clone())
506 else {
507 continue;
508 };
509 let next_annotations = self.find_annotations(app, &last_event);
510 if !app.get(self).mouse_states.contains_key(&device) {
511 continue;
512 }
513 let retained = MouseTracker::retain_annotations(app, &next_annotations);
514 let Some(dirty_state) = app.get_mut(self).mouse_states.get_mut(&device) else {
515 continue;
516 };
517 let (last_annotations, last_retained) =
518 dirty_state.replace_annotations(next_annotations.clone(), retained);
519 self.handle_device_update(
520 app,
521 MouseTrackerUpdateDetails::by_new_frame(
522 last_annotations,
523 next_annotations,
524 last_event,
525 ),
526 );
527 MouseTracker::release_annotations(app, last_retained);
528 }
529 });
530 }
531
532 pub fn debug_device_active_cursor(
539 self: Handle<Self>,
540 app: &App,
541 device: i64,
542 ) -> Option<MouseCursorRef> {
543 app.get(self)
544 .mouse_cursor_mixin
545 .debug_device_active_cursor(device)
546 }
547
548 fn monitor_mouse_connection(self: Handle<Self>, app: &mut App, task: impl FnOnce(&mut App)) {
553 let mouse_was_connected = self.mouse_is_connected(app);
554 task(app);
555 if mouse_was_connected != self.mouse_is_connected(app) {
556 self.notify_listeners(app);
557 }
558 }
559
560 fn device_update_phase(self: Handle<Self>, app: &mut App, task: impl FnOnce(&mut App)) {
565 debug_assert!(!app.get(self).debug_during_device_update);
566 if cfg!(debug_assertions) {
567 app.get_mut(self).debug_during_device_update = true;
568 }
569 task(app);
570 if cfg!(debug_assertions) {
571 app.get_mut(self).debug_during_device_update = false;
572 }
573 }
574
575 fn handle_device_update_mouse_events(app: &mut App, details: &MouseTrackerUpdateDetails) {
577 let latest_event = details.latest_event();
578 let last_annotations = &details.last_annotations;
579 let next_annotations = &details.next_annotations;
580
581 let base_exit_event = PointerExitEvent::from_mouse_event(latest_event);
589 for (target, transform) in last_annotations {
590 if next_annotations.contains_key(target) {
591 continue;
592 }
593 let Some(annotation) = target.annotation(app) else {
594 continue;
595 };
596 if annotation.valid_for_mouse_tracker
597 && let Some(on_exit) = annotation.on_exit
598 {
599 on_exit(app, base_exit_event.transformed(Some(*transform)));
600 }
601 }
602
603 let entering_annotations: Vec<(TrackedAnnotation, Matrix4)> = next_annotations
606 .iter()
607 .filter(|(target, _)| !last_annotations.contains_key(*target))
608 .map(|(target, transform)| (target.clone(), *transform))
609 .collect();
610 let base_enter_event = PointerEnterEvent::from_mouse_event(latest_event);
611 for (target, transform) in entering_annotations.into_iter().rev() {
612 let Some(annotation) = target.annotation(app) else {
613 continue;
614 };
615 if annotation.valid_for_mouse_tracker
616 && let Some(on_enter) = annotation.on_enter
617 {
618 on_enter(app, base_enter_event.transformed(Some(transform)));
619 }
620 }
621 }
622}
623
624fn is_signal_event(event: &PointerEvent) -> bool {
626 matches!(
627 event,
628 PointerEvent::Scroll(_) | PointerEvent::ScrollInertiaCancel(_) | PointerEvent::Scale(_)
629 )
630}
631
632fn annotation_target(app: &App, entry: &HitTestEntry) -> Option<TrackedAnnotation> {
636 let target: Rc<dyn Any> = entry.shared_target().clone();
637 let target = match target.downcast::<TextSpan>() {
638 Ok(span) => return Some(TrackedAnnotation::Span(span)),
639 Err(target) => target,
640 };
641 let target = target
642 .downcast_ref::<BoxHitTestEntry>()?
643 .target()
644 .as_object();
645 target
646 .mouse_tracker_annotation(app)
647 .map(|_| TrackedAnnotation::RenderObject(target))
648}
649
650#[cfg(test)]
651mod tests {
652 use inset_foundation::AppCell;
653 use std::cell::{Cell, RefCell};
654
655 use inset_embedder::{Size, SystemMouseCursorKind, ViewRef};
656 use inset_gestures::{
657 PointerAddedEvent, PointerHoverEvent, PointerRemovedEvent, PointerScrollEvent,
658 };
659 use inset_test::{TestPlatform, TestView};
660
661 use inset_foundation::{Listenable, Listener};
662
663 use super::*;
664 use crate::box_::{BoxConstraints, BoxHitTestResult, RenderBox};
665 use crate::object::RenderObject;
666 use crate::object::RenderObjectWithChildMixin;
667 use crate::pipeline_owner::PipelineOwner;
668 use crate::proxy_box::{RenderConstrainedBox, RenderMouseRegion, RenderRepaintBoundary};
669 use crate::shifted_box::RenderPadding;
670
671 fn app_with_cursor_host() -> (Rc<AppCell>, Rc<TestView>, Rc<TestView>) {
674 let first = Rc::new(TestView::new(200.0, 200.0).with_id(ViewId(0)));
675 let second = Rc::new(TestView::new(200.0, 200.0).with_id(ViewId(2)));
676 let platform = TestPlatform::new()
677 .with_view(Rc::clone(&first) as ViewRef)
678 .with_view(Rc::clone(&second) as ViewRef);
679 (AppCell::with_platform(Rc::new(platform)), first, second)
680 }
681
682 struct RegionTree {
685 owner: Handle<PipelineOwner>,
686 root: crate::RenderHandle<RenderRepaintBoundary>,
687 padding: crate::RenderHandle<RenderPadding>,
688 region: crate::RenderHandle<RenderMouseRegion>,
689 }
690
691 fn tree_with_region(app: &mut App) -> RegionTree {
692 let region = RenderMouseRegion::new(app, true, None);
693 let padding = RenderPadding::new(
694 app,
695 inset_painting::EdgeInsets::all(50.0).into(),
696 None,
697 Some(region.as_box()),
698 );
699 let root = RenderRepaintBoundary::new(app, Some(padding.as_box()));
700 let owner = PipelineOwner::new(app, None, None);
701 owner.set_root_node(app, Some(root.as_object()));
702 root.schedule_initial_layout(app);
703 root.layout(app, BoxConstraints::tight(Size::new(200.0, 200.0)), false);
704 RegionTree {
705 owner,
706 root,
707 padding,
708 region,
709 }
710 }
711
712 fn tracker_for(
713 app: &mut App,
714 root: crate::RenderHandle<RenderRepaintBoundary>,
715 ) -> Handle<MouseTracker> {
716 MouseTracker::new(
717 app,
718 Rc::new(move |app: &mut App, position: Offset, _view_id: ViewId| {
719 let mut result = HitTestResult::new();
720 root.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
721 result
722 }),
723 )
724 }
725
726 fn mouse_hover(position: Offset) -> PointerEvent {
727 PointerEvent::Hover(PointerHoverEvent {
728 kind: PointerDeviceKind::Mouse,
729 position,
730 ..PointerHoverEvent::default()
731 })
732 }
733
734 fn mouse_added(position: Offset) -> PointerEvent {
735 PointerEvent::Added(PointerAddedEvent {
736 kind: PointerDeviceKind::Mouse,
737 position,
738 ..PointerAddedEvent::default()
739 })
740 }
741
742 fn mouse_removed(position: Offset) -> PointerEvent {
743 PointerEvent::Removed(PointerRemovedEvent {
744 kind: PointerDeviceKind::Mouse,
745 position,
746 ..PointerRemovedEvent::default()
747 })
748 }
749
750 fn record_events(
752 app: &mut App,
753 region: crate::RenderHandle<RenderMouseRegion>,
754 ) -> Rc<RefCell<Vec<String>>> {
755 let log = Rc::new(RefCell::new(Vec::new()));
756 let enter_log = Rc::clone(&log);
757 region.set_on_enter(
758 app,
759 Some(Rc::new(move |_app: &mut App, event: PointerEnterEvent| {
760 enter_log
761 .borrow_mut()
762 .push(format!("enter {:?}", event.local_position()));
763 })),
764 );
765 let hover_log = Rc::clone(&log);
766 region.set_on_hover(
767 app,
768 Some(Rc::new(move |_app: &mut App, event: PointerHoverEvent| {
769 hover_log
770 .borrow_mut()
771 .push(format!("hover {:?}", event.local_position()));
772 })),
773 );
774 let exit_log = Rc::clone(&log);
775 region.set_on_exit(
776 app,
777 Some(Rc::new(move |_app: &mut App, event: PointerExitEvent| {
778 exit_log
779 .borrow_mut()
780 .push(format!("exit {:?}", event.local_position()));
781 })),
782 );
783 log
784 }
785
786 #[test]
787 fn entering_and_leaving_a_region_sends_enter_then_exit_in_local_coordinates() {
788 let cell = AppCell::new();
789 let mut app = cell.borrow_mut();
790 let RegionTree { root, region, .. } = tree_with_region(&mut app);
791 let tracker = tracker_for(&mut app, root);
792 let log = record_events(&mut app, region);
793
794 tracker.update_with_event(&mut app, &mouse_added(Offset::new(10.0, 10.0)), None);
795 assert!(log.borrow().is_empty(), "outside the region: nothing");
796
797 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(60.0, 70.0)), None);
798 assert_eq!(*log.borrow(), ["enter Offset(10.0, 20.0)"]);
799
800 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(160.0, 70.0)), None);
801 assert_eq!(
802 *log.borrow(),
803 ["enter Offset(10.0, 20.0)", "exit Offset(110.0, 20.0)"]
804 );
805 }
806
807 #[test]
808 fn a_still_mouse_notices_the_tree_moving_under_it_after_a_frame() {
809 let cell = AppCell::new();
810 let mut app = cell.borrow_mut();
811 let RegionTree {
812 owner,
813 root,
814 padding,
815 region,
816 } = tree_with_region(&mut app);
817 let tracker = tracker_for(&mut app, root);
818 let log = record_events(&mut app, region);
819 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
820 assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
821
822 padding.set_padding(
824 &mut app,
825 inset_painting::EdgeInsetsGeometry::only(150.0, 0.0, 0.0, 0.0),
826 );
827 owner.flush_layout(&mut app);
828 tracker.update_all_devices(&mut app);
829 assert_eq!(
830 *log.borrow(),
831 ["enter Offset(50.0, 50.0)", "exit Offset(50.0, 50.0)"],
832 "the exit is placed with the transform recorded when the region was entered"
833 );
834 }
835
836 #[test]
837 fn a_region_removed_from_the_tree_sends_no_exit() {
838 let cell = AppCell::new();
839 let mut app = cell.borrow_mut();
840 let RegionTree {
841 owner,
842 root,
843 padding,
844 region,
845 } = tree_with_region(&mut app);
846 let tracker = tracker_for(&mut app, root);
847 let log = record_events(&mut app, region);
848 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
849 assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
850
851 padding.set_child(&mut app, None);
852 owner.flush_layout(&mut app);
853 tracker.update_all_devices(&mut app);
854 assert_eq!(
855 *log.borrow(),
856 ["enter Offset(50.0, 50.0)"],
857 "Flutter: a detached region is no longer valid for the tracker, so no exit is sent"
858 );
859 }
860
861 #[test]
862 fn a_detached_region_is_invalid_for_the_tracker() {
863 let cell = AppCell::new();
864 let mut app = cell.borrow_mut();
865 let region = RenderMouseRegion::new(&mut app, true, None);
866 assert!(region.valid_for_mouse_tracker(&app));
867 let owner = PipelineOwner::new(&mut app, None, None);
868 region.as_object().attach(&mut app, owner);
869 assert!(region.valid_for_mouse_tracker(&app));
870 region.as_object().detach(&mut app);
871 assert!(!region.valid_for_mouse_tracker(&app));
872 assert!(
873 !region
874 .mouse_tracker_annotation(&app)
875 .unwrap()
876 .valid_for_mouse_tracker
877 );
878 }
879
880 #[test]
881 fn a_removed_mouse_exits_and_disconnects() {
882 let cell = AppCell::new();
883 let mut app = cell.borrow_mut();
884 let RegionTree { root, region, .. } = tree_with_region(&mut app);
885 let tracker = tracker_for(&mut app, root);
886 let log = record_events(&mut app, region);
887 let notifications = Rc::new(Cell::new(0));
888 let counted = Rc::clone(¬ifications);
889 tracker.add_listener(
890 &mut app,
891 Listener::new(move |_app| counted.set(counted.get() + 1)),
892 );
893
894 assert!(!tracker.mouse_is_connected(&app));
895 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
896 assert!(tracker.mouse_is_connected(&app));
897 assert_eq!(notifications.get(), 1);
898
899 tracker.update_with_event(&mut app, &mouse_removed(Offset::new(100.0, 100.0)), None);
900 assert!(!tracker.mouse_is_connected(&app));
901 assert_eq!(notifications.get(), 2);
902 assert_eq!(
903 *log.borrow(),
904 ["enter Offset(50.0, 50.0)", "exit Offset(50.0, 50.0)"]
905 );
906 }
907
908 #[test]
909 fn touch_and_signal_events_are_ignored() {
910 let cell = AppCell::new();
911 let mut app = cell.borrow_mut();
912 let RegionTree { root, region, .. } = tree_with_region(&mut app);
913 let tracker = tracker_for(&mut app, root);
914 let log = record_events(&mut app, region);
915
916 tracker.update_with_event(
917 &mut app,
918 &PointerEvent::Hover(PointerHoverEvent {
919 position: Offset::new(100.0, 100.0),
920 ..PointerHoverEvent::default()
921 }),
922 None,
923 );
924 assert!(!tracker.mouse_is_connected(&app), "touch: not tracked");
925
926 tracker.update_with_event(
927 &mut app,
928 &PointerEvent::Scroll(PointerScrollEvent {
929 kind: PointerDeviceKind::Mouse,
930 position: Offset::new(100.0, 100.0),
931 ..PointerScrollEvent::default()
932 }),
933 None,
934 );
935 assert!(!tracker.mouse_is_connected(&app), "a signal: not tracked");
936 assert!(log.borrow().is_empty());
937 }
938
939 #[test]
940 fn the_front_regions_cursor_wins_and_deferring_regions_let_the_one_behind_decide() {
941 let (cell, first, _) = app_with_cursor_host();
942 let mut app = cell.borrow_mut();
943 let RegionTree {
944 owner,
945 root,
946 region,
947 ..
948 } = tree_with_region(&mut app);
949 region.set_cursor(&mut app, SystemMouseCursors::TEXT.into());
950 let front = RenderMouseRegion::new(&mut app, true, None);
952 region.set_child(&mut app, Some(front.as_box()));
953 owner.flush_layout(&mut app);
954 let tracker = tracker_for(&mut app, root);
955
956 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
957 assert_eq!(
958 first.test_host().cursors(),
959 [(0, SystemMouseCursorKind::Text)]
960 );
961
962 front.set_cursor(&mut app, SystemMouseCursors::CLICK.into());
963 tracker.update_all_devices(&mut app);
964 assert_eq!(
965 first.test_host().cursors(),
966 [
967 (0, SystemMouseCursorKind::Text),
968 (0, SystemMouseCursorKind::Click)
969 ]
970 );
971 let click: MouseCursorRef = SystemMouseCursors::CLICK.into();
972 assert!(*tracker.debug_device_active_cursor(&app, 0).unwrap() == *click);
973
974 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(10.0, 10.0)), None);
975 assert_eq!(
976 first.test_host().cursors().last().unwrap().1,
977 SystemMouseCursorKind::Basic,
978 "over nothing, the fallback cursor"
979 );
980 }
981
982 #[test]
983 fn the_cursor_goes_to_the_view_the_pointer_was_last_seen_on() {
984 let (cell, first, second) = app_with_cursor_host();
985 let mut app = cell.borrow_mut();
986 let RegionTree { root, region, .. } = tree_with_region(&mut app);
987 region.set_cursor(&mut app, SystemMouseCursors::TEXT.into());
988 let tracker = tracker_for(&mut app, root);
989
990 let added = PointerEvent::Added(PointerAddedEvent {
991 kind: PointerDeviceKind::Mouse,
992 position: Offset::new(100.0, 100.0),
993 view_id: ViewId(2),
994 ..PointerAddedEvent::default()
995 });
996 tracker.update_with_event(&mut app, &added, None);
997 assert_eq!(
998 second.test_host().cursors(),
999 [(0, SystemMouseCursorKind::Text)]
1000 );
1001 assert!(first.test_host().cursors().is_empty());
1002
1003 region.set_cursor(&mut app, SystemMouseCursors::CLICK.into());
1004 tracker.update_all_devices(&mut app);
1005 assert_eq!(
1006 second.test_host().cursors().last(),
1007 Some(&(0, SystemMouseCursorKind::Click)),
1008 "a frame-triggered update has no event of its own; the device's last one names the view"
1009 );
1010 }
1011
1012 #[test]
1013 fn an_opaque_region_hides_the_region_behind_it_and_a_transparent_one_does_not() {
1014 let cell = AppCell::new();
1015 let mut app = cell.borrow_mut();
1016 let back = RenderMouseRegion::new(&mut app, true, None);
1017 let front = RenderMouseRegion::new(&mut app, true, Some(back.as_box()));
1018 let root = RenderRepaintBoundary::new(&mut app, Some(front.as_box()));
1019 let owner = PipelineOwner::new(&mut app, None, None);
1020 owner.set_root_node(&mut app, Some(root.as_object()));
1021 root.schedule_initial_layout(&mut app);
1022 root.layout(
1023 &mut app,
1024 BoxConstraints::tight(Size::new(100.0, 100.0)),
1025 false,
1026 );
1027 let tracker = tracker_for(&mut app, root);
1028 let back_log = record_events(&mut app, back);
1029 let front_log = record_events(&mut app, front);
1030
1031 tracker.update_with_event(&mut app, &mouse_added(Offset::new(10.0, 10.0)), None);
1032 assert_eq!(*front_log.borrow(), ["enter Offset(10.0, 10.0)"]);
1033 assert_eq!(
1034 *back_log.borrow(),
1035 ["enter Offset(10.0, 10.0)"],
1036 "the front region's hit test still visits its child; opacity only stops what is behind the region in the parent's hit test"
1037 );
1038 }
1039
1040 #[test]
1041 fn hover_reaches_the_region_through_handle_event() {
1042 let cell = AppCell::new();
1043 let mut app = cell.borrow_mut();
1044 let region = RenderMouseRegion::new(&mut app, true, None);
1045 let log = record_events(&mut app, region);
1046 let event = mouse_hover(Offset::new(3.0, 4.0));
1047 let entry = BoxHitTestEntry::new(region.as_box(), Offset::new(3.0, 4.0));
1048 region.handle_event(&mut app, &event, &entry);
1049 assert_eq!(*log.borrow(), ["hover Offset(3.0, 4.0)"]);
1050 }
1051
1052 #[test]
1053 fn an_opaque_region_absorbs_the_hit_test_for_what_is_behind_it() {
1054 let cell = AppCell::new();
1055 let mut app = cell.borrow_mut();
1056 let region = RenderMouseRegion::new(&mut app, true, None);
1057 let root = RenderRepaintBoundary::new(&mut app, Some(region.as_box()));
1058 let owner = PipelineOwner::new(&mut app, None, None);
1059 owner.set_root_node(&mut app, Some(root.as_object()));
1060 root.schedule_initial_layout(&mut app);
1061 root.layout(
1062 &mut app,
1063 BoxConstraints::tight(Size::new(100.0, 100.0)),
1064 false,
1065 );
1066
1067 let mut result = HitTestResult::new();
1068 assert!(region.hit_test(
1069 &mut app,
1070 &mut BoxHitTestResult::wrap(&mut result),
1071 Offset::new(5.0, 5.0)
1072 ));
1073 region.set_opaque(&mut app, false);
1074 let mut result = HitTestResult::new();
1075 assert!(!region.hit_test(
1076 &mut app,
1077 &mut BoxHitTestResult::wrap(&mut result),
1078 Offset::new(5.0, 5.0)
1079 ));
1080 assert_eq!(
1081 result.path().len(),
1082 1,
1083 "the region is still on the path; it just does not claim the hit"
1084 );
1085 }
1086
1087 #[test]
1088 fn a_region_with_no_annotation_neighbours_still_uses_the_fallback_cursor() {
1089 let (cell, first, _) = app_with_cursor_host();
1090 let mut app = cell.borrow_mut();
1091 let child =
1092 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
1093 let root = RenderRepaintBoundary::new(&mut app, Some(child.as_box()));
1094 let owner = PipelineOwner::new(&mut app, None, None);
1095 owner.set_root_node(&mut app, Some(root.as_object()));
1096 root.schedule_initial_layout(&mut app);
1097 root.layout(
1098 &mut app,
1099 BoxConstraints::tight(Size::new(100.0, 100.0)),
1100 false,
1101 );
1102 let tracker = tracker_for(&mut app, root);
1103 tracker.update_with_event(&mut app, &mouse_added(Offset::new(5.0, 5.0)), None);
1104 assert_eq!(
1105 first.test_host().cursors(),
1106 [(0, SystemMouseCursorKind::Basic)]
1107 );
1108 }
1109}