UNPKG

@nativescript/core

Version:

A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.

631 lines • 24.6 kB
// Types. import { GesturesObserverBase, toString, TouchAction, GestureStateTypes, GestureTypes, SwipeDirection, GestureEvents } from './gestures-common'; // Import layout from utils directly to avoid circular references import { layout } from '../../utils/layout-helper'; import * as timer from '../../timer'; export * from './gestures-common'; export * from './gestures-types'; export * from './touch-manager'; let TapAndDoubleTapGestureListener; function initializeTapAndDoubleTapGestureListener() { if (TapAndDoubleTapGestureListener) { return; } var TapAndDoubleTapGestureListenerImpl = (function (_super) { __extends(TapAndDoubleTapGestureListenerImpl, _super); function TapAndDoubleTapGestureListenerImpl(observer, target, type) { var _this = _super.call(this) || this; _this._lastUpTime = 0; _this._observer = observer; _this._target = target; _this._type = type; return global.__native(_this); } TapAndDoubleTapGestureListenerImpl.prototype.onSingleTapUp = function (motionEvent) { this._handleSingleTap(motionEvent); this._lastUpTime = Date.now(); return true; }; TapAndDoubleTapGestureListenerImpl.prototype.onDown = function (motionEvent) { var tapTime = Date.now(); if (tapTime - this._lastUpTime <= TapAndDoubleTapGestureListenerImpl.DoubleTapTimeout) { this._handleDoubleTap(motionEvent); } return true; }; TapAndDoubleTapGestureListenerImpl.prototype.onLongPress = function (motionEvent) { if (this._type === GestureTypes.longPress) { var args = _getLongPressArgs(GestureTypes.longPress, this._target, GestureStateTypes.began, motionEvent); _executeCallback(this._observer, args); } }; TapAndDoubleTapGestureListenerImpl.prototype._handleSingleTap = function (motionEvent) { var _this = this; if (this._target.getGestureObservers(GestureTypes.doubleTap)) { this._tapTimeoutId = timer.setTimeout(function () { if (_this._type === GestureTypes.tap) { var args = _getTapArgs(GestureTypes.tap, _this._target, motionEvent); _executeCallback(_this._observer, args); } timer.clearTimeout(_this._tapTimeoutId); }, TapAndDoubleTapGestureListenerImpl.DoubleTapTimeout); } else { if (this._type === GestureTypes.tap) { var args = _getTapArgs(GestureTypes.tap, this._target, motionEvent); _executeCallback(this._observer, args); } } }; TapAndDoubleTapGestureListenerImpl.prototype._handleDoubleTap = function (motionEvent) { if (this._tapTimeoutId) { timer.clearTimeout(this._tapTimeoutId); } if (this._type === GestureTypes.doubleTap) { var args = _getTapArgs(GestureTypes.doubleTap, this._target, motionEvent); _executeCallback(this._observer, args); } }; TapAndDoubleTapGestureListenerImpl.DoubleTapTimeout = android.view.ViewConfiguration.getDoubleTapTimeout(); return TapAndDoubleTapGestureListenerImpl; }(android.view.GestureDetector.SimpleOnGestureListener)); TapAndDoubleTapGestureListener = TapAndDoubleTapGestureListenerImpl; } let PinchGestureListener; function initializePinchGestureListener() { if (PinchGestureListener) { return; } var PinchGestureListenerImpl = (function (_super) { __extends(PinchGestureListenerImpl, _super); function PinchGestureListenerImpl(observer, target) { var _this = _super.call(this) || this; _this._observer = observer; _this._target = target; return global.__native(_this); } PinchGestureListenerImpl.prototype.onScaleBegin = function (detector) { this._scale = detector.getScaleFactor(); var args = new PinchGestureEventDataImpl(this._target, detector, this._scale, this._target, GestureStateTypes.began); _executeCallback(this._observer, args); return true; }; PinchGestureListenerImpl.prototype.onScale = function (detector) { this._scale *= detector.getScaleFactor(); var args = new PinchGestureEventDataImpl(this._target, detector, this._scale, this._target, GestureStateTypes.changed); _executeCallback(this._observer, args); return true; }; PinchGestureListenerImpl.prototype.onScaleEnd = function (detector) { this._scale *= detector.getScaleFactor(); var args = new PinchGestureEventDataImpl(this._target, detector, this._scale, this._target, GestureStateTypes.ended); _executeCallback(this._observer, args); }; return PinchGestureListenerImpl; }(android.view.ScaleGestureDetector.SimpleOnScaleGestureListener)); PinchGestureListener = PinchGestureListenerImpl; } let SwipeGestureListener; function initializeSwipeGestureListener() { if (SwipeGestureListener) { return; } var SwipeGestureListenerImpl = (function (_super) { __extends(SwipeGestureListenerImpl, _super); function SwipeGestureListenerImpl(observer, target) { var _this = _super.call(this) || this; _this._observer = observer; _this._target = target; return global.__native(_this); } SwipeGestureListenerImpl.prototype.onDown = function (motionEvent) { return true; }; SwipeGestureListenerImpl.prototype.onFling = function (initialEvent, currentEvent, velocityX, velocityY) { var result = false; var args; try { var deltaY = currentEvent.getY() - initialEvent.getY(); var 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; } } } } catch (ex) { } return result; }; return SwipeGestureListenerImpl; }(android.view.GestureDetector.SimpleOnGestureListener)); SwipeGestureListener = SwipeGestureListenerImpl; } const SWIPE_THRESHOLD = 100; const SWIPE_VELOCITY_THRESHOLD = 100; const INVALID_POINTER_ID = -1; const TO_DEGREES = 180 / Math.PI; export function observe(target, type, callback, context) { const observer = new GesturesObserver(target, callback, context); observer.observe(type); return observer; } export class GesturesObserver extends GesturesObserverBase { observe(type) { this.type = type; if (!this.target) { return; } this._onTargetLoaded = () => { this._attach(this.target, type); }; this._onTargetUnloaded = () => { this._detach(); }; this.target.on('loaded', this._onTargetLoaded); this.target.on('unloaded', this._onTargetUnloaded); if (this.target.isLoaded) { this._attach(this.target, type); } } disconnect() { this._detach(); if (this.target) { this.target.off('loaded', this._onTargetLoaded); this.target.off('unloaded', this._onTargetUnloaded); this._onTargetLoaded = null; this._onTargetUnloaded = null; } // clears target, context and callback references super.disconnect(); } _detach() { this._notifyTouch = false; this._simpleGestureDetector = null; this._scaleGestureDetector = null; this._swipeGestureDetector = null; this._panGestureDetector = null; this._rotateGestureDetector = null; this._eventData = null; } _attach(target, type) { this._detach(); let recognizer; switch (type) { // Whether it's a tap, doubleTap, or longPress, we handle with the same // listener. It'll listen for all three of these gesture types, but only // notify if the type it was registered with matched the relevant gesture. case GestureTypes.tap: case GestureTypes.doubleTap: case GestureTypes.longPress: { initializeTapAndDoubleTapGestureListener(); recognizer = this._simpleGestureDetector = new androidx.core.view.GestureDetectorCompat(target._context, new TapAndDoubleTapGestureListener(this, this.target, type)); break; } case GestureTypes.pinch: { initializePinchGestureListener(); recognizer = this._scaleGestureDetector = new android.view.ScaleGestureDetector(target._context, new PinchGestureListener(this, this.target)); break; } case GestureTypes.swipe: { initializeSwipeGestureListener(); recognizer = this._swipeGestureDetector = new androidx.core.view.GestureDetectorCompat(target._context, new SwipeGestureListener(this, this.target)); break; } case GestureTypes.pan: { recognizer = this._panGestureDetector = new CustomPanGestureDetector(this, this.target); break; } case GestureTypes.rotation: { recognizer = this._rotateGestureDetector = new CustomRotateGestureDetector(this, this.target); break; } case GestureTypes.touch: { this._notifyTouch = true; // For touch events, return early rather than breaking from the switch // statement. return; } } this.target.notify({ eventName: GestureEvents.gestureAttached, object: this.target, type: type, view: this.target, android: recognizer, }); } androidOnTouchEvent(motionEvent) { if (this._notifyTouch) { if (!this._eventData) { this._eventData = new TouchGestureEventData(); } this._eventData.prepare(this.target, motionEvent); _executeCallback(this, this._eventData); } if (this._simpleGestureDetector) { this._simpleGestureDetector.onTouchEvent(motionEvent); } if (this._scaleGestureDetector) { this._scaleGestureDetector.onTouchEvent(motionEvent); } if (this._swipeGestureDetector) { this._swipeGestureDetector.onTouchEvent(motionEvent); } if (this._panGestureDetector) { this._panGestureDetector.onTouchEvent(motionEvent); } if (this._rotateGestureDetector) { this._rotateGestureDetector.onTouchEvent(motionEvent); } } } function _getTapArgs(type, view, e) { return { type: type, view: view, android: e, ios: undefined, object: view, eventName: toString(type), getPointerCount: () => e.getPointerCount(), getX: () => layout.toDeviceIndependentPixels(e.getX()), getY: () => layout.toDeviceIndependentPixels(e.getY()), }; } function _getLongPressArgs(type, view, state, e) { return { type: type, view: view, android: e, ios: undefined, object: view, eventName: toString(type), state: state, }; } function _getSwipeArgs(direction, view, initialEvent, currentEvent) { return { type: GestureTypes.swipe, view: view, android: { initial: initialEvent, current: currentEvent }, direction: direction, ios: undefined, object: view, eventName: toString(GestureTypes.swipe), }; } function _executeCallback(observer, args) { if (observer && observer.callback) { observer.callback.call(observer.context, args); } } class CustomGestureDetector { constructor(observer, target) { this._observer = observer; this._target = target; this._density = layout.getDisplayDensity(); } } class CustomPanGestureDetector extends CustomGestureDetector { constructor() { super(...arguments); this._isTracking = false; this._startRawX = 0; this._startRawY = 0; this._startX = 0; this._startY = 0; } get _isTrackingGesture() { return this._isTracking; } onTouchEvent(event) { switch (event.getActionMasked()) { case android.view.MotionEvent.ACTION_UP: case android.view.MotionEvent.ACTION_CANCEL: if (this._isTrackingGesture) { this._trackStop(event); } break; case android.view.MotionEvent.ACTION_DOWN: case android.view.MotionEvent.ACTION_POINTER_DOWN: case android.view.MotionEvent.ACTION_POINTER_UP: if (this._isTrackingGesture) { this._trackStop(event); } this._trackInit(event); break; case android.view.MotionEvent.ACTION_MOVE: if (!this._isTrackingGesture) { this._trackStart(event); } this._trackChange(event); break; } return true; } _notifyEvent(state, event, initialEvent) { const args = { type: GestureTypes.pan, view: this._target, android: { initial: initialEvent, current: event }, startX: this._startX, startY: this._startY, deltaX: this._deltaX, deltaY: this._deltaY, ios: undefined, object: this._target, eventName: toString(GestureTypes.pan), state, }; _executeCallback(this._observer, args); } _trackInit(currentEvent) { if (this._isTrackingGesture) { this._trackStop(currentEvent); } const initialPos = this._getEventCoordinates(currentEvent); this._startRawX = initialPos.rawX; this._startRawY = initialPos.rawY; this._startX = initialPos.getViewX(); this._startY = initialPos.getViewY(); } _trackStop(currentEvent) { this._notifyEvent(GestureStateTypes.ended, currentEvent); this._deltaX = undefined; this._deltaY = undefined; this._startRawX = 0; this._startRawY = 0; this._startX = 0; this._startY = 0; this._isTracking = false; } _trackStart(currentEvent) { this._deltaX = 0; this._deltaY = 0; this._isTracking = true; this._notifyEvent(GestureStateTypes.began, currentEvent); } _trackChange(currentEvent) { const currentPos = this._getEventCoordinates(currentEvent); this._deltaX = currentPos.rawX - this._startRawX; this._deltaY = currentPos.rawY - this._startRawY; this._notifyEvent(GestureStateTypes.changed, currentEvent); } _getEventCoordinates(event) { const count = event.getPointerCount(); let res; if (count === 1) { res = { rawX: event.getRawX() / this._density, rawY: event.getRawY() / this._density, getViewX: () => event.getX() / this._density, getViewY: () => event.getY() / this._density, }; } else { const offX = event.getRawX() - event.getX(); const offY = event.getRawY() - event.getY(); let rawX = 0; let rawY = 0; let viewX = 0; let viewY = 0; for (let i = 0; i < count; i++) { const x = event.getX(i); const y = event.getY(i); rawX += x + offX; rawY += y + offY; viewX += x; viewY += y; } res = { rawX: (rawX / count) * this._density, rawY: (rawY / count) * this._density, getViewX: () => (viewX / count) * this._density, getViewY: () => (viewY / count) * this._density, }; } return res; } } class CustomRotateGestureDetector extends CustomGestureDetector { constructor() { super(...arguments); this._trackedPtrId1 = INVALID_POINTER_ID; this._trackedPtrId2 = INVALID_POINTER_ID; } get _isTrackingGesture() { return this._trackedPtrId1 !== INVALID_POINTER_ID && this._trackedPtrId2 !== INVALID_POINTER_ID; } onTouchEvent(event) { const pointerID = event.getPointerId(event.getActionIndex()); const wasTracking = this._isTrackingGesture; switch (event.getActionMasked()) { case android.view.MotionEvent.ACTION_DOWN: case android.view.MotionEvent.ACTION_POINTER_DOWN: { let assigned = false; if (this._trackedPtrId1 === INVALID_POINTER_ID && pointerID !== this._trackedPtrId2) { this._trackedPtrId1 = pointerID; assigned = true; } else if (this._trackedPtrId2 === INVALID_POINTER_ID && pointerID !== this._trackedPtrId1) { this._trackedPtrId2 = pointerID; assigned = true; } if (assigned && this._isTrackingGesture) { // We have started tracking 2 pointers this._angle = 0; this._initalPointersAngle = this._getPointersAngle(event); this._notifyEvent(GestureStateTypes.began, event); } break; } case android.view.MotionEvent.ACTION_MOVE: if (this._isTrackingGesture) { this._updateAngle(event); this._notifyEvent(GestureStateTypes.changed, event); } break; case android.view.MotionEvent.ACTION_UP: case android.view.MotionEvent.ACTION_POINTER_UP: if (pointerID === this._trackedPtrId1) { this._trackedPtrId1 = INVALID_POINTER_ID; } else if (pointerID === this._trackedPtrId2) { this._trackedPtrId2 = INVALID_POINTER_ID; } if (wasTracking && !this._isTrackingGesture) { this._notifyEvent(GestureStateTypes.ended, event); } break; case android.view.MotionEvent.ACTION_CANCEL: this._trackedPtrId1 = INVALID_POINTER_ID; this._trackedPtrId2 = INVALID_POINTER_ID; if (wasTracking) { this._notifyEvent(GestureStateTypes.cancelled, event); } break; } return true; } _notifyEvent(state, event, initialEvent) { const args = { type: GestureTypes.rotation, view: this._target, android: event, rotation: this._angle, ios: undefined, object: this._target, eventName: toString(GestureTypes.rotation), state: state, }; _executeCallback(this._observer, args); } _updateAngle(event) { const newPointersAngle = this._getPointersAngle(event); let result = ((newPointersAngle - this._initalPointersAngle) * TO_DEGREES) % 360; if (result < -180) { result += 360; } if (result > 180) { result -= 360; } this._angle = result; } _getPointersAngle(event) { const firstX = event.getX(event.findPointerIndex(this._trackedPtrId1)); const firstY = event.getY(event.findPointerIndex(this._trackedPtrId1)); const secondX = event.getX(event.findPointerIndex(this._trackedPtrId2)); const secondY = event.getY(event.findPointerIndex(this._trackedPtrId2)); return Math.atan2(secondY - firstY, secondX - firstX); } } class Pointer { constructor(id, event) { this.event = event; this.ios = undefined; this.android = id; } getX() { return this.event.getX(this.android) / layout.getDisplayDensity(); } getY() { return this.event.getY(this.android) / layout.getDisplayDensity(); } } class PinchGestureEventDataImpl { constructor(view, android, scale, object, state) { this.view = view; this.android = android; this.scale = scale; this.object = object; this.state = state; this.type = GestureTypes.pinch; this.eventName = toString(GestureTypes.pinch); } getFocusX() { return this.android.getFocusX() / layout.getDisplayDensity(); } getFocusY() { return this.android.getFocusY() / layout.getDisplayDensity(); } } export class TouchGestureEventData { constructor() { this.eventName = toString(GestureTypes.touch); this.type = GestureTypes.touch; this.ios = undefined; } prepare(view, e) { this.view = view; this.object = view; this.android = e; this.action = this.getActionType(e); this._activePointers = undefined; this._allPointers = undefined; } getPointerCount() { return this.android.getPointerCount(); } getActivePointers() { // Only one active pointer in Android if (!this._activePointers) { this._activePointers = [new Pointer(this.android.getActionIndex(), this.android)]; } return this._activePointers; } getAllPointers() { if (!this._allPointers) { this._allPointers = []; for (let i = 0; i < this.getPointerCount(); i++) { this._allPointers.push(new Pointer(i, this.android)); } } return this._allPointers; } getX() { return this.getActivePointers()[0].getX(); } getY() { return this.getActivePointers()[0].getY(); } getActionType(e) { switch (e.getActionMasked()) { case android.view.MotionEvent.ACTION_DOWN: case android.view.MotionEvent.ACTION_POINTER_DOWN: return TouchAction.down; case android.view.MotionEvent.ACTION_MOVE: return TouchAction.move; case android.view.MotionEvent.ACTION_UP: case android.view.MotionEvent.ACTION_POINTER_UP: return TouchAction.up; case android.view.MotionEvent.ACTION_CANCEL: return TouchAction.cancel; } return ''; } } //# sourceMappingURL=index.android.js.map