diff --git a/packages/flutter/lib/src/gestures/double_tap.dart b/packages/flutter/lib/src/gestures/double_tap.dart index f441239184..510f024479 100644 --- a/packages/flutter/lib/src/gestures/double_tap.dart +++ b/packages/flutter/lib/src/gestures/double_tap.dart @@ -10,42 +10,150 @@ import 'package:sky/src/gestures/constants.dart'; import 'package:sky/src/gestures/recognizer.dart'; import 'package:sky/src/gestures/tap.dart'; -class DoubleTapGestureRecognizer extends PrimaryPointerGestureRecognizer { - DoubleTapGestureRecognizer({ PointerRouter router, this.onDoubleTap }) - : super(router: router, deadline: kTapTimeout); +class DoubleTapGestureRecognizer extends GestureArenaMember { + static int sInstances = 0; + DoubleTapGestureRecognizer({ this.router, this.onDoubleTap }) { + _instance = sInstances++; + } + + PointerRouter router; GestureTapListener onDoubleTap; - int _numTaps = 0; - Timer _longTimer; - void resolve(GestureDisposition disposition) { - super.resolve(disposition); - if (disposition == GestureDisposition.rejected) { - _numTaps = 0; + int _numTaps = 0; + int _instance = 0; + bool _isTrackingPointer = false; + int _pointer; + sky.Point _initialPosition; + Timer _tapTimer; + Timer _doubleTapTimer; + GestureArenaEntry _entry = null; + + void addPointer(sky.PointerEvent event) { + message("add pointer"); + if (_initialPosition != null && !_isWithinTolerance(event)) { + message("reset"); + _reset(); + } + _pointer = event.pointer; + _initialPosition = _getPoint(event); + _isTrackingPointer = false; + _startTapTimer(); + _stopDoubleTapTimer(); + _startTrackingPointer(); + if (_entry == null) { + message("register entry"); + _entry = GestureArena.instance.add(event.pointer, this); } } - void didExceedDeadline() { - stopTrackingPointer(primaryPointer); - resolve(GestureDisposition.rejected); + void message(String s) { + print("Double tap " + _instance.toString() + ": " + s); } - void didExceedLongDeadline() { - _numTaps = 0; - _longTimer = null; - } - - void handlePrimaryPointer(sky.PointerEvent event) { + void handleEvent(sky.PointerEvent event) { + message("handle event"); if (event.type == 'pointerup') { _numTaps++; + _stopTapTimer(); + _stopTrackingPointer(); if (_numTaps == 1) { - _longTimer = new Timer(kDoubleTapTimeout, didExceedLongDeadline); + message("start long timer"); + _startDoubleTapTimer(); } else if (_numTaps == 2) { - resolve(GestureDisposition.accepted); - onDoubleTap(); + message("start found second tap"); + _entry.resolve(GestureDisposition.accepted); } + } else if (event.type == 'pointermove' && !_isWithinTolerance(event)) { + message("outside tap tolerance"); + _entry.resolve(GestureDisposition.rejected); + } else if (event.type == 'pointercancel') { + message("cancel"); + _entry.resolve(GestureDisposition.rejected); } } + void acceptGesture(int pointer) { + message("accepted"); + _reset(); + _entry = null; + print ("Entry is assigned null"); + onDoubleTap?.call(); + } + + void rejectGesture(int pointer) { + message("rejected"); + _reset(); + _entry = null; + print ("Entry is assigned null"); + } + + void dispose() { + _entry?.resolve(GestureDisposition.rejected); + router = null; + } + + void _reset() { + _numTaps = 0; + _initialPosition = null; + _stopTapTimer(); + _stopDoubleTapTimer(); + _stopTrackingPointer(); + } + + void _startTapTimer() { + if (_tapTimer == null) { + _tapTimer = new Timer( + kTapTimeout, + () => _entry.resolve(GestureDisposition.rejected) + ); + } + } + + void _stopTapTimer() { + if (_tapTimer != null) { + _tapTimer.cancel(); + _tapTimer = null; + } + } + + void _startDoubleTapTimer() { + if (_doubleTapTimer == null) { + _doubleTapTimer = new Timer( + kDoubleTapTimeout, + () => _entry.resolve(GestureDisposition.rejected) + ); + } + } + + void _stopDoubleTapTimer() { + if (_doubleTapTimer != null) { + _doubleTapTimer.cancel(); + _doubleTapTimer = null; + } + } + + void _startTrackingPointer() { + if (!_isTrackingPointer) { + _isTrackingPointer = true; + router.addRoute(_pointer, handleEvent); + } + } + + void _stopTrackingPointer() { + if (_isTrackingPointer) { + _isTrackingPointer = false; + router.removeRoute(_pointer, handleEvent); + } + } + + sky.Point _getPoint(sky.PointerEvent event) { + return new sky.Point(event.x, event.y); + } + + bool _isWithinTolerance(sky.PointerEvent event) { + sky.Offset offset = _getPoint(event) - _initialPosition; + return offset.distance <= kDoubleTapTouchSlop; + } } diff --git a/packages/flutter/lib/src/gestures/tap.dart b/packages/flutter/lib/src/gestures/tap.dart index fdb1db6e88..521d1f047a 100644 --- a/packages/flutter/lib/src/gestures/tap.dart +++ b/packages/flutter/lib/src/gestures/tap.dart @@ -2,44 +2,161 @@ // 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:sky' as sky; import 'package:sky/src/gestures/arena.dart'; import 'package:sky/src/gestures/constants.dart'; -import 'package:sky/src/gestures/recognizer.dart'; typedef void GestureTapCallback(); -class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { - TapGestureRecognizer({ PointerRouter router, this.onTap }) - : super(router: router, deadline: kTapTimeout); +enum TapResolution { + tap, + cancel +} +class _TapGesture { + _TapGesture({ this.gestureRecognizer, sky.PointerEvent event }) { + assert(event.type == 'pointerdown'); + _pointer = event.pointer; + _isTrackingPointer = false; + _initialPosition = _getPoint(event); + _entry = GestureArena.instance.add(_pointer, gestureRecognizer); + _wonArena = false; + _didTap = false; + _startTimer(); + _startTrackingPointer(); + } + + TapGestureRecognizer gestureRecognizer; + + int _pointer; + bool _isTrackingPointer; + sky.Point _initialPosition; + GestureArenaEntry _entry; + Timer _deadline; + bool _wonArena; + bool _didTap; + + void handleEvent(sky.PointerEvent event) { + print("Tap gesture handleEvent"); + assert(event.pointer == _pointer); + if (event.type == 'pointermove' && !_isWithinTolerance(event)) { + _entry.resolve(GestureDisposition.rejected); + } else if (event.type == 'pointercancel') { + _entry.resolve(GestureDisposition.rejected); + } else if (event.type == 'pointerup') { + _stopTimer(); + _stopTrackingPointer(); + _didTap = true; + _check(); + } + } + + void accept() { + print("Tap gesture accept"); + _wonArena = true; + _check(); + } + + void reject() { + print("Tap gesture reject"); + _stopTimer(); + _stopTrackingPointer(); + gestureRecognizer._resolveTap(_pointer, TapResolution.cancel); + } + + void abort() { + _entry.resolve(GestureDisposition.rejected); + } + + void _check() { + if (_wonArena && _didTap) + gestureRecognizer._resolveTap(_pointer, TapResolution.tap); + } + + void _startTimer() { + if (_deadline == null) { + _deadline = new Timer( + kTapTimeout, + () => _entry.resolve(GestureDisposition.rejected) + ); + } + } + + void _stopTimer() { + if (_deadline != null) { + _deadline.cancel(); + _deadline = null; + } + } + + void _startTrackingPointer() { + if (!_isTrackingPointer) { + _isTrackingPointer = true; + gestureRecognizer.router.addRoute(_pointer, handleEvent); + } + } + + void _stopTrackingPointer() { + if (_isTrackingPointer) { + _isTrackingPointer = false; + gestureRecognizer.router.removeRoute(_pointer, handleEvent); + } + } + + sky.Point _getPoint(sky.PointerEvent event) { + return new sky.Point(event.x, event.y); + } + + bool _isWithinTolerance(sky.PointerEvent event) { + sky.Offset offset = _getPoint(event) - _initialPosition; + return offset.distance <= kTouchSlop; + } + +} + +class TapGestureRecognizer extends GestureArenaMember { + TapGestureRecognizer({ this.router, this.onTap, this.onTapDown, this.onTapCancel }); + + PointerRouter router; GestureTapCallback onTap; GestureTapCallback onTapDown; GestureTapCallback onTapCancel; - void didExceedDeadline() { - stopTrackingPointer(primaryPointer); - resolve(GestureDisposition.rejected); + Map _gestureMap = new Map(); + + void addPointer(sky.PointerEvent event) { + _gestureMap[event.pointer] = new _TapGesture( + gestureRecognizer: this, + event: event + ); + onTapDown?.call(); } - void handlePrimaryPointer(sky.PointerEvent event) { - if (event.type == 'pointerdown') { - if (onTapDown != null) - onTapDown(); - } else if (event.type == 'pointerup') { - resolve(GestureDisposition.accepted); - if (onTap != null) - onTap(); - } + void acceptGesture(int pointer) { + _gestureMap[pointer]?.accept(); } void rejectGesture(int pointer) { - super.rejectGesture(pointer); - if (pointer == primaryPointer) { - assert(state == GestureRecognizerState.defunct); - if (onTapCancel != null) - onTapCancel(); - } + _gestureMap[pointer]?.reject(); } + + void _resolveTap(int pointer, TapResolution resolution) { + _gestureMap.remove(pointer); + if (resolution == TapResolution.tap) + onTap?.call(); + else + onTapCancel?.call(); + } + + void dispose() { + List<_TapGesture> localGestures = new List.from(_gestureMap.values); + for (_TapGesture gesture in localGestures) + entry.abort(); + // Rejection of each gesture should cause it to be removed from our map + assert(_gestureMap.isEmpty); + router = null; + } + } diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 13e74e6cc0..d47ba05815 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -119,10 +119,12 @@ class _GestureDetectorState extends State { } void _syncDoubleTap() { - if (config.onDoubleTap == null) + if (config.onDoubleTap == null) { _doubleTap = _ensureDisposed(_doubleTap); - else - _ensureDoubleTap().onDoubleTap = config.onDoubleTap; + } else { + _doubleTap = new DoubleTapGestureRecognizer(router: _router); + _doubleTap.onDoubleTap = config.onDoubleTap; + } } void _syncShowPress() {