Unverified Commit fb0b9823 authored by Tong Mu's avatar Tong Mu Committed by GitHub

Change MouseTracker's interface for clarity. Simplify MouseRegion's implementation. (#64119)

* Redesigns the interface between MouseTracker and RendererBinding&RenderView.
* Simplifies the structure of RenderMouseRegion.
* Extracts the common utility code between mouse_tracker_test and mouse_tracker_cursor_test.
parent 1fc3a5e4
...@@ -173,8 +173,9 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H ...@@ -173,8 +173,9 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
@override // from HitTestDispatcher @override // from HitTestDispatcher
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) { void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
assert(!locked); assert(!locked);
// No hit test information implies that this is a hover or pointer // No hit test information implies that this is a pointer hover or
// add/remove event. // add/remove event. These events are specially routed here; other events
// will be routed through the `handleEvent` below.
if (hitTestResult == null) { if (hitTestResult == null) {
assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent); assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent);
try { try {
......
...@@ -869,7 +869,7 @@ class PointerHoverEvent extends PointerEvent { ...@@ -869,7 +869,7 @@ class PointerHoverEvent extends PointerEvent {
/// * [PointerExitEvent], which reports when the pointer has left an object. /// * [PointerExitEvent], which reports when the pointer has left an object.
/// * [PointerMoveEvent], which reports movement while the pointer is in /// * [PointerMoveEvent], which reports movement while the pointer is in
/// contact with the device. /// contact with the device.
/// * [Listener.onPointerEnter], which allows callers to be notified of these /// * [MouseRegion.onEnter], which allows callers to be notified of these
/// events in a widget tree. /// events in a widget tree.
class PointerEnterEvent extends PointerEvent { class PointerEnterEvent extends PointerEvent {
/// Creates a pointer enter event. /// Creates a pointer enter event.
...@@ -1020,7 +1020,7 @@ class PointerEnterEvent extends PointerEvent { ...@@ -1020,7 +1020,7 @@ class PointerEnterEvent extends PointerEvent {
/// * [PointerEnterEvent], which reports when the pointer has entered an object. /// * [PointerEnterEvent], which reports when the pointer has entered an object.
/// * [PointerMoveEvent], which reports movement while the pointer is in /// * [PointerMoveEvent], which reports movement while the pointer is in
/// contact with the device. /// contact with the device.
/// * [Listener.onPointerExit], which allows callers to be notified of these /// * [MouseRegion.onExit], which allows callers to be notified of these
/// events in a widget tree. /// events in a widget tree.
class PointerExitEvent extends PointerEvent { class PointerExitEvent extends PointerEvent {
/// Creates a pointer exit event. /// Creates a pointer exit event.
......
...@@ -248,7 +248,19 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture ...@@ -248,7 +248,19 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
@visibleForTesting @visibleForTesting
void initMouseTracker([MouseTracker tracker]) { void initMouseTracker([MouseTracker tracker]) {
_mouseTracker?.dispose(); _mouseTracker?.dispose();
_mouseTracker = tracker ?? MouseTracker(pointerRouter, renderView.hitTestMouseTrackers); _mouseTracker = tracker ?? MouseTracker();
}
@override // from GestureBinding
void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
if (hitTestResult != null ||
event is PointerHoverEvent ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
_mouseTracker.updateWithEvent(event,
() => hitTestResult ?? renderView.hitTestMouseTrackers(event.position));
}
super.dispatchEvent(event, hitTestResult);
} }
void _handleSemanticsEnabledChanged() { void _handleSemanticsEnabledChanged() {
...@@ -284,7 +296,24 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture ...@@ -284,7 +296,24 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
void _handlePersistentFrameCallback(Duration timeStamp) { void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame(); drawFrame();
_mouseTracker.schedulePostFrameCheck(); _scheduleMouseTrackerUpdate();
}
bool _debugMouseTrackerUpdateScheduled = false;
void _scheduleMouseTrackerUpdate() {
assert(!_debugMouseTrackerUpdateScheduled);
assert(() {
_debugMouseTrackerUpdateScheduled = true;
return true;
}());
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
assert(_debugMouseTrackerUpdateScheduled);
assert(() {
_debugMouseTrackerUpdateScheduled = false;
return true;
}());
_mouseTracker.updateAllDevices(renderView.hitTestMouseTrackers);
});
} }
int _firstFrameDeferredCount = 0; int _firstFrameDeferredCount = 0;
......
...@@ -12,7 +12,6 @@ import 'package:flutter/gestures.dart'; ...@@ -12,7 +12,6 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/semantics.dart'; import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'binding.dart';
import 'box.dart'; import 'box.dart';
import 'layer.dart'; import 'layer.dart';
import 'mouse_cursor.dart'; import 'mouse_cursor.dart';
...@@ -668,7 +667,7 @@ mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation { ...@@ -668,7 +667,7 @@ mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation {
if (value != _hitTestBehavior) { if (value != _hitTestBehavior) {
_hitTestBehavior = value; _hitTestBehavior = value;
if (owner != null) if (owner != null)
RendererBinding.instance.mouseTracker.schedulePostFrameCheck(); markNeedsPaint();
} }
} }
PlatformViewHitTestBehavior _hitTestBehavior; PlatformViewHitTestBehavior _hitTestBehavior;
......
...@@ -2762,20 +2762,16 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation ...@@ -2762,20 +2762,16 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
/// mouse region with no callbacks and cursor being [MouseCursor.defer]. The /// mouse region with no callbacks and cursor being [MouseCursor.defer]. The
/// [cursor] must not be null. /// [cursor] must not be null.
RenderMouseRegion({ RenderMouseRegion({
PointerEnterEventListener onEnter, this.onEnter,
PointerHoverEventListener onHover, this.onHover,
PointerExitEventListener onExit, this.onExit,
MouseCursor cursor = MouseCursor.defer, MouseCursor cursor = MouseCursor.defer,
bool opaque = true, bool opaque = true,
RenderBox child, RenderBox child,
}) : assert(opaque != null), }) : assert(opaque != null),
assert(cursor != null), assert(cursor != null),
_onEnter = onEnter,
_onHover = onHover,
_onExit = onExit,
_cursor = cursor, _cursor = cursor,
_opaque = opaque, _opaque = opaque,
_annotationIsActive = false,
super(child); super(child);
@protected @protected
...@@ -2787,6 +2783,13 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation ...@@ -2787,6 +2783,13 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
return super.hitTest(result, position: position) && _opaque; return super.hitTest(result, position: position) && _opaque;
} }
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (onHover != null && event is PointerHoverEvent)
return onHover(event);
}
/// Whether this object should prevent [RenderMouseRegion]s visually behind it /// Whether this object should prevent [RenderMouseRegion]s visually behind it
/// from detecting the pointer, thus affecting how their [onHover], [onEnter], /// from detecting the pointer, thus affecting how their [onHover], [onEnter],
/// and [onExit] behave. /// and [onExit] behave.
...@@ -2806,41 +2809,19 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation ...@@ -2806,41 +2809,19 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
set opaque(bool value) { set opaque(bool value) {
if (_opaque != value) { if (_opaque != value) {
_opaque = value; _opaque = value;
// A repaint is needed in order to propagate the new value to // Trigger [MouseTracker]'s device update to recalculate mouse states.
// AnnotatedRegionLayer via [paint]. markNeedsPaint();
_markPropertyUpdated(mustRepaint: true);
} }
} }
@override @override
PointerEnterEventListener get onEnter => _onEnter; PointerEnterEventListener onEnter;
PointerEnterEventListener _onEnter;
set onEnter(PointerEnterEventListener value) {
if (_onEnter != value) {
_onEnter = value;
_markPropertyUpdated(mustRepaint: false);
}
}
@override @override
PointerHoverEventListener get onHover => _onHover; PointerHoverEventListener onHover;
PointerHoverEventListener _onHover;
set onHover(PointerHoverEventListener value) {
if (_onHover != value) {
_onHover = value;
_markPropertyUpdated(mustRepaint: false);
}
}
@override @override
PointerExitEventListener get onExit => _onExit; PointerExitEventListener onExit;
PointerExitEventListener _onExit;
set onExit(PointerExitEventListener value) {
if (_onExit != value) {
_onExit = value;
_markPropertyUpdated(mustRepaint: false);
}
}
@override @override
MouseCursor get cursor => _cursor; MouseCursor get cursor => _cursor;
...@@ -2850,61 +2831,10 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation ...@@ -2850,61 +2831,10 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
_cursor = value; _cursor = value;
// A repaint is needed in order to trigger a device update of // A repaint is needed in order to trigger a device update of
// [MouseTracker] so that this new value can be found. // [MouseTracker] so that this new value can be found.
_markPropertyUpdated(mustRepaint: true);
}
}
// Call this method when a property has changed and might affect the
// `_annotationIsActive` bit.
//
// If `mustRepaint` is false, this method does NOT call `markNeedsPaint`
// unless the `_annotationIsActive` bit is changed. If there is a property
// that needs updating while `_annotationIsActive` stays true, make
// `mustRepaint` true.
//
// This method must not be called during `paint`.
void _markPropertyUpdated({@required bool mustRepaint}) {
assert(owner == null || !owner.debugDoingPaint);
final bool newAnnotationIsActive = (
_onEnter != null ||
_onHover != null ||
_onExit != null ||
_cursor != MouseCursor.defer ||
opaque
) && RendererBinding.instance.mouseTracker.mouseIsConnected;
_setAnnotationIsActive(newAnnotationIsActive);
if (mustRepaint)
markNeedsPaint(); markNeedsPaint();
}
bool _annotationIsActive = false;
void _setAnnotationIsActive(bool value) {
final bool annotationWasActive = _annotationIsActive;
_annotationIsActive = value;
if (annotationWasActive != value) {
markNeedsPaint();
markNeedsCompositingBitsUpdate();
} }
} }
void _handleUpdatedMouseIsConnected() {
_markPropertyUpdated(mustRepaint: false);
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
// Add a listener to listen for changes in mouseIsConnected.
RendererBinding.instance.mouseTracker.addListener(_handleUpdatedMouseIsConnected);
_markPropertyUpdated(mustRepaint: false);
}
@override
void detach() {
RendererBinding.instance.mouseTracker.removeListener(_handleUpdatedMouseIsConnected);
super.detach();
}
@override @override
void performResize() { void performResize() {
size = constraints.biggest; size = constraints.biggest;
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
// @dart = 2.8 // @dart = 2.8
import 'dart:collection' show LinkedHashMap;
import 'dart:developer'; import 'dart:developer';
import 'dart:io' show Platform; import 'dart:io' show Platform;
import 'dart:ui' as ui show Scene, SceneBuilder, Window; import 'dart:ui' as ui show Scene, SceneBuilder, Window;
...@@ -18,7 +17,6 @@ import 'binding.dart'; ...@@ -18,7 +17,6 @@ import 'binding.dart';
import 'box.dart'; import 'box.dart';
import 'debug.dart'; import 'debug.dart';
import 'layer.dart'; import 'layer.dart';
import 'mouse_tracking.dart';
import 'object.dart'; import 'object.dart';
/// The layout constraints for the root render object. /// The layout constraints for the root render object.
...@@ -199,22 +197,13 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> ...@@ -199,22 +197,13 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
/// ///
/// * [Layer.findAllAnnotations], which is used by this method to find all /// * [Layer.findAllAnnotations], which is used by this method to find all
/// [AnnotatedRegionLayer]s annotated for mouse tracking. /// [AnnotatedRegionLayer]s annotated for mouse tracking.
LinkedHashMap<MouseTrackerAnnotation, Matrix4> hitTestMouseTrackers(Offset position) { HitTestResult hitTestMouseTrackers(Offset position) {
// Layer hit testing is done using device pixels, so we have to convert // Layer hit testing is done using device pixels, so we have to convert
// the logical coordinates of the event location back to device pixels // the logical coordinates of the event location back to device pixels
// here. // here.
final BoxHitTestResult result = BoxHitTestResult(); final BoxHitTestResult result = BoxHitTestResult();
if (child != null) hitTest(result, position: position);
child.hitTest(result, position: position); return result;
result.add(HitTestEntry(this));
final LinkedHashMap<MouseTrackerAnnotation, Matrix4> annotations = <MouseTrackerAnnotation, Matrix4>{}
as LinkedHashMap<MouseTrackerAnnotation, Matrix4>;
for (final HitTestEntry entry in result.path) {
if (entry.target is MouseTrackerAnnotation) {
annotations[entry.target as MouseTrackerAnnotation] = entry.transform;
}
}
return annotations;
} }
@override @override
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:vector_math/vector_math_64.dart' show Matrix4;
class _TestHitTester extends RenderBox {
_TestHitTester(this.hitTestOverride);
final BoxHitTest hitTestOverride;
@override
bool hitTest(BoxHitTestResult result, {ui.Offset position}) {
return hitTestOverride(result, position);
}
}
// A binding used to test MouseTracker, allowing the test to override hit test
// searching.
class TestMouseTrackerFlutterBinding extends BindingBase
with SchedulerBinding, ServicesBinding, GestureBinding, SemanticsBinding, RendererBinding {
@override
void initInstances() {
super.initInstances();
postFrameCallbacks = <void Function(Duration)>[];
}
void setHitTest(BoxHitTest hitTest) {
renderView.child = _TestHitTester(hitTest);
}
SchedulerPhase _overridePhase;
@override
SchedulerPhase get schedulerPhase => _overridePhase ?? super.schedulerPhase;
// Manually schedule a post-frame check.
//
// In real apps this is done by the renderer binding, but in tests we have to
// bypass the phase assertion of [MouseTracker.schedulePostFrameCheck].
void scheduleMouseTrackerPostFrameCheck() {
final SchedulerPhase lastPhase = _overridePhase;
_overridePhase = SchedulerPhase.persistentCallbacks;
addPostFrameCallback((_) {
mouseTracker.updateAllDevices(renderView.hitTestMouseTrackers);
});
_overridePhase = lastPhase;
}
List<void Function(Duration)> postFrameCallbacks;
// Proxy post-frame callbacks.
@override
void addPostFrameCallback(void Function(Duration) callback) {
postFrameCallbacks.add(callback);
}
void flushPostFrameCallbacks(Duration duration) {
for (final void Function(Duration) callback in postFrameCallbacks) {
callback(duration);
}
postFrameCallbacks.clear();
}
}
// An object that mocks the behavior of a render object with [MouseTrackerAnnotation].
class TestAnnotationTarget with Diagnosticable implements MouseTrackerAnnotation, HitTestTarget {
const TestAnnotationTarget({this.onEnter, this.onHover, this.onExit, this.cursor = MouseCursor.defer});
@override
final PointerEnterEventListener onEnter;
@override
final PointerHoverEventListener onHover;
@override
final PointerExitEventListener onExit;
@override
final MouseCursor cursor;
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
if (event is PointerHoverEvent)
if (onHover != null)
onHover(event);
}
}
// A hit test entry that can be assigned with a [TestAnnotationTarget] and an
// optional transform matrix.
class TestAnnotationEntry extends HitTestEntry {
TestAnnotationEntry(TestAnnotationTarget target, [Matrix4 transform])
: transform = transform ?? Matrix4.identity(), super(target);
@override
final Matrix4 transform;
}
...@@ -4,12 +4,13 @@ ...@@ -4,12 +4,13 @@
// @dart = 2.8 // @dart = 2.8
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../gestures/gesture_tester.dart';
import '../services/fake_platform_views.dart'; import '../services/fake_platform_views.dart';
import 'rendering_tester.dart'; import 'rendering_tester.dart';
...@@ -73,16 +74,33 @@ void main() { ...@@ -73,16 +74,33 @@ void main() {
semanticsHandle.dispose(); semanticsHandle.dispose();
}); });
testGesture('hover events are dispatched via PlatformViewController.dispatchPointerEvent', (GestureTester tester) { test('mouse hover events are dispatched via PlatformViewController.dispatchPointerEvent', () {
layout(platformViewRenderBox); layout(platformViewRenderBox);
pumpFrame(phase: EnginePhase.flushSemantics); pumpFrame(phase: EnginePhase.flushSemantics);
final TestPointer pointer = TestPointer(1, PointerDeviceKind.mouse); ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
tester.route(pointer.addPointer()); _pointerData(ui.PointerChange.add, const Offset(0, 0)),
tester.route(pointer.hover(const Offset(10, 10))); _pointerData(ui.PointerChange.hover, const Offset(10, 10)),
_pointerData(ui.PointerChange.remove, const Offset(10, 10)),
]));
expect(fakePlatformViewController.dispatchedPointerEvents, isNotEmpty); expect(fakePlatformViewController.dispatchedPointerEvents, isNotEmpty);
}); });
}, skip: isBrowser); // TODO(yjbanov): fails on Web with obscured stack trace: https://github.com/flutter/flutter/issues/42770 }, skip: isBrowser); // TODO(yjbanov): fails on Web with obscured stack trace: https://github.com/flutter/flutter/issues/42770
} }
ui.PointerData _pointerData(
ui.PointerChange change,
Offset logicalPosition, {
int device = 0,
PointerDeviceKind kind = PointerDeviceKind.mouse,
}) {
return ui.PointerData(
change: change,
physicalX: logicalPosition.dx * ui.window.devicePixelRatio,
physicalY: logicalPosition.dy * ui.window.devicePixelRatio,
kind: kind,
device: device,
);
}
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
// @dart = 2.8 // @dart = 2.8
import 'dart:collection' show LinkedHashMap;
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui show Gradient, Image, ImageFilter; import 'dart:ui' as ui show Gradient, Image, ImageFilter;
...@@ -491,10 +490,6 @@ void main() { ...@@ -491,10 +490,6 @@ void main() {
}); });
test('RenderMouseRegion can change properties when detached', () { test('RenderMouseRegion can change properties when detached', () {
renderer.initMouseTracker(MouseTracker(
renderer.pointerRouter,
(_) => <MouseTrackerAnnotation, Matrix4>{} as LinkedHashMap<MouseTrackerAnnotation, Matrix4>,
));
final RenderMouseRegion object = RenderMouseRegion(); final RenderMouseRegion object = RenderMouseRegion();
object object
..opaque = false ..opaque = false
......
...@@ -601,6 +601,8 @@ void main() { ...@@ -601,6 +601,8 @@ void main() {
// Start outside, move inside, then move outside // Start outside, move inside, then move outside
await gesture.moveTo(const Offset(150.0, 150.0)); await gesture.moveTo(const Offset(150.0, 150.0));
await tester.pump(); await tester.pump();
expect(logs, isEmpty);
logs.clear();
await gesture.moveTo(const Offset(50.0, 50.0)); await gesture.moveTo(const Offset(50.0, 50.0));
await tester.pump(); await tester.pump();
await gesture.moveTo(const Offset(150.0, 150.0)); await gesture.moveTo(const Offset(150.0, 150.0));
...@@ -1106,14 +1108,15 @@ void main() { ...@@ -1106,14 +1108,15 @@ void main() {
// Same as MouseRegion, but when opaque is null, use the default value. // Same as MouseRegion, but when opaque is null, use the default value.
Widget mouseRegionWithOptionalOpaque({ Widget mouseRegionWithOptionalOpaque({
void Function(PointerEnterEvent e) onEnter, void Function(PointerEnterEvent e) onEnter,
void Function(PointerHoverEvent e) onHover,
void Function(PointerExitEvent e) onExit, void Function(PointerExitEvent e) onExit,
Widget child, Widget child,
bool opaque, bool opaque,
}) { }) {
if (opaque == null) { if (opaque == null) {
return MouseRegion(onEnter: onEnter, onExit: onExit, child: child); return MouseRegion(onEnter: onEnter, onHover: onHover, onExit: onExit, child: child);
} }
return MouseRegion(onEnter: onEnter, onExit: onExit, child: child, opaque: opaque); return MouseRegion(onEnter: onEnter, onHover: onHover, onExit: onExit, child: child, opaque: opaque);
} }
return Directionality( return Directionality(
...@@ -1122,6 +1125,7 @@ void main() { ...@@ -1122,6 +1125,7 @@ void main() {
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: MouseRegion( child: MouseRegion(
onEnter: (PointerEnterEvent e) { addLog('enterA'); }, onEnter: (PointerEnterEvent e) { addLog('enterA'); },
onHover: (PointerHoverEvent e) { addLog('hoverA'); },
onExit: (PointerExitEvent e) { addLog('exitA'); }, onExit: (PointerExitEvent e) { addLog('exitA'); },
child: SizedBox( child: SizedBox(
width: 150, width: 150,
...@@ -1135,6 +1139,7 @@ void main() { ...@@ -1135,6 +1139,7 @@ void main() {
height: 80, height: 80,
child: MouseRegion( child: MouseRegion(
onEnter: (PointerEnterEvent e) { addLog('enterB'); }, onEnter: (PointerEnterEvent e) { addLog('enterB'); },
onHover: (PointerHoverEvent e) { addLog('hoverB'); },
onExit: (PointerExitEvent e) { addLog('exitB'); }, onExit: (PointerExitEvent e) { addLog('exitB'); },
), ),
), ),
...@@ -1146,6 +1151,7 @@ void main() { ...@@ -1146,6 +1151,7 @@ void main() {
child: mouseRegionWithOptionalOpaque( child: mouseRegionWithOptionalOpaque(
opaque: opaqueC, opaque: opaqueC,
onEnter: (PointerEnterEvent e) { addLog('enterC'); }, onEnter: (PointerEnterEvent e) { addLog('enterC'); },
onHover: (PointerHoverEvent e) { addLog('hoverC'); },
onExit: (PointerExitEvent e) { addLog('exitC'); }, onExit: (PointerExitEvent e) { addLog('exitC'); },
), ),
), ),
...@@ -1172,31 +1178,31 @@ void main() { ...@@ -1172,31 +1178,31 @@ void main() {
// Move to the overlapping area. // Move to the overlapping area.
await gesture.moveTo(const Offset(75, 75)); await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>['enterA', 'enterB', 'enterC']); expect(logs, <String>['enterA', 'enterB', 'enterC', 'hoverA', 'hoverB', 'hoverC']);
logs.clear(); logs.clear();
// Move to the B only area. // Move to the B only area.
await gesture.moveTo(const Offset(25, 75)); await gesture.moveTo(const Offset(25, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>['exitC']); expect(logs, <String>['exitC', 'hoverA', 'hoverB']);
logs.clear(); logs.clear();
// Move back to the overlapping area. // Move back to the overlapping area.
await gesture.moveTo(const Offset(75, 75)); await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>['enterC']); expect(logs, <String>['enterC', 'hoverA', 'hoverB', 'hoverC']);
logs.clear(); logs.clear();
// Move to the C only area. // Move to the C only area.
await gesture.moveTo(const Offset(125, 75)); await gesture.moveTo(const Offset(125, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>['exitB']); expect(logs, <String>['exitB', 'hoverA', 'hoverC']);
logs.clear(); logs.clear();
// Move back to the overlapping area. // Move back to the overlapping area.
await gesture.moveTo(const Offset(75, 75)); await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>['enterB']); expect(logs, <String>['enterB', 'hoverA', 'hoverB', 'hoverC']);
logs.clear(); logs.clear();
// Move out. // Move out.
...@@ -1220,31 +1226,31 @@ void main() { ...@@ -1220,31 +1226,31 @@ void main() {
// Move to the overlapping area. // Move to the overlapping area.
await gesture.moveTo(const Offset(75, 75)); await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>['enterA', 'enterC']); expect(logs, <String>['enterA', 'enterC', 'hoverA', 'hoverC']);
logs.clear(); logs.clear();
// Move to the B only area. // Move to the B only area.
await gesture.moveTo(const Offset(25, 75)); await gesture.moveTo(const Offset(25, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>['exitC', 'enterB']); expect(logs, <String>['exitC', 'enterB', 'hoverA', 'hoverB']);
logs.clear(); logs.clear();
// Move back to the overlapping area. // Move back to the overlapping area.
await gesture.moveTo(const Offset(75, 75)); await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>['exitB', 'enterC']); expect(logs, <String>['exitB', 'enterC', 'hoverA', 'hoverC']);
logs.clear(); logs.clear();
// Move to the C only area. // Move to the C only area.
await gesture.moveTo(const Offset(125, 75)); await gesture.moveTo(const Offset(125, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>[]); expect(logs, <String>['hoverA', 'hoverC']);
logs.clear(); logs.clear();
// Move back to the overlapping area. // Move back to the overlapping area.
await gesture.moveTo(const Offset(75, 75)); await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>[]); expect(logs, <String>['hoverA', 'hoverC']);
logs.clear(); logs.clear();
// Move out. // Move out.
...@@ -1268,7 +1274,7 @@ void main() { ...@@ -1268,7 +1274,7 @@ void main() {
// Move to the overlapping area. // Move to the overlapping area.
await gesture.moveTo(const Offset(75, 75)); await gesture.moveTo(const Offset(75, 75));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logs, <String>['enterA', 'enterC']); expect(logs, <String>['enterA', 'enterC', 'hoverA', 'hoverC']);
logs.clear(); logs.clear();
// Move out. // Move out.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment