| // 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/gestures.dart'; | |
| import 'test_async_utils.dart'; | |
| export 'dart:ui' show Point; | |
| /// 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. | |
| 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. | |
| Point get location => _location; | |
| Point _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(Point 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(Point newLocation, { Duration timeStamp: Duration.ZERO }) { | |
| assert(isDown); | |
| Offset delta = newLocation - location; | |
| _location = newLocation; | |
| return new PointerMoveEvent( | |
| timeStamp: timeStamp, | |
| pointer: pointer, | |
| down: _isDown, | |
| 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(Point 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 ID 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(Point downLocation, { | |
| int pointer: 1, | |
| HitTester hitTester, | |
| EventDispatcher dispatcher | |
| }) async { | |
| assert(hitTester != null); | |
| assert(dispatcher != null); | |
| final Completer<TestGesture> completer = new Completer<TestGesture>(); | |
| TestGesture result; | |
| 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; | |
| }).whenComplete(() { | |
| completer.complete(result); | |
| }); | |
| return completer.future; | |
| } | |
| 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) { | |
| assert(_pointer._isDown); | |
| return moveTo(_pointer.location + offset); | |
| } | |
| /// Send a move event moving the pointer to the given location. | |
| Future<Null> moveTo(Point location) { | |
| return TestAsyncUtils.guard(() { | |
| assert(_pointer._isDown); | |
| return _dispatcher(_pointer.move(location), _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; | |
| }); | |
| } | |
| } |