1 /****************************************************************************
  2  Copyright (c) 2011-2012 cocos2d-x.org
  3  Copyright (c) 2013-2014 Chukong Technologies Inc.
  4 
  5  http://www.cocos2d-x.org
  6 
  7  Permission is hereby granted, free of charge, to any person obtaining a copy
  8  of this software and associated documentation files (the "Software"), to deal
  9  in the Software without restriction, including without limitation the rights
 10  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11  copies of the Software, and to permit persons to whom the Software is
 12  furnished to do so, subject to the following conditions:
 13 
 14  The above copyright notice and this permission notice shall be included in
 15  all copies or substantial portions of the Software.
 16 
 17  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23  THE SOFTWARE.
 24  ****************************************************************************/
 25 
 26 /**
 27  * ignore
 28  */
 29 
 30 /**
 31  * @constant
 32  * @type {number}
 33  */
 34 cc.UIInterfaceOrientationLandscapeLeft = -90;
 35 /**
 36  * @constant
 37  * @type {number}
 38  */
 39 cc.UIInterfaceOrientationLandscapeRight = 90;
 40 /**
 41  * @constant
 42  * @type {number}
 43  */
 44 cc.UIInterfaceOrientationPortraitUpsideDown = 180;
 45 /**
 46  * @constant
 47  * @type {number}
 48  */
 49 cc.UIInterfaceOrientationPortrait = 0;
 50 
 51 /**
 52  * <p>
 53  *  This class manages all events of input. include: touch, mouse, accelerometer, keyboard                                       <br/>
 54  * </p>
 55  * @class
 56  * @name cc.inputManager
 57  */
 58 cc.inputManager = /** @lends cc.inputManager# */{
 59     _mousePressed: false,
 60 
 61     _isRegisterEvent: false,
 62 
 63     _preTouchPoint: cc.p(0,0),
 64     _prevMousePoint: cc.p(0,0),
 65 
 66     _preTouchPool: [],
 67     _preTouchPoolPointer: 0,
 68 
 69     _touches: [],
 70     _touchesIntegerDict:{},
 71 
 72     _indexBitsUsed: 0,
 73     _maxTouches: 5,
 74 
 75     _accelEnabled: false,
 76     _accelInterval: 1/30,
 77     _accelMinus: 1,
 78     _accelCurTime: 0,
 79     _acceleration: null,
 80     _accelDeviceEvent: null,
 81 
 82     _getUnUsedIndex: function () {
 83         var temp = this._indexBitsUsed;
 84 
 85         for (var i = 0; i < this._maxTouches; i++) {
 86             if (!(temp & 0x00000001)) {
 87                 this._indexBitsUsed |= (1 << i);
 88                 return i;
 89             }
 90             temp >>= 1;
 91         }
 92 
 93         // all bits are used
 94         return -1;
 95     },
 96 
 97     _removeUsedIndexBit: function (index) {
 98         if (index < 0 || index >= this._maxTouches)
 99             return;
100 
101         var temp = 1 << index;
102         temp = ~temp;
103         this._indexBitsUsed &= temp;
104     },
105 
106     _glView: null,
107 
108     /**
109      * @function
110      * @param {Array} touches
111      */
112     handleTouchesBegin: function (touches) {
113         var selTouch, index, curTouch, touchID, handleTouches = [], locTouchIntDict = this._touchesIntegerDict;
114         for(var i = 0, len = touches.length; i< len; i ++){
115             selTouch = touches[i];
116             touchID = selTouch.getID();
117             index = locTouchIntDict[touchID];
118 
119             if(index == null){
120                 var unusedIndex = this._getUnUsedIndex();
121                 if (unusedIndex === -1) {
122                     cc.log(cc._LogInfos.inputManager_handleTouchesBegin, unusedIndex);
123                     continue;
124                 }
125                 //curTouch = this._touches[unusedIndex] = selTouch;
126                 curTouch = this._touches[unusedIndex] = new cc.Touch(selTouch._point.x, selTouch._point.y, selTouch.getID());
127                 curTouch._setPrevPoint(selTouch._prevPoint);
128                 locTouchIntDict[touchID] = unusedIndex;
129                 handleTouches.push(curTouch);
130             }
131         }
132         if(handleTouches.length > 0){
133             this._glView._convertTouchesWithScale(handleTouches);
134             var touchEvent = new cc.EventTouch(handleTouches);
135             touchEvent._eventCode = cc.EventTouch.EventCode.BEGAN;
136             cc.eventManager.dispatchEvent(touchEvent);
137         }
138     },
139 
140     /**
141      * @function
142      * @param {Array} touches
143      */
144     handleTouchesMove: function(touches){
145         var selTouch, index, touchID, handleTouches = [], locTouches = this._touches;
146         for(var i = 0, len = touches.length; i< len; i ++){
147             selTouch = touches[i];
148             touchID = selTouch.getID();
149             index = this._touchesIntegerDict[touchID];
150 
151             if(index == null){
152                 //cc.log("if the index doesn't exist, it is an error");
153                 continue;
154             }
155             if(locTouches[index]){
156                 locTouches[index]._setPoint(selTouch._point);
157                 locTouches[index]._setPrevPoint(selTouch._prevPoint);
158                 handleTouches.push(locTouches[index]);
159             }
160         }
161         if(handleTouches.length > 0){
162             this._glView._convertTouchesWithScale(handleTouches);
163             var touchEvent = new cc.EventTouch(handleTouches);
164             touchEvent._eventCode = cc.EventTouch.EventCode.MOVED;
165             cc.eventManager.dispatchEvent(touchEvent);
166         }
167     },
168 
169     /**
170      * @function
171      * @param {Array} touches
172      */
173     handleTouchesEnd: function(touches){
174         var handleTouches = this.getSetOfTouchesEndOrCancel(touches);
175         if(handleTouches.length > 0) {
176             this._glView._convertTouchesWithScale(handleTouches);
177             var touchEvent = new cc.EventTouch(handleTouches);
178             touchEvent._eventCode = cc.EventTouch.EventCode.ENDED;
179             cc.eventManager.dispatchEvent(touchEvent);
180         }
181     },
182 
183     /**
184      * @function
185      * @param {Array} touches
186      */
187     handleTouchesCancel: function(touches){
188         var handleTouches = this.getSetOfTouchesEndOrCancel(touches);
189         if(handleTouches.length > 0) {
190             this._glView._convertTouchesWithScale(handleTouches);
191             var touchEvent = new cc.EventTouch(handleTouches);
192             touchEvent._eventCode = cc.EventTouch.EventCode.CANCELLED;
193             cc.eventManager.dispatchEvent(touchEvent);
194         }
195     },
196 
197     /**
198      * @function
199      * @param {Array} touches
200      * @returns {Array}
201      */
202     getSetOfTouchesEndOrCancel: function(touches) {
203         var selTouch, index, touchID, handleTouches = [], locTouches = this._touches, locTouchesIntDict = this._touchesIntegerDict;
204         for(var i = 0, len = touches.length; i< len; i ++){
205             selTouch = touches[i];
206             touchID = selTouch.getID();
207             index = locTouchesIntDict[touchID];
208 
209             if(index == null){
210                 continue;  //cc.log("if the index doesn't exist, it is an error");
211             }
212             if(locTouches[index]){
213                 locTouches[index]._setPoint(selTouch._point);
214                 locTouches[index]._setPrevPoint(selTouch._prevPoint);
215                 handleTouches.push(locTouches[index]);
216                 this._removeUsedIndexBit(index);
217                 delete locTouchesIntDict[touchID];
218             }
219         }
220         return handleTouches;
221     },
222 
223     /**
224      * @function
225      * @param {HTMLElement} element
226      * @return {Object}
227      */
228     getHTMLElementPosition: function (element) {
229         var docElem = document.documentElement;
230         var win = window;
231         var box = null;
232         if (cc.isFunction(element.getBoundingClientRect)) {
233             box = element.getBoundingClientRect();
234         } else {
235             if (element instanceof HTMLCanvasElement) {
236                 box = {
237                     left: 0,
238                     top: 0,
239                     width: element.width,
240                     height: element.height
241                 };
242             } else {
243                 box = {
244                     left: 0,
245                     top: 0,
246                     width: parseInt(element.style.width),
247                     height: parseInt(element.style.height)
248                 };
249             }
250         }
251         return {
252             left: box.left + win.pageXOffset - docElem.clientLeft,
253             top: box.top + win.pageYOffset - docElem.clientTop,
254             width: box.width,
255             height: box.height
256         };
257     },
258 
259     /**
260      * @function
261      * @param {cc.Touch} touch
262      * @return {cc.Touch}
263      */
264     getPreTouch: function(touch){
265         var preTouch = null;
266         var locPreTouchPool = this._preTouchPool;
267         var id = touch.getID();
268         for (var i = locPreTouchPool.length - 1; i >= 0; i--) {
269             if (locPreTouchPool[i].getID() === id) {
270                 preTouch = locPreTouchPool[i];
271                 break;
272             }
273         }
274         if (!preTouch)
275             preTouch = touch;
276         return preTouch;
277     },
278 
279     /**
280      * @function
281      * @param {cc.Touch} touch
282      */
283     setPreTouch: function(touch){
284         var find = false;
285         var locPreTouchPool = this._preTouchPool;
286         var id = touch.getID();
287         for (var i = locPreTouchPool.length - 1; i >= 0; i--) {
288             if (locPreTouchPool[i].getID() === id) {
289                 locPreTouchPool[i] = touch;
290                 find = true;
291                 break;
292             }
293         }
294         if (!find) {
295             if (locPreTouchPool.length <= 50) {
296                 locPreTouchPool.push(touch);
297             } else {
298                 locPreTouchPool[this._preTouchPoolPointer] = touch;
299                 this._preTouchPoolPointer = (this._preTouchPoolPointer + 1) % 50;
300             }
301         }
302     },
303 
304     /**
305      * @function
306      * @param {Number} tx
307      * @param {Number} ty
308      * @param {cc.Point} pos
309      * @return {cc.Touch}
310      */
311     getTouchByXY: function(tx, ty, pos){
312         var locPreTouch = this._preTouchPoint;
313         var location = this._glView.convertToLocationInView(tx, ty, pos);
314         var touch = new cc.Touch(location.x,  location.y);
315         touch._setPrevPoint(locPreTouch.x, locPreTouch.y);
316         locPreTouch.x = location.x;
317         locPreTouch.y = location.y;
318         return touch;
319     },
320 
321     /**
322      * @function
323      * @param {cc.Point} location
324      * @param {cc.Point} pos
325      * @param {Number} eventType
326      * @returns {cc.EventMouse}
327      */
328     getMouseEvent: function(location, pos, eventType){
329         var locPreMouse = this._prevMousePoint;
330         this._glView._convertMouseToLocationInView(location, pos);
331         var mouseEvent = new cc.EventMouse(eventType);
332         mouseEvent.setLocation(location.x, location.y);
333         mouseEvent._setPrevCursor(locPreMouse.x, locPreMouse.y);
334         locPreMouse.x = location.x;
335         locPreMouse.y = location.y;
336         return mouseEvent;
337     },
338 
339     /**
340      * @function
341      * @param {Touch} event
342      * @param {cc.Point} pos
343      * @return {cc.Point}
344      */
345     getPointByEvent: function(event, pos){
346         if (event.pageX != null)  //not avalable in <= IE8
347             return {x: event.pageX, y: event.pageY};
348 
349         pos.left -= document.body.scrollLeft;
350         pos.top -= document.body.scrollTop;
351         return {x: event.clientX, y: event.clientY};
352     },
353 
354     /**
355      * @function
356      * @param {Touch} event
357      * @param {cc.Point} pos
358      * @returns {Array}
359      */
360     getTouchesByEvent: function(event, pos){
361         var touchArr = [], locView = this._glView;
362         var touch_event, touch, preLocation;
363         var locPreTouch = this._preTouchPoint;
364 
365         var length = event.changedTouches.length;
366         for (var i = 0; i < length; i++) {
367             touch_event = event.changedTouches[i];
368             if (touch_event) {
369                 var location;
370                 if (cc.sys.BROWSER_TYPE_FIREFOX === cc.sys.browserType)
371                     location = locView.convertToLocationInView(touch_event.pageX, touch_event.pageY, pos);
372                 else
373                     location = locView.convertToLocationInView(touch_event.clientX, touch_event.clientY, pos);
374                 if (touch_event.identifier != null) {
375                     touch = new cc.Touch(location.x, location.y, touch_event.identifier);
376                     //use Touch Pool
377                     preLocation = this.getPreTouch(touch).getLocation();
378                     touch._setPrevPoint(preLocation.x, preLocation.y);
379                     this.setPreTouch(touch);
380                 } else {
381                     touch = new cc.Touch(location.x, location.y);
382                     touch._setPrevPoint(locPreTouch.x, locPreTouch.y);
383                 }
384                 locPreTouch.x = location.x;
385                 locPreTouch.y = location.y;
386                 touchArr.push(touch);
387             }
388         }
389         return touchArr;
390     },
391 
392     /**
393      * @function
394      * @param {HTMLElement} element
395      */
396     registerSystemEvent: function(element){
397         if(this._isRegisterEvent) return;
398 
399         var locView = this._glView = cc.view;
400         var selfPointer = this;
401         var supportMouse = ('mouse' in cc.sys.capabilities), supportTouches = ('touches' in cc.sys.capabilities);
402 
403         //HACK
404         //  - At the same time to trigger the ontouch event and onmouse event
405         //  - The function will execute 2 times
406         //The known browser:
407         //  liebiao
408         //  miui
409         //  WECHAT
410         var prohibition = false;
411         if( cc.sys.isMobile)
412             prohibition = true;
413 
414         //register touch event
415         if (supportMouse) {
416             cc._addEventListener(window, 'mousedown', function () {
417                 selfPointer._mousePressed = true;
418             }, false);
419 
420             cc._addEventListener(window, 'mouseup', function (event) {
421                 if(prohibition) return;
422                 var savePressed = selfPointer._mousePressed;
423                 selfPointer._mousePressed = false;
424 
425                 if(!savePressed)
426                     return;
427 
428                 var pos = selfPointer.getHTMLElementPosition(element);
429                 var location = selfPointer.getPointByEvent(event, pos);
430                 if (!cc.rectContainsPoint(new cc.Rect(pos.left, pos.top, pos.width, pos.height), location)){
431                     selfPointer.handleTouchesEnd([selfPointer.getTouchByXY(location.x, location.y, pos)]);
432 
433                     var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.UP);
434                     mouseEvent.setButton(event.button);
435                     cc.eventManager.dispatchEvent(mouseEvent);
436                 }
437             }, false);
438 
439             //register canvas mouse event
440             cc._addEventListener(element,"mousedown", function (event) {
441                 if(prohibition) return;
442                 selfPointer._mousePressed = true;
443 
444                 var pos = selfPointer.getHTMLElementPosition(element);
445                 var location = selfPointer.getPointByEvent(event, pos);
446 
447                 selfPointer.handleTouchesBegin([selfPointer.getTouchByXY(location.x, location.y, pos)]);
448 
449                 var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.DOWN);
450                 mouseEvent.setButton(event.button);
451                 cc.eventManager.dispatchEvent(mouseEvent);
452 
453                 event.stopPropagation();
454                 event.preventDefault();
455                 element.focus();
456             }, false);
457 
458             cc._addEventListener(element, "mouseup", function (event) {
459                 if(prohibition) return;
460                 selfPointer._mousePressed = false;
461 
462                 var pos = selfPointer.getHTMLElementPosition(element);
463                 var location = selfPointer.getPointByEvent(event, pos);
464 
465                 selfPointer.handleTouchesEnd([selfPointer.getTouchByXY(location.x, location.y, pos)]);
466 
467                 var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.UP);
468                 mouseEvent.setButton(event.button);
469                 cc.eventManager.dispatchEvent(mouseEvent);
470 
471                 event.stopPropagation();
472                 event.preventDefault();
473             }, false);
474 
475             cc._addEventListener(element, "mousemove", function (event) {
476                 if(prohibition) return;
477 
478                 var pos = selfPointer.getHTMLElementPosition(element);
479                 var location = selfPointer.getPointByEvent(event, pos);
480 
481                 selfPointer.handleTouchesMove([selfPointer.getTouchByXY(location.x, location.y, pos)]);
482 
483                 var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.MOVE);
484                 if(selfPointer._mousePressed)
485                     mouseEvent.setButton(event.button);
486                 else
487                     mouseEvent.setButton(null);
488                 cc.eventManager.dispatchEvent(mouseEvent);
489 
490                 event.stopPropagation();
491                 event.preventDefault();
492             }, false);
493 
494             cc._addEventListener(element, "mousewheel", function (event) {
495                 var pos = selfPointer.getHTMLElementPosition(element);
496                 var location = selfPointer.getPointByEvent(event, pos);
497 
498                 var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.SCROLL);
499                 mouseEvent.setButton(event.button);
500                 mouseEvent.setScrollData(0, event.wheelDelta);
501                 cc.eventManager.dispatchEvent(mouseEvent);
502 
503                 event.stopPropagation();
504                 event.preventDefault();
505             }, false);
506 
507             /* firefox fix */
508             cc._addEventListener(element, "DOMMouseScroll", function(event) {
509                 var pos = selfPointer.getHTMLElementPosition(element);
510                 var location = selfPointer.getPointByEvent(event, pos);
511 
512                 var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.SCROLL);
513                 mouseEvent.setButton(event.button);
514                 mouseEvent.setScrollData(0, event.detail * -120);
515                 cc.eventManager.dispatchEvent(mouseEvent);
516 
517                 event.stopPropagation();
518                 event.preventDefault();
519             }, false);
520         }
521 
522         if(window.navigator.msPointerEnabled){
523             var _pointerEventsMap = {
524                 "MSPointerDown"     : selfPointer.handleTouchesBegin,
525                 "MSPointerMove"     : selfPointer.handleTouchesMove,
526                 "MSPointerUp"       : selfPointer.handleTouchesEnd,
527                 "MSPointerCancel"   : selfPointer.handleTouchesCancel
528             };
529 
530             for(var eventName in _pointerEventsMap){
531                 (function(_pointerEvent, _touchEvent){
532                     cc._addEventListener(element, _pointerEvent, function (event){
533                         var pos = selfPointer.getHTMLElementPosition(element);
534                         pos.left -= document.documentElement.scrollLeft;
535                         pos.top -= document.documentElement.scrollTop;
536 
537                         _touchEvent.call(selfPointer, [selfPointer.getTouchByXY(event.clientX, event.clientY, pos)]);
538                         event.stopPropagation();
539                     }, false);
540                 })(eventName, _pointerEventsMap[eventName]);
541             }
542         }
543 
544         if(supportTouches) {
545             //register canvas touch event
546             cc._addEventListener(element,"touchstart", function (event) {
547                 if (!event.changedTouches) return;
548 
549                 var pos = selfPointer.getHTMLElementPosition(element);
550                 pos.left -= document.body.scrollLeft;
551                 pos.top -= document.body.scrollTop;
552                 selfPointer.handleTouchesBegin(selfPointer.getTouchesByEvent(event, pos));
553                 event.stopPropagation();
554                 event.preventDefault();
555                 element.focus();
556             }, false);
557 
558             cc._addEventListener(element, "touchmove", function (event) {
559                 if (!event.changedTouches) return;
560 
561                 var pos = selfPointer.getHTMLElementPosition(element);
562                 pos.left -= document.body.scrollLeft;
563                 pos.top -= document.body.scrollTop;
564                 selfPointer.handleTouchesMove(selfPointer.getTouchesByEvent(event, pos));
565                 event.stopPropagation();
566                 event.preventDefault();
567             }, false);
568 
569             cc._addEventListener(element, "touchend", function (event) {
570                 if (!event.changedTouches) return;
571 
572                 var pos = selfPointer.getHTMLElementPosition(element);
573                 pos.left -= document.body.scrollLeft;
574                 pos.top -= document.body.scrollTop;
575                 selfPointer.handleTouchesEnd(selfPointer.getTouchesByEvent(event, pos));
576                 event.stopPropagation();
577                 event.preventDefault();
578             }, false);
579 
580             cc._addEventListener(element, "touchcancel", function (event) {
581                 if (!event.changedTouches) return;
582 
583                 var pos = selfPointer.getHTMLElementPosition(element);
584                 pos.left -= document.body.scrollLeft;
585                 pos.top -= document.body.scrollTop;
586                 selfPointer.handleTouchesCancel(selfPointer.getTouchesByEvent(event, pos));
587                 event.stopPropagation();
588                 event.preventDefault();
589             }, false);
590         }
591 
592         //register keyboard event
593         this._registerKeyboardEvent();
594 
595         //register Accelerometer event
596         this._registerAccelerometerEvent();
597 
598         this._isRegisterEvent = true;
599     },
600 
601     _registerKeyboardEvent: function(){},
602 
603     _registerAccelerometerEvent: function(){},
604 
605     /**
606      * @function
607      * @param {Number} dt
608      */
609     update:function(dt){
610         if(this._accelCurTime > this._accelInterval){
611             this._accelCurTime -= this._accelInterval;
612             cc.eventManager.dispatchEvent(new cc.EventAcceleration(this._acceleration));
613         }
614         this._accelCurTime += dt;
615     }
616 };
617