| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| /// @docImport 'dart:ui'; | |
| /// | |
| /// @docImport 'controller.dart'; | |
| /// @docImport 'widget_tester.dart'; | |
| library; | |
| import 'package:flutter/gestures.dart'; | |
| import 'package:flutter/widgets.dart'; | |
| import 'test_async_utils.dart'; | |
| export 'dart:ui' show Offset; | |
| /// A class for generating coherent artificial pointer events. | |
| /// | |
| /// You can use this to manually simulate individual events, but the simplest | |
| /// way to generate coherent gestures is to use [TestGesture]. | |
| class TestPointer { | |
| /// Creates a [TestPointer]. By default, the pointer identifier used is 1, | |
| /// however this can be overridden by providing an argument to the | |
| /// constructor. | |
| /// | |
| /// Multiple [TestPointer]s created with the same pointer identifier will | |
| /// interfere with each other if they are used in parallel. | |
| TestPointer([ | |
| this.pointer = 1, | |
| this.kind = PointerDeviceKind.touch, | |
| int? device, | |
| int buttons = kPrimaryButton, | |
| ]) : _buttons = buttons { | |
| switch (kind) { | |
| case PointerDeviceKind.mouse: | |
| _device = device ?? 1; | |
| case PointerDeviceKind.stylus: | |
| case PointerDeviceKind.invertedStylus: | |
| case PointerDeviceKind.touch: | |
| case PointerDeviceKind.trackpad: | |
| case PointerDeviceKind.unknown: | |
| _device = device ?? 0; | |
| } | |
| } | |
| /// The device identifier used for events generated by this object. | |
| /// | |
| /// Set when the object is constructed. Defaults to 1 if the [kind] is | |
| /// [PointerDeviceKind.mouse], and 0 otherwise. | |
| int get device => _device; | |
| late int _device; | |
| /// The pointer identifier used for events generated by this object. | |
| /// | |
| /// Set when the object is constructed. Defaults to 1. | |
| final int pointer; | |
| /// The kind of pointing device to simulate. Defaults to | |
| /// [PointerDeviceKind.touch]. | |
| final PointerDeviceKind kind; | |
| /// The kind of buttons to simulate on Down and Move events. Defaults to | |
| /// [kPrimaryButton]. | |
| int get buttons => _buttons; | |
| int _buttons; | |
| /// Whether the pointer simulated by this object is currently down. | |
| /// | |
| /// A pointer is released (goes up) by calling [up] or [cancel]. | |
| /// | |
| /// Once a pointer is released, it can no longer generate events. | |
| bool get isDown => _isDown; | |
| bool _isDown = false; | |
| /// Whether the pointer simulated by this object currently has | |
| /// an active pan/zoom gesture. | |
| /// | |
| /// A pan/zoom gesture begins when [panZoomStart] is called, and | |
| /// ends when [panZoomEnd] is called. | |
| bool get isPanZoomActive => _isPanZoomActive; | |
| bool _isPanZoomActive = false; | |
| /// The position of the last event sent by this object. | |
| /// | |
| /// If no event has ever been sent by this object, returns null. | |
| Offset? get location => _location; | |
| Offset? _location; | |
| /// The pan offset of the last pointer pan/zoom event sent by this object. | |
| /// | |
| /// If no pan/zoom event has ever been sent by this object, returns null. | |
| Offset? get pan => _pan; | |
| Offset? _pan; | |
| /// If a custom event is created outside of this class, this function is used | |
| /// to set the [isDown]. | |
| bool setDownInfo(PointerEvent event, Offset newLocation, {int? buttons}) { | |
| _location = newLocation; | |
| if (buttons != null) { | |
| _buttons = buttons; | |
| } | |
| switch (event.runtimeType) { | |
| case const (PointerDownEvent): | |
| assert(!isDown); | |
| _isDown = true; | |
| case const (PointerUpEvent): | |
| case const (PointerCancelEvent): | |
| assert(isDown); | |
| _isDown = false; | |
| default: | |
| break; | |
| } | |
| return isDown; | |
| } | |
| /// Create a [PointerDownEvent] at the given location. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| /// | |
| /// By default, the set of buttons in the last down or move event is used. | |
| /// You can give a specific set of buttons by passing the `buttons` argument. | |
| PointerDownEvent down( | |
| Offset newLocation, { | |
| Duration timeStamp = Duration.zero, | |
| int? buttons, | |
| FlutterView? view, | |
| }) { | |
| assert(!isDown); | |
| assert(!isPanZoomActive); | |
| _isDown = true; | |
| _location = newLocation; | |
| if (buttons != null) { | |
| _buttons = buttons; | |
| } | |
| return PointerDownEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| kind: kind, | |
| device: _device, | |
| pointer: pointer, | |
| position: location!, | |
| buttons: _buttons, | |
| ); | |
| } | |
| /// Create a [PointerMoveEvent] to the given location. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| /// | |
| /// [isDown] must be true when this is called, since move events can only | |
| /// be generated when the pointer is down. | |
| /// | |
| /// By default, the set of buttons in the last down or move event is used. | |
| /// You can give a specific set of buttons by passing the `buttons` argument. | |
| PointerMoveEvent move( | |
| Offset newLocation, { | |
| Duration timeStamp = Duration.zero, | |
| int? buttons, | |
| FlutterView? view, | |
| }) { | |
| assert( | |
| isDown, | |
| 'Move events can only be generated when the pointer is down. To ' | |
| 'create a movement event simulating a pointer move when the pointer is ' | |
| 'up, use hover() instead.', | |
| ); | |
| assert(!isPanZoomActive); | |
| final Offset delta = newLocation - location!; | |
| _location = newLocation; | |
| if (buttons != null) { | |
| _buttons = buttons; | |
| } | |
| return PointerMoveEvent( | |
| timeStamp: timeStamp, | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| kind: kind, | |
| device: _device, | |
| pointer: pointer, | |
| position: newLocation, | |
| delta: delta, | |
| buttons: _buttons, | |
| ); | |
| } | |
| /// Create a [PointerUpEvent]. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| /// | |
| /// The object is no longer usable after this method has been called. | |
| PointerUpEvent up({Duration timeStamp = Duration.zero, FlutterView? view}) { | |
| assert(!isPanZoomActive); | |
| assert(isDown); | |
| _isDown = false; | |
| return PointerUpEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| kind: kind, | |
| device: _device, | |
| pointer: pointer, | |
| position: location!, | |
| ); | |
| } | |
| /// Create a [PointerCancelEvent]. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| /// | |
| /// The object is no longer usable after this method has been called. | |
| PointerCancelEvent cancel({Duration timeStamp = Duration.zero, FlutterView? view}) { | |
| assert(isDown); | |
| _isDown = false; | |
| return PointerCancelEvent( | |
| timeStamp: timeStamp, | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| kind: kind, | |
| device: _device, | |
| pointer: pointer, | |
| position: location!, | |
| ); | |
| } | |
| /// Create a [PointerAddedEvent] with the [PointerDeviceKind] the pointer was | |
| /// created with. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| PointerAddedEvent addPointer({ | |
| Duration timeStamp = Duration.zero, | |
| Offset? location, | |
| FlutterView? view, | |
| }) { | |
| _location = location ?? _location; | |
| return PointerAddedEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| kind: kind, | |
| device: _device, | |
| position: _location ?? Offset.zero, | |
| ); | |
| } | |
| /// Create a [PointerRemovedEvent] with the [PointerDeviceKind] the pointer | |
| /// was created with. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| PointerRemovedEvent removePointer({ | |
| Duration timeStamp = Duration.zero, | |
| Offset? location, | |
| FlutterView? view, | |
| }) { | |
| _location = location ?? _location; | |
| return PointerRemovedEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| kind: kind, | |
| device: _device, | |
| pointer: pointer, | |
| position: _location ?? Offset.zero, | |
| ); | |
| } | |
| /// Create a [PointerHoverEvent] to the given location. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| /// | |
| /// [isDown] must be false, since hover events can't be sent when the pointer | |
| /// is up. | |
| PointerHoverEvent hover( | |
| Offset newLocation, { | |
| Duration timeStamp = Duration.zero, | |
| FlutterView? view, | |
| }) { | |
| assert( | |
| !isDown, | |
| 'Hover events can only be generated when the pointer is up. To ' | |
| 'simulate movement when the pointer is down, use move() instead.', | |
| ); | |
| final Offset delta = location != null ? newLocation - location! : Offset.zero; | |
| _location = newLocation; | |
| return PointerHoverEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| kind: kind, | |
| device: _device, | |
| pointer: pointer, | |
| position: newLocation, | |
| delta: delta, | |
| ); | |
| } | |
| /// Create a [PointerScrollEvent] (e.g., scroll wheel scroll; not finger-drag | |
| /// scroll) with the given delta. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| PointerScrollEvent scroll( | |
| Offset scrollDelta, { | |
| Duration timeStamp = Duration.zero, | |
| RespondPointerEventCallback? onRespond, | |
| FlutterView? view, | |
| }) { | |
| assert(kind != PointerDeviceKind.touch, "Touch pointers can't generate pointer signal events"); | |
| assert(location != null); | |
| return PointerScrollEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| kind: kind, | |
| device: _device, | |
| position: location!, | |
| scrollDelta: scrollDelta, | |
| onRespond: onRespond, | |
| ); | |
| } | |
| /// Create a [PointerScrollInertiaCancelEvent] (e.g., user resting their finger on the trackpad). | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| PointerScrollInertiaCancelEvent scrollInertiaCancel({ | |
| Duration timeStamp = Duration.zero, | |
| FlutterView? view, | |
| }) { | |
| assert(kind != PointerDeviceKind.touch, "Touch pointers can't generate pointer signal events"); | |
| assert(location != null); | |
| return PointerScrollInertiaCancelEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| kind: kind, | |
| device: _device, | |
| position: location!, | |
| ); | |
| } | |
| /// Create a [PointerScaleEvent] (e.g., legacy pinch-to-zoom). | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| PointerScaleEvent scale(double scale, {Duration timeStamp = Duration.zero, FlutterView? view}) { | |
| assert(kind != PointerDeviceKind.touch, "Touch pointers can't generate pointer signal events"); | |
| assert(location != null); | |
| return PointerScaleEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| kind: kind, | |
| device: _device, | |
| position: location!, | |
| scale: scale, | |
| ); | |
| } | |
| /// Create a [PointerPanZoomStartEvent] (e.g., trackpad scroll; not scroll wheel | |
| /// or finger-drag scroll) with the given delta. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| PointerPanZoomStartEvent panZoomStart( | |
| Offset location, { | |
| Duration timeStamp = Duration.zero, | |
| FlutterView? view, | |
| }) { | |
| assert(!isPanZoomActive); | |
| assert(kind == PointerDeviceKind.trackpad); | |
| _location = location; | |
| _pan = Offset.zero; | |
| _isPanZoomActive = true; | |
| return PointerPanZoomStartEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| device: _device, | |
| pointer: pointer, | |
| position: location, | |
| ); | |
| } | |
| /// Create a [PointerPanZoomUpdateEvent] to update the active pan/zoom sequence | |
| /// on this pointer with updated pan, scale, and/or rotation values. | |
| /// | |
| /// [rotation] is in units of radians. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| PointerPanZoomUpdateEvent panZoomUpdate( | |
| Offset location, { | |
| Offset pan = Offset.zero, | |
| double scale = 1, | |
| double rotation = 0, | |
| Duration timeStamp = Duration.zero, | |
| FlutterView? view, | |
| }) { | |
| assert(isPanZoomActive); | |
| assert(kind == PointerDeviceKind.trackpad); | |
| _location = location; | |
| final Offset panDelta = pan - _pan!; | |
| _pan = pan; | |
| return PointerPanZoomUpdateEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| device: _device, | |
| pointer: pointer, | |
| position: location, | |
| pan: pan, | |
| panDelta: panDelta, | |
| scale: scale, | |
| rotation: rotation, | |
| ); | |
| } | |
| /// Create a [PointerPanZoomEndEvent] to end the active pan/zoom sequence | |
| /// on this pointer. | |
| /// | |
| /// By default, the time stamp on the event is [Duration.zero]. You can give a | |
| /// specific time stamp by passing the `timeStamp` argument. | |
| PointerPanZoomEndEvent panZoomEnd({Duration timeStamp = Duration.zero, FlutterView? view}) { | |
| assert(isPanZoomActive); | |
| assert(kind == PointerDeviceKind.trackpad); | |
| _isPanZoomActive = false; | |
| _pan = null; | |
| return PointerPanZoomEndEvent( | |
| viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId, | |
| timeStamp: timeStamp, | |
| device: _device, | |
| pointer: pointer, | |
| position: location!, | |
| ); | |
| } | |
| } | |
| /// 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); | |
| /// Signature for callbacks that perform hit-testing at a given location. | |
| typedef HitTester = HitTestResult Function(Offset location); | |
| /// A class for performing gestures in tests. | |
| /// | |
| /// The simplest way to create a [TestGesture] is to call | |
| /// [WidgetTester.startGesture]. | |
| class TestGesture { | |
| /// Create a [TestGesture] without dispatching any events from it. | |
| /// The [TestGesture] can then be manipulated to perform future actions. | |
| /// | |
| /// By default, the pointer identifier used is 1. This can be overridden by | |
| /// providing the `pointer` argument. | |
| /// | |
| /// A function to use for hit testing must be provided via the `hitTester` | |
| /// argument, and a function to use for dispatching events must be provided | |
| /// via the `dispatcher` argument. | |
| /// | |
| /// The device `kind` defaults to [PointerDeviceKind.touch], but move events | |
| /// when the pointer is "up" require a kind other than | |
| /// [PointerDeviceKind.touch], like [PointerDeviceKind.mouse], for example, | |
| /// because touch devices can't produce movement events when they are "up". | |
| /// | |
| /// None of the arguments may be null. The `dispatcher` and `hitTester` | |
| /// arguments are required. | |
| TestGesture({ | |
| required this._dispatcher, | |
| int pointer = 1, | |
| PointerDeviceKind kind = PointerDeviceKind.touch, | |
| int? device, | |
| int buttons = kPrimaryButton, | |
| }) : _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, | |
| FlutterView? view, | |
| }) async { | |
| assert( | |
| _pointer.kind != PointerDeviceKind.trackpad, | |
| 'Trackpads are expected to send panZoomStart events, not down events.', | |
| ); | |
| return TestAsyncUtils.guard<void>(() async { | |
| return _dispatcher(_pointer.down(downLocation, timeStamp: timeStamp, view: view)); | |
| }); | |
| } | |
| /// Dispatch a pointer down event at the given `downLocation`, caching the | |
| /// hit test result with a custom down event. | |
| Future<void> downWithCustomEvent(Offset downLocation, PointerDownEvent event) async { | |
| assert( | |
| _pointer.kind != PointerDeviceKind.trackpad, | |
| 'Trackpads are expected to send panZoomStart events, not down events', | |
| ); | |
| _pointer.setDownInfo(event, downLocation); | |
| return TestAsyncUtils.guard<void>(() async { | |
| return _dispatcher(event); | |
| }); | |
| } | |
| final EventDispatcher _dispatcher; | |
| final TestPointer _pointer; | |
| /// 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); | |
| }); | |
| } | |
| /// 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), | |
| ); | |
| }); | |
| } | |
| /// 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), | |
| ); | |
| }); | |
| } | |
| /// Send a move event moving the pointer by the given offset. | |
| /// | |
| /// If the pointer is down, then a move event is dispatched. If the pointer is | |
| /// up, then a hover event is dispatched. | |
| /// | |
| /// See also: | |
| /// * [WidgetController.drag], a method to simulate a drag. | |
| /// * [WidgetController.timedDrag], a method to simulate the drag of a given widget in a given duration. | |
| /// It sends move events at a given frequency and it is useful when there are listeners involved. | |
| /// * [WidgetController.fling], a method to simulate a fling. | |
| Future<void> moveBy(Offset offset, {Duration timeStamp = Duration.zero, FlutterView? view}) { | |
| assert(_pointer.location != null); | |
| if (_pointer.isPanZoomActive) { | |
| return panZoomUpdate( | |
| _pointer.location!, | |
| pan: (_pointer.pan ?? Offset.zero) + offset, | |
| timeStamp: timeStamp, | |
| view: view, | |
| ); | |
| } else { | |
| return moveTo(_pointer.location! + offset, timeStamp: timeStamp, view: view); | |
| } | |
| } | |
| /// Send a move event moving the pointer to the given location. | |
| /// | |
| /// If the pointer is down, then a move event is dispatched. If the pointer is | |
| /// up, then a hover event is dispatched. | |
| /// | |
| /// See also: | |
| /// * [WidgetController.drag], a method to simulate a drag. | |
| /// * [WidgetController.timedDrag], a method to simulate the drag of a given widget in a given duration. | |
| /// It sends move events at a given frequency and it is useful when there are listeners involved. | |
| /// * [WidgetController.fling], a method to simulate a fling. | |
| Future<void> moveTo(Offset location, {Duration timeStamp = Duration.zero, FlutterView? view}) { | |
| assert(_pointer.kind != PointerDeviceKind.trackpad); | |
| return TestAsyncUtils.guard<void>(() { | |
| if (_pointer._isDown) { | |
| return _dispatcher(_pointer.move(location, timeStamp: timeStamp, view: view)); | |
| } else { | |
| return _dispatcher(_pointer.hover(location, timeStamp: timeStamp, view: view)); | |
| } | |
| }); | |
| } | |
| /// End the gesture by releasing the pointer. For trackpad pointers this | |
| /// will send a panZoomEnd event instead of an up event. | |
| Future<void> up({Duration timeStamp = Duration.zero, FlutterView? view}) { | |
| return TestAsyncUtils.guard<void>(() async { | |
| if (_pointer.kind == PointerDeviceKind.trackpad) { | |
| assert(_pointer._isPanZoomActive); | |
| await _dispatcher(_pointer.panZoomEnd(timeStamp: timeStamp, view: view)); | |
| assert(!_pointer._isPanZoomActive); | |
| } else { | |
| assert(_pointer._isDown); | |
| await _dispatcher(_pointer.up(timeStamp: timeStamp, view: view)); | |
| assert(!_pointer._isDown); | |
| } | |
| }); | |
| } | |
| /// End the gesture by canceling the pointer (as would happen if the | |
| /// system showed a modal dialog on top of the Flutter application, | |
| /// for instance). | |
| Future<void> cancel({Duration timeStamp = Duration.zero}) { | |
| assert(_pointer.kind != PointerDeviceKind.trackpad, 'Trackpads do not send cancel events.'); | |
| return TestAsyncUtils.guard<void>(() async { | |
| assert(_pointer._isDown); | |
| await _dispatcher(_pointer.cancel(timeStamp: timeStamp)); | |
| assert(!_pointer._isDown); | |
| }); | |
| } | |
| /// Dispatch a pointer pan zoom start event at the given `location`, caching the | |
| /// hit test result. | |
| Future<void> panZoomStart( | |
| Offset location, { | |
| Duration timeStamp = Duration.zero, | |
| FlutterView? view, | |
| }) async { | |
| assert( | |
| _pointer.kind == PointerDeviceKind.trackpad, | |
| 'Only trackpads can send PointerPanZoom events.', | |
| ); | |
| return TestAsyncUtils.guard<void>(() async { | |
| return _dispatcher(_pointer.panZoomStart(location, timeStamp: timeStamp, view: view)); | |
| }); | |
| } | |
| /// Dispatch a pointer pan zoom update event at the given `location`, caching the | |
| /// hit test result. | |
| Future<void> panZoomUpdate( | |
| Offset location, { | |
| Offset pan = Offset.zero, | |
| double scale = 1, | |
| double rotation = 0, | |
| Duration timeStamp = Duration.zero, | |
| FlutterView? view, | |
| }) async { | |
| assert( | |
| _pointer.kind == PointerDeviceKind.trackpad, | |
| 'Only trackpads can send PointerPanZoom events.', | |
| ); | |
| return TestAsyncUtils.guard<void>(() async { | |
| return _dispatcher( | |
| _pointer.panZoomUpdate( | |
| location, | |
| pan: pan, | |
| scale: scale, | |
| rotation: rotation, | |
| timeStamp: timeStamp, | |
| view: view, | |
| ), | |
| ); | |
| }); | |
| } | |
| /// Dispatch a pointer pan zoom end event, caching the hit test result. | |
| Future<void> panZoomEnd({Duration timeStamp = Duration.zero}) async { | |
| assert( | |
| _pointer.kind == PointerDeviceKind.trackpad, | |
| 'Only trackpads can send PointerPanZoom events.', | |
| ); | |
| return TestAsyncUtils.guard<void>(() async { | |
| return _dispatcher(_pointer.panZoomEnd(timeStamp: timeStamp)); | |
| }); | |
| } | |
| } | |
| /// A record of input [PointerEvent] list with the timeStamp of when it is | |
| /// injected. | |
| /// | |
| /// The [timeDelay] is used to indicate the time when the event packet should | |
| /// be sent. | |
| /// | |
| /// This is a simulation of how the framework is receiving input events from | |
| /// the engine. See [GestureBinding] and [PointerDataPacket]. | |
| class PointerEventRecord { | |
| /// Creates a pack of [PointerEvent]s. | |
| PointerEventRecord(this.timeDelay, this.events); | |
| /// The time delay of when the event record should be sent. | |
| /// | |
| /// This value is used as the time delay relative to the start of | |
| /// [WidgetTester.handlePointerEventRecord] call. | |
| final Duration timeDelay; | |
| /// The event list of the record. | |
| /// | |
| /// This can be considered as a simulation of the events expanded from the | |
| /// [PointerDataPacket]. | |
| /// | |
| /// See [PointerEventConverter.expand]. | |
| final List<PointerEvent> events; | |
| } |