| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/gestures.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 ]); |
| |
| /// The pointer identifier used for events generated by this object. |
| /// |
| /// Set when the object is constructed. Defaults to 1. |
| final int pointer; |
| |
| /// 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; |
| |
| /// 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; |
| |
| /// 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. |
| PointerDownEvent down(Offset newLocation, { Duration timeStamp: Duration.ZERO }) { |
| assert(!isDown); |
| _isDown = true; |
| _location = newLocation; |
| return new PointerDownEvent( |
| timeStamp: timeStamp, |
| pointer: pointer, |
| position: location |
| ); |
| } |
| |
| /// 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. |
| PointerMoveEvent move(Offset newLocation, { Duration timeStamp: Duration.ZERO }) { |
| assert(isDown); |
| final Offset delta = newLocation - location; |
| _location = newLocation; |
| return new PointerMoveEvent( |
| timeStamp: timeStamp, |
| pointer: pointer, |
| position: newLocation, |
| delta: delta |
| ); |
| } |
| |
| /// 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 }) { |
| assert(isDown); |
| _isDown = false; |
| return new PointerUpEvent( |
| timeStamp: timeStamp, |
| 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 }) { |
| assert(isDown); |
| _isDown = false; |
| return new PointerCancelEvent( |
| timeStamp: timeStamp, |
| 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 Future<Null> EventDispatcher(PointerEvent event, HitTestResult result); |
| |
| /// Signature for callbacks that perform hit-testing at a given location. |
| typedef HitTestResult HitTester(Offset location); |
| |
| /// A class for performing gestures in tests. |
| /// |
| /// The simplest way to create a [TestGesture] is to call |
| /// [WidgetTester.startGesture]. |
| class TestGesture { |
| TestGesture._(this._dispatcher, this._result, this._pointer); |
| |
| /// Create a [TestGesture] by starting with a pointerDown at the |
| /// given point. |
| /// |
| /// By default, the pointer identifier used is 1. This can be overridden by |
| /// providing the `pointer` argument. |
| /// |
| /// A function to use for hit testing should be provided via the `hitTester` |
| /// argument, and a function to use for dispatching events should be provided |
| /// via the `dispatcher` argument. |
| static Future<TestGesture> down(Offset downLocation, { |
| int pointer: 1, |
| @required HitTester hitTester, |
| @required EventDispatcher dispatcher, |
| }) async { |
| assert(hitTester != null); |
| assert(dispatcher != null); |
| TestGesture result; |
| return TestAsyncUtils.guard(() async { |
| // dispatch down event |
| final HitTestResult hitTestResult = hitTester(downLocation); |
| final TestPointer testPointer = new TestPointer(pointer); |
| await dispatcher(testPointer.down(downLocation), hitTestResult); |
| |
| // create a TestGesture |
| result = new TestGesture._(dispatcher, hitTestResult, testPointer); |
| return null; |
| }).then<TestGesture>((Null value) { |
| return result; |
| }, onError: (dynamic error, StackTrace stack) { |
| return new Future<TestGesture>.error(error, stack); |
| }); |
| } |
| |
| final EventDispatcher _dispatcher; |
| final HitTestResult _result; |
| final TestPointer _pointer; |
| |
| /// Send a move event moving the pointer by the given offset. |
| Future<Null> moveBy(Offset offset, { Duration timeStamp: Duration.ZERO }) { |
| assert(_pointer._isDown); |
| return moveTo(_pointer.location + offset, timeStamp: timeStamp); |
| } |
| |
| /// Send a move event moving the pointer to the given location. |
| Future<Null> moveTo(Offset location, { Duration timeStamp: Duration.ZERO }) { |
| return TestAsyncUtils.guard(() { |
| assert(_pointer._isDown); |
| return _dispatcher(_pointer.move(location, timeStamp: timeStamp), _result); |
| }); |
| } |
| |
| /// End the gesture by releasing the pointer. |
| /// |
| /// The object is no longer usable after this method has been called. |
| Future<Null> up() { |
| return TestAsyncUtils.guard(() async { |
| assert(_pointer._isDown); |
| await _dispatcher(_pointer.up(), _result); |
| assert(!_pointer._isDown); |
| return null; |
| }); |
| } |
| |
| /// 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). |
| /// |
| /// The object is no longer usable after this method has been called. |
| Future<Null> cancel() { |
| return TestAsyncUtils.guard(() async { |
| assert(_pointer._isDown); |
| await _dispatcher(_pointer.cancel(), _result); |
| assert(!_pointer._isDown); |
| return null; |
| }); |
| } |
| } |