| // 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. |
| |
| import 'dart:math' as math; |
| |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:flutter/gestures.dart'; |
| |
| import 'gesture_tester.dart'; |
| |
| void main() { |
| setUp(ensureGestureBinding); |
| |
| testGesture('Should recognize scale gestures', (GestureTester tester) { |
| final ScaleGestureRecognizer scale = ScaleGestureRecognizer(); |
| final TapGestureRecognizer tap = TapGestureRecognizer(); |
| |
| bool didStartScale = false; |
| Offset updatedFocalPoint; |
| scale.onStart = (ScaleStartDetails details) { |
| didStartScale = true; |
| updatedFocalPoint = details.focalPoint; |
| }; |
| |
| double updatedScale; |
| double updatedHorizontalScale; |
| double updatedVerticalScale; |
| scale.onUpdate = (ScaleUpdateDetails details) { |
| updatedScale = details.scale; |
| updatedHorizontalScale = details.horizontalScale; |
| updatedVerticalScale = details.verticalScale; |
| updatedFocalPoint = details.focalPoint; |
| }; |
| |
| bool didEndScale = false; |
| scale.onEnd = (ScaleEndDetails details) { |
| didEndScale = true; |
| }; |
| |
| bool didTap = false; |
| tap.onTap = () { |
| didTap = true; |
| }; |
| |
| final TestPointer pointer1 = TestPointer(1); |
| |
| final PointerDownEvent down = pointer1.down(const Offset(0.0, 0.0)); |
| scale.addPointer(down); |
| tap.addPointer(down); |
| |
| tester.closeArena(1); |
| expect(didStartScale, isFalse); |
| expect(updatedScale, isNull); |
| expect(updatedFocalPoint, isNull); |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| // One-finger panning |
| tester.route(down); |
| expect(didStartScale, isFalse); |
| expect(updatedScale, isNull); |
| expect(updatedFocalPoint, isNull); |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| tester.route(pointer1.move(const Offset(20.0, 30.0))); |
| expect(didStartScale, isTrue); |
| didStartScale = false; |
| expect(updatedFocalPoint, const Offset(20.0, 30.0)); |
| updatedFocalPoint = null; |
| expect(updatedScale, 1.0); |
| updatedScale = null; |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| // Two-finger scaling |
| final TestPointer pointer2 = TestPointer(2); |
| final PointerDownEvent down2 = pointer2.down(const Offset(10.0, 20.0)); |
| scale.addPointer(down2); |
| tap.addPointer(down2); |
| tester.closeArena(2); |
| tester.route(down2); |
| |
| expect(didEndScale, isTrue); |
| didEndScale = false; |
| expect(updatedScale, isNull); |
| expect(updatedFocalPoint, isNull); |
| expect(didStartScale, isFalse); |
| |
| // Zoom in |
| tester.route(pointer2.move(const Offset(0.0, 10.0))); |
| expect(didStartScale, isTrue); |
| didStartScale = false; |
| expect(updatedFocalPoint, const Offset(10.0, 20.0)); |
| updatedFocalPoint = null; |
| expect(updatedScale, 2.0); |
| expect(updatedHorizontalScale, 2.0); |
| expect(updatedVerticalScale, 2.0); |
| updatedScale = null; |
| updatedHorizontalScale = null; |
| updatedVerticalScale = null; |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| // Zoom out |
| tester.route(pointer2.move(const Offset(15.0, 25.0))); |
| expect(updatedFocalPoint, const Offset(17.5, 27.5)); |
| expect(updatedScale, 0.5); |
| expect(updatedHorizontalScale, 0.5); |
| expect(updatedVerticalScale, 0.5); |
| expect(didTap, isFalse); |
| |
| // Horizontal scaling |
| tester.route(pointer2.move(const Offset(0.0, 20.0))); |
| expect(updatedHorizontalScale, 2.0); |
| expect(updatedVerticalScale, 1.0); |
| |
| // Vertical scaling |
| tester.route(pointer2.move(const Offset(10.0, 10.0))); |
| expect(updatedHorizontalScale, 1.0); |
| expect(updatedVerticalScale, 2.0); |
| tester.route(pointer2.move(const Offset(15.0, 25.0))); |
| updatedFocalPoint = null; |
| updatedScale = null; |
| |
| // Three-finger scaling |
| final TestPointer pointer3 = TestPointer(3); |
| final PointerDownEvent down3 = pointer3.down(const Offset(25.0, 35.0)); |
| scale.addPointer(down3); |
| tap.addPointer(down3); |
| tester.closeArena(3); |
| tester.route(down3); |
| |
| expect(didEndScale, isTrue); |
| didEndScale = false; |
| expect(updatedScale, isNull); |
| expect(updatedFocalPoint, isNull); |
| expect(didStartScale, isFalse); |
| |
| // Zoom in |
| tester.route(pointer3.move(const Offset(55.0, 65.0))); |
| expect(didStartScale, isTrue); |
| didStartScale = false; |
| expect(updatedFocalPoint, const Offset(30.0, 40.0)); |
| updatedFocalPoint = null; |
| expect(updatedScale, 5.0); |
| updatedScale = null; |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| // Return to original positions but with different fingers |
| tester.route(pointer1.move(const Offset(25.0, 35.0))); |
| tester.route(pointer2.move(const Offset(20.0, 30.0))); |
| tester.route(pointer3.move(const Offset(15.0, 25.0))); |
| expect(didStartScale, isFalse); |
| expect(updatedFocalPoint, const Offset(20.0, 30.0)); |
| updatedFocalPoint = null; |
| expect(updatedScale, 1.0); |
| updatedScale = null; |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| tester.route(pointer1.up()); |
| expect(didStartScale, isFalse); |
| expect(updatedFocalPoint, isNull); |
| expect(updatedScale, isNull); |
| expect(didEndScale, isTrue); |
| didEndScale = false; |
| expect(didTap, isFalse); |
| |
| // Continue scaling with two fingers |
| tester.route(pointer3.move(const Offset(10.0, 20.0))); |
| expect(didStartScale, isTrue); |
| didStartScale = false; |
| expect(updatedFocalPoint, const Offset(15.0, 25.0)); |
| updatedFocalPoint = null; |
| expect(updatedScale, 2.0); |
| updatedScale = null; |
| |
| // Continue rotating with two fingers |
| tester.route(pointer3.move(const Offset(30.0, 40.0))); |
| expect(updatedFocalPoint, const Offset(25.0, 35.0)); |
| updatedFocalPoint = null; |
| expect(updatedScale, 2.0); |
| updatedScale = null; |
| tester.route(pointer3.move(const Offset(10.0, 20.0))); |
| expect(updatedFocalPoint, const Offset(15.0, 25.0)); |
| updatedFocalPoint = null; |
| expect(updatedScale, 2.0); |
| updatedScale = null; |
| |
| tester.route(pointer2.up()); |
| expect(didStartScale, isFalse); |
| expect(updatedFocalPoint, isNull); |
| expect(updatedScale, isNull); |
| expect(didEndScale, isTrue); |
| didEndScale = false; |
| expect(didTap, isFalse); |
| |
| // Continue panning with one finger |
| tester.route(pointer3.move(const Offset(0.0, 0.0))); |
| expect(didStartScale, isTrue); |
| didStartScale = false; |
| expect(updatedFocalPoint, const Offset(0.0, 0.0)); |
| updatedFocalPoint = null; |
| expect(updatedScale, 1.0); |
| updatedScale = null; |
| |
| // We are done |
| tester.route(pointer3.up()); |
| expect(didStartScale, isFalse); |
| expect(updatedFocalPoint, isNull); |
| expect(updatedScale, isNull); |
| expect(didEndScale, isTrue); |
| didEndScale = false; |
| expect(didTap, isFalse); |
| |
| scale.dispose(); |
| tap.dispose(); |
| }); |
| |
| testGesture('Rejects scale gestures from unallowed device kinds', (GestureTester tester) { |
| final ScaleGestureRecognizer scale = ScaleGestureRecognizer(kind: PointerDeviceKind.touch); |
| |
| bool didStartScale = false; |
| scale.onStart = (ScaleStartDetails details) { |
| didStartScale = true; |
| }; |
| |
| double updatedScale; |
| scale.onUpdate = (ScaleUpdateDetails details) { |
| updatedScale = details.scale; |
| }; |
| |
| final TestPointer mousePointer = TestPointer(1, PointerDeviceKind.mouse); |
| |
| final PointerDownEvent down = mousePointer.down(const Offset(0.0, 0.0)); |
| scale.addPointer(down); |
| tester.closeArena(1); |
| |
| // One-finger panning |
| tester.route(down); |
| expect(didStartScale, isFalse); |
| expect(updatedScale, isNull); |
| |
| // Using a mouse, the scale gesture shouldn't even start. |
| tester.route(mousePointer.move(const Offset(20.0, 30.0))); |
| expect(didStartScale, isFalse); |
| expect(updatedScale, isNull); |
| |
| scale.dispose(); |
| }); |
| |
| testGesture('Scale gestures starting from allowed device kinds cannot be ended from unallowed devices', (GestureTester tester) { |
| final ScaleGestureRecognizer scale = ScaleGestureRecognizer(kind: PointerDeviceKind.touch); |
| |
| bool didStartScale = false; |
| Offset updatedFocalPoint; |
| scale.onStart = (ScaleStartDetails details) { |
| didStartScale = true; |
| updatedFocalPoint = details.focalPoint; |
| }; |
| |
| double updatedScale; |
| scale.onUpdate = (ScaleUpdateDetails details) { |
| updatedScale = details.scale; |
| updatedFocalPoint = details.focalPoint; |
| }; |
| |
| bool didEndScale = false; |
| scale.onEnd = (ScaleEndDetails details) { |
| didEndScale = true; |
| }; |
| |
| final TestPointer touchPointer = TestPointer(1, PointerDeviceKind.touch); |
| |
| final PointerDownEvent down = touchPointer.down(const Offset(0.0, 0.0)); |
| scale.addPointer(down); |
| tester.closeArena(1); |
| |
| // One-finger panning |
| tester.route(down); |
| expect(didStartScale, isTrue); |
| didStartScale = false; |
| expect(updatedScale, isNull); |
| expect(updatedFocalPoint, const Offset(0.0, 0.0)); |
| expect(didEndScale, isFalse); |
| |
| // The gesture can start using one touch finger. |
| tester.route(touchPointer.move(const Offset(20.0, 30.0))); |
| expect(updatedFocalPoint, const Offset(20.0, 30.0)); |
| updatedFocalPoint = null; |
| expect(updatedScale, 1.0); |
| updatedScale = null; |
| expect(didEndScale, isFalse); |
| |
| // Two-finger scaling |
| final TestPointer mousePointer = TestPointer(2, PointerDeviceKind.mouse); |
| final PointerDownEvent down2 = mousePointer.down(const Offset(10.0, 20.0)); |
| scale.addPointer(down2); |
| tester.closeArena(2); |
| tester.route(down2); |
| |
| // Mouse-generated events are ignored. |
| expect(didEndScale, isFalse); |
| expect(updatedScale, isNull); |
| expect(didStartScale, isFalse); |
| |
| // Zoom in using a mouse doesn't work either. |
| tester.route(mousePointer.move(const Offset(0.0, 10.0))); |
| expect(updatedScale, isNull); |
| expect(didEndScale, isFalse); |
| |
| scale.dispose(); |
| }); |
| |
| testGesture('Scale gesture competes with drag', (GestureTester tester) { |
| final ScaleGestureRecognizer scale = ScaleGestureRecognizer(); |
| final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); |
| |
| final List<String> log = <String>[]; |
| |
| scale.onStart = (ScaleStartDetails details) { log.add('scale-start'); }; |
| scale.onUpdate = (ScaleUpdateDetails details) { log.add('scale-update'); }; |
| scale.onEnd = (ScaleEndDetails details) { log.add('scale-end'); }; |
| |
| drag.onStart = (DragStartDetails details) { log.add('drag-start'); }; |
| drag.onEnd = (DragEndDetails details) { log.add('drag-end'); }; |
| |
| final TestPointer pointer1 = TestPointer(1); |
| |
| final PointerDownEvent down = pointer1.down(const Offset(10.0, 10.0)); |
| scale.addPointer(down); |
| drag.addPointer(down); |
| |
| tester.closeArena(1); |
| expect(log, isEmpty); |
| |
| // Vertical moves are scales. |
| tester.route(down); |
| expect(log, isEmpty); |
| |
| // scale will win if focal point delta exceeds 18.0*2 |
| |
| tester.route(pointer1.move(const Offset(10.0, 50.0))); // delta of 40.0 exceeds 18.0*2 |
| expect(log, equals(<String>['scale-start', 'scale-update'])); |
| log.clear(); |
| |
| final TestPointer pointer2 = TestPointer(2); |
| final PointerDownEvent down2 = pointer2.down(const Offset(10.0, 20.0)); |
| scale.addPointer(down2); |
| drag.addPointer(down2); |
| |
| tester.closeArena(2); |
| expect(log, isEmpty); |
| |
| // Second pointer joins scale even though it moves horizontally. |
| tester.route(down2); |
| expect(log, <String>['scale-end']); |
| log.clear(); |
| |
| tester.route(pointer2.move(const Offset(30.0, 20.0))); |
| expect(log, equals(<String>['scale-start', 'scale-update'])); |
| log.clear(); |
| |
| tester.route(pointer1.up()); |
| expect(log, equals(<String>['scale-end'])); |
| log.clear(); |
| |
| tester.route(pointer2.up()); |
| expect(log, isEmpty); |
| log.clear(); |
| |
| // Horizontal moves are either drags or scales, depending on which wins first. |
| // TODO(ianh): https://github.com/flutter/flutter/issues/11384 |
| // In this case, we move fast, so that the scale wins. If we moved slowly, |
| // the horizontal drag would win, since it was added first. |
| final TestPointer pointer3 = TestPointer(3); |
| final PointerDownEvent down3 = pointer3.down(const Offset(30.0, 30.0)); |
| scale.addPointer(down3); |
| drag.addPointer(down3); |
| tester.closeArena(3); |
| tester.route(down3); |
| |
| expect(log, isEmpty); |
| |
| tester.route(pointer3.move(const Offset(100.0, 30.0))); |
| expect(log, equals(<String>['scale-start', 'scale-update'])); |
| log.clear(); |
| |
| tester.route(pointer3.up()); |
| expect(log, equals(<String>['scale-end'])); |
| log.clear(); |
| |
| scale.dispose(); |
| drag.dispose(); |
| }); |
| |
| testGesture('Should recognize rotation gestures', (GestureTester tester) { |
| final ScaleGestureRecognizer scale = ScaleGestureRecognizer(); |
| final TapGestureRecognizer tap = TapGestureRecognizer(); |
| |
| bool didStartScale = false; |
| Offset updatedFocalPoint; |
| scale.onStart = (ScaleStartDetails details) { |
| didStartScale = true; |
| updatedFocalPoint = details.focalPoint; |
| }; |
| |
| double updatedRotation; |
| scale.onUpdate = (ScaleUpdateDetails details) { |
| updatedRotation = details.rotation; |
| updatedFocalPoint = details.focalPoint; |
| }; |
| |
| bool didEndScale = false; |
| scale.onEnd = (ScaleEndDetails details) { |
| didEndScale = true; |
| }; |
| |
| bool didTap = false; |
| tap.onTap = () { |
| didTap = true; |
| }; |
| |
| final TestPointer pointer1 = TestPointer(1); |
| |
| final PointerDownEvent down = pointer1.down(const Offset(0.0, 0.0)); |
| scale.addPointer(down); |
| tap.addPointer(down); |
| |
| tester.closeArena(1); |
| expect(didStartScale, isFalse); |
| expect(updatedRotation, isNull); |
| expect(updatedFocalPoint, isNull); |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| tester.route(down); |
| tester.route(pointer1.move(const Offset(20.0, 30.0))); |
| expect(didStartScale, isTrue); |
| didStartScale = false; |
| |
| expect(updatedFocalPoint, const Offset(20.0, 30.0)); |
| updatedFocalPoint = null; |
| expect(updatedRotation, 0.0); |
| updatedRotation = null; |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| // Two-finger scaling |
| final TestPointer pointer2 = TestPointer(2); |
| final PointerDownEvent down2 = pointer2.down(const Offset(30.0, 40.0)); |
| scale.addPointer(down2); |
| tap.addPointer(down2); |
| tester.closeArena(2); |
| tester.route(down2); |
| |
| expect(didEndScale, isTrue); |
| didEndScale = false; |
| expect(updatedFocalPoint, isNull); |
| expect(updatedRotation, isNull); |
| expect(didStartScale, isFalse); |
| |
| |
| // Zoom in |
| tester.route(pointer2.move(const Offset(40.0, 50.0))); |
| expect(didStartScale, isTrue); |
| didStartScale = false; |
| expect(updatedFocalPoint, const Offset(30.0, 40.0)); |
| updatedFocalPoint = null; |
| expect(updatedRotation, 0.0); |
| updatedRotation = null; |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| // Rotation |
| tester.route(pointer2.move(const Offset(0.0, 10.0))); |
| expect(updatedFocalPoint, const Offset(10.0, 20.0)); |
| updatedFocalPoint = null; |
| expect(updatedRotation, math.pi); |
| updatedRotation = null; |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| // Three-finger scaling |
| final TestPointer pointer3 = TestPointer(3); |
| final PointerDownEvent down3 = pointer3.down(const Offset(25.0, 35.0)); |
| scale.addPointer(down3); |
| tap.addPointer(down3); |
| tester.closeArena(3); |
| tester.route(down3); |
| |
| expect(didEndScale, isTrue); |
| didEndScale = false; |
| expect(updatedFocalPoint, isNull); |
| expect(updatedRotation, isNull); |
| expect(didStartScale, isFalse); |
| |
| // Zoom in |
| tester.route(pointer3.move(const Offset(55.0, 65.0))); |
| expect(didStartScale, isTrue); |
| didStartScale = false; |
| expect(updatedFocalPoint, const Offset(25.0, 35.0)); |
| updatedFocalPoint = null; |
| expect(updatedRotation, 0.0); |
| updatedRotation = null; |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| // Return to original positions but with different fingers |
| tester.route(pointer1.move(const Offset(25.0, 35.0))); |
| tester.route(pointer2.move(const Offset(20.0, 30.0))); |
| tester.route(pointer3.move(const Offset(15.0, 25.0))); |
| expect(didStartScale, isFalse); |
| expect(updatedFocalPoint, const Offset(20.0, 30.0)); |
| updatedFocalPoint = null; |
| expect(updatedRotation, 0.0); |
| updatedRotation = null; |
| expect(didEndScale, isFalse); |
| expect(didTap, isFalse); |
| |
| tester.route(pointer1.up()); |
| expect(didStartScale, isFalse); |
| expect(updatedFocalPoint, isNull); |
| expect(updatedRotation, isNull); |
| expect(didEndScale, isTrue); |
| didEndScale = false; |
| expect(didTap, isFalse); |
| |
| |
| // Continue scaling with two fingers |
| tester.route(pointer3.move(const Offset(10.0, 20.0))); |
| expect(didStartScale, isTrue); |
| didStartScale = false; |
| expect(updatedFocalPoint, const Offset(15.0, 25.0)); |
| updatedFocalPoint = null; |
| expect(updatedRotation, 0.0); |
| updatedRotation = null; |
| |
| // Continue rotating with two fingers |
| tester.route(pointer3.move(const Offset(30.0, 40.0))); |
| expect(updatedFocalPoint, const Offset(25.0, 35.0)); |
| updatedFocalPoint = null; |
| expect(updatedRotation, - math.pi); |
| updatedRotation = null; |
| tester.route(pointer3.move(const Offset(10.0, 20.0))); |
| expect(updatedFocalPoint, const Offset(15.0, 25.0)); |
| updatedFocalPoint = null; |
| expect(updatedRotation, 0.0); |
| updatedRotation = null; |
| |
| tester.route(pointer2.up()); |
| expect(didStartScale, isFalse); |
| expect(updatedFocalPoint, isNull); |
| expect(updatedRotation, isNull); |
| expect(didEndScale, isTrue); |
| didEndScale = false; |
| expect(didTap, isFalse); |
| |
| // We are done |
| tester.route(pointer3.up()); |
| expect(didStartScale, isFalse); |
| expect(updatedFocalPoint, isNull); |
| expect(updatedRotation, isNull); |
| expect(didEndScale, isFalse); |
| didEndScale = false; |
| expect(didTap, isFalse); |
| |
| scale.dispose(); |
| tap.dispose(); |
| }); |
| } |