328 lines
9.1 KiB
JavaScript
328 lines
9.1 KiB
JavaScript
/**
|
|
* 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
|
|
*/
|
|
var STATE_POSSIBLE = 1;
|
|
var STATE_BEGAN = 2;
|
|
var STATE_CHANGED = 4;
|
|
var STATE_ENDED = 8;
|
|
var STATE_RECOGNIZED = STATE_ENDED;
|
|
var STATE_CANCELLED = 16;
|
|
var STATE_FAILED = 32;
|
|
|
|
/**
|
|
* Recognizer
|
|
* Every recognizer needs to extend from this class.
|
|
* @constructor
|
|
* @param {Object} options
|
|
*/
|
|
function Recognizer(options) {
|
|
this.options = assign({}, this.defaults, options || {});
|
|
|
|
this.id = uniqueId();
|
|
|
|
this.manager = null;
|
|
|
|
// default is enable true
|
|
this.options.enable = ifUndefined(this.options.enable, true);
|
|
|
|
this.state = STATE_POSSIBLE;
|
|
|
|
this.simultaneous = {};
|
|
this.requireFail = [];
|
|
}
|
|
|
|
Recognizer.prototype = {
|
|
/**
|
|
* @virtual
|
|
* @type {Object}
|
|
*/
|
|
defaults: {},
|
|
|
|
/**
|
|
* set options
|
|
* @param {Object} options
|
|
* @return {Recognizer}
|
|
*/
|
|
set: function(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;
|
|
},
|
|
|
|
/**
|
|
* recognize simultaneous with an other recognizer.
|
|
* @param {Recognizer} otherRecognizer
|
|
* @returns {Recognizer} this
|
|
*/
|
|
recognizeWith: function(otherRecognizer) {
|
|
if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
|
|
return this;
|
|
}
|
|
|
|
var simultaneous = this.simultaneous;
|
|
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
|
|
if (!simultaneous[otherRecognizer.id]) {
|
|
simultaneous[otherRecognizer.id] = otherRecognizer;
|
|
otherRecognizer.recognizeWith(this);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* drop the simultaneous link. it doesnt remove the link on the other recognizer.
|
|
* @param {Recognizer} otherRecognizer
|
|
* @returns {Recognizer} this
|
|
*/
|
|
dropRecognizeWith: function(otherRecognizer) {
|
|
if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
|
|
return this;
|
|
}
|
|
|
|
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
|
|
delete this.simultaneous[otherRecognizer.id];
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* recognizer can only run when an other is failing
|
|
* @param {Recognizer} otherRecognizer
|
|
* @returns {Recognizer} this
|
|
*/
|
|
requireFailure: function(otherRecognizer) {
|
|
if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
|
|
return this;
|
|
}
|
|
|
|
var requireFail = this.requireFail;
|
|
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
|
|
if (inArray(requireFail, otherRecognizer) === -1) {
|
|
requireFail.push(otherRecognizer);
|
|
otherRecognizer.requireFailure(this);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* drop the requireFailure link. it does not remove the link on the other recognizer.
|
|
* @param {Recognizer} otherRecognizer
|
|
* @returns {Recognizer} this
|
|
*/
|
|
dropRequireFailure: function(otherRecognizer) {
|
|
if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
|
|
return this;
|
|
}
|
|
|
|
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
|
|
var index = inArray(this.requireFail, otherRecognizer);
|
|
if (index > -1) {
|
|
this.requireFail.splice(index, 1);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* has require failures boolean
|
|
* @returns {boolean}
|
|
*/
|
|
hasRequireFailures: function() {
|
|
return this.requireFail.length > 0;
|
|
},
|
|
|
|
/**
|
|
* if the recognizer can recognize simultaneous with an other recognizer
|
|
* @param {Recognizer} otherRecognizer
|
|
* @returns {Boolean}
|
|
*/
|
|
canRecognizeWith: function(otherRecognizer) {
|
|
return !!this.simultaneous[otherRecognizer.id];
|
|
},
|
|
|
|
/**
|
|
* You should use `tryEmit` instead of `emit` directly to check
|
|
* that all the needed recognizers has failed before emitting.
|
|
* @param {Object} input
|
|
*/
|
|
emit: function(input) {
|
|
var self = this;
|
|
var state = this.state;
|
|
|
|
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));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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: function(input) {
|
|
if (this.canEmit()) {
|
|
return this.emit(input);
|
|
}
|
|
// it's failing anyway
|
|
this.state = STATE_FAILED;
|
|
},
|
|
|
|
/**
|
|
* can we emit?
|
|
* @returns {boolean}
|
|
*/
|
|
canEmit: function() {
|
|
var i = 0;
|
|
while (i < this.requireFail.length) {
|
|
if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
|
|
return false;
|
|
}
|
|
i++;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* update the recognizer
|
|
* @param {Object} inputData
|
|
*/
|
|
recognize: function(inputData) {
|
|
// make a new copy of the inputData
|
|
// so we can change the inputData without messing up the other recognizers
|
|
var 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);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* return the state of the recognizer
|
|
* the actual recognizing happens in this method
|
|
* @virtual
|
|
* @param {Object} inputData
|
|
* @returns {Const} STATE
|
|
*/
|
|
process: function(inputData) { }, // jshint ignore:line
|
|
|
|
/**
|
|
* return the preferred touch-action
|
|
* @virtual
|
|
* @returns {Array}
|
|
*/
|
|
getTouchAction: function() { },
|
|
|
|
/**
|
|
* called when the gesture isn't allowed to recognize
|
|
* like when another is being recognized or it is disabled
|
|
* @virtual
|
|
*/
|
|
reset: function() { }
|
|
};
|
|
|
|
/**
|
|
* get a usable string, used as event postfix
|
|
* @param {Const} state
|
|
* @returns {String} state
|
|
*/
|
|
function stateStr(state) {
|
|
if (state & STATE_CANCELLED) {
|
|
return 'cancel';
|
|
} else if (state & STATE_ENDED) {
|
|
return 'end';
|
|
} else if (state & STATE_CHANGED) {
|
|
return 'move';
|
|
} else if (state & STATE_BEGAN) {
|
|
return 'start';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* direction cons to string
|
|
* @param {Const} direction
|
|
* @returns {String}
|
|
*/
|
|
function directionStr(direction) {
|
|
if (direction == DIRECTION_DOWN) {
|
|
return 'down';
|
|
} else if (direction == DIRECTION_UP) {
|
|
return 'up';
|
|
} else if (direction == DIRECTION_LEFT) {
|
|
return 'left';
|
|
} else if (direction == DIRECTION_RIGHT) {
|
|
return 'right';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* get a recognizer by name if it is bound to a manager
|
|
* @param {Recognizer|String} otherRecognizer
|
|
* @param {Recognizer} recognizer
|
|
* @returns {Recognizer}
|
|
*/
|
|
function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
|
|
var manager = recognizer.manager;
|
|
if (manager) {
|
|
return manager.get(otherRecognizer);
|
|
}
|
|
return otherRecognizer;
|
|
}
|