diff --git a/packages/flutter/lib/gestures.dart b/packages/flutter/lib/gestures.dart index 8a3f7dfde3..f5432bc455 100644 --- a/packages/flutter/lib/gestures.dart +++ b/packages/flutter/lib/gestures.dart @@ -8,7 +8,6 @@ library gestures; export 'src/gestures/arena.dart'; export 'src/gestures/constants.dart'; export 'src/gestures/drag.dart'; -export 'src/gestures/double_tap.dart'; export 'src/gestures/events.dart'; export 'src/gestures/long_press.dart'; export 'src/gestures/pointer_router.dart'; diff --git a/packages/flutter/lib/src/gestures/double_tap.dart b/packages/flutter/lib/src/gestures/double_tap.dart deleted file mode 100644 index 1ecaf68068..0000000000 --- a/packages/flutter/lib/src/gestures/double_tap.dart +++ /dev/null @@ -1,161 +0,0 @@ -// 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 'arena.dart'; -import 'constants.dart'; -import 'events.dart'; -import 'recognizer.dart'; -import 'tap.dart'; - -class DoubleTapGestureRecognizer extends DisposableArenaMember { - - DoubleTapGestureRecognizer({ this.router, this.onDoubleTap }); - - // Implementation notes: - // The double tap recognizer can be in one of four states. There's no - // explicit enum for the states, because they are already captured by - // the state of existing fields. Specifically: - // Waiting on first tap: In this state, the _trackers list is empty, and - // _firstTap is null. - // First tap in progress: In this state, the _trackers list contains all - // the states for taps that have begun but not completed. This list can - // have more than one entry if two pointers begin to tap. - // Waiting on second tap: In this state, one of the in-progress taps has - // completed successfully. The _trackers list is again empty, and - // _firstTap records the successful tap. - // Second tap in progress: Much like the "first tap in progress" state, but - // _firstTap is non-null. If a tap completes successfully while in this - // state, the callback is invoked and the state is reset. - // There are various other scenarios that cause the state to reset: - // - All in-progress taps are rejected (by time, distance, pointercancel, etc) - // - The long timer between taps expires - // - The gesture arena decides we have been rejected wholesale - - PointerRouter router; - GestureTapCallback onDoubleTap; - - Timer _doubleTapTimer; - TapTracker _firstTap; - final Map _trackers = new Map(); - - void addPointer(PointerInputEvent event) { - // Ignore out-of-bounds second taps - if (_firstTap != null && - !_firstTap.isWithinTolerance(event, kDoubleTapTouchSlop)) - return; - _stopDoubleTapTimer(); - TapTracker tracker = new TapTracker( - event: event, - entry: GestureArena.instance.add(event.pointer, this) - ); - _trackers[event.pointer] = tracker; - tracker.startTrackingPointer(router, handleEvent); - } - - void handleEvent(PointerInputEvent event) { - TapTracker tracker = _trackers[event.pointer]; - assert(tracker != null); - if (event.type == 'pointerup') { - if (_firstTap == null) - _registerFirstTap(tracker); - else - _registerSecondTap(tracker); - } else if (event.type == 'pointermove' && - !tracker.isWithinTolerance(event, kTouchSlop)) { - _reject(tracker); - } else if (event.type == 'pointercancel') { - _reject(tracker); - } - } - - void acceptGesture(int pointer) {} - - void rejectGesture(int pointer) { - TapTracker tracker = _trackers[pointer]; - // If tracker isn't in the list, check if this is the first tap tracker - if (tracker == null && - _firstTap != null && - _firstTap.pointer == pointer) - tracker = _firstTap; - // If tracker is still null, we rejected ourselves already - if (tracker != null) - _reject(tracker); - } - - void _reject(TapTracker tracker) { - _trackers.remove(tracker.pointer); - tracker.entry.resolve(GestureDisposition.rejected); - _freezeTracker(tracker); - // If the first tap is in progress, and we've run out of taps to track, - // reset won't have any work to do. But if we're in the second tap, we need - // to clear intermediate state. - if (_firstTap != null && - (_trackers.isEmpty || tracker == _firstTap)) - _reset(); - } - - void dispose() { - _reset(); - router = null; - } - - void _reset() { - _stopDoubleTapTimer(); - if (_firstTap != null) { - // Note, order is important below in order for the resolve -> reject logic - // to work properly - TapTracker tracker = _firstTap; - _firstTap = null; - _reject(tracker); - GestureArena.instance.release(tracker.pointer); - } - _clearTrackers(); - } - - void _registerFirstTap(TapTracker tracker) { - _startDoubleTapTimer(); - GestureArena.instance.hold(tracker.pointer); - // Note, order is important below in order for the clear -> reject logic to - // work properly. - _freezeTracker(tracker); - _trackers.remove(tracker.pointer); - _clearTrackers(); - _firstTap = tracker; - } - - void _registerSecondTap(TapTracker tracker) { - _firstTap.entry.resolve(GestureDisposition.accepted); - tracker.entry.resolve(GestureDisposition.accepted); - _freezeTracker(tracker); - _trackers.remove(tracker.pointer); - if (onDoubleTap != null) - onDoubleTap(); - _reset(); - } - - void _clearTrackers() { - List localTrackers = new List.from(_trackers.values); - for (TapTracker tracker in localTrackers) - _reject(tracker); - assert(_trackers.isEmpty); - } - - void _freezeTracker(TapTracker tracker) { - tracker.stopTrackingPointer(router, handleEvent); - } - - void _startDoubleTapTimer() { - _doubleTapTimer ??= new Timer(kDoubleTapTimeout, () => _reset()); - } - - void _stopDoubleTapTimer() { - if (_doubleTapTimer != null) { - _doubleTapTimer.cancel(); - _doubleTapTimer = null; - } - } - -} diff --git a/packages/flutter/lib/src/gestures/tap.dart b/packages/flutter/lib/src/gestures/tap.dart index 1ca0967761..56a8add85e 100644 --- a/packages/flutter/lib/src/gestures/tap.dart +++ b/packages/flutter/lib/src/gestures/tap.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:ui' as ui; import 'arena.dart'; @@ -66,9 +67,9 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { /// TapTracker helps track individual tap sequences as part of a /// larger gesture. -class TapTracker { +class _TapTracker { - TapTracker({ PointerInputEvent event, this.entry }) + _TapTracker({ PointerInputEvent event, this.entry }) : pointer = event.pointer, _initialPosition = event.position, _isTrackingPointer = false { @@ -109,9 +110,9 @@ enum TapResolution { /// TapGesture represents a full gesture resulting from a single tap /// sequence. Tap gestures are passive, meaning that they will not /// pre-empt any other arena member in play. -class TapGesture extends TapTracker { +class _TapGesture extends _TapTracker { - TapGesture({ this.gestureRecognizer, PointerInputEvent event }) + _TapGesture({ this.gestureRecognizer, PointerInputEvent event }) : super(event: event) { entry = GestureArena.instance.add(event.pointer, gestureRecognizer); _wonArena = false; @@ -175,11 +176,11 @@ class MultiTapGestureRecognizer extends DisposableArenaMember { GestureTapCallback onTapDown; GestureTapCallback onTapCancel; - Map _gestureMap = new Map(); + Map _gestureMap = new Map(); void addPointer(PointerInputEvent event) { assert(!_gestureMap.containsKey(event.pointer)); - _gestureMap[event.pointer] = new TapGesture( + _gestureMap[event.pointer] = new _TapGesture( gestureRecognizer: this, event: event ); @@ -209,8 +210,8 @@ class MultiTapGestureRecognizer extends DisposableArenaMember { } void dispose() { - List localGestures = new List.from(_gestureMap.values); - for (TapGesture gesture in localGestures) + List<_TapGesture> localGestures = new List<_TapGesture>.from(_gestureMap.values); + for (_TapGesture gesture in localGestures) gesture.cancel(); // Rejection of each gesture should cause it to be removed from our map assert(_gestureMap.isEmpty); @@ -218,3 +219,153 @@ class MultiTapGestureRecognizer extends DisposableArenaMember { } } + +class DoubleTapGestureRecognizer extends DisposableArenaMember { + + DoubleTapGestureRecognizer({ this.router, this.onDoubleTap }); + + // Implementation notes: + // The double tap recognizer can be in one of four states. There's no + // explicit enum for the states, because they are already captured by + // the state of existing fields. Specifically: + // Waiting on first tap: In this state, the _trackers list is empty, and + // _firstTap is null. + // First tap in progress: In this state, the _trackers list contains all + // the states for taps that have begun but not completed. This list can + // have more than one entry if two pointers begin to tap. + // Waiting on second tap: In this state, one of the in-progress taps has + // completed successfully. The _trackers list is again empty, and + // _firstTap records the successful tap. + // Second tap in progress: Much like the "first tap in progress" state, but + // _firstTap is non-null. If a tap completes successfully while in this + // state, the callback is invoked and the state is reset. + // There are various other scenarios that cause the state to reset: + // - All in-progress taps are rejected (by time, distance, pointercancel, etc) + // - The long timer between taps expires + // - The gesture arena decides we have been rejected wholesale + + PointerRouter router; + GestureTapCallback onDoubleTap; + + Timer _doubleTapTimer; + _TapTracker _firstTap; + final Map _trackers = new Map(); + + void addPointer(PointerInputEvent event) { + // Ignore out-of-bounds second taps + if (_firstTap != null && + !_firstTap.isWithinTolerance(event, kDoubleTapTouchSlop)) + return; + _stopDoubleTapTimer(); + _TapTracker tracker = new _TapTracker( + event: event, + entry: GestureArena.instance.add(event.pointer, this) + ); + _trackers[event.pointer] = tracker; + tracker.startTrackingPointer(router, handleEvent); + } + + void handleEvent(PointerInputEvent event) { + _TapTracker tracker = _trackers[event.pointer]; + assert(tracker != null); + if (event.type == 'pointerup') { + if (_firstTap == null) + _registerFirstTap(tracker); + else + _registerSecondTap(tracker); + } else if (event.type == 'pointermove' && + !tracker.isWithinTolerance(event, kTouchSlop)) { + _reject(tracker); + } else if (event.type == 'pointercancel') { + _reject(tracker); + } + } + + void acceptGesture(int pointer) {} + + void rejectGesture(int pointer) { + _TapTracker tracker = _trackers[pointer]; + // If tracker isn't in the list, check if this is the first tap tracker + if (tracker == null && + _firstTap != null && + _firstTap.pointer == pointer) + tracker = _firstTap; + // If tracker is still null, we rejected ourselves already + if (tracker != null) + _reject(tracker); + } + + void _reject(_TapTracker tracker) { + _trackers.remove(tracker.pointer); + tracker.entry.resolve(GestureDisposition.rejected); + _freezeTracker(tracker); + // If the first tap is in progress, and we've run out of taps to track, + // reset won't have any work to do. But if we're in the second tap, we need + // to clear intermediate state. + if (_firstTap != null && + (_trackers.isEmpty || tracker == _firstTap)) + _reset(); + } + + void dispose() { + _reset(); + router = null; + } + + void _reset() { + _stopDoubleTapTimer(); + if (_firstTap != null) { + // Note, order is important below in order for the resolve -> reject logic + // to work properly + _TapTracker tracker = _firstTap; + _firstTap = null; + _reject(tracker); + GestureArena.instance.release(tracker.pointer); + } + _clearTrackers(); + } + + void _registerFirstTap(_TapTracker tracker) { + _startDoubleTapTimer(); + GestureArena.instance.hold(tracker.pointer); + // Note, order is important below in order for the clear -> reject logic to + // work properly. + _freezeTracker(tracker); + _trackers.remove(tracker.pointer); + _clearTrackers(); + _firstTap = tracker; + } + + void _registerSecondTap(_TapTracker tracker) { + _firstTap.entry.resolve(GestureDisposition.accepted); + tracker.entry.resolve(GestureDisposition.accepted); + _freezeTracker(tracker); + _trackers.remove(tracker.pointer); + if (onDoubleTap != null) + onDoubleTap(); + _reset(); + } + + void _clearTrackers() { + List<_TapTracker> localTrackers = new List<_TapTracker>.from(_trackers.values); + for (_TapTracker tracker in localTrackers) + _reject(tracker); + assert(_trackers.isEmpty); + } + + void _freezeTracker(_TapTracker tracker) { + tracker.stopTrackingPointer(router, handleEvent); + } + + void _startDoubleTapTimer() { + _doubleTapTimer ??= new Timer(kDoubleTapTimeout, () => _reset()); + } + + void _stopDoubleTapTimer() { + if (_doubleTapTimer != null) { + _doubleTapTimer.cancel(); + _doubleTapTimer = null; + } + } + +}