UNPKG

react-native-gesture-handler

Version:

Declarative API exposing native platform touch and gesture system to React Native

341 lines (265 loc) 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _PointerType = require("../../PointerType"); var _State = require("../../State"); var _PointerTracker = _interopRequireDefault(require("./PointerTracker")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } class GestureHandlerOrchestrator { // Private beacuse of Singleton // eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function constructor() { _defineProperty(this, "gestureHandlers", []); _defineProperty(this, "awaitingHandlers", []); _defineProperty(this, "awaitingHandlersTags", new Set()); _defineProperty(this, "handlingChangeSemaphore", 0); _defineProperty(this, "activationIndex", 0); } scheduleFinishedHandlersCleanup() { if (this.handlingChangeSemaphore === 0) { this.cleanupFinishedHandlers(); } } cleanHandler(handler) { handler.reset(); handler.setActive(false); handler.setAwaiting(false); handler.setActivationIndex(Number.MAX_VALUE); } removeHandlerFromOrchestrator(handler) { const indexInGestureHandlers = this.gestureHandlers.indexOf(handler); const indexInAwaitingHandlers = this.awaitingHandlers.indexOf(handler); if (indexInGestureHandlers >= 0) { this.gestureHandlers.splice(indexInGestureHandlers, 1); } if (indexInAwaitingHandlers >= 0) { this.awaitingHandlers.splice(indexInAwaitingHandlers, 1); this.awaitingHandlersTags.delete(handler.getTag()); } } cleanupFinishedHandlers() { const handlersToRemove = new Set(); for (let i = this.gestureHandlers.length - 1; i >= 0; --i) { const handler = this.gestureHandlers[i]; if (this.isFinished(handler.getState()) && !handler.isAwaiting()) { this.cleanHandler(handler); handlersToRemove.add(handler); } } this.gestureHandlers = this.gestureHandlers.filter(handler => !handlersToRemove.has(handler)); } hasOtherHandlerToWaitFor(handler) { const hasToWaitFor = otherHandler => { return !this.isFinished(otherHandler.getState()) && this.shouldHandlerWaitForOther(handler, otherHandler); }; return this.gestureHandlers.some(hasToWaitFor); } shouldBeCancelledByFinishedHandler(handler) { const shouldBeCancelled = otherHandler => { return this.shouldHandlerWaitForOther(handler, otherHandler) && otherHandler.getState() === _State.State.END; }; return this.gestureHandlers.some(shouldBeCancelled); } tryActivate(handler) { if (this.shouldBeCancelledByFinishedHandler(handler)) { handler.cancel(); return; } if (this.hasOtherHandlerToWaitFor(handler)) { this.addAwaitingHandler(handler); return; } const handlerState = handler.getState(); if (handlerState === _State.State.CANCELLED || handlerState === _State.State.FAILED) { return; } if (this.shouldActivate(handler)) { this.makeActive(handler); return; } if (handlerState === _State.State.ACTIVE) { handler.fail(); return; } if (handlerState === _State.State.BEGAN) { handler.cancel(); } } shouldActivate(handler) { const shouldBeCancelledBy = otherHandler => { return this.shouldHandlerBeCancelledBy(handler, otherHandler); }; return !this.gestureHandlers.some(shouldBeCancelledBy); } cleanupAwaitingHandlers(handler) { const shouldWait = otherHandler => { return !otherHandler.isAwaiting() && this.shouldHandlerWaitForOther(otherHandler, handler); }; for (const otherHandler of this.awaitingHandlers) { if (shouldWait(otherHandler)) { this.cleanHandler(otherHandler); this.awaitingHandlersTags.delete(otherHandler.getTag()); } } this.awaitingHandlers = this.awaitingHandlers.filter(otherHandler => this.awaitingHandlersTags.has(otherHandler.getTag())); } onHandlerStateChange(handler, newState, oldState, sendIfDisabled) { if (!handler.isEnabled() && !sendIfDisabled) { return; } this.handlingChangeSemaphore += 1; if (this.isFinished(newState)) { for (const otherHandler of this.awaitingHandlers) { if (!this.shouldHandlerWaitForOther(otherHandler, handler) || !this.awaitingHandlersTags.has(otherHandler.getTag())) { continue; } if (newState !== _State.State.END) { this.tryActivate(otherHandler); continue; } otherHandler.cancel(); if (otherHandler.getState() === _State.State.END) { // Handle edge case, where discrete gestures end immediately after activation thus // their state is set to END and when the gesture they are waiting for activates they // should be cancelled, however `cancel` was never sent as gestures were already in the END state. // Send synthetic BEGAN -> CANCELLED to properly handle JS logic otherHandler.sendEvent(_State.State.CANCELLED, _State.State.BEGAN); } otherHandler.setAwaiting(false); } } if (newState === _State.State.ACTIVE) { this.tryActivate(handler); } else if (oldState === _State.State.ACTIVE || oldState === _State.State.END) { if (handler.isActive()) { handler.sendEvent(newState, oldState); } else if (oldState === _State.State.ACTIVE && (newState === _State.State.CANCELLED || newState === _State.State.FAILED)) { handler.sendEvent(newState, _State.State.BEGAN); } } else if (oldState !== _State.State.UNDETERMINED || newState !== _State.State.CANCELLED) { handler.sendEvent(newState, oldState); } this.handlingChangeSemaphore -= 1; this.scheduleFinishedHandlersCleanup(); if (!this.awaitingHandlers.includes(handler)) { this.cleanupAwaitingHandlers(handler); } } makeActive(handler) { const currentState = handler.getState(); handler.setActive(true); handler.setShouldResetProgress(true); handler.setActivationIndex(this.activationIndex++); for (let i = this.gestureHandlers.length - 1; i >= 0; --i) { if (this.shouldHandlerBeCancelledBy(this.gestureHandlers[i], handler)) { this.gestureHandlers[i].cancel(); } } for (const otherHandler of this.awaitingHandlers) { if (this.shouldHandlerBeCancelledBy(otherHandler, handler)) { otherHandler.setAwaiting(false); } } handler.sendEvent(_State.State.ACTIVE, _State.State.BEGAN); if (currentState !== _State.State.ACTIVE) { handler.sendEvent(_State.State.END, _State.State.ACTIVE); if (currentState !== _State.State.END) { handler.sendEvent(_State.State.UNDETERMINED, _State.State.END); } } if (!handler.isAwaiting()) { return; } handler.setAwaiting(false); this.awaitingHandlers = this.awaitingHandlers.filter(otherHandler => otherHandler !== handler); } addAwaitingHandler(handler) { if (this.awaitingHandlers.includes(handler)) { return; } this.awaitingHandlers.push(handler); this.awaitingHandlersTags.add(handler.getTag()); handler.setAwaiting(true); handler.setActivationIndex(this.activationIndex++); } recordHandlerIfNotPresent(handler) { if (this.gestureHandlers.includes(handler)) { return; } this.gestureHandlers.push(handler); handler.setActive(false); handler.setAwaiting(false); handler.setActivationIndex(Number.MAX_SAFE_INTEGER); } shouldHandlerWaitForOther(handler, otherHandler) { return handler !== otherHandler && (handler.shouldWaitForHandlerFailure(otherHandler) || otherHandler.shouldRequireToWaitForFailure(handler)); } canRunSimultaneously(gh1, gh2) { return gh1 === gh2 || gh1.shouldRecognizeSimultaneously(gh2) || gh2.shouldRecognizeSimultaneously(gh1); } shouldHandlerBeCancelledBy(handler, otherHandler) { if (this.canRunSimultaneously(handler, otherHandler)) { return false; } if (handler.isAwaiting() || handler.getState() === _State.State.ACTIVE) { // For now it always returns false return handler.shouldBeCancelledByOther(otherHandler); } const handlerPointers = handler.getTrackedPointersID(); const otherPointers = otherHandler.getTrackedPointersID(); if (!_PointerTracker.default.shareCommonPointers(handlerPointers, otherPointers) && handler.getDelegate().getView() !== otherHandler.getDelegate().getView()) { return this.checkOverlap(handler, otherHandler); } return true; } checkOverlap(handler, otherHandler) { // If handlers don't have common pointers, default return value is false. // However, if at least on pointer overlaps with both handlers, we return true // This solves issue in overlapping parents example // TODO: Find better way to handle that issue, for example by activation order and handler cancelling const isPointerWithinBothBounds = pointer => { const point = handler.getTracker().getLastAbsoluteCoords(pointer); return handler.getDelegate().isPointerInBounds(point) && otherHandler.getDelegate().isPointerInBounds(point); }; return handler.getTrackedPointersID().some(isPointerWithinBothBounds); } isFinished(state) { return state === _State.State.END || state === _State.State.FAILED || state === _State.State.CANCELLED; } // This function is called when handler receives touchdown event // If handler is using mouse or pen as a pointer and any handler receives touch event, // mouse/pen event dissappears - it doesn't send onPointerCancel nor onPointerUp (and others) // This became a problem because handler was left at active state without any signal to end or fail // To handle this, when new touch event is received, we loop through active handlers and check which type of // pointer they're using. If there are any handler with mouse/pen as a pointer, we cancel them cancelMouseAndPenGestures(currentHandler) { this.gestureHandlers.forEach(handler => { if (handler.getPointerType() !== _PointerType.PointerType.MOUSE && handler.getPointerType() !== _PointerType.PointerType.STYLUS) { return; } if (handler !== currentHandler) { handler.cancel(); } else { // Handler that received touch event should have its pointer tracker reset // This allows handler to smoothly change from mouse/pen to touch // The drawback is, that when we try to use mouse/pen one more time, it doesn't send onPointerDown at the first time // so it is required to click two times to get handler to work // // However, handler will receive manually created onPointerEnter that is triggered in EventManager in onPointerMove method. // There may be possibility to use that fact to make handler respond properly to first mouse click handler.getTracker().resetTracker(); } }); } static getInstance() { if (!GestureHandlerOrchestrator.instance) { GestureHandlerOrchestrator.instance = new GestureHandlerOrchestrator(); } return GestureHandlerOrchestrator.instance; } } exports.default = GestureHandlerOrchestrator; _defineProperty(GestureHandlerOrchestrator, "instance", void 0); //# sourceMappingURL=GestureHandlerOrchestrator.js.map