UNPKG

@nativescript-community/gesturehandler

Version:

Declarative API exposing platform native touch and gesture system to NativeScript.

641 lines 26.7 kB
import { Utils } from '@nativescript/core'; import { Page } from '@nativescript/core/ui/page'; import { BaseGestureRootView, BaseNative, GestureHandlerStateEvent, GestureHandlerTouchEvent, GestureState, HandlerType, ManagerBase, ROOT_GESTURE_HANDLER_TAG, ViewDisposeEvent, ViewInitEvent, applyMixins, install as installBase, nativeProperty } from './gesturehandler.common'; import { observe as gestureObserve } from './gestures_override'; export { GestureState, GestureHandlerStateEvent, GestureHandlerTouchEvent, HandlerType, ViewInitEvent, ViewDisposeEvent }; let PageLayout; class PageGestureExtended extends Page { initNativeView() { this.nativeViewProtected.initialize(); } disposeNativeView() { this.nativeViewProtected.tearDown(); } get registry() { return this.nativeViewProtected && this.nativeViewProtected.registry(); } } let installed = false; let installedOverrides = false; export class GestureRootView extends BaseGestureRootView { createNativeView() { if (!PageLayout) { PageLayout = com.nativescript.gesturehandler.PageLayout; } const layout = new PageLayout(this._context, ROOT_GESTURE_HANDLER_TAG); return layout; } initNativeView() { super.initNativeView(); this.nativeView.initialize(); } disposeNativeView() { super.disposeNativeView(); this.nativeView.tearDown(); } get registry() { return this.nativeView && this.nativeView.registry(); } } export function install(overrideNGestures = false) { if (!installed) { installed = true; installBase(overrideNGestures); const NSPage = require('@nativescript/core/ui/page').Page; NSPage.prototype.createNativeView = function () { if (!PageLayout) { PageLayout = com.nativescript.gesturehandler.PageLayout; } const layout = new PageLayout(this._context, ROOT_GESTURE_HANDLER_TAG); //@ts-ignore if (layout.addRowsFromJSON) { //@ts-ignore layout.addRowsFromJSON(JSON.stringify([ { value: 1, type: 0 /* org.nativescript.widgets.GridUnitType.auto */ }, { value: 1, type: 2 /* org.nativescript.widgets.GridUnitType.star */ } ])); } else { layout.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto)); layout.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star)); } // this.gestureRegistry = layout.registry(); return layout; }; applyMixins(NSPage, [PageGestureExtended]); } if (overrideNGestures === true && !installedOverrides) { installedOverrides = true; const NSView = require('@nativescript/core/ui/core/view').View; const NSButtonBase = require('@nativescript/core/ui/button').ButtonBase; const NSButton = require('@nativescript/core/ui/button').Button; delete NSButtonBase.tapEvent; delete NSButton.tapEvent; // we need to disable on click listener NSButton.prototype.initNativeView = function () { NSButtonBase.prototype.initNativeView.call(this); }; NSButton.prototype.disposeNativeView = function () { NSButtonBase.prototype.disposeNativeView.call(this); }; NSView.prototype._observe = function (type, callback, thisArg) { if (!this._gestureObservers[type]) { this._gestureObservers[type] = []; } this._gestureObservers[type].push(gestureObserve(this, type, callback, thisArg)); if (this.isLoaded && !this.touchListenerIsSet) { this.setOnTouchListener(); } }; // NSView.prototype.setOnTouchListener = function () { // if (!this.nativeViewProtected || !this.getGestureObservers(GestureTypes.touch)) { // return; // } // // do not set noop listener that handles the event (disabled listener) if IsUserInteractionEnabled is // // false as we might need the ability for the event to pass through to a parent view // this.touchListener = // this.touchListener || // new android.view.View.OnTouchListener({ // onTouch: (view: android.view.View, event: android.view.MotionEvent) => { // this.handleGestureTouch(event); // const nativeView = this.nativeViewProtected; // if (!nativeView || !nativeView.onTouchEvent) { // return false; // } // return nativeView.onTouchEvent(event); // }, // }); // this.nativeViewProtected.setOnTouchListener(this.touchListener); // this.touchListenerIsSet = true; // console.log('setOnTouchListener', this); // if (this.nativeViewProtected.setClickable) { // this.nativeViewProtected.setClickable(this.isUserInteractionEnabled); // } // }; } } const KEY_HIT_SLOP_LEFT = 'left'; const KEY_HIT_SLOP_TOP = 'top'; const KEY_HIT_SLOP_RIGHT = 'right'; const KEY_HIT_SLOP_BOTTOM = 'bottom'; const KEY_HIT_SLOP_VERTICAL = 'vertical'; const KEY_HIT_SLOP_HORIZONTAL = 'horizontal'; const KEY_HIT_SLOP_WIDTH = 'width'; const KEY_HIT_SLOP_HEIGHT = 'height'; export class Handler extends BaseNative { constructor() { super(...arguments); this.nativeGetterKey = 'nativeView'; this.tag = 0; } getExtraData(handler) { const numberOfPointers = handler.getNumberOfPointers(); const positions = []; for (let index = 0; index < numberOfPointers; index++) { positions.push(Utils.layout.toDeviceIndependentPixels(handler.getXAtIndex(index))); positions.push(Utils.layout.toDeviceIndependentPixels(handler.getYAtIndex(index))); } return { // x: Utils.layout.toDeviceIndependentPixels(handler.getX()), // y: Utils.layout.toDeviceIndependentPixels(handler.getY()), positions, numberOfPointers }; } initNativeView(native, options) { super.initNativeView(native, options); this.native.setTag(this.tag); this.touchListener = new com.swmansion.gesturehandler.OnTouchEventListener({ shouldStartGesture: this.handleShouldStartGesture.bind(this), onTouchEvent: this.onTouchEvent.bind(this), onStateChange: this.onStateChange.bind(this) }); native.setOnTouchEventListener(this.touchListener); this.manager?.get()?.configureInteractions(this, options); } disposeNativeView() { this.native.setInteractionController(null); this.native.setOnTouchEventListener(null); this.touchListener = null; super.disposeNativeView(); } handleShouldStartGesture(handler, event) { if (this.shouldStartGesture) { return this.shouldStartGesture(this.getExtraData(handler)); } return true; } onTouchEvent(handler, event) { const view = handler.getView(); this.notify({ eventName: GestureHandlerTouchEvent, object: this, data: { state: handler.getState(), android: handler, extraData: this.getExtraData(handler), view: view.nsView ? view.nsView?.get() : null } }); } onStateChange(handler, state, prevState) { const view = handler.getView(); this.notify({ eventName: GestureHandlerStateEvent, object: this, data: { state, prevState, android: handler, extraData: this.getExtraData(handler), view: view.nsView ? view.nsView?.get() : null } }); } setTag(tag) { this.tag = tag; if (this.native) { this.native.setTag(tag); } } getTag() { return this.tag; // return this.getNative().getTag(); } getView() { return this.getNative().getView(); } cancel() { return this.getNative().cancel(); } attachToView(view) { if (view === this.attachedView) { return; } if (this.attachedView) { this.detachFromView(this.attachedView); } this.attachedView = view; this.manager?.get()?.attachGestureHandler(this, view); } detachFromView(view) { if ((view && view !== this.attachedView) || !this.attachedView) { return; } if (!this.attachedView) { return; } this.manager?.get()?.detachGestureHandler(this, this.attachedView); this.attachedView = null; } } __decorate([ nativeProperty({ nativeSetterName: 'setHitSlop', nativegetterName: 'getHitSlop', converter: { toNative(value) { const HIT_SLOP_NONE = GestureHandler.HIT_SLOP_NONE; if (typeof value === 'number') { const hitSlop = Utils.layout.toDevicePixels(value); return [hitSlop, hitSlop, hitSlop, hitSlop, HIT_SLOP_NONE, HIT_SLOP_NONE]; } else { let left = HIT_SLOP_NONE, top = HIT_SLOP_NONE, right = HIT_SLOP_NONE, bottom = HIT_SLOP_NONE; let width = HIT_SLOP_NONE, height = HIT_SLOP_NONE; if (value.hasOwnProperty(KEY_HIT_SLOP_HORIZONTAL)) { const horizontalPad = Utils.layout.toDevicePixels(value[KEY_HIT_SLOP_HORIZONTAL]); left = right = horizontalPad; } if (value.hasOwnProperty(KEY_HIT_SLOP_VERTICAL)) { const verticalPad = Utils.layout.toDevicePixels(value[KEY_HIT_SLOP_VERTICAL]); top = bottom = verticalPad; } if (value.hasOwnProperty(KEY_HIT_SLOP_LEFT)) { left = Utils.layout.toDevicePixels(value[KEY_HIT_SLOP_LEFT]); } if (value.hasOwnProperty(KEY_HIT_SLOP_TOP)) { top = Utils.layout.toDevicePixels(value[KEY_HIT_SLOP_TOP]); } if (value.hasKey(KEY_HIT_SLOP_RIGHT)) { right = Utils.layout.toDevicePixels(value[KEY_HIT_SLOP_RIGHT]); } if (value.hasOwnProperty(KEY_HIT_SLOP_BOTTOM)) { bottom = Utils.layout.toDevicePixels(value[KEY_HIT_SLOP_BOTTOM]); } if (value.hasOwnProperty(KEY_HIT_SLOP_WIDTH)) { width = Utils.layout.toDevicePixels(value[KEY_HIT_SLOP_WIDTH]); } if (value.hasOwnProperty(KEY_HIT_SLOP_HEIGHT)) { height = Utils.layout.toDevicePixels(value[KEY_HIT_SLOP_HEIGHT]); } return [left, top, right, bottom, width, height]; } } } }) ], Handler.prototype, "hitSlop", void 0); __decorate([ nativeProperty ], Handler.prototype, "enabled", void 0); __decorate([ nativeProperty ], Handler.prototype, "shouldCancelWhenOutside", void 0); export class TapGestureHandler extends Handler { createNative(options) { return new com.swmansion.gesturehandler.TapGestureHandler(); } getExtraData(handler) { return Object.assign(super.getExtraData(handler), { x: Utils.layout.toDeviceIndependentPixels(handler.getLastRelativePositionX()), y: Utils.layout.toDeviceIndependentPixels(handler.getLastRelativePositionY()), absoluteX: Utils.layout.toDeviceIndependentPixels(handler.getLastAbsolutePositionX()), absoluteY: Utils.layout.toDeviceIndependentPixels(handler.getLastAbsolutePositionY()) }); } } __decorate([ nativeProperty ], TapGestureHandler.prototype, "numberOfTaps", void 0); __decorate([ nativeProperty ], TapGestureHandler.prototype, "maxDurationMs", void 0); __decorate([ nativeProperty ], TapGestureHandler.prototype, "maxDelayMs", void 0); __decorate([ nativeProperty({ nativeSetterName: 'setMaxDx' }) ], TapGestureHandler.prototype, "maxDeltaX", void 0); __decorate([ nativeProperty({ nativeSetterName: 'setMaxDy' }) ], TapGestureHandler.prototype, "maxDeltaY", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], TapGestureHandler.prototype, "maxDist", void 0); __decorate([ nativeProperty({ nativeSetterName: 'setMinNumberOfPointers' }) ], TapGestureHandler.prototype, "minPointers", void 0); export class PanGestureHandler extends Handler { createNative(options) { const context = Utils.android.getApplicationContext(); return new com.swmansion.gesturehandler.PanGestureHandler(context); } getExtraData(handler) { return Object.assign(super.getExtraData(handler), { x: Utils.layout.toDeviceIndependentPixels(handler.getLastRelativePositionX()), y: Utils.layout.toDeviceIndependentPixels(handler.getLastRelativePositionY()), absoluteX: Utils.layout.toDeviceIndependentPixels(handler.getLastAbsolutePositionX()), absoluteY: Utils.layout.toDeviceIndependentPixels(handler.getLastAbsolutePositionY()), translationX: Utils.layout.toDeviceIndependentPixels(handler.getTranslationX()), translationY: Utils.layout.toDeviceIndependentPixels(handler.getTranslationY()), velocityX: Utils.layout.toDeviceIndependentPixels(handler.getVelocityX()), velocityY: Utils.layout.toDeviceIndependentPixels(handler.getVelocityY()) }); } } __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "minDist", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "activeOffsetXStart", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "activeOffsetXEnd", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "failOffsetXStart", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "failOffsetXEnd", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "activeOffsetYStart", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "activeOffsetYEnd", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "failOffsetYStart", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "failOffsetYEnd", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "minVelocity", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "minVelocityX", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PanGestureHandler.prototype, "minVelocityY", void 0); __decorate([ nativeProperty ], PanGestureHandler.prototype, "minPointers", void 0); __decorate([ nativeProperty ], PanGestureHandler.prototype, "maxPointers", void 0); __decorate([ nativeProperty({ nativeSetterName: 'setAverageTouches' }) ], PanGestureHandler.prototype, "avgTouches", void 0); __decorate([ nativeProperty ], PanGestureHandler.prototype, "numberOfPointers", void 0); export class PinchGestureHandler extends Handler { createNative(options) { return new com.swmansion.gesturehandler.PinchGestureHandler(); } getExtraData(handler) { return Object.assign(super.getExtraData(handler), { x: Utils.layout.toDeviceIndependentPixels(handler.getLastRelativePositionX()), y: Utils.layout.toDeviceIndependentPixels(handler.getLastRelativePositionY()), absoluteX: Utils.layout.toDeviceIndependentPixels(handler.getLastAbsolutePositionX()), absoluteY: Utils.layout.toDeviceIndependentPixels(handler.getLastAbsolutePositionY()), scale: handler.getScale(), focalX: Utils.layout.toDeviceIndependentPixels(handler.getFocalPointX()), focalY: Utils.layout.toDeviceIndependentPixels(handler.getFocalPointY()), velocity: handler.getVelocity() }); } } __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], PinchGestureHandler.prototype, "minSpan", void 0); const GestureHandler = com.swmansion.gesturehandler.GestureHandler; export var FlingDirection; (function (FlingDirection) { FlingDirection[FlingDirection["DIRECTION_LEFT"] = GestureHandler.DIRECTION_LEFT] = "DIRECTION_LEFT"; FlingDirection[FlingDirection["DIRECTION_UP"] = GestureHandler.DIRECTION_UP] = "DIRECTION_UP"; FlingDirection[FlingDirection["DIRECTION_DOWN"] = GestureHandler.DIRECTION_DOWN] = "DIRECTION_DOWN"; FlingDirection[FlingDirection["DIRECTION_RIGHT"] = GestureHandler.DIRECTION_RIGHT] = "DIRECTION_RIGHT"; })(FlingDirection || (FlingDirection = {})); function directionToString(direction) { switch (direction) { case GestureHandler.DIRECTION_RIGHT: return 'right'; case GestureHandler.DIRECTION_UP: return 'up'; case GestureHandler.DIRECTION_DOWN: return 'down'; default: return 'left'; } } function directionFromString(direction) { switch (direction) { case 'right': return GestureHandler.DIRECTION_RIGHT; case 'up': return GestureHandler.DIRECTION_UP; case 'down': return GestureHandler.DIRECTION_DOWN; default: return GestureHandler.DIRECTION_LEFT; } } export class FlingGestureHandler extends Handler { createNative(options) { return new com.swmansion.gesturehandler.FlingGestureHandler(); } getExtraData(handler) { return Object.assign(super.getExtraData(handler), { direction: directionToString(handler.getRecognizedDirection()) }); } } __decorate([ nativeProperty ], FlingGestureHandler.prototype, "numberOfPointers", void 0); __decorate([ nativeProperty ], FlingGestureHandler.prototype, "direction", void 0); export class LongPressGestureHandler extends Handler { createNative(options) { const context = Utils.android.getApplicationContext(); return new com.swmansion.gesturehandler.LongPressGestureHandler(context); } getExtraData(handler) { return Object.assign(super.getExtraData(handler), { x: Utils.layout.toDeviceIndependentPixels(handler.getLastRelativePositionX()), y: Utils.layout.toDeviceIndependentPixels(handler.getLastRelativePositionY()), absoluteX: Utils.layout.toDeviceIndependentPixels(handler.getLastAbsolutePositionX()), absoluteY: Utils.layout.toDeviceIndependentPixels(handler.getLastAbsolutePositionY()) }); } } __decorate([ nativeProperty ], LongPressGestureHandler.prototype, "minDurationMs", void 0); __decorate([ nativeProperty({ converter: { fromNative: Utils.layout.toDevicePixels } }) ], LongPressGestureHandler.prototype, "maxDist", void 0); export class RotationGestureHandler extends Handler { createNative(options) { return new com.swmansion.gesturehandler.RotationGestureHandler(); } getExtraData(handler) { return Object.assign(super.getExtraData(handler), { x: Utils.layout.toDeviceIndependentPixels(handler.getLastRelativePositionX()), y: Utils.layout.toDeviceIndependentPixels(handler.getLastRelativePositionY()), absoluteX: Utils.layout.toDeviceIndependentPixels(handler.getLastAbsolutePositionX()), absoluteY: Utils.layout.toDeviceIndependentPixels(handler.getLastAbsolutePositionY()), rotation: handler.getRotation(), anchorX: Utils.layout.toDeviceIndependentPixels(handler.getAnchorX()), anchorY: Utils.layout.toDeviceIndependentPixels(handler.getAnchorY()), velocity: handler.getVelocity() }); } } export class NativeViewGestureHandler extends Handler { createNative(options) { return new com.swmansion.gesturehandler.NativeViewGestureHandler(); } getExtraData(handler) { return Object.assign(super.getExtraData(handler), { pointerInside: handler.isWithinBounds() }); } } __decorate([ nativeProperty ], NativeViewGestureHandler.prototype, "shouldActivateOnStart", void 0); __decorate([ nativeProperty ], NativeViewGestureHandler.prototype, "disallowInterruption", void 0); export class Manager extends ManagerBase { constructor() { super(...arguments); this.viewListeners = new Map(); } configureInteractions(handler, options = {}) { this.interactionManager.configureInteractions(handler.getNative(), options.waitFor, options.simultaneousHandlers); } get interactionManager() { if (!this._interactionManager) { this._interactionManager = new com.nativescript.gesturehandler.GestureHandlerInteractionController(); } return this._interactionManager; } static getInstance() { if (!Manager.sManager) { Manager.sManager = new Manager(); } return Manager.sManager; } createGestureHandler(handlerName, handlerTag, config) { let handler = null; switch (handlerName) { case 'tap': handler = new TapGestureHandler(config); break; case 'pan': handler = new PanGestureHandler(config); break; case 'nativeView': handler = new NativeViewGestureHandler(config); break; case 'pinch': handler = new PinchGestureHandler(config); break; case 'fling': handler = new FlingGestureHandler(config); break; case 'rotation': handler = new RotationGestureHandler(config); break; case 'longPress': handler = new LongPressGestureHandler(config); break; } if (handler) { handler.manager = new WeakRef(this); handler.setTag(handlerTag); } return handler; } findRegistry(view) { if (view instanceof GestureRootView) { return view.registry; } let registry; let parent = view.parent; // first test for GestureRootView // otherwise it could fail with components like BottomSheet // where the bottomSheet parent is set to app rootView (which could be a page) // thus the registry to add the handler would not be the same // as the one use for touch while (parent) { if (parent instanceof GestureRootView) { return parent.registry; } // we need to break if it is a modal page or we will get the wrong registry if (parent['_dialogFragment']) { break; } parent = parent.parent; } const page = view.page; if (page) { registry = page.registry; } return registry; } attachGestureHandlerToView(handler, view) { const nHandler = handler.getNative(); if (nHandler) { const registry = this.findRegistry(view); if (registry) { registry.registerHandler(nHandler); registry.attachHandlerToView(nHandler.getTag(), view[handler.nativeGetterKey]); } else { throw new Error('a Page or a GestureRootView is needed to attach a gesture'); } } } detachGestureHandlerFromView(handler, view) { const nHandler = handler.getNative(); if (nHandler) { const registry = this.findRegistry(view); if (registry) { registry.dropHandler(nHandler.getTag()); } } } attachGestureHandler(handler, view) { if (view.nativeView) { this.attachGestureHandlerToView(handler, view); } const onInit = () => this.attachGestureHandlerToView(handler, view); const onDispose = () => this.detachGestureHandlerFromView(handler, view); view.on(ViewInitEvent, onInit, this); view.on(ViewDisposeEvent, onDispose, this); let viewListeners = this.viewListeners.get(view); if (!viewListeners) { viewListeners = new Map(); this.viewListeners.set(view, viewListeners); } viewListeners.set(handler.getTag(), { init: onInit, dispose: onDispose }); } detachGestureHandler(handler, view) { if (view) { const viewListeners = this.viewListeners.get(view); if (viewListeners) { const listeners = viewListeners.get(handler.getTag()); if (listeners) { view.off(ViewInitEvent, listeners.init, this); view.off(ViewDisposeEvent, () => listeners.dispose, this); viewListeners.delete(handler.getTag()); if (viewListeners.size === 0) { this.viewListeners.delete(view); } } } } this.detachGestureHandlerFromView(handler, view); } } //# sourceMappingURL=gesturehandler.android.js.map