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::{MouseCursor, Size, SystemMouseCursorKind};
656 use inset_gestures::{
657 PointerAddedEvent, PointerHoverEvent, PointerRemovedEvent, PointerScrollEvent,
658 };
659 use inset_test::TestPlatform;
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 #[derive(Default)]
673 struct RecordingCursor {
674 activated: RefCell<Vec<(ViewId, i64, SystemMouseCursorKind)>>,
675 }
676
677 impl MouseCursor for RecordingCursor {
678 fn activate_system_cursor(&self, view: ViewId, device: i64, kind: SystemMouseCursorKind) {
679 self.activated.borrow_mut().push((view, device, kind));
680 }
681 }
682
683 fn app_with_cursor_host() -> (Rc<AppCell>, Rc<RecordingCursor>) {
684 let cursor = Rc::new(RecordingCursor::default());
685 let platform = TestPlatform::new().with_mouse_cursor(cursor.clone());
686 (AppCell::with_platform(Rc::new(platform)), cursor)
687 }
688
689 struct RegionTree {
692 owner: Handle<PipelineOwner>,
693 root: crate::RenderHandle<RenderRepaintBoundary>,
694 padding: crate::RenderHandle<RenderPadding>,
695 region: crate::RenderHandle<RenderMouseRegion>,
696 }
697
698 fn tree_with_region(app: &mut App) -> RegionTree {
699 let region = RenderMouseRegion::new(app, true, None);
700 let padding = RenderPadding::new(
701 app,
702 inset_painting::EdgeInsets::all(50.0).into(),
703 None,
704 Some(region.as_box()),
705 );
706 let root = RenderRepaintBoundary::new(app, Some(padding.as_box()));
707 let owner = PipelineOwner::new(app, None, None);
708 owner.set_root_node(app, Some(root.as_object()));
709 root.schedule_initial_layout(app);
710 root.layout(app, BoxConstraints::tight(Size::new(200.0, 200.0)), false);
711 RegionTree {
712 owner,
713 root,
714 padding,
715 region,
716 }
717 }
718
719 fn tracker_for(
720 app: &mut App,
721 root: crate::RenderHandle<RenderRepaintBoundary>,
722 ) -> Handle<MouseTracker> {
723 MouseTracker::new(
724 app,
725 Rc::new(move |app: &mut App, position: Offset, _view_id: ViewId| {
726 let mut result = HitTestResult::new();
727 root.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
728 result
729 }),
730 )
731 }
732
733 fn mouse_hover(position: Offset) -> PointerEvent {
734 PointerEvent::Hover(PointerHoverEvent {
735 kind: PointerDeviceKind::Mouse,
736 position,
737 ..PointerHoverEvent::default()
738 })
739 }
740
741 fn mouse_added(position: Offset) -> PointerEvent {
742 PointerEvent::Added(PointerAddedEvent {
743 kind: PointerDeviceKind::Mouse,
744 position,
745 ..PointerAddedEvent::default()
746 })
747 }
748
749 fn mouse_removed(position: Offset) -> PointerEvent {
750 PointerEvent::Removed(PointerRemovedEvent {
751 kind: PointerDeviceKind::Mouse,
752 position,
753 ..PointerRemovedEvent::default()
754 })
755 }
756
757 fn record_events(
759 app: &mut App,
760 region: crate::RenderHandle<RenderMouseRegion>,
761 ) -> Rc<RefCell<Vec<String>>> {
762 let log = Rc::new(RefCell::new(Vec::new()));
763 let enter_log = Rc::clone(&log);
764 region.set_on_enter(
765 app,
766 Some(Rc::new(move |_app: &mut App, event: PointerEnterEvent| {
767 enter_log
768 .borrow_mut()
769 .push(format!("enter {:?}", event.local_position()));
770 })),
771 );
772 let hover_log = Rc::clone(&log);
773 region.set_on_hover(
774 app,
775 Some(Rc::new(move |_app: &mut App, event: PointerHoverEvent| {
776 hover_log
777 .borrow_mut()
778 .push(format!("hover {:?}", event.local_position()));
779 })),
780 );
781 let exit_log = Rc::clone(&log);
782 region.set_on_exit(
783 app,
784 Some(Rc::new(move |_app: &mut App, event: PointerExitEvent| {
785 exit_log
786 .borrow_mut()
787 .push(format!("exit {:?}", event.local_position()));
788 })),
789 );
790 log
791 }
792
793 #[test]
794 fn entering_and_leaving_a_region_sends_enter_then_exit_in_local_coordinates() {
795 let cell = AppCell::new();
796 let mut app = cell.borrow_mut();
797 let RegionTree { root, region, .. } = tree_with_region(&mut app);
798 let tracker = tracker_for(&mut app, root);
799 let log = record_events(&mut app, region);
800
801 tracker.update_with_event(&mut app, &mouse_added(Offset::new(10.0, 10.0)), None);
802 assert!(log.borrow().is_empty(), "outside the region: nothing");
803
804 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(60.0, 70.0)), None);
805 assert_eq!(*log.borrow(), ["enter Offset(10.0, 20.0)"]);
806
807 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(160.0, 70.0)), None);
808 assert_eq!(
809 *log.borrow(),
810 ["enter Offset(10.0, 20.0)", "exit Offset(110.0, 20.0)"]
811 );
812 }
813
814 #[test]
815 fn a_still_mouse_notices_the_tree_moving_under_it_after_a_frame() {
816 let cell = AppCell::new();
817 let mut app = cell.borrow_mut();
818 let RegionTree {
819 owner,
820 root,
821 padding,
822 region,
823 } = tree_with_region(&mut app);
824 let tracker = tracker_for(&mut app, root);
825 let log = record_events(&mut app, region);
826 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
827 assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
828
829 padding.set_padding(
831 &mut app,
832 inset_painting::EdgeInsetsGeometry::only(150.0, 0.0, 0.0, 0.0),
833 );
834 owner.flush_layout(&mut app);
835 tracker.update_all_devices(&mut app);
836 assert_eq!(
837 *log.borrow(),
838 ["enter Offset(50.0, 50.0)", "exit Offset(50.0, 50.0)"],
839 "the exit is placed with the transform recorded when the region was entered"
840 );
841 }
842
843 #[test]
844 fn a_region_removed_from_the_tree_sends_no_exit() {
845 let cell = AppCell::new();
846 let mut app = cell.borrow_mut();
847 let RegionTree {
848 owner,
849 root,
850 padding,
851 region,
852 } = tree_with_region(&mut app);
853 let tracker = tracker_for(&mut app, root);
854 let log = record_events(&mut app, region);
855 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
856 assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
857
858 padding.set_child(&mut app, None);
859 owner.flush_layout(&mut app);
860 tracker.update_all_devices(&mut app);
861 assert_eq!(
862 *log.borrow(),
863 ["enter Offset(50.0, 50.0)"],
864 "Flutter: a detached region is no longer valid for the tracker, so no exit is sent"
865 );
866 }
867
868 #[test]
869 fn a_detached_region_is_invalid_for_the_tracker() {
870 let cell = AppCell::new();
871 let mut app = cell.borrow_mut();
872 let region = RenderMouseRegion::new(&mut app, true, None);
873 assert!(region.valid_for_mouse_tracker(&app));
874 let owner = PipelineOwner::new(&mut app, None, None);
875 region.as_object().attach(&mut app, owner);
876 assert!(region.valid_for_mouse_tracker(&app));
877 region.as_object().detach(&mut app);
878 assert!(!region.valid_for_mouse_tracker(&app));
879 assert!(
880 !region
881 .mouse_tracker_annotation(&app)
882 .unwrap()
883 .valid_for_mouse_tracker
884 );
885 }
886
887 #[test]
888 fn a_removed_mouse_exits_and_disconnects() {
889 let cell = AppCell::new();
890 let mut app = cell.borrow_mut();
891 let RegionTree { root, region, .. } = tree_with_region(&mut app);
892 let tracker = tracker_for(&mut app, root);
893 let log = record_events(&mut app, region);
894 let notifications = Rc::new(Cell::new(0));
895 let counted = Rc::clone(¬ifications);
896 tracker.add_listener(
897 &mut app,
898 Listener::new(move |_app| counted.set(counted.get() + 1)),
899 );
900
901 assert!(!tracker.mouse_is_connected(&app));
902 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
903 assert!(tracker.mouse_is_connected(&app));
904 assert_eq!(notifications.get(), 1);
905
906 tracker.update_with_event(&mut app, &mouse_removed(Offset::new(100.0, 100.0)), None);
907 assert!(!tracker.mouse_is_connected(&app));
908 assert_eq!(notifications.get(), 2);
909 assert_eq!(
910 *log.borrow(),
911 ["enter Offset(50.0, 50.0)", "exit Offset(50.0, 50.0)"]
912 );
913 }
914
915 #[test]
916 fn touch_and_signal_events_are_ignored() {
917 let cell = AppCell::new();
918 let mut app = cell.borrow_mut();
919 let RegionTree { root, region, .. } = tree_with_region(&mut app);
920 let tracker = tracker_for(&mut app, root);
921 let log = record_events(&mut app, region);
922
923 tracker.update_with_event(
924 &mut app,
925 &PointerEvent::Hover(PointerHoverEvent {
926 position: Offset::new(100.0, 100.0),
927 ..PointerHoverEvent::default()
928 }),
929 None,
930 );
931 assert!(!tracker.mouse_is_connected(&app), "touch: not tracked");
932
933 tracker.update_with_event(
934 &mut app,
935 &PointerEvent::Scroll(PointerScrollEvent {
936 kind: PointerDeviceKind::Mouse,
937 position: Offset::new(100.0, 100.0),
938 ..PointerScrollEvent::default()
939 }),
940 None,
941 );
942 assert!(!tracker.mouse_is_connected(&app), "a signal: not tracked");
943 assert!(log.borrow().is_empty());
944 }
945
946 #[test]
947 fn the_front_regions_cursor_wins_and_deferring_regions_let_the_one_behind_decide() {
948 let (cell, cursor) = app_with_cursor_host();
949 let mut app = cell.borrow_mut();
950 let RegionTree {
951 owner,
952 root,
953 region,
954 ..
955 } = tree_with_region(&mut app);
956 region.set_cursor(&mut app, SystemMouseCursors::TEXT.into());
957 let front = RenderMouseRegion::new(&mut app, true, None);
959 region.set_child(&mut app, Some(front.as_box()));
960 owner.flush_layout(&mut app);
961 let tracker = tracker_for(&mut app, root);
962
963 tracker.update_with_event(&mut app, &mouse_added(Offset::new(100.0, 100.0)), None);
964 assert_eq!(
965 *cursor.activated.borrow(),
966 [(ViewId(0), 0, SystemMouseCursorKind::Text)]
967 );
968
969 front.set_cursor(&mut app, SystemMouseCursors::CLICK.into());
970 tracker.update_all_devices(&mut app);
971 assert_eq!(
972 *cursor.activated.borrow(),
973 [
974 (ViewId(0), 0, SystemMouseCursorKind::Text),
975 (ViewId(0), 0, SystemMouseCursorKind::Click)
976 ]
977 );
978 let click: MouseCursorRef = SystemMouseCursors::CLICK.into();
979 assert!(*tracker.debug_device_active_cursor(&app, 0).unwrap() == *click);
980
981 tracker.update_with_event(&mut app, &mouse_hover(Offset::new(10.0, 10.0)), None);
982 assert_eq!(
983 cursor.activated.borrow().last().unwrap().2,
984 SystemMouseCursorKind::Basic,
985 "over nothing, the fallback cursor"
986 );
987 }
988
989 #[test]
990 fn the_cursor_goes_to_the_view_the_pointer_was_last_seen_on() {
991 let (cell, cursor) = app_with_cursor_host();
992 let mut app = cell.borrow_mut();
993 let RegionTree { root, region, .. } = tree_with_region(&mut app);
994 region.set_cursor(&mut app, SystemMouseCursors::TEXT.into());
995 let tracker = tracker_for(&mut app, root);
996
997 let added = PointerEvent::Added(PointerAddedEvent {
998 kind: PointerDeviceKind::Mouse,
999 position: Offset::new(100.0, 100.0),
1000 view_id: ViewId(2),
1001 ..PointerAddedEvent::default()
1002 });
1003 tracker.update_with_event(&mut app, &added, None);
1004 assert_eq!(
1005 *cursor.activated.borrow(),
1006 [(ViewId(2), 0, SystemMouseCursorKind::Text)]
1007 );
1008
1009 region.set_cursor(&mut app, SystemMouseCursors::CLICK.into());
1010 tracker.update_all_devices(&mut app);
1011 assert_eq!(
1012 cursor.activated.borrow().last(),
1013 Some(&(ViewId(2), 0, SystemMouseCursorKind::Click)),
1014 "a frame-triggered update has no event of its own; the device's last one names the view"
1015 );
1016 }
1017
1018 #[test]
1019 fn an_opaque_region_hides_the_region_behind_it_and_a_transparent_one_does_not() {
1020 let cell = AppCell::new();
1021 let mut app = cell.borrow_mut();
1022 let back = RenderMouseRegion::new(&mut app, true, None);
1023 let front = RenderMouseRegion::new(&mut app, true, Some(back.as_box()));
1024 let root = RenderRepaintBoundary::new(&mut app, Some(front.as_box()));
1025 let owner = PipelineOwner::new(&mut app, None, None);
1026 owner.set_root_node(&mut app, Some(root.as_object()));
1027 root.schedule_initial_layout(&mut app);
1028 root.layout(
1029 &mut app,
1030 BoxConstraints::tight(Size::new(100.0, 100.0)),
1031 false,
1032 );
1033 let tracker = tracker_for(&mut app, root);
1034 let back_log = record_events(&mut app, back);
1035 let front_log = record_events(&mut app, front);
1036
1037 tracker.update_with_event(&mut app, &mouse_added(Offset::new(10.0, 10.0)), None);
1038 assert_eq!(*front_log.borrow(), ["enter Offset(10.0, 10.0)"]);
1039 assert_eq!(
1040 *back_log.borrow(),
1041 ["enter Offset(10.0, 10.0)"],
1042 "the front region's hit test still visits its child; opacity only stops what is behind the region in the parent's hit test"
1043 );
1044 }
1045
1046 #[test]
1047 fn hover_reaches_the_region_through_handle_event() {
1048 let cell = AppCell::new();
1049 let mut app = cell.borrow_mut();
1050 let region = RenderMouseRegion::new(&mut app, true, None);
1051 let log = record_events(&mut app, region);
1052 let event = mouse_hover(Offset::new(3.0, 4.0));
1053 let entry = BoxHitTestEntry::new(region.as_box(), Offset::new(3.0, 4.0));
1054 region.handle_event(&mut app, &event, &entry);
1055 assert_eq!(*log.borrow(), ["hover Offset(3.0, 4.0)"]);
1056 }
1057
1058 #[test]
1059 fn an_opaque_region_absorbs_the_hit_test_for_what_is_behind_it() {
1060 let cell = AppCell::new();
1061 let mut app = cell.borrow_mut();
1062 let region = RenderMouseRegion::new(&mut app, true, None);
1063 let root = RenderRepaintBoundary::new(&mut app, Some(region.as_box()));
1064 let owner = PipelineOwner::new(&mut app, None, None);
1065 owner.set_root_node(&mut app, Some(root.as_object()));
1066 root.schedule_initial_layout(&mut app);
1067 root.layout(
1068 &mut app,
1069 BoxConstraints::tight(Size::new(100.0, 100.0)),
1070 false,
1071 );
1072
1073 let mut result = HitTestResult::new();
1074 assert!(region.hit_test(
1075 &mut app,
1076 &mut BoxHitTestResult::wrap(&mut result),
1077 Offset::new(5.0, 5.0)
1078 ));
1079 region.set_opaque(&mut app, false);
1080 let mut result = HitTestResult::new();
1081 assert!(!region.hit_test(
1082 &mut app,
1083 &mut BoxHitTestResult::wrap(&mut result),
1084 Offset::new(5.0, 5.0)
1085 ));
1086 assert_eq!(
1087 result.path().len(),
1088 1,
1089 "the region is still on the path; it just does not claim the hit"
1090 );
1091 }
1092
1093 #[test]
1094 fn a_region_with_no_annotation_neighbours_still_uses_the_fallback_cursor() {
1095 let (cell, cursor) = app_with_cursor_host();
1096 let mut app = cell.borrow_mut();
1097 let child =
1098 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
1099 let root = RenderRepaintBoundary::new(&mut app, Some(child.as_box()));
1100 let owner = PipelineOwner::new(&mut app, None, None);
1101 owner.set_root_node(&mut app, Some(root.as_object()));
1102 root.schedule_initial_layout(&mut app);
1103 root.layout(
1104 &mut app,
1105 BoxConstraints::tight(Size::new(100.0, 100.0)),
1106 false,
1107 );
1108 let tracker = tracker_for(&mut app, root);
1109 tracker.update_with_event(&mut app, &mouse_added(Offset::new(5.0, 5.0)), None);
1110 assert_eq!(
1111 *cursor.activated.borrow(),
1112 [(ViewId(0), 0, SystemMouseCursorKind::Basic)]
1113 );
1114 }
1115}