recognizer.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. /**
  2. * Recognizer flow explained; *
  3. * All recognizers have the initial state of POSSIBLE when a input session starts.
  4. * The definition of a input session is from the first input until the last input, with all it's movement in it. *
  5. * Example session for mouse-input: mousedown -> mousemove -> mouseup
  6. *
  7. * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
  8. * which determines with state it should be.
  9. *
  10. * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
  11. * POSSIBLE to give it another change on the next cycle.
  12. *
  13. * Possible
  14. * |
  15. * +-----+---------------+
  16. * | |
  17. * +-----+-----+ |
  18. * | | |
  19. * Failed Cancelled |
  20. * +-------+------+
  21. * | |
  22. * Recognized Began
  23. * |
  24. * Changed
  25. * |
  26. * Ended/Recognized
  27. */
  28. var STATE_POSSIBLE = 1;
  29. var STATE_BEGAN = 2;
  30. var STATE_CHANGED = 4;
  31. var STATE_ENDED = 8;
  32. var STATE_RECOGNIZED = STATE_ENDED;
  33. var STATE_CANCELLED = 16;
  34. var STATE_FAILED = 32;
  35. /**
  36. * Recognizer
  37. * Every recognizer needs to extend from this class.
  38. * @constructor
  39. * @param {Object} options
  40. */
  41. function Recognizer(options) {
  42. this.options = assign({}, this.defaults, options || {});
  43. this.id = uniqueId();
  44. this.manager = null;
  45. // default is enable true
  46. this.options.enable = ifUndefined(this.options.enable, true);
  47. this.state = STATE_POSSIBLE;
  48. this.simultaneous = {};
  49. this.requireFail = [];
  50. }
  51. Recognizer.prototype = {
  52. /**
  53. * @virtual
  54. * @type {Object}
  55. */
  56. defaults: {},
  57. /**
  58. * set options
  59. * @param {Object} options
  60. * @return {Recognizer}
  61. */
  62. set: function(options) {
  63. assign(this.options, options);
  64. // also update the touchAction, in case something changed about the directions/enabled state
  65. this.manager && this.manager.touchAction.update();
  66. return this;
  67. },
  68. /**
  69. * recognize simultaneous with an other recognizer.
  70. * @param {Recognizer} otherRecognizer
  71. * @returns {Recognizer} this
  72. */
  73. recognizeWith: function(otherRecognizer) {
  74. if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
  75. return this;
  76. }
  77. var simultaneous = this.simultaneous;
  78. otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  79. if (!simultaneous[otherRecognizer.id]) {
  80. simultaneous[otherRecognizer.id] = otherRecognizer;
  81. otherRecognizer.recognizeWith(this);
  82. }
  83. return this;
  84. },
  85. /**
  86. * drop the simultaneous link. it doesnt remove the link on the other recognizer.
  87. * @param {Recognizer} otherRecognizer
  88. * @returns {Recognizer} this
  89. */
  90. dropRecognizeWith: function(otherRecognizer) {
  91. if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
  92. return this;
  93. }
  94. otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  95. delete this.simultaneous[otherRecognizer.id];
  96. return this;
  97. },
  98. /**
  99. * recognizer can only run when an other is failing
  100. * @param {Recognizer} otherRecognizer
  101. * @returns {Recognizer} this
  102. */
  103. requireFailure: function(otherRecognizer) {
  104. if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
  105. return this;
  106. }
  107. var requireFail = this.requireFail;
  108. otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  109. if (inArray(requireFail, otherRecognizer) === -1) {
  110. requireFail.push(otherRecognizer);
  111. otherRecognizer.requireFailure(this);
  112. }
  113. return this;
  114. },
  115. /**
  116. * drop the requireFailure link. it does not remove the link on the other recognizer.
  117. * @param {Recognizer} otherRecognizer
  118. * @returns {Recognizer} this
  119. */
  120. dropRequireFailure: function(otherRecognizer) {
  121. if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
  122. return this;
  123. }
  124. otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  125. var index = inArray(this.requireFail, otherRecognizer);
  126. if (index > -1) {
  127. this.requireFail.splice(index, 1);
  128. }
  129. return this;
  130. },
  131. /**
  132. * has require failures boolean
  133. * @returns {boolean}
  134. */
  135. hasRequireFailures: function() {
  136. return this.requireFail.length > 0;
  137. },
  138. /**
  139. * if the recognizer can recognize simultaneous with an other recognizer
  140. * @param {Recognizer} otherRecognizer
  141. * @returns {Boolean}
  142. */
  143. canRecognizeWith: function(otherRecognizer) {
  144. return !!this.simultaneous[otherRecognizer.id];
  145. },
  146. /**
  147. * You should use `tryEmit` instead of `emit` directly to check
  148. * that all the needed recognizers has failed before emitting.
  149. * @param {Object} input
  150. */
  151. emit: function(input) {
  152. var self = this;
  153. var state = this.state;
  154. function emit(event) {
  155. self.manager.emit(event, input);
  156. }
  157. // 'panstart' and 'panmove'
  158. if (state < STATE_ENDED) {
  159. emit(self.options.event + stateStr(state));
  160. }
  161. emit(self.options.event); // simple 'eventName' events
  162. if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
  163. emit(input.additionalEvent);
  164. }
  165. // panend and pancancel
  166. if (state >= STATE_ENDED) {
  167. emit(self.options.event + stateStr(state));
  168. }
  169. },
  170. /**
  171. * Check that all the require failure recognizers has failed,
  172. * if true, it emits a gesture event,
  173. * otherwise, setup the state to FAILED.
  174. * @param {Object} input
  175. */
  176. tryEmit: function(input) {
  177. if (this.canEmit()) {
  178. return this.emit(input);
  179. }
  180. // it's failing anyway
  181. this.state = STATE_FAILED;
  182. },
  183. /**
  184. * can we emit?
  185. * @returns {boolean}
  186. */
  187. canEmit: function() {
  188. var i = 0;
  189. while (i < this.requireFail.length) {
  190. if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
  191. return false;
  192. }
  193. i++;
  194. }
  195. return true;
  196. },
  197. /**
  198. * update the recognizer
  199. * @param {Object} inputData
  200. */
  201. recognize: function(inputData) {
  202. // make a new copy of the inputData
  203. // so we can change the inputData without messing up the other recognizers
  204. var inputDataClone = assign({}, inputData);
  205. // is is enabled and allow recognizing?
  206. if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
  207. this.reset();
  208. this.state = STATE_FAILED;
  209. return;
  210. }
  211. // reset when we've reached the end
  212. if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
  213. this.state = STATE_POSSIBLE;
  214. }
  215. this.state = this.process(inputDataClone);
  216. // the recognizer has recognized a gesture
  217. // so trigger an event
  218. if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
  219. this.tryEmit(inputDataClone);
  220. }
  221. },
  222. /**
  223. * return the state of the recognizer
  224. * the actual recognizing happens in this method
  225. * @virtual
  226. * @param {Object} inputData
  227. * @returns {Const} STATE
  228. */
  229. process: function(inputData) { }, // jshint ignore:line
  230. /**
  231. * return the preferred touch-action
  232. * @virtual
  233. * @returns {Array}
  234. */
  235. getTouchAction: function() { },
  236. /**
  237. * called when the gesture isn't allowed to recognize
  238. * like when another is being recognized or it is disabled
  239. * @virtual
  240. */
  241. reset: function() { }
  242. };
  243. /**
  244. * get a usable string, used as event postfix
  245. * @param {Const} state
  246. * @returns {String} state
  247. */
  248. function stateStr(state) {
  249. if (state & STATE_CANCELLED) {
  250. return 'cancel';
  251. } else if (state & STATE_ENDED) {
  252. return 'end';
  253. } else if (state & STATE_CHANGED) {
  254. return 'move';
  255. } else if (state & STATE_BEGAN) {
  256. return 'start';
  257. }
  258. return '';
  259. }
  260. /**
  261. * direction cons to string
  262. * @param {Const} direction
  263. * @returns {String}
  264. */
  265. function directionStr(direction) {
  266. if (direction == DIRECTION_DOWN) {
  267. return 'down';
  268. } else if (direction == DIRECTION_UP) {
  269. return 'up';
  270. } else if (direction == DIRECTION_LEFT) {
  271. return 'left';
  272. } else if (direction == DIRECTION_RIGHT) {
  273. return 'right';
  274. }
  275. return '';
  276. }
  277. /**
  278. * get a recognizer by name if it is bound to a manager
  279. * @param {Recognizer|String} otherRecognizer
  280. * @param {Recognizer} recognizer
  281. * @returns {Recognizer}
  282. */
  283. function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
  284. var manager = recognizer.manager;
  285. if (manager) {
  286. return manager.get(otherRecognizer);
  287. }
  288. return otherRecognizer;
  289. }