@nativescript-community/gesturehandler
Version:
Declarative API exposing platform native touch and gesture system to NativeScript.
641 lines • 26.7 kB
JavaScript
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