create-expo-cljs-app
Version:
Create a react native application with Expo and Shadow-CLJS!
300 lines (266 loc) • 7.84 kB
JavaScript
import {
STATE_POSSIBLE,
STATE_ENDED,
STATE_FAILED,
STATE_RECOGNIZED,
STATE_CANCELLED,
STATE_BEGAN,
STATE_CHANGED
} from './recognizer-consts';
import assign from '../utils/assign';
import uniqueId from '../utils/unique-id';
import invokeArrayArg from '../utils/invoke-array-arg';
import inArray from '../utils/in-array';
import boolOrFn from '../utils/bool-or-fn';
import getRecognizerByNameIfManager from './get-recognizer-by-name-if-manager';
import stateStr from './state-str';
/**
* @private
* Recognizer flow explained; *
* All recognizers have the initial state of POSSIBLE when a input session starts.
* The definition of a input session is from the first input until the last input, with all it's movement in it. *
* Example session for mouse-input: mousedown -> mousemove -> mouseup
*
* On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
* which determines with state it should be.
*
* If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
* POSSIBLE to give it another change on the next cycle.
*
* Possible
* |
* +-----+---------------+
* | |
* +-----+-----+ |
* | | |
* Failed Cancelled |
* +-------+------+
* | |
* Recognized Began
* |
* Changed
* |
* Ended/Recognized
*/
/**
* @private
* Recognizer
* Every recognizer needs to extend from this class.
* @constructor
* @param {Object} options
*/
export default class Recognizer {
constructor(options = {}) {
this.options = {
enable: true,
...options,
};
this.id = uniqueId();
this.manager = null;
// default is enable true
this.state = STATE_POSSIBLE;
this.simultaneous = {};
this.requireFail = [];
}
/**
* @private
* set options
* @param {Object} options
* @return {Recognizer}
*/
set(options) {
assign(this.options, options);
// also update the touchAction, in case something changed about the directions/enabled state
this.manager && this.manager.touchAction.update();
return this;
}
/**
* @private
* recognize simultaneous with an other recognizer.
* @param {Recognizer} otherRecognizer
* @returns {Recognizer} this
*/
recognizeWith(otherRecognizer) {
if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
return this;
}
let { simultaneous } = this;
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
if (!simultaneous[otherRecognizer.id]) {
simultaneous[otherRecognizer.id] = otherRecognizer;
otherRecognizer.recognizeWith(this);
}
return this;
}
/**
* @private
* drop the simultaneous link. it doesnt remove the link on the other recognizer.
* @param {Recognizer} otherRecognizer
* @returns {Recognizer} this
*/
dropRecognizeWith(otherRecognizer) {
if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
return this;
}
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
delete this.simultaneous[otherRecognizer.id];
return this;
}
/**
* @private
* recognizer can only run when an other is failing
* @param {Recognizer} otherRecognizer
* @returns {Recognizer} this
*/
requireFailure(otherRecognizer) {
if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
return this;
}
let { requireFail } = this;
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
if (inArray(requireFail, otherRecognizer) === -1) {
requireFail.push(otherRecognizer);
otherRecognizer.requireFailure(this);
}
return this;
}
/**
* @private
* drop the requireFailure link. it does not remove the link on the other recognizer.
* @param {Recognizer} otherRecognizer
* @returns {Recognizer} this
*/
dropRequireFailure(otherRecognizer) {
if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
return this;
}
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
let index = inArray(this.requireFail, otherRecognizer);
if (index > -1) {
this.requireFail.splice(index, 1);
}
return this;
}
/**
* @private
* has require failures boolean
* @returns {boolean}
*/
hasRequireFailures() {
return this.requireFail.length > 0;
}
/**
* @private
* if the recognizer can recognize simultaneous with an other recognizer
* @param {Recognizer} otherRecognizer
* @returns {Boolean}
*/
canRecognizeWith(otherRecognizer) {
return !!this.simultaneous[otherRecognizer.id];
}
/**
* @private
* You should use `tryEmit` instead of `emit` directly to check
* that all the needed recognizers has failed before emitting.
* @param {Object} input
*/
emit(input) {
let self = this;
let { state } = this;
function emit(event) {
self.manager.emit(event, input);
}
// 'panstart' and 'panmove'
if (state < STATE_ENDED) {
emit(self.options.event + stateStr(state));
}
emit(self.options.event); // simple 'eventName' events
if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
emit(input.additionalEvent);
}
// panend and pancancel
if (state >= STATE_ENDED) {
emit(self.options.event + stateStr(state));
}
}
/**
* @private
* Check that all the require failure recognizers has failed,
* if true, it emits a gesture event,
* otherwise, setup the state to FAILED.
* @param {Object} input
*/
tryEmit(input) {
if (this.canEmit()) {
return this.emit(input);
}
// it's failing anyway
this.state = STATE_FAILED;
}
/**
* @private
* can we emit?
* @returns {boolean}
*/
canEmit() {
let i = 0;
while (i < this.requireFail.length) {
if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
return false;
}
i++;
}
return true;
}
/**
* @private
* update the recognizer
* @param {Object} inputData
*/
recognize(inputData) {
// make a new copy of the inputData
// so we can change the inputData without messing up the other recognizers
let inputDataClone = assign({}, inputData);
// is is enabled and allow recognizing?
if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
this.reset();
this.state = STATE_FAILED;
return;
}
// reset when we've reached the end
if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
this.state = STATE_POSSIBLE;
}
this.state = this.process(inputDataClone);
// the recognizer has recognized a gesture
// so trigger an event
if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
this.tryEmit(inputDataClone);
}
}
/**
* @private
* return the state of the recognizer
* the actual recognizing happens in this method
* @virtual
* @param {Object} inputData
* @returns {constant} STATE
*/
/* jshint ignore:start */
process(inputData) { }
/* jshint ignore:end */
/**
* @private
* return the preferred touch-action
* @virtual
* @returns {Array}
*/
getTouchAction() { }
/**
* @private
* called when the gesture isn't allowed to recognize
* like when another is being recognized or it is disabled
* @virtual
*/
reset() { }
}