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

Expose GestureBinding.handlePointerEvent, replacing dispatchEvent as the...

Expose GestureBinding.handlePointerEvent, replacing dispatchEvent as the preferred way to dispatch events (#64846)
parent ea039ed3
......@@ -16,17 +16,6 @@ import 'package:e2e/e2e.dart';
import 'package:complex_layout/main.dart' as app;
class PointerDataTestBinding extends E2EWidgetsFlutterBinding {
// PointerData injection would usually be considered device input and therefore
// blocked by [TestWidgetsFlutterBinding]. Override this behavior
// to help events go into widget tree.
@override
void dispatchEvent(
PointerEvent event,
HitTestResult hitTestResult, {
TestBindingEventSource source = TestBindingEventSource.device,
}) {
super.dispatchEvent(event, hitTestResult, source: TestBindingEventSource.test);
}
}
/// A union of [ui.PointerDataPacket] and the time it should be sent.
......
......@@ -144,13 +144,8 @@ class _Tester {
TestGesture get gesture {
return _gesture ??= TestGesture(
dispatcher: (PointerEvent event, HitTestResult result) async {
RendererBinding.instance.dispatchEvent(event, result);
},
hitTester: (Offset location) {
final HitTestResult result = HitTestResult();
RendererBinding.instance.hitTest(result, location);
return result;
dispatcher: (PointerEvent event) async {
RendererBinding.instance.handlePointerEvent(event);
},
kind: PointerDeviceKind.mouse,
);
......
......@@ -116,13 +116,8 @@ class _Tester {
TestGesture get gesture {
return _gesture ??= TestGesture(
dispatcher: (PointerEvent event, HitTestResult result) async {
RendererBinding.instance.dispatchEvent(event, result);
},
hitTester: (Offset location) {
final HitTestResult result = HitTestResult();
RendererBinding.instance.hitTest(result, location);
return result;
dispatcher: (PointerEvent event) async {
RendererBinding.instance.handlePointerEvent(event);
},
kind: PointerDeviceKind.mouse,
);
......
......@@ -166,13 +166,8 @@ class _Tester {
TestGesture get gesture {
return _gesture ??= TestGesture(
dispatcher: (PointerEvent event, HitTestResult result) async {
RendererBinding.instance.dispatchEvent(event, result);
},
hitTester: (Offset location) {
final HitTestResult result = HitTestResult();
RendererBinding.instance.hitTest(result, location);
return result;
dispatcher: (PointerEvent event) async {
RendererBinding.instance.handlePointerEvent(event);
},
kind: PointerDeviceKind.mouse,
);
......
......@@ -176,9 +176,9 @@ const Duration _defaultSamplingOffset = Duration(milliseconds: -38);
///
/// A pointer that is [PointerEvent.down] may send further events, such as
/// [PointerMoveEvent], [PointerUpEvent], or [PointerCancelEvent]. These are
/// sent to the same [HitTestTarget] nodes as were found when the down event was
/// received (even if they have since been disposed; it is the responsibility of
/// those objects to be aware of that possibility).
/// sent to the same [HitTestTarget] nodes as were found when the
/// [PointerDownEvent] was received (even if they have since been disposed; it is
/// the responsibility of those objects to be aware of that possibility).
///
/// Then, the events are routed to any still-registered entrants in the
/// [PointerRouter]'s table for that pointer.
......@@ -237,7 +237,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
_resampler.stop();
while (_pendingPointerEvents.isNotEmpty)
_handlePointerEvent(_pendingPointerEvents.removeFirst());
handlePointerEvent(_pendingPointerEvents.removeFirst());
}
/// A router that routes all pointer events received from the engine.
......@@ -247,8 +247,8 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
/// pointer events.
final GestureArenaManager gestureArena = GestureArenaManager();
/// The resolver used for determining which widget handles a pointer
/// signal event.
/// The resolver used for determining which widget handles a
/// [PointerSignalEvent].
final PointerSignalResolver pointerSignalResolver = PointerSignalResolver();
/// State for all pointers which are currently down.
......@@ -257,7 +257,17 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
/// hit-testing on every frame.
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
void _handlePointerEvent(PointerEvent event) {
/// Dispatch an event to the targets found by a hit test on its position.
///
/// This method sends the given event to [dispatchEvent] based on event types:
///
/// * [PointerDownEvent]s and [PointerSignalEvent]s are dispatched to the
/// result of a new [hitTest].
/// * [PointerUpEvent]s and [PointerMoveEvent]s are dispatched to the result of hit test of the
/// preceding [PointerDownEvent]s.
/// * [PointerHoverEvent]s, [PointerAddedEvent]s, and [PointerRemovedEvent]s
/// are dispatched without a hit test result.
void handlePointerEvent(PointerEvent event) {
assert(!locked);
HitTestResult? hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent) {
......@@ -276,7 +286,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
hitTestResult = _hitTests.remove(event.pointer);
} else if (event.down) {
// Because events that occur with the pointer down (like
// PointerMoveEvents) should be dispatched to the same place that their
// [PointerMoveEvent]s) should be dispatched to the same place that their
// initial PointerDownEvent was, we want to re-use the path we found when
// the pointer went down, rather than do hit detection each time we get
// such an event.
......@@ -302,18 +312,20 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
result.add(HitTestEntry(this));
}
/// Dispatch an event to a hit test result's path.
/// Dispatch an event to [pointerRouter] and the path of a hit test result.
///
/// This sends the given event to every [HitTestTarget] in the entries of the
/// given [HitTestResult], and catches exceptions that any of the handlers
/// might throw. The [hitTestResult] argument may only be null for
/// [PointerHoverEvent], [PointerAddedEvent], or [PointerRemovedEvent] events.
/// The `event` is routed to [pointerRouter]. If the `hitTestResult` is not
/// null, the event is also sent to every [HitTestTarget] in the entries of the
/// given [HitTestResult]. Any exceptions from the handlers are caught.
///
/// The `hitTestResult` argument may only be null for [PointerHoverEvent]s,
/// [PointerAddedEvent]s, or [PointerRemovedEvent]s.
@override // from HitTestDispatcher
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
assert(!locked);
// No hit test information implies that this is a pointer hover or
// add/remove event. These events are specially routed here; other events
// will be routed through the `handleEvent` below.
// No hit test information implies that this is a [PointerHoverEvent],
// [PointerAddedEvent], or [PointerRemovedEvent]. These events are specially
// routed here; other events will be routed through the `handleEvent` below.
if (hitTestResult == null) {
assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent);
try {
......@@ -365,6 +377,16 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
}
}
/// Reset states of [GestureBinding].
///
/// This clears the hit test records.
///
/// This is typically called between tests.
@protected
void resetGestureBinding() {
_hitTests.clear();
}
void _handleSampleTimeChanged() {
if (!locked) {
_flushPointerEventQueue();
......@@ -374,7 +396,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
// Resampler used to filter incoming pointer events when resampling
// is enabled.
late final _Resampler _resampler = _Resampler(
_handlePointerEvent,
handlePointerEvent,
_handleSampleTimeChanged,
);
......@@ -429,7 +451,8 @@ class FlutterErrorDetailsForPointerEventDispatcher extends FlutterErrorDetails {
/// The hit test result entry for the object whose handleEvent method threw
/// the exception. May be null if no hit test entry is associated with the
/// event (e.g. hover and pointer add/remove events).
/// event (e.g. [PointerHoverEvent]s, [PointerAddedEvent]s, and
/// [PointerRemovedEvent]s).
///
/// The target object itself is given by the [HitTestEntry.target] property of
/// the hitTestEntry object.
......
......@@ -6,6 +6,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -1471,6 +1472,25 @@ void main() {
expect(find.text('first', skipOffstage: false), findsOneWidget);
expect(find.text('second'), findsOneWidget);
});
testWidgets('Popping routes should cancel down events', (WidgetTester tester) async {
await tester.pumpWidget(_TestPostRouteCancel());
final TestGesture gesture = await tester.createGesture();
await gesture.down(tester.getCenter(find.text('PointerCancelEvents: 0')));
await gesture.up();
await tester.pumpAndSettle();
expect(find.byType(CupertinoButton), findsNothing);
expect(find.text('Hold'), findsOneWidget);
await gesture.down(tester.getCenter(find.text('Hold')));
await tester.pump(const Duration(seconds: 2));
await tester.pumpAndSettle();
expect(find.text('Hold'), findsNothing);
expect(find.byType(CupertinoButton), findsOneWidget);
expect(find.text('PointerCancelEvents: 1'), findsOneWidget);
});
}
class MockNavigatorObserver extends NavigatorObserver {
......@@ -1571,3 +1591,75 @@ Widget buildNavigator({
),
);
}
// A test target for post-route cancel events.
//
// It contains 2 routes:
//
// * The initial route, 'home', displays a button showing 'PointerCancelEvents: #',
// where # is the number of cancel events received. Tapping the button pushes
// route 'sub'.
// * The 'sub' route, displays a text showing 'Hold'. Holding the button (a down
// event) will pop this route after 1 second.
//
// Holding the 'Hold' button at the moment of popping will force the navigator to
// cancel the down event, increasing the Home counter by 1.
class _TestPostRouteCancel extends StatefulWidget {
@override
State<StatefulWidget> createState() => _TestPostRouteCancelState();
}
class _TestPostRouteCancelState extends State<_TestPostRouteCancel> {
int counter = 0;
Widget _buildHome(BuildContext context) {
return Center(
child: CupertinoButton(
child: Text('PointerCancelEvents: $counter'),
onPressed: () => Navigator.pushNamed<void>(context, 'sub'),
),
);
}
Widget _buildSub(BuildContext context) {
return Listener(
onPointerDown: (_) {
Future<void>.delayed(const Duration(seconds: 1)).then((_) {
Navigator.pop(context);
});
},
onPointerCancel: (_) {
setState(() {
counter += 1;
});
},
child: const Center(
child: Text('Hold', style: TextStyle(color: Colors.blue)),
),
);
}
@override
Widget build(BuildContext context) {
return CupertinoApp(
initialRoute: 'home',
onGenerateRoute: (RouteSettings settings) {
return CupertinoPageRoute<void>(
settings: settings,
builder: (BuildContext context) {
switch (settings.name) {
case 'home':
return _buildHome(context);
case 'sub':
return _buildSub(context);
default:
throw UnimplementedError();
}
},
);
},
);
}
}
......@@ -16,22 +16,8 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
class PointerDataAutomatedTestWidgetsFlutterBinding extends AutomatedTestWidgetsFlutterBinding {
// PointerData injection would usually considerred device input and therefore
// blocked by [AutomatedTestWidgetsFlutterBinding]. Override this behavior
// to help events go into widget tree.
@override
void dispatchEvent(
PointerEvent event,
HitTestResult hitTestResult, {
TestBindingEventSource source = TestBindingEventSource.device,
}) {
super.dispatchEvent(event, hitTestResult, source: TestBindingEventSource.test);
}
}
void main() {
final TestWidgetsFlutterBinding binding = PointerDataAutomatedTestWidgetsFlutterBinding();
final TestWidgetsFlutterBinding binding = AutomatedTestWidgetsFlutterBinding();
testWidgets('PointerEvent resampling on a widget', (WidgetTester tester) async {
assert(WidgetsBinding.instance == binding);
Duration currentTestFrameTime() => Duration(milliseconds: binding.clock.now().millisecondsSinceEpoch);
......
......@@ -753,6 +753,7 @@ void main() {
await hoverGesture.addPointer();
await hoverGesture.moveTo(center);
await tester.pumpAndSettle();
await hoverGesture.moveTo(const Offset(0, 0));
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
......@@ -761,7 +762,6 @@ void main() {
inkFeatures,
paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.04)),
);
await hoverGesture.removePointer();
// focusColor
focusNode.requestFocus();
......@@ -770,6 +770,8 @@ void main() {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.12)));
await hoverGesture.removePointer();
});
testWidgets('Default InkWell colors - selected', (WidgetTester tester) async {
......@@ -825,7 +827,7 @@ void main() {
inkFeatures,
paints..rect(color: theme.colorScheme.primary.withOpacity(0.04)),
);
await hoverGesture.removePointer();
await hoverGesture.moveTo(const Offset(0, 0));
// focusColor
focusNode.requestFocus();
......@@ -834,6 +836,8 @@ void main() {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: theme.colorScheme.primary.withOpacity(0.12)));
await hoverGesture.removePointer();
});
testWidgets('Custom InkWell colors', (WidgetTester tester) async {
......@@ -894,7 +898,7 @@ void main() {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: hoverColor));
await hoverGesture.removePointer();
await hoverGesture.moveTo(const Offset(0, 0));
// focusColor
focusNode.requestFocus();
......@@ -903,6 +907,8 @@ void main() {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: focusColor));
await hoverGesture.removePointer();
});
testWidgets(
......
......@@ -429,7 +429,7 @@ void main() {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: hoverColor));
await hoverGesture.removePointer();
await hoverGesture.moveTo(const Offset(0, 0));
// focusColor
focusNode.requestFocus();
......@@ -438,6 +438,8 @@ void main() {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: focusColor));
await hoverGesture.removePointer();
});
......
......@@ -11,6 +11,5 @@ Future<void> scrollAt(Offset position, WidgetTester tester, [Offset offset = con
final TestPointer testPointer = TestPointer(1, PointerDeviceKind.mouse);
// Create a hover event so that |testPointer| has a location when generating the scroll.
testPointer.hover(position);
final HitTestResult result = tester.hitTestOnBinding(position);
return tester.sendEventToBinding(testPointer.scroll(offset), result);
return tester.sendEventToBinding(testPointer.scroll(offset));
}
......@@ -290,11 +290,10 @@ void main() {
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
// Create a hover event so that |testPointer| has a location when generating the scroll.
testPointer.hover(scrollEventLocation);
final HitTestResult result = tester.hitTestOnBinding(scrollEventLocation);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)), result);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)));
expect(getScrollOffset(tester), 20.0);
// Pointer signals should not cause overscroll.
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, -30.0)), result);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, -30.0)));
expect(getScrollOffset(tester), 0.0);
});
......@@ -308,11 +307,10 @@ void main() {
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
// Create a hover event so that |testPointer| has a location when generating the scroll.
testPointer.hover(scrollEventLocation);
final HitTestResult result = tester.hitTestOnBinding(scrollEventLocation);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)), result);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)));
expect(getScrollOffset(tester, last: true), 20.0);
// Pointer signals should not cause overscroll.
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, -30.0)), result);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, -30.0)));
expect(getScrollOffset(tester, last: true), 0.0);
});
......@@ -322,8 +320,7 @@ void main() {
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
// Create a hover event so that |testPointer| has a location when generating the scroll.
testPointer.hover(scrollEventLocation);
final HitTestResult result = tester.hitTestOnBinding(scrollEventLocation);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)), result);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)));
expect(getScrollOffset(tester), 0.0);
});
......@@ -334,8 +331,7 @@ void main() {
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
// Create a hover event so that |testPointer| has a location when generating the scroll.
testPointer.hover(scrollEventLocation);
final HitTestResult result = tester.hitTestOnBinding(scrollEventLocation);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, -20.0)), result);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, -20.0)));
expect(getScrollOffset(tester), 20.0);
});
......
......@@ -596,17 +596,14 @@ class FlutterDriverExtension with DeserializeFinderFactory {
final Offset startLocation = _prober.getCenter(target);
Offset currentLocation = startLocation;
final TestPointer pointer = TestPointer(1);
final HitTestResult hitTest = HitTestResult();
_prober.binding.hitTest(hitTest, startLocation);
_prober.binding.dispatchEvent(pointer.down(startLocation), hitTest);
_prober.binding.handlePointerEvent(pointer.down(startLocation));
await Future<void>.value(); // so that down and move don't happen in the same microtask
for (int moves = 0; moves < totalMoves; moves += 1) {
currentLocation = currentLocation + delta;
_prober.binding.dispatchEvent(pointer.move(currentLocation), hitTest);
_prober.binding.handlePointerEvent(pointer.move(currentLocation));
await Future<void>.delayed(pause);
}
_prober.binding.dispatchEvent(pointer.up(), hitTest);
_prober.binding.handlePointerEvent(pointer.up());
return const ScrollResult();
}
......
......@@ -196,6 +196,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// prepare the binding for the next test.
void reset() {
_restorationManager = createRestorationManager();
resetGestureBinding();
}
@override
......@@ -488,19 +489,25 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// events from the device).
Offset localToGlobal(Offset point) => point;
// The source of the current pointer event.
//
// The [pointerEventSource] is set as the `source` parameter of
// [handlePointerEvent] and can be used in the immediate enclosing
// [dispatchEvent].
TestBindingEventSource _pointerEventSource = TestBindingEventSource.device;
@override
void dispatchEvent(
PointerEvent event,
HitTestResult hitTestResult, {
void handlePointerEvent(
PointerEvent event, {
TestBindingEventSource source = TestBindingEventSource.device,
}) {
// This override disables calling this method from base class
// [GestureBinding] when the runtime type is [TestWidgetsFlutterBinding],
// while enables sub class [LiveTestWidgetsFlutterBinding] to override
// this behavior and use this argument to determine the souce of the event
// especially when the test app is running on a device.
assert(source == TestBindingEventSource.test);
super.dispatchEvent(event, hitTestResult);
final TestBindingEventSource previousSource = source;
_pointerEventSource = source;
try {
super.handlePointerEvent(event);
} finally {
_pointerEventSource = previousSource;
}
}
/// A stub for the system's onscreen keyboard. Callers must set the
......@@ -1486,14 +1493,13 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
HitTestDispatcher deviceEventDispatcher;
/// Dispatch an event to a hit test result's path.
/// Dispatch an event to the targets found by a hit test on its position.
///
/// Apart from forwarding the event to [GestureBinding.dispatchEvent],
/// This also paint all events that's down on the screen.
@override
void dispatchEvent(
PointerEvent event,
HitTestResult hitTestResult, {
void handlePointerEvent(
PointerEvent event, {
TestBindingEventSource source = TestBindingEventSource.device,
}) {
switch (source) {
......@@ -1510,11 +1516,23 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
);
_handleViewNeedsPaint();
}
super.dispatchEvent(event, hitTestResult, source: source);
super.handlePointerEvent(event, source: TestBindingEventSource.test);
break;
case TestBindingEventSource.device:
if (deviceEventDispatcher != null)
deviceEventDispatcher.dispatchEvent(event, hitTestResult);
super.handlePointerEvent(event, source: TestBindingEventSource.device);
break;
}
}
@override
void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
switch (_pointerEventSource) {
case TestBindingEventSource.test:
super.dispatchEvent(event, hitTestResult);
break;
case TestBindingEventSource.device:
deviceEventDispatcher.dispatchEvent(event, hitTestResult);
break;
}
}
......
......@@ -378,27 +378,26 @@ abstract class WidgetController {
assert(speed > 0.0); // speed is pixels/second
return TestAsyncUtils.guard<void>(() async {
final TestPointer testPointer = TestPointer(pointer ?? _getNextPointer(), PointerDeviceKind.touch, null, buttons);
final HitTestResult result = hitTestOnBinding(startLocation);
const int kMoveCount = 50; // Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
final double timeStampDelta = 1000000.0 * offset.distance / (kMoveCount * speed);
double timeStamp = 0.0;
double lastTimeStamp = timeStamp;
await sendEventToBinding(testPointer.down(startLocation, timeStamp: Duration(microseconds: timeStamp.round())), result);
await sendEventToBinding(testPointer.down(startLocation, timeStamp: Duration(microseconds: timeStamp.round())));
if (initialOffset.distance > 0.0) {
await sendEventToBinding(testPointer.move(startLocation + initialOffset, timeStamp: Duration(microseconds: timeStamp.round())), result);
await sendEventToBinding(testPointer.move(startLocation + initialOffset, timeStamp: Duration(microseconds: timeStamp.round())));
timeStamp += initialOffsetDelay.inMicroseconds;
await pump(initialOffsetDelay);
}
for (int i = 0; i <= kMoveCount; i += 1) {
final Offset location = startLocation + initialOffset + Offset.lerp(Offset.zero, offset, i / kMoveCount);
await sendEventToBinding(testPointer.move(location, timeStamp: Duration(microseconds: timeStamp.round())), result);
await sendEventToBinding(testPointer.move(location, timeStamp: Duration(microseconds: timeStamp.round())));
timeStamp += timeStampDelta;
if (timeStamp - lastTimeStamp > frameInterval.inMicroseconds) {
await pump(Duration(microseconds: (timeStamp - lastTimeStamp).truncate()));
lastTimeStamp = timeStamp;
}
}
await sendEventToBinding(testPointer.up(timeStamp: Duration(microseconds: timeStamp.round())), result);
await sendEventToBinding(testPointer.up(timeStamp: Duration(microseconds: timeStamp.round())));
});
}
......@@ -739,7 +738,6 @@ abstract class WidgetController {
int buttons = kPrimaryButton,
}) async {
return TestGesture(
hitTester: hitTestOnBinding,
dispatcher: sendEventToBinding,
kind: kind,
pointer: pointer ?? _getNextPointer(),
......@@ -777,9 +775,9 @@ abstract class WidgetController {
}
/// Forwards the given pointer event to the binding.
Future<void> sendEventToBinding(PointerEvent event, HitTestResult result) {
Future<void> sendEventToBinding(PointerEvent event) {
return TestAsyncUtils.guard<void>(() async {
binding.dispatchEvent(event, result);
binding.handlePointerEvent(event);
});
}
......@@ -1087,9 +1085,7 @@ class LiveWidgetController extends WidgetController {
// processing of the events.
// Flush all past events
handleTimeStampDiff.add(-timeDiff);
for (final PointerEvent event in record.events) {
_handlePointerEvent(event, hitTestHistory);
}
record.events.forEach(binding.handlePointerEvent);
} else {
await Future<void>.delayed(timeDiff);
handleTimeStampDiff.add(
......@@ -1098,9 +1094,7 @@ class LiveWidgetController extends WidgetController {
// fake async this new diff should be zero.
clock.now().difference(startTime) - record.timeDelay,
);
for (final PointerEvent event in record.events) {
_handlePointerEvent(event, hitTestHistory);
}
record.events.forEach(binding.handlePointerEvent);
}
}
// This makes sure that a gesture is completed, with no more pointers
......@@ -1109,46 +1103,4 @@ class LiveWidgetController extends WidgetController {
return handleTimeStampDiff;
});
}
// This method is almost identical to [GestureBinding._handlePointerEvent]
// to replicate the behavior of the real binding.
void _handlePointerEvent(
PointerEvent event,
Map<int, HitTestResult> _hitTests
) {
HitTestResult hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent) {
assert(!_hitTests.containsKey(event.pointer));
hitTestResult = HitTestResult();
binding.hitTest(hitTestResult, event.position);
if (event is PointerDownEvent) {
_hitTests[event.pointer] = hitTestResult;
}
assert(() {
if (debugPrintHitTestResults)
debugPrint('$event: $hitTestResult');
return true;
}());
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
hitTestResult = _hitTests.remove(event.pointer);
} else if (event.down) {
// Because events that occur with the pointer down (like
// PointerMoveEvents) should be dispatched to the same place that their
// initial PointerDownEvent was, we want to re-use the path we found when
// the pointer went down, rather than do hit detection each time we get
// such an event.
hitTestResult = _hitTests[event.pointer];
}
assert(() {
if (debugPrintMouseHoverEvents && event is PointerHoverEvent)
debugPrint('$event');
return true;
}());
if (hitTestResult != null ||
event is PointerHoverEvent ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
binding.dispatchEvent(event, hitTestResult);
}
}
}
......@@ -295,7 +295,7 @@ class TestPointer {
/// Signature for a callback that can dispatch events and returns a future that
/// completes when the event dispatch is complete.
typedef EventDispatcher = Future<void> Function(PointerEvent event, HitTestResult result);
typedef EventDispatcher = Future<void> Function(PointerEvent event);
/// Signature for callbacks that perform hit-testing at a given location.
typedef HitTester = HitTestResult Function(Offset location);
......@@ -324,27 +324,22 @@ class TestGesture {
/// arguments are required.
TestGesture({
@required EventDispatcher dispatcher,
@required HitTester hitTester,
int pointer = 1,
PointerDeviceKind kind = PointerDeviceKind.touch,
int device,
int buttons = kPrimaryButton,
}) : assert(dispatcher != null),
assert(hitTester != null),
assert(pointer != null),
assert(kind != null),
assert(buttons != null),
_dispatcher = dispatcher,
_hitTester = hitTester,
_pointer = TestPointer(pointer, kind, device, buttons),
_result = null;
_pointer = TestPointer(pointer, kind, device, buttons);
/// Dispatch a pointer down event at the given `downLocation`, caching the
/// hit test result.
Future<void> down(Offset downLocation, { Duration timeStamp = Duration.zero }) async {
return TestAsyncUtils.guard<void>(() async {
_result = _hitTester(downLocation);
return _dispatcher(_pointer.down(downLocation, timeStamp: timeStamp), _result);
return _dispatcher(_pointer.down(downLocation, timeStamp: timeStamp));
});
}
......@@ -353,36 +348,33 @@ class TestGesture {
Future<void> downWithCustomEvent(Offset downLocation, PointerDownEvent event) async {
_pointer.setDownInfo(event, downLocation);
return TestAsyncUtils.guard<void>(() async {
_result = _hitTester(downLocation);
return _dispatcher(event, _result);
return _dispatcher(event);
});
}
final EventDispatcher _dispatcher;
final HitTester _hitTester;
final TestPointer _pointer;
HitTestResult _result;
/// In a test, send a move event that moves the pointer by the given offset.
@visibleForTesting
Future<void> updateWithCustomEvent(PointerEvent event, { Duration timeStamp = Duration.zero }) {
_pointer.setDownInfo(event, event.position);
return TestAsyncUtils.guard<void>(() {
return _dispatcher(event, _result);
return _dispatcher(event);
});
}
/// In a test, send a pointer add event for this pointer.
Future<void> addPointer({ Duration timeStamp = Duration.zero, Offset location }) {
return TestAsyncUtils.guard<void>(() {
return _dispatcher(_pointer.addPointer(timeStamp: timeStamp, location: location ?? _pointer.location), null);
return _dispatcher(_pointer.addPointer(timeStamp: timeStamp, location: location ?? _pointer.location));
});
}
/// In a test, send a pointer remove event for this pointer.
Future<void> removePointer({ Duration timeStamp = Duration.zero, Offset location }) {
return TestAsyncUtils.guard<void>(() {
return _dispatcher(_pointer.removePointer(timeStamp: timeStamp, location: location ?? _pointer.location), null);
return _dispatcher(_pointer.removePointer(timeStamp: timeStamp, location: location ?? _pointer.location));
});
}
......@@ -403,14 +395,11 @@ class TestGesture {
Future<void> moveTo(Offset location, { Duration timeStamp = Duration.zero }) {
return TestAsyncUtils.guard<void>(() {
if (_pointer._isDown) {
assert(_result != null,
'Move events with the pointer down must be preceded by a down '
'event that captures a hit test result.');
return _dispatcher(_pointer.move(location, timeStamp: timeStamp), _result);
return _dispatcher(_pointer.move(location, timeStamp: timeStamp));
} else {
assert(_pointer.kind != PointerDeviceKind.touch,
'Touch device move events can only be sent if the pointer is down.');
return _dispatcher(_pointer.hover(location, timeStamp: timeStamp), null);
return _dispatcher(_pointer.hover(location, timeStamp: timeStamp));
}
});
}
......@@ -419,9 +408,8 @@ class TestGesture {
Future<void> up({ Duration timeStamp = Duration.zero }) {
return TestAsyncUtils.guard<void>(() async {
assert(_pointer._isDown);
await _dispatcher(_pointer.up(timeStamp: timeStamp), _result);
await _dispatcher(_pointer.up(timeStamp: timeStamp));
assert(!_pointer._isDown);
_result = null;
});
}
......@@ -431,9 +419,8 @@ class TestGesture {
Future<void> cancel({ Duration timeStamp = Duration.zero }) {
return TestAsyncUtils.guard<void>(() async {
assert(_pointer._isDown);
await _dispatcher(_pointer.cancel(timeStamp: timeStamp), _result);
await _dispatcher(_pointer.cancel(timeStamp: timeStamp));
assert(!_pointer._isDown);
_result = null;
});
}
}
......
......@@ -543,7 +543,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
// Flush all past events
handleTimeStampDiff.add(-timeDiff);
for (final PointerEvent event in record.events) {
_handlePointerEvent(event, hitTestHistory);
binding.handlePointerEvent(event, source: TestBindingEventSource.test);
}
} else {
// TODO(CareF): reconsider the pumping strategy after
......@@ -554,7 +554,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
binding.clock.now().difference(startTime) - record.timeDelay,
);
for (final PointerEvent event in record.events) {
_handlePointerEvent(event, hitTestHistory);
binding.handlePointerEvent(event, source: TestBindingEventSource.test);
}
}
}
......@@ -566,48 +566,6 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
});
}
// This is a parallel implementation of [GestureBinding._handlePointerEvent]
// to make compatible with test bindings.
void _handlePointerEvent(
PointerEvent event,
Map<int, HitTestResult> _hitTests
) {
HitTestResult hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent) {
assert(!_hitTests.containsKey(event.pointer));
hitTestResult = HitTestResult();
binding.hitTest(hitTestResult, event.position);
if (event is PointerDownEvent) {
_hitTests[event.pointer] = hitTestResult;
}
assert(() {
if (debugPrintHitTestResults)
debugPrint('$event: $hitTestResult');
return true;
}());
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
hitTestResult = _hitTests.remove(event.pointer);
} else if (event.down) {
// Because events that occur with the pointer down (like
// PointerMoveEvents) should be dispatched to the same place that their
// initial PointerDownEvent was, we want to re-use the path we found when
// the pointer went down, rather than do hit detection each time we get
// such an event.
hitTestResult = _hitTests[event.pointer];
}
assert(() {
if (debugPrintMouseHoverEvents && event is PointerHoverEvent)
debugPrint('$event');
return true;
}());
if (hitTestResult != null ||
event is PointerHoverEvent ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
binding.dispatchEvent(event, hitTestResult, source: TestBindingEventSource.test);
}
}
/// Triggers a frame after `duration` amount of time.
///
/// This makes the framework act as if the application had janked (missed
......@@ -824,9 +782,9 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
}
@override
Future<void> sendEventToBinding(PointerEvent event, HitTestResult result) {
Future<void> sendEventToBinding(PointerEvent event) {
return TestAsyncUtils.guard<void>(() async {
binding.dispatchEvent(event, result, source: TestBindingEventSource.test);
binding.handlePointerEvent(event, source: TestBindingEventSource.test);
});
}
......
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