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;
|
import 'package:complex_layout/main.dart' as app;
|
||||||
|
|
||||||
class PointerDataTestBinding extends E2EWidgetsFlutterBinding {
|
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.
|
/// A union of [ui.PointerDataPacket] and the time it should be sent.
|
||||||
|
@ -144,13 +144,8 @@ class _Tester {
|
|||||||
|
|
||||||
TestGesture get gesture {
|
TestGesture get gesture {
|
||||||
return _gesture ??= TestGesture(
|
return _gesture ??= TestGesture(
|
||||||
dispatcher: (PointerEvent event, HitTestResult result) async {
|
dispatcher: (PointerEvent event) async {
|
||||||
RendererBinding.instance.dispatchEvent(event, result);
|
RendererBinding.instance.handlePointerEvent(event);
|
||||||
},
|
|
||||||
hitTester: (Offset location) {
|
|
||||||
final HitTestResult result = HitTestResult();
|
|
||||||
RendererBinding.instance.hitTest(result, location);
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
kind: PointerDeviceKind.mouse,
|
kind: PointerDeviceKind.mouse,
|
||||||
);
|
);
|
||||||
|
@ -116,13 +116,8 @@ class _Tester {
|
|||||||
|
|
||||||
TestGesture get gesture {
|
TestGesture get gesture {
|
||||||
return _gesture ??= TestGesture(
|
return _gesture ??= TestGesture(
|
||||||
dispatcher: (PointerEvent event, HitTestResult result) async {
|
dispatcher: (PointerEvent event) async {
|
||||||
RendererBinding.instance.dispatchEvent(event, result);
|
RendererBinding.instance.handlePointerEvent(event);
|
||||||
},
|
|
||||||
hitTester: (Offset location) {
|
|
||||||
final HitTestResult result = HitTestResult();
|
|
||||||
RendererBinding.instance.hitTest(result, location);
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
kind: PointerDeviceKind.mouse,
|
kind: PointerDeviceKind.mouse,
|
||||||
);
|
);
|
||||||
|
@ -166,13 +166,8 @@ class _Tester {
|
|||||||
|
|
||||||
TestGesture get gesture {
|
TestGesture get gesture {
|
||||||
return _gesture ??= TestGesture(
|
return _gesture ??= TestGesture(
|
||||||
dispatcher: (PointerEvent event, HitTestResult result) async {
|
dispatcher: (PointerEvent event) async {
|
||||||
RendererBinding.instance.dispatchEvent(event, result);
|
RendererBinding.instance.handlePointerEvent(event);
|
||||||
},
|
|
||||||
hitTester: (Offset location) {
|
|
||||||
final HitTestResult result = HitTestResult();
|
|
||||||
RendererBinding.instance.hitTest(result, location);
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
kind: PointerDeviceKind.mouse,
|
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
|
/// A pointer that is [PointerEvent.down] may send further events, such as
|
||||||
/// [PointerMoveEvent], [PointerUpEvent], or [PointerCancelEvent]. These are
|
/// [PointerMoveEvent], [PointerUpEvent], or [PointerCancelEvent]. These are
|
||||||
/// sent to the same [HitTestTarget] nodes as were found when the down event was
|
/// sent to the same [HitTestTarget] nodes as were found when the
|
||||||
/// received (even if they have since been disposed; it is the responsibility of
|
/// [PointerDownEvent] was received (even if they have since been disposed; it is
|
||||||
/// those objects to be aware of that possibility).
|
/// the responsibility of those objects to be aware of that possibility).
|
||||||
///
|
///
|
||||||
/// Then, the events are routed to any still-registered entrants in the
|
/// Then, the events are routed to any still-registered entrants in the
|
||||||
/// [PointerRouter]'s table for that pointer.
|
/// [PointerRouter]'s table for that pointer.
|
||||||
@ -237,7 +237,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
|||||||
_resampler.stop();
|
_resampler.stop();
|
||||||
|
|
||||||
while (_pendingPointerEvents.isNotEmpty)
|
while (_pendingPointerEvents.isNotEmpty)
|
||||||
_handlePointerEvent(_pendingPointerEvents.removeFirst());
|
handlePointerEvent(_pendingPointerEvents.removeFirst());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A router that routes all pointer events received from the engine.
|
/// 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.
|
/// pointer events.
|
||||||
final GestureArenaManager gestureArena = GestureArenaManager();
|
final GestureArenaManager gestureArena = GestureArenaManager();
|
||||||
|
|
||||||
/// The resolver used for determining which widget handles a pointer
|
/// The resolver used for determining which widget handles a
|
||||||
/// signal event.
|
/// [PointerSignalEvent].
|
||||||
final PointerSignalResolver pointerSignalResolver = PointerSignalResolver();
|
final PointerSignalResolver pointerSignalResolver = PointerSignalResolver();
|
||||||
|
|
||||||
/// State for all pointers which are currently down.
|
/// 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.
|
/// hit-testing on every frame.
|
||||||
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
|
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);
|
assert(!locked);
|
||||||
HitTestResult? hitTestResult;
|
HitTestResult? hitTestResult;
|
||||||
if (event is PointerDownEvent || event is PointerSignalEvent) {
|
if (event is PointerDownEvent || event is PointerSignalEvent) {
|
||||||
@ -276,7 +286,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
|||||||
hitTestResult = _hitTests.remove(event.pointer);
|
hitTestResult = _hitTests.remove(event.pointer);
|
||||||
} else if (event.down) {
|
} else if (event.down) {
|
||||||
// Because events that occur with the pointer down (like
|
// 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
|
// 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
|
// the pointer went down, rather than do hit detection each time we get
|
||||||
// such an event.
|
// such an event.
|
||||||
@ -302,18 +312,20 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
|||||||
result.add(HitTestEntry(this));
|
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
|
/// The `event` is routed to [pointerRouter]. If the `hitTestResult` is not
|
||||||
/// given [HitTestResult], and catches exceptions that any of the handlers
|
/// null, the event is also sent to every [HitTestTarget] in the entries of the
|
||||||
/// might throw. The [hitTestResult] argument may only be null for
|
/// given [HitTestResult]. Any exceptions from the handlers are caught.
|
||||||
/// [PointerHoverEvent], [PointerAddedEvent], or [PointerRemovedEvent] events.
|
///
|
||||||
|
/// The `hitTestResult` argument may only be null for [PointerHoverEvent]s,
|
||||||
|
/// [PointerAddedEvent]s, or [PointerRemovedEvent]s.
|
||||||
@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 pointer hover or
|
// No hit test information implies that this is a [PointerHoverEvent],
|
||||||
// add/remove event. These events are specially routed here; other events
|
// [PointerAddedEvent], or [PointerRemovedEvent]. These events are specially
|
||||||
// will be routed through the `handleEvent` below.
|
// 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 {
|
||||||
@ -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() {
|
void _handleSampleTimeChanged() {
|
||||||
if (!locked) {
|
if (!locked) {
|
||||||
_flushPointerEventQueue();
|
_flushPointerEventQueue();
|
||||||
@ -374,7 +396,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
|
|||||||
// Resampler used to filter incoming pointer events when resampling
|
// Resampler used to filter incoming pointer events when resampling
|
||||||
// is enabled.
|
// is enabled.
|
||||||
late final _Resampler _resampler = _Resampler(
|
late final _Resampler _resampler = _Resampler(
|
||||||
_handlePointerEvent,
|
handlePointerEvent,
|
||||||
_handleSampleTimeChanged,
|
_handleSampleTimeChanged,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -429,7 +451,8 @@ class FlutterErrorDetailsForPointerEventDispatcher extends FlutterErrorDetails {
|
|||||||
|
|
||||||
/// The hit test result entry for the object whose handleEvent method threw
|
/// 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
|
/// 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 target object itself is given by the [HitTestEntry.target] property of
|
||||||
/// the hitTestEntry object.
|
/// the hitTestEntry object.
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.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';
|
||||||
|
|
||||||
@ -1471,6 +1472,25 @@ void main() {
|
|||||||
expect(find.text('first', skipOffstage: false), findsOneWidget);
|
expect(find.text('first', skipOffstage: false), findsOneWidget);
|
||||||
expect(find.text('second'), 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 {
|
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/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.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() {
|
void main() {
|
||||||
final TestWidgetsFlutterBinding binding = PointerDataAutomatedTestWidgetsFlutterBinding();
|
final TestWidgetsFlutterBinding binding = AutomatedTestWidgetsFlutterBinding();
|
||||||
testWidgets('PointerEvent resampling on a widget', (WidgetTester tester) async {
|
testWidgets('PointerEvent resampling on a widget', (WidgetTester tester) async {
|
||||||
assert(WidgetsBinding.instance == binding);
|
assert(WidgetsBinding.instance == binding);
|
||||||
Duration currentTestFrameTime() => Duration(milliseconds: binding.clock.now().millisecondsSinceEpoch);
|
Duration currentTestFrameTime() => Duration(milliseconds: binding.clock.now().millisecondsSinceEpoch);
|
||||||
|
@ -753,6 +753,7 @@ void main() {
|
|||||||
await hoverGesture.addPointer();
|
await hoverGesture.addPointer();
|
||||||
await hoverGesture.moveTo(center);
|
await hoverGesture.moveTo(center);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
await hoverGesture.moveTo(const Offset(0, 0));
|
||||||
|
|
||||||
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
|
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
|
||||||
return object.runtimeType.toString() == '_RenderInkFeatures';
|
return object.runtimeType.toString() == '_RenderInkFeatures';
|
||||||
@ -761,7 +762,6 @@ void main() {
|
|||||||
inkFeatures,
|
inkFeatures,
|
||||||
paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.04)),
|
paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.04)),
|
||||||
);
|
);
|
||||||
await hoverGesture.removePointer();
|
|
||||||
|
|
||||||
// focusColor
|
// focusColor
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
@ -770,6 +770,8 @@ void main() {
|
|||||||
return object.runtimeType.toString() == '_RenderInkFeatures';
|
return object.runtimeType.toString() == '_RenderInkFeatures';
|
||||||
});
|
});
|
||||||
expect(inkFeatures, paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.12)));
|
expect(inkFeatures, paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.12)));
|
||||||
|
|
||||||
|
await hoverGesture.removePointer();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Default InkWell colors - selected', (WidgetTester tester) async {
|
testWidgets('Default InkWell colors - selected', (WidgetTester tester) async {
|
||||||
@ -825,7 +827,7 @@ void main() {
|
|||||||
inkFeatures,
|
inkFeatures,
|
||||||
paints..rect(color: theme.colorScheme.primary.withOpacity(0.04)),
|
paints..rect(color: theme.colorScheme.primary.withOpacity(0.04)),
|
||||||
);
|
);
|
||||||
await hoverGesture.removePointer();
|
await hoverGesture.moveTo(const Offset(0, 0));
|
||||||
|
|
||||||
// focusColor
|
// focusColor
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
@ -834,6 +836,8 @@ void main() {
|
|||||||
return object.runtimeType.toString() == '_RenderInkFeatures';
|
return object.runtimeType.toString() == '_RenderInkFeatures';
|
||||||
});
|
});
|
||||||
expect(inkFeatures, paints..rect(color: theme.colorScheme.primary.withOpacity(0.12)));
|
expect(inkFeatures, paints..rect(color: theme.colorScheme.primary.withOpacity(0.12)));
|
||||||
|
|
||||||
|
await hoverGesture.removePointer();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Custom InkWell colors', (WidgetTester tester) async {
|
testWidgets('Custom InkWell colors', (WidgetTester tester) async {
|
||||||
@ -894,7 +898,7 @@ void main() {
|
|||||||
return object.runtimeType.toString() == '_RenderInkFeatures';
|
return object.runtimeType.toString() == '_RenderInkFeatures';
|
||||||
});
|
});
|
||||||
expect(inkFeatures, paints..rect(color: hoverColor));
|
expect(inkFeatures, paints..rect(color: hoverColor));
|
||||||
await hoverGesture.removePointer();
|
await hoverGesture.moveTo(const Offset(0, 0));
|
||||||
|
|
||||||
// focusColor
|
// focusColor
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
@ -903,6 +907,8 @@ void main() {
|
|||||||
return object.runtimeType.toString() == '_RenderInkFeatures';
|
return object.runtimeType.toString() == '_RenderInkFeatures';
|
||||||
});
|
});
|
||||||
expect(inkFeatures, paints..rect(color: focusColor));
|
expect(inkFeatures, paints..rect(color: focusColor));
|
||||||
|
|
||||||
|
await hoverGesture.removePointer();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
|
@ -429,7 +429,7 @@ void main() {
|
|||||||
return object.runtimeType.toString() == '_RenderInkFeatures';
|
return object.runtimeType.toString() == '_RenderInkFeatures';
|
||||||
});
|
});
|
||||||
expect(inkFeatures, paints..rect(color: hoverColor));
|
expect(inkFeatures, paints..rect(color: hoverColor));
|
||||||
await hoverGesture.removePointer();
|
await hoverGesture.moveTo(const Offset(0, 0));
|
||||||
|
|
||||||
// focusColor
|
// focusColor
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
@ -438,6 +438,8 @@ void main() {
|
|||||||
return object.runtimeType.toString() == '_RenderInkFeatures';
|
return object.runtimeType.toString() == '_RenderInkFeatures';
|
||||||
});
|
});
|
||||||
expect(inkFeatures, paints..rect(color: focusColor));
|
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);
|
final TestPointer testPointer = TestPointer(1, PointerDeviceKind.mouse);
|
||||||
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
||||||
testPointer.hover(position);
|
testPointer.hover(position);
|
||||||
final HitTestResult result = tester.hitTestOnBinding(position);
|
return tester.sendEventToBinding(testPointer.scroll(offset));
|
||||||
return tester.sendEventToBinding(testPointer.scroll(offset), result);
|
|
||||||
}
|
}
|
||||||
|
@ -290,11 +290,10 @@ void main() {
|
|||||||
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
|
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
|
||||||
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
||||||
testPointer.hover(scrollEventLocation);
|
testPointer.hover(scrollEventLocation);
|
||||||
final HitTestResult result = tester.hitTestOnBinding(scrollEventLocation);
|
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)));
|
||||||
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)), result);
|
|
||||||
expect(getScrollOffset(tester), 20.0);
|
expect(getScrollOffset(tester), 20.0);
|
||||||
// Pointer signals should not cause overscroll.
|
// 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);
|
expect(getScrollOffset(tester), 0.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -308,11 +307,10 @@ void main() {
|
|||||||
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
|
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
|
||||||
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
||||||
testPointer.hover(scrollEventLocation);
|
testPointer.hover(scrollEventLocation);
|
||||||
final HitTestResult result = tester.hitTestOnBinding(scrollEventLocation);
|
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)));
|
||||||
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)), result);
|
|
||||||
expect(getScrollOffset(tester, last: true), 20.0);
|
expect(getScrollOffset(tester, last: true), 20.0);
|
||||||
// Pointer signals should not cause overscroll.
|
// 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);
|
expect(getScrollOffset(tester, last: true), 0.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -322,8 +320,7 @@ void main() {
|
|||||||
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
|
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
|
||||||
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
||||||
testPointer.hover(scrollEventLocation);
|
testPointer.hover(scrollEventLocation);
|
||||||
final HitTestResult result = tester.hitTestOnBinding(scrollEventLocation);
|
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)));
|
||||||
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)), result);
|
|
||||||
expect(getScrollOffset(tester), 0.0);
|
expect(getScrollOffset(tester), 0.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -334,8 +331,7 @@ void main() {
|
|||||||
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
|
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
|
||||||
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
// Create a hover event so that |testPointer| has a location when generating the scroll.
|
||||||
testPointer.hover(scrollEventLocation);
|
testPointer.hover(scrollEventLocation);
|
||||||
final HitTestResult result = tester.hitTestOnBinding(scrollEventLocation);
|
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, -20.0)));
|
||||||
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, -20.0)), result);
|
|
||||||
|
|
||||||
expect(getScrollOffset(tester), 20.0);
|
expect(getScrollOffset(tester), 20.0);
|
||||||
});
|
});
|
||||||
|
@ -596,17 +596,14 @@ class FlutterDriverExtension with DeserializeFinderFactory {
|
|||||||
final Offset startLocation = _prober.getCenter(target);
|
final Offset startLocation = _prober.getCenter(target);
|
||||||
Offset currentLocation = startLocation;
|
Offset currentLocation = startLocation;
|
||||||
final TestPointer pointer = TestPointer(1);
|
final TestPointer pointer = TestPointer(1);
|
||||||
final HitTestResult hitTest = HitTestResult();
|
_prober.binding.handlePointerEvent(pointer.down(startLocation));
|
||||||
|
|
||||||
_prober.binding.hitTest(hitTest, startLocation);
|
|
||||||
_prober.binding.dispatchEvent(pointer.down(startLocation), hitTest);
|
|
||||||
await Future<void>.value(); // so that down and move don't happen in the same microtask
|
await Future<void>.value(); // so that down and move don't happen in the same microtask
|
||||||
for (int moves = 0; moves < totalMoves; moves += 1) {
|
for (int moves = 0; moves < totalMoves; moves += 1) {
|
||||||
currentLocation = currentLocation + delta;
|
currentLocation = currentLocation + delta;
|
||||||
_prober.binding.dispatchEvent(pointer.move(currentLocation), hitTest);
|
_prober.binding.handlePointerEvent(pointer.move(currentLocation));
|
||||||
await Future<void>.delayed(pause);
|
await Future<void>.delayed(pause);
|
||||||
}
|
}
|
||||||
_prober.binding.dispatchEvent(pointer.up(), hitTest);
|
_prober.binding.handlePointerEvent(pointer.up());
|
||||||
|
|
||||||
return const ScrollResult();
|
return const ScrollResult();
|
||||||
}
|
}
|
||||||
|
@ -196,6 +196,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
|||||||
/// prepare the binding for the next test.
|
/// prepare the binding for the next test.
|
||||||
void reset() {
|
void reset() {
|
||||||
_restorationManager = createRestorationManager();
|
_restorationManager = createRestorationManager();
|
||||||
|
resetGestureBinding();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -488,19 +489,25 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
|||||||
/// events from the device).
|
/// events from the device).
|
||||||
Offset localToGlobal(Offset point) => point;
|
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
|
@override
|
||||||
void dispatchEvent(
|
void handlePointerEvent(
|
||||||
PointerEvent event,
|
PointerEvent event, {
|
||||||
HitTestResult hitTestResult, {
|
|
||||||
TestBindingEventSource source = TestBindingEventSource.device,
|
TestBindingEventSource source = TestBindingEventSource.device,
|
||||||
}) {
|
}) {
|
||||||
// This override disables calling this method from base class
|
final TestBindingEventSource previousSource = source;
|
||||||
// [GestureBinding] when the runtime type is [TestWidgetsFlutterBinding],
|
_pointerEventSource = source;
|
||||||
// while enables sub class [LiveTestWidgetsFlutterBinding] to override
|
try {
|
||||||
// this behavior and use this argument to determine the souce of the event
|
super.handlePointerEvent(event);
|
||||||
// especially when the test app is running on a device.
|
} finally {
|
||||||
assert(source == TestBindingEventSource.test);
|
_pointerEventSource = previousSource;
|
||||||
super.dispatchEvent(event, hitTestResult);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A stub for the system's onscreen keyboard. Callers must set the
|
/// A stub for the system's onscreen keyboard. Callers must set the
|
||||||
@ -1486,14 +1493,13 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
|||||||
HitTestDispatcher deviceEventDispatcher;
|
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],
|
/// Apart from forwarding the event to [GestureBinding.dispatchEvent],
|
||||||
/// This also paint all events that's down on the screen.
|
/// This also paint all events that's down on the screen.
|
||||||
@override
|
@override
|
||||||
void dispatchEvent(
|
void handlePointerEvent(
|
||||||
PointerEvent event,
|
PointerEvent event, {
|
||||||
HitTestResult hitTestResult, {
|
|
||||||
TestBindingEventSource source = TestBindingEventSource.device,
|
TestBindingEventSource source = TestBindingEventSource.device,
|
||||||
}) {
|
}) {
|
||||||
switch (source) {
|
switch (source) {
|
||||||
@ -1510,11 +1516,23 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
|||||||
);
|
);
|
||||||
_handleViewNeedsPaint();
|
_handleViewNeedsPaint();
|
||||||
}
|
}
|
||||||
super.dispatchEvent(event, hitTestResult, source: source);
|
super.handlePointerEvent(event, source: TestBindingEventSource.test);
|
||||||
break;
|
break;
|
||||||
case TestBindingEventSource.device:
|
case TestBindingEventSource.device:
|
||||||
if (deviceEventDispatcher != null)
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,27 +378,26 @@ abstract class WidgetController {
|
|||||||
assert(speed > 0.0); // speed is pixels/second
|
assert(speed > 0.0); // speed is pixels/second
|
||||||
return TestAsyncUtils.guard<void>(() async {
|
return TestAsyncUtils.guard<void>(() async {
|
||||||
final TestPointer testPointer = TestPointer(pointer ?? _getNextPointer(), PointerDeviceKind.touch, null, buttons);
|
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
|
const int kMoveCount = 50; // Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
|
||||||
final double timeStampDelta = 1000000.0 * offset.distance / (kMoveCount * speed);
|
final double timeStampDelta = 1000000.0 * offset.distance / (kMoveCount * speed);
|
||||||
double timeStamp = 0.0;
|
double timeStamp = 0.0;
|
||||||
double lastTimeStamp = timeStamp;
|
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) {
|
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;
|
timeStamp += initialOffsetDelay.inMicroseconds;
|
||||||
await pump(initialOffsetDelay);
|
await pump(initialOffsetDelay);
|
||||||
}
|
}
|
||||||
for (int i = 0; i <= kMoveCount; i += 1) {
|
for (int i = 0; i <= kMoveCount; i += 1) {
|
||||||
final Offset location = startLocation + initialOffset + Offset.lerp(Offset.zero, offset, i / kMoveCount);
|
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;
|
timeStamp += timeStampDelta;
|
||||||
if (timeStamp - lastTimeStamp > frameInterval.inMicroseconds) {
|
if (timeStamp - lastTimeStamp > frameInterval.inMicroseconds) {
|
||||||
await pump(Duration(microseconds: (timeStamp - lastTimeStamp).truncate()));
|
await pump(Duration(microseconds: (timeStamp - lastTimeStamp).truncate()));
|
||||||
lastTimeStamp = timeStamp;
|
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,
|
int buttons = kPrimaryButton,
|
||||||
}) async {
|
}) async {
|
||||||
return TestGesture(
|
return TestGesture(
|
||||||
hitTester: hitTestOnBinding,
|
|
||||||
dispatcher: sendEventToBinding,
|
dispatcher: sendEventToBinding,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
pointer: pointer ?? _getNextPointer(),
|
pointer: pointer ?? _getNextPointer(),
|
||||||
@ -777,9 +775,9 @@ abstract class WidgetController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Forwards the given pointer event to the binding.
|
/// 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 {
|
return TestAsyncUtils.guard<void>(() async {
|
||||||
binding.dispatchEvent(event, result);
|
binding.handlePointerEvent(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1087,9 +1085,7 @@ class LiveWidgetController extends WidgetController {
|
|||||||
// processing of the events.
|
// processing of the events.
|
||||||
// Flush all past events
|
// Flush all past events
|
||||||
handleTimeStampDiff.add(-timeDiff);
|
handleTimeStampDiff.add(-timeDiff);
|
||||||
for (final PointerEvent event in record.events) {
|
record.events.forEach(binding.handlePointerEvent);
|
||||||
_handlePointerEvent(event, hitTestHistory);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
await Future<void>.delayed(timeDiff);
|
await Future<void>.delayed(timeDiff);
|
||||||
handleTimeStampDiff.add(
|
handleTimeStampDiff.add(
|
||||||
@ -1098,9 +1094,7 @@ class LiveWidgetController extends WidgetController {
|
|||||||
// fake async this new diff should be zero.
|
// fake async this new diff should be zero.
|
||||||
clock.now().difference(startTime) - record.timeDelay,
|
clock.now().difference(startTime) - record.timeDelay,
|
||||||
);
|
);
|
||||||
for (final PointerEvent event in record.events) {
|
record.events.forEach(binding.handlePointerEvent);
|
||||||
_handlePointerEvent(event, hitTestHistory);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This makes sure that a gesture is completed, with no more pointers
|
// This makes sure that a gesture is completed, with no more pointers
|
||||||
@ -1109,46 +1103,4 @@ class LiveWidgetController extends WidgetController {
|
|||||||
return handleTimeStampDiff;
|
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
|
/// Signature for a callback that can dispatch events and returns a future that
|
||||||
/// completes when the event dispatch is complete.
|
/// 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.
|
/// Signature for callbacks that perform hit-testing at a given location.
|
||||||
typedef HitTester = HitTestResult Function(Offset location);
|
typedef HitTester = HitTestResult Function(Offset location);
|
||||||
@ -324,27 +324,22 @@ class TestGesture {
|
|||||||
/// arguments are required.
|
/// arguments are required.
|
||||||
TestGesture({
|
TestGesture({
|
||||||
@required EventDispatcher dispatcher,
|
@required EventDispatcher dispatcher,
|
||||||
@required HitTester hitTester,
|
|
||||||
int pointer = 1,
|
int pointer = 1,
|
||||||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||||
int device,
|
int device,
|
||||||
int buttons = kPrimaryButton,
|
int buttons = kPrimaryButton,
|
||||||
}) : assert(dispatcher != null),
|
}) : assert(dispatcher != null),
|
||||||
assert(hitTester != null),
|
|
||||||
assert(pointer != null),
|
assert(pointer != null),
|
||||||
assert(kind != null),
|
assert(kind != null),
|
||||||
assert(buttons != null),
|
assert(buttons != null),
|
||||||
_dispatcher = dispatcher,
|
_dispatcher = dispatcher,
|
||||||
_hitTester = hitTester,
|
_pointer = TestPointer(pointer, kind, device, buttons);
|
||||||
_pointer = TestPointer(pointer, kind, device, buttons),
|
|
||||||
_result = null;
|
|
||||||
|
|
||||||
/// Dispatch a pointer down event at the given `downLocation`, caching the
|
/// Dispatch a pointer down event at the given `downLocation`, caching the
|
||||||
/// hit test result.
|
/// hit test result.
|
||||||
Future<void> down(Offset downLocation, { Duration timeStamp = Duration.zero }) async {
|
Future<void> down(Offset downLocation, { Duration timeStamp = Duration.zero }) async {
|
||||||
return TestAsyncUtils.guard<void>(() async {
|
return TestAsyncUtils.guard<void>(() async {
|
||||||
_result = _hitTester(downLocation);
|
return _dispatcher(_pointer.down(downLocation, timeStamp: timeStamp));
|
||||||
return _dispatcher(_pointer.down(downLocation, timeStamp: timeStamp), _result);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,36 +348,33 @@ class TestGesture {
|
|||||||
Future<void> downWithCustomEvent(Offset downLocation, PointerDownEvent event) async {
|
Future<void> downWithCustomEvent(Offset downLocation, PointerDownEvent event) async {
|
||||||
_pointer.setDownInfo(event, downLocation);
|
_pointer.setDownInfo(event, downLocation);
|
||||||
return TestAsyncUtils.guard<void>(() async {
|
return TestAsyncUtils.guard<void>(() async {
|
||||||
_result = _hitTester(downLocation);
|
return _dispatcher(event);
|
||||||
return _dispatcher(event, _result);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final EventDispatcher _dispatcher;
|
final EventDispatcher _dispatcher;
|
||||||
final HitTester _hitTester;
|
|
||||||
final TestPointer _pointer;
|
final TestPointer _pointer;
|
||||||
HitTestResult _result;
|
|
||||||
|
|
||||||
/// In a test, send a move event that moves the pointer by the given offset.
|
/// In a test, send a move event that moves the pointer by the given offset.
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
Future<void> updateWithCustomEvent(PointerEvent event, { Duration timeStamp = Duration.zero }) {
|
Future<void> updateWithCustomEvent(PointerEvent event, { Duration timeStamp = Duration.zero }) {
|
||||||
_pointer.setDownInfo(event, event.position);
|
_pointer.setDownInfo(event, event.position);
|
||||||
return TestAsyncUtils.guard<void>(() {
|
return TestAsyncUtils.guard<void>(() {
|
||||||
return _dispatcher(event, _result);
|
return _dispatcher(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// In a test, send a pointer add event for this pointer.
|
/// In a test, send a pointer add event for this pointer.
|
||||||
Future<void> addPointer({ Duration timeStamp = Duration.zero, Offset location }) {
|
Future<void> addPointer({ Duration timeStamp = Duration.zero, Offset location }) {
|
||||||
return TestAsyncUtils.guard<void>(() {
|
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.
|
/// In a test, send a pointer remove event for this pointer.
|
||||||
Future<void> removePointer({ Duration timeStamp = Duration.zero, Offset location }) {
|
Future<void> removePointer({ Duration timeStamp = Duration.zero, Offset location }) {
|
||||||
return TestAsyncUtils.guard<void>(() {
|
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 }) {
|
Future<void> moveTo(Offset location, { Duration timeStamp = Duration.zero }) {
|
||||||
return TestAsyncUtils.guard<void>(() {
|
return TestAsyncUtils.guard<void>(() {
|
||||||
if (_pointer._isDown) {
|
if (_pointer._isDown) {
|
||||||
assert(_result != null,
|
return _dispatcher(_pointer.move(location, timeStamp: timeStamp));
|
||||||
'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);
|
|
||||||
} else {
|
} else {
|
||||||
assert(_pointer.kind != PointerDeviceKind.touch,
|
assert(_pointer.kind != PointerDeviceKind.touch,
|
||||||
'Touch device move events can only be sent if the pointer is down.');
|
'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 }) {
|
Future<void> up({ Duration timeStamp = Duration.zero }) {
|
||||||
return TestAsyncUtils.guard<void>(() async {
|
return TestAsyncUtils.guard<void>(() async {
|
||||||
assert(_pointer._isDown);
|
assert(_pointer._isDown);
|
||||||
await _dispatcher(_pointer.up(timeStamp: timeStamp), _result);
|
await _dispatcher(_pointer.up(timeStamp: timeStamp));
|
||||||
assert(!_pointer._isDown);
|
assert(!_pointer._isDown);
|
||||||
_result = null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,9 +419,8 @@ class TestGesture {
|
|||||||
Future<void> cancel({ Duration timeStamp = Duration.zero }) {
|
Future<void> cancel({ Duration timeStamp = Duration.zero }) {
|
||||||
return TestAsyncUtils.guard<void>(() async {
|
return TestAsyncUtils.guard<void>(() async {
|
||||||
assert(_pointer._isDown);
|
assert(_pointer._isDown);
|
||||||
await _dispatcher(_pointer.cancel(timeStamp: timeStamp), _result);
|
await _dispatcher(_pointer.cancel(timeStamp: timeStamp));
|
||||||
assert(!_pointer._isDown);
|
assert(!_pointer._isDown);
|
||||||
_result = null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -543,7 +543,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
|
|||||||
// Flush all past events
|
// Flush all past events
|
||||||
handleTimeStampDiff.add(-timeDiff);
|
handleTimeStampDiff.add(-timeDiff);
|
||||||
for (final PointerEvent event in record.events) {
|
for (final PointerEvent event in record.events) {
|
||||||
_handlePointerEvent(event, hitTestHistory);
|
binding.handlePointerEvent(event, source: TestBindingEventSource.test);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO(CareF): reconsider the pumping strategy after
|
// 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,
|
binding.clock.now().difference(startTime) - record.timeDelay,
|
||||||
);
|
);
|
||||||
for (final PointerEvent event in record.events) {
|
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.
|
/// Triggers a frame after `duration` amount of time.
|
||||||
///
|
///
|
||||||
/// This makes the framework act as if the application had janked (missed
|
/// This makes the framework act as if the application had janked (missed
|
||||||
@ -824,9 +782,9 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> sendEventToBinding(PointerEvent event, HitTestResult result) {
|
Future<void> sendEventToBinding(PointerEvent event) {
|
||||||
return TestAsyncUtils.guard<void>(() async {
|
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