Expose GestureBinding.handlePointerEvent, replacing dispatchEvent as the preferred way to dispatch events (#64846)
This commit is contained in:
parent
ea039ed3f9
commit
a48e14308e
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user