diff --git a/apps/app/ui-tests-app/events/gestures.ts b/apps/app/ui-tests-app/events/gestures.ts index 264106db2..f378b443a 100644 --- a/apps/app/ui-tests-app/events/gestures.ts +++ b/apps/app/ui-tests-app/events/gestures.ts @@ -7,43 +7,49 @@ import * as stackLayoutModule from "tns-core-modules/ui/layouts/stack-layout"; export function createPage() { - var stack = new stackLayoutModule.StackLayout(); - var labelHeight = Math.round(deviceProperties.screen.mainScreen.heightPixels / (7 * deviceProperties.screen.mainScreen.scale)); - var stopButton = new button.Button(); + const stack = new stackLayoutModule.StackLayout(); + const labelHeight = Math.round(deviceProperties.screen.mainScreen.heightPixels / (7 * deviceProperties.screen.mainScreen.scale)); + const stopButton = new button.Button(); stopButton.text = "Stop Detecting Gestures"; stack.addChild(stopButton); - var tapLabel = new labelModule.Label(); + const tapLabel = new labelModule.Label(); tapLabel.text = "Tap here"; stack.addChild(tapLabel); - var doubletapLabel = new labelModule.Label(); + const doubletapLabel = new labelModule.Label(); doubletapLabel.text = "Double Tap here"; stack.addChild(doubletapLabel); - var longpressLabel = new labelModule.Label(); + const longpressLabel = new labelModule.Label(); longpressLabel.text = "Long Press here"; stack.addChild(longpressLabel); - var swipeLabel = new labelModule.Label(); + const tapAndDoubleTapLabel = new labelModule.Label(); + tapAndDoubleTapLabel.height = labelHeight; + tapAndDoubleTapLabel.text = "Tap or Double Tap"; + tapAndDoubleTapLabel.textWrap = true; + stack.addChild(tapAndDoubleTapLabel); + + const swipeLabel = new labelModule.Label(); swipeLabel.height = labelHeight; swipeLabel.text = "Swipe here"; swipeLabel.textWrap = true; stack.addChild(swipeLabel); - var panLabel = new labelModule.Label(); + const panLabel = new labelModule.Label(); panLabel.height = labelHeight; panLabel.text = "Pan here"; panLabel.textWrap = true; stack.addChild(panLabel); - var pinchLabel = new labelModule.Label(); + const pinchLabel = new labelModule.Label(); pinchLabel.height = labelHeight; pinchLabel.text = "Pinch here"; pinchLabel.textWrap = true; stack.addChild(pinchLabel); - var rotaionLabel = new labelModule.Label(); + const rotaionLabel = new labelModule.Label(); rotaionLabel.height = labelHeight; rotaionLabel.text = "Rotate here"; rotaionLabel.textWrap = true; @@ -57,6 +63,8 @@ export function createPage() { observer5.disconnect(); observer6.disconnect(); observer7.disconnect(); + observer8.disconnect(); + observer9.disconnect(); tapLabel.text = "Gestures detection disabled"; doubletapLabel.text = "Gestures detection disabled"; longpressLabel.text = "Gestures detection disabled"; @@ -64,56 +72,69 @@ export function createPage() { panLabel.text = "Gestures detection disabled"; pinchLabel.text = "Gestures detection disabled"; rotaionLabel.text = "Gestures detection disabled"; + tapAndDoubleTapLabel.text = "Gestures detection disabled"; }); tapLabel.on(gestures.GestureTypes.tap, function (args: gestures.GestureEventData) { tapLabel.text = "Tap gesture detected, " + (args.object === tapLabel); }); - var observer1 = tapLabel.getGestureObservers(gestures.GestureTypes.tap)[0]; + const observer1 = tapLabel.getGestureObservers(gestures.GestureTypes.tap)[0]; doubletapLabel.on(gestures.GestureTypes.doubleTap, function (args: gestures.GestureEventData) { doubletapLabel.text = "Double Tap gesture detected, " + (args.object === doubletapLabel); }); - var observer2 = doubletapLabel.getGestureObservers(gestures.GestureTypes.doubleTap)[0]; + const observer2 = doubletapLabel.getGestureObservers(gestures.GestureTypes.doubleTap)[0]; longpressLabel.on(gestures.GestureTypes.longPress, function (args: gestures.GestureEventData) { longpressLabel.text = "Long Press gesture detected, " + (args.object === longpressLabel); }); - var observer3 = longpressLabel.getGestureObservers(gestures.GestureTypes.longPress)[0]; + const observer3 = longpressLabel.getGestureObservers(gestures.GestureTypes.longPress)[0]; swipeLabel.on(gestures.GestureTypes.swipe, function (args: gestures.SwipeGestureEventData) { swipeLabel.text = "Swipe Direction: " + args.direction + ", " + (args.object === swipeLabel); // + getStateAsString(args.state); }); - var observer4 = swipeLabel.getGestureObservers(gestures.GestureTypes.swipe)[0]; + const observer4 = swipeLabel.getGestureObservers(gestures.GestureTypes.swipe)[0]; panLabel.on(gestures.GestureTypes.pan, function (args: gestures.PanGestureEventData) { panLabel.text = "Pan deltaX:" + Math.round(args.deltaX) + "; deltaY:" + Math.round(args.deltaY) + ";" + ", " + (args.object === panLabel) + getStateAsString(args.state); }); - var observer5 = panLabel.getGestureObservers(gestures.GestureTypes.pan)[0]; + const observer5 = panLabel.getGestureObservers(gestures.GestureTypes.pan)[0]; pinchLabel.on(gestures.GestureTypes.pinch, function (args: gestures.PinchGestureEventData) { pinchLabel.text = "Pinch Scale: " + Math.round(args.scale) + ", " + (args.object === pinchLabel) + getStateAsString(args.state); }); - var observer6 = pinchLabel.getGestureObservers(gestures.GestureTypes.pinch)[0]; + const observer6 = pinchLabel.getGestureObservers(gestures.GestureTypes.pinch)[0]; rotaionLabel.on(gestures.GestureTypes.rotation, function (args: gestures.RotationGestureEventData) { rotaionLabel.text = "Rotation: " + Math.round(args.rotation) + ", " + (args.object === rotaionLabel) + getStateAsString(args.state); }); - var observer7 = rotaionLabel.getGestureObservers(gestures.GestureTypes.rotation)[0]; + const observer7 = rotaionLabel.getGestureObservers(gestures.GestureTypes.rotation)[0]; - var page = new pages.Page(); + tapAndDoubleTapLabel.on(gestures.GestureTypes.doubleTap, function (args: gestures.GestureEventData) { + tapAndDoubleTapLabel.text = "Last action: Double tap gesture, " + (args.object === tapAndDoubleTapLabel); + }); + + const observer8 = tapAndDoubleTapLabel.getGestureObservers(gestures.GestureTypes.doubleTap)[0]; + + tapAndDoubleTapLabel.on(gestures.GestureTypes.tap, function (args: gestures.GestureEventData) { + tapAndDoubleTapLabel.text = "Last action: Tap gesture, " + (args.object === tapAndDoubleTapLabel); + }); + + const observer9 = tapAndDoubleTapLabel.getGestureObservers(gestures.GestureTypes.tap)[0]; + + const page = new pages.Page(); page.content = stack; return page; } -var states = new Array(); +const states = new Array(); function getStateAsString(state: gestures.GestureStateTypes): string { if (state === gestures.GestureStateTypes.began) { states.length = 0; diff --git a/tns-core-modules/ui/gestures/gestures.android.ts b/tns-core-modules/ui/gestures/gestures.android.ts index f6d85fb91..6d7a0e650 100644 --- a/tns-core-modules/ui/gestures/gestures.android.ts +++ b/tns-core-modules/ui/gestures/gestures.android.ts @@ -10,6 +10,8 @@ import { // Import layout from utils directly to avoid circular references import { layout } from "../../utils/utils"; +import * as timer from "../../timer"; + export * from "./gestures-common"; interface TapAndDoubleTapGestureListener { @@ -27,6 +29,11 @@ function initializeTapAndDoubleTapGestureListener() { private _target: View; private _type: number; + private _lastUpTime: number = 0; + private _tapTimeoutId: number; + + private static DoubleTapTimeout = android.view.ViewConfiguration.getDoubleTapTimeout(); + constructor(observer: GesturesObserver, target: View, type: number) { super(); @@ -37,28 +44,42 @@ function initializeTapAndDoubleTapGestureListener() { } public onSingleTapUp(motionEvent: android.view.MotionEvent): boolean { - if (this._type & GestureTypes.tap) { - let args = _getArgs(GestureTypes.tap, this._target, motionEvent); - _executeCallback(this._observer, args); - } - return true; - } - - public onDoubleTap(motionEvent: android.view.MotionEvent): boolean { - if (this._type & GestureTypes.doubleTap) { - let args = _getArgs(GestureTypes.doubleTap, this._target, motionEvent); - _executeCallback(this._observer, args); - } + this._handleSingleTap(motionEvent); + this._lastUpTime = Date.now(); return true; } public onDown(motionEvent: android.view.MotionEvent): boolean { + const tapTime = Date.now(); + if ((tapTime - this._lastUpTime) <= TapAndDoubleTapGestureListenerImpl.DoubleTapTimeout) { + this._handleDoubleTap(motionEvent); + } return true; } public onLongPress(motionEvent: android.view.MotionEvent): void { if (this._type & GestureTypes.longPress) { - let args = _getArgs(GestureTypes.longPress, this._target, motionEvent); + const args = _getArgs(GestureTypes.longPress, this._target, motionEvent); + _executeCallback(this._observer, args); + } + } + + private _handleSingleTap(motionEvent: android.view.MotionEvent): void { + this._tapTimeoutId = timer.setTimeout(() => { + if (this._type & GestureTypes.tap) { + const args = _getArgs(GestureTypes.tap, this._target, motionEvent); + _executeCallback(this._observer, args); + } + timer.clearTimeout(this._tapTimeoutId); + }, TapAndDoubleTapGestureListenerImpl.DoubleTapTimeout); + } + + private _handleDoubleTap(motionEvent: android.view.MotionEvent): void { + if (this._tapTimeoutId) { + timer.clearTimeout(this._tapTimeoutId); + } + if (this._type & GestureTypes.doubleTap) { + const args = _getArgs(GestureTypes.doubleTap, this._target, motionEvent); _executeCallback(this._observer, args); } } @@ -94,7 +115,7 @@ function initializePinchGestureListener() { public onScaleBegin(detector: android.view.ScaleGestureDetector): boolean { this._scale = detector.getScaleFactor(); - let args = new PinchGestureEventData( + const args = new PinchGestureEventData( this._target, detector, this._scale, @@ -109,7 +130,7 @@ function initializePinchGestureListener() { public onScale(detector: android.view.ScaleGestureDetector): boolean { this._scale *= detector.getScaleFactor(); - let args = new PinchGestureEventData( + const args = new PinchGestureEventData( this._target, detector, this._scale, @@ -123,7 +144,7 @@ function initializePinchGestureListener() { public onScaleEnd(detector: android.view.ScaleGestureDetector): void { this._scale *= detector.getScaleFactor(); - let args = new PinchGestureEventData( + const args = new PinchGestureEventData( this._target, detector, this._scale, @@ -172,41 +193,28 @@ function initializeSwipeGestureListener() { let deltaX = currentEvent.getX() - initialEvent.getX(); if (Math.abs(deltaX) > Math.abs(deltaY)) { - if (Math.abs(deltaX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) { - if (deltaX > 0) { - args = _getSwipeArgs(SwipeDirection.right, this._target, initialEvent, currentEvent); _executeCallback(this._observer, args); - result = true; } else { - args = _getSwipeArgs(SwipeDirection.left, this._target, initialEvent, currentEvent); _executeCallback(this._observer, args); - result = true; } } - } else { - if (Math.abs(deltaY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) { - if (deltaY > 0) { - args = _getSwipeArgs(SwipeDirection.down, this._target, initialEvent, currentEvent); _executeCallback(this._observer, args); - result = true; } else { - args = _getSwipeArgs(SwipeDirection.up, this._target, initialEvent, currentEvent); _executeCallback(this._observer, args); - result = true; } } @@ -228,7 +236,7 @@ const INVALID_POINTER_ID = -1; const TO_DEGREES = (180 / Math.PI); export function observe(target: View, type: GestureTypes, callback: (args: GestureEventData) => void, context?: any): GesturesObserver { - let observer = new GesturesObserver(target, callback, context); + const observer = new GesturesObserver(target, callback, context); observer.observe(type); return observer; } @@ -292,7 +300,7 @@ export class GesturesObserver extends GesturesObserverBase { private _attach(target: View, type: GestureTypes) { this._detach(); - if (type & GestureTypes.tap || type & GestureTypes.doubleTap || type & GestureTypes.longPress) { + if ((type & GestureTypes.tap) || (type & GestureTypes.doubleTap) || (type & GestureTypes.longPress)) { initializeTapAndDoubleTapGestureListener(); this._simpleGestureDetector = new androidx.core.view.GestureDetectorCompat(target._context, new TapAndDoubleTapGestureListener(this, this.target, type)); } @@ -465,7 +473,7 @@ class CustomPanGestureDetector { return true; } - private trackStop(currentEvent: android.view.MotionEvent, cahceEvent: boolean) { + private trackStop(currentEvent: android.view.MotionEvent, cacheEvent: boolean) { if (this.isTracking) { let args = _getPanArgs(this.deltaX, this.deltaY, this.target, GestureStateTypes.ended, null, currentEvent); _executeCallback(this.observer, args); @@ -475,10 +483,9 @@ class CustomPanGestureDetector { this.isTracking = false; } - if (cahceEvent) { + if (cacheEvent) { this.lastEventCache = currentEvent; - } - else { + } else { this.lastEventCache = undefined; } } @@ -509,8 +516,7 @@ class CustomPanGestureDetector { x: event.getRawX() / this.density, y: event.getRawY() / this.density }; - } - else { + } else { const offX = event.getRawX() - event.getX(); const offY = event.getRawY() - event.getY(); let res = { x: 0, y: 0 }; @@ -559,8 +565,7 @@ class CustomRotateGestureDetector { if (this.trackedPtrId1 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId2) { this.trackedPtrId1 = pointerID; assigned = true; - } - else if (this.trackedPtrId2 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId1) { + } else if (this.trackedPtrId2 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId1) { this.trackedPtrId2 = pointerID; assigned = true; } @@ -585,8 +590,7 @@ class CustomRotateGestureDetector { case android.view.MotionEvent.ACTION_POINTER_UP: if (pointerID === this.trackedPtrId1) { this.trackedPtrId1 = INVALID_POINTER_ID; - } - else if (pointerID === this.trackedPtrId2) { + } else if (pointerID === this.trackedPtrId2) { this.trackedPtrId2 = INVALID_POINTER_ID; } diff --git a/tns-core-modules/ui/gestures/gestures.ios.ts b/tns-core-modules/ui/gestures/gestures.ios.ts index f2753760a..d0a6a7246 100644 --- a/tns-core-modules/ui/gestures/gestures.ios.ts +++ b/tns-core-modules/ui/gestures/gestures.ios.ts @@ -8,16 +8,33 @@ import { GesturesObserverBase, toString, TouchAction, GestureStateTypes, Gesture export * from "./gestures-common"; export function observe(target: View, type: GestureTypes, callback: (args: GestureEventData) => void, context?: any): GesturesObserver { - let observer = new GesturesObserver(target, callback, context); + const observer = new GesturesObserver(target, callback, context); observer.observe(type); return observer; } class UIGestureRecognizerDelegateImpl extends NSObject implements UIGestureRecognizerDelegate { public static ObjCProtocols = [UIGestureRecognizerDelegate]; + public gestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer(gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer): boolean { + // If both gesture recognizers are of type UITapGestureRecognizer, do not allow + // simultaneous recognition. + if (gestureRecognizer instanceof UITapGestureRecognizer && otherGestureRecognizer instanceof UITapGestureRecognizer) { + return false; + } return true; } + + public gestureRecognizerShouldRequireFailureOfGestureRecognizer(gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer): boolean { + // If both gesture recognizers are of type UITapGestureRecognizer & one of them is a doubleTap, + // we must require a failure. + if (gestureRecognizer instanceof UITapGestureRecognizer + && otherGestureRecognizer instanceof UITapGestureRecognizer + && otherGestureRecognizer.numberOfTapsRequired === 2) { + return true; + } + return false; + } } let recognizerDelegateInstance: UIGestureRecognizerDelegateImpl = UIGestureRecognizerDelegateImpl.new(); @@ -32,7 +49,7 @@ class UIGestureRecognizerImpl extends NSObject { private _context: any; public static initWithOwnerTypeCallback(owner: WeakRef, type: any, callback?: Function, thisArg?: any): UIGestureRecognizerImpl { - let handler = UIGestureRecognizerImpl.new(); + const handler = UIGestureRecognizerImpl.new(); handler._owner = owner; handler._type = type; @@ -48,12 +65,12 @@ class UIGestureRecognizerImpl extends NSObject { } public recognize(recognizer: UIGestureRecognizer): void { - let owner = this._owner.get(); - let callback = this._callback ? this._callback : (owner ? owner.callback : null); - let typeParam = this._type; - let target = owner ? owner.target : undefined; + const owner = this._owner.get(); + const callback = this._callback ? this._callback : (owner ? owner.callback : null); + const typeParam = this._type; + const target = owner ? owner.target : undefined; - let args = { + const args = { type: typeParam, view: target, ios: recognizer, @@ -82,7 +99,7 @@ export class GesturesObserver extends GesturesObserverBase { public androidOnTouchEvent(motionEvent: android.view.MotionEvent): void { // } - + public observe(type: GestureTypes) { if (this.target) { this.type = type; @@ -106,17 +123,14 @@ export class GesturesObserver extends GesturesObserverBase { this._detach(); if (target && target.nativeViewProtected && target.nativeViewProtected.addGestureRecognizer) { - let nativeView = target.nativeViewProtected; + const nativeView = target.nativeViewProtected; if (type & GestureTypes.tap) { nativeView.addGestureRecognizer(this._createRecognizer(GestureTypes.tap)); } if (type & GestureTypes.doubleTap) { - let r = this._createRecognizer(GestureTypes.doubleTap); - r.numberOfTapsRequired = 2; - - nativeView.addGestureRecognizer(r); + nativeView.addGestureRecognizer(this._createRecognizer(GestureTypes.doubleTap)); } if (type & GestureTypes.pinch) { @@ -203,8 +217,8 @@ export class GesturesObserver extends GesturesObserverBase { private _createRecognizer(type: GestureTypes, callback?: (args: GestureEventData) => void, swipeDirection?: UISwipeGestureRecognizerDirection): UIGestureRecognizer { let recognizer: UIGestureRecognizer; let name = toString(type); - let target = _createUIGestureRecognizerTarget(this, type, callback, this.context); - let recognizerType = _getUIGestureRecognizerType(type); + const target = _createUIGestureRecognizerTarget(this, type, callback, this.context); + const recognizerType = _getUIGestureRecognizerType(type); if (recognizerType) { recognizer = recognizerType.alloc().initWithTargetAction(target, "recognize"); @@ -212,9 +226,10 @@ export class GesturesObserver extends GesturesObserverBase { if (type === GestureTypes.swipe && swipeDirection) { name = name + swipeDirection.toString(); (recognizer).direction = swipeDirection; - } - else if (type === GestureTypes.touch) { + } else if (type === GestureTypes.touch) { (recognizer).observer = this; + } else if (type === GestureTypes.doubleTap) { + (recognizer).numberOfTapsRequired = 2; } if (recognizer) { @@ -285,8 +300,8 @@ function _getSwipeDirection(direction: UISwipeGestureRecognizerDirection): Swipe } function _getPinchData(args: GestureEventData): PinchGestureEventData { - let recognizer = args.ios; - let center = recognizer.locationInView(args.view.nativeViewProtected); + const recognizer = args.ios; + const center = recognizer.locationInView(args.view.nativeViewProtected); return { type: args.type, @@ -303,7 +318,7 @@ function _getPinchData(args: GestureEventData): PinchGestureEventData { } function _getSwipeData(args: GestureEventData): SwipeGestureEventData { - let recognizer = args.ios; + const recognizer = args.ios; return { type: args.type, @@ -317,7 +332,8 @@ function _getSwipeData(args: GestureEventData): SwipeGestureEventData { } function _getPanData(args: GestureEventData, view: UIView): PanGestureEventData { - let recognizer = args.ios; + const recognizer = args.ios; + return { type: args.type, view: args.view, @@ -332,7 +348,8 @@ function _getPanData(args: GestureEventData, view: UIView): PanGestureEventData } function _getRotationData(args: GestureEventData): RotationGestureEventData { - let recognizer = args.ios; + const recognizer = args.ios; + return { type: args.type, view: args.view, @@ -394,6 +411,7 @@ class Pointer implements Pointer { private _view: View; private _location: CGPoint; + private get location(): CGPoint { if (!this._location) { this._location = this.ios.locationInView(this._view.nativeViewProtected); @@ -486,4 +504,4 @@ class TouchGestureEventData implements TouchGestureEventData { getY(): number { return this.getMainPointer().locationInView(this.view.nativeViewProtected).y } -} \ No newline at end of file +}