1 /****************************************************************************
  2  Copyright (c) 2010-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 cc.UIInterfaceOrientationLandscapeLeft = -90;
 30 
 31 cc.UIInterfaceOrientationLandscapeRight = 90;
 32 
 33 cc.UIInterfaceOrientationPortraitUpsideDown = 180;
 34 
 35 cc.UIInterfaceOrientationPortrait = 0;
 36 
 37 /**
 38  * <p>
 39  *  This class manages all events of input. include: touch, mouse, accelerometer, keyboard                                       <br/>
 40  * </p>
 41  * @namespace
 42  */
 43 cc.inputManager = /** @lends cc.inputManager# */{
 44     _mousePressed: false,
 45 
 46     _isRegisterEvent: false,
 47 
 48     _preTouchPoint: cc.p(0,0),
 49     _prevMousePoint: cc.p(0,0),
 50 
 51     _preTouchPool: [],
 52     _preTouchPoolPointer: 0,
 53 
 54     _touches: [],
 55     _touchesIntegerDict:{},
 56 
 57     _indexBitsUsed: 0,
 58     _maxTouches: 5,
 59 
 60     _accelEnabled: false,
 61     _accelInterval: 1/30,
 62     _accelMinus: 1,
 63     _accelCurTime: 0,
 64     _acceleration: null,
 65     _accelDeviceEvent: null,
 66 
 67     /**
 68      * whether enable accelerometer event
 69      * @param {Boolean} isEnable
 70      */
 71     setAccelerometerEnabled: function(isEnable){
 72         if(this._accelEnabled === isEnable)
 73             return;
 74 
 75         this._accelEnabled = isEnable;
 76         var scheduler = cc.director.getScheduler();
 77         if(this._accelEnabled){
 78             this._accelCurTime = 0;
 79             scheduler.scheduleUpdateForTarget(this);
 80         } else {
 81             this._accelCurTime = 0;
 82             scheduler.unscheduleUpdateForTarget(this);
 83         }
 84     },
 85 
 86     /**
 87      * set accelerometer interval value
 88      * @param {Number} interval
 89      */
 90     setAccelerometerInterval: function(interval){
 91         if (this._accelInterval !== interval) {
 92             this._accelInterval = interval;
 93         }
 94     },
 95 
 96     _getUnUsedIndex: function () {
 97         var temp = this._indexBitsUsed;
 98 
 99         for (var i = 0; i < this._maxTouches; i++) {
100             if (!(temp & 0x00000001)) {
101                 this._indexBitsUsed |= (1 << i);
102                 return i;
103             }
104             temp >>= 1;
105         }
106 
107         // all bits are used
108         return -1;
109     },
110 
111     _removeUsedIndexBit: function (index) {
112         if (index < 0 || index >= this._maxTouches)
113             return;
114 
115         var temp = 1 << index;
116         temp = ~temp;
117         this._indexBitsUsed &= temp;
118     },
119 
120     _glView: null,
121 
122     handleTouchesBegin: function (touches) {
123         var selTouch, index, curTouch, touchID, handleTouches = [], locTouchIntDict = this._touchesIntegerDict;
124         for(var i = 0, len = touches.length; i< len; i ++){
125             selTouch = touches[i];
126             touchID = selTouch.getID();
127             index = locTouchIntDict[touchID];
128 
129             if(index == null){
130                 var unusedIndex = this._getUnUsedIndex();
131                 if (unusedIndex == -1) {
132                     cc.log("The touches is more than MAX_TOUCHES, nUnusedIndex = " + unusedIndex);
133                     continue;
134                 }
135                 curTouch = this._touches[unusedIndex] = selTouch;
136                 locTouchIntDict[touchID] = unusedIndex;
137                 handleTouches.push(curTouch);
138             }
139         }
140         if(handleTouches.length > 0){
141             this._glView._convertTouchesWithScale(handleTouches);
142             var touchEvent = new cc.EventTouch(handleTouches);
143             touchEvent._eventCode = cc.EventTouch.EventCode.BEGAN;
144             cc.eventManager.dispatchEvent(touchEvent);
145         }
146     },
147 
148     handleTouchesMove: function(touches){
149         var selTouch, index, touchID, handleTouches = [], locTouches = this._touches;
150         for(var i = 0, len = touches.length; i< len; i ++){
151             selTouch = touches[i];
152             touchID = selTouch.getID();
153             index = this._touchesIntegerDict[touchID];
154 
155             if(index == null){
156                 //cc.log("if the index doesn't exist, it is an error");
157                 continue;
158             }
159             if(locTouches[index]){
160                 locTouches[index]._setPoint(selTouch._point);
161                 locTouches[index]._setPrevPoint(selTouch._prevPoint);
162                 handleTouches.push(locTouches[index]);
163             }
164         }
165         if(handleTouches.length > 0){
166             this._glView._convertTouchesWithScale(handleTouches);
167             var touchEvent = new cc.EventTouch(handleTouches);
168             touchEvent._eventCode = cc.EventTouch.EventCode.MOVED;
169             cc.eventManager.dispatchEvent(touchEvent);
170         }
171     },
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     handleTouchesCancel: function(touches){
184         var handleTouches = this.getSetOfTouchesEndOrCancel(touches);
185         if(handleTouches.length > 0) {
186             this._glView._convertTouchesWithScale(handleTouches);
187             var touchEvent = new cc.EventTouch(handleTouches);
188             touchEvent._eventCode = cc.EventTouch.EventCode.CANCELLED;
189             cc.eventManager.dispatchEvent(touchEvent);
190         }
191     },
192 
193     getSetOfTouchesEndOrCancel: function(touches) {
194         var selTouch, index, touchID, handleTouches = [], locTouches = this._touches, locTouchesIntDict = this._touchesIntegerDict;
195         for(var i = 0, len = touches.length; i< len; i ++){
196             selTouch = touches[i];
197             touchID = selTouch.getID();
198             index = locTouchesIntDict[touchID];
199 
200             if(index == null){
201                 continue;  //cc.log("if the index doesn't exist, it is an error");
202             }
203             if(locTouches[index]){
204                 locTouches[index]._setPoint(selTouch._point);
205                 locTouches[index]._setPrevPoint(selTouch._prevPoint);         //TODO
206                 handleTouches.push(locTouches[index]);
207                 this._removeUsedIndexBit(index);
208                 delete locTouchesIntDict[touchID];
209             }
210         }
211         return handleTouches;
212     },
213 
214     getHTMLElementPosition: function (element) {
215         var docElem = document.documentElement;
216         var win = window;
217         var box = null;
218         if (typeof element.getBoundingClientRect === 'function') {
219             box = element.getBoundingClientRect();
220         } else {
221             if (element instanceof HTMLCanvasElement) {
222                 box = {
223                     left: 0,
224                     top: 0,
225                     width: element.width,
226                     height: element.height
227                 };
228             } else {
229                 box = {
230                     left: 0,
231                     top: 0,
232                     width: parseInt(element.style.width),
233                     height: parseInt(element.style.height)
234                 };
235             }
236         }
237         return {
238             left: box.left + win.pageXOffset - docElem.clientLeft,
239             top: box.top + win.pageYOffset - docElem.clientTop,
240             width: box.width,
241             height: box.height
242         };
243     },
244 
245     getPreTouch: function(touch){
246         var preTouch = null;
247         var locPreTouchPool = this._preTouchPool;
248         var id = touch.getId();
249         for (var i = locPreTouchPool.length - 1; i >= 0; i--) {
250             if (locPreTouchPool[i].getId() == id) {
251                 preTouch = locPreTouchPool[i];
252                 break;
253             }
254         }
255         if (!preTouch)
256             preTouch = touch;
257         return preTouch;
258     },
259 
260     setPreTouch: function(touch){
261         var find = false;
262         var locPreTouchPool = this._preTouchPool;
263         var id = touch.getId();
264         for (var i = locPreTouchPool.length - 1; i >= 0; i--) {
265             if (locPreTouchPool[i].getId() == id) {
266                 locPreTouchPool[i] = touch;
267                 find = true;
268                 break;
269             }
270         }
271         if (!find) {
272             if (locPreTouchPool.length <= 50) {
273                 locPreTouchPool.push(touch);
274             } else {
275                 locPreTouchPool[this._preTouchPoolPointer] = touch;
276                 this._preTouchPoolPointer = (this._preTouchPoolPointer + 1) % 50;
277             }
278         }
279     },
280 
281     getTouchByXY: function(tx, ty, pos){
282         var locPreTouch = this._preTouchPoint;
283         var location = this._glView.convertToLocationInView(tx, ty, pos);
284         var touch = new cc.Touch(location.x,  location.y);
285         touch._setPrevPoint(locPreTouch.x, locPreTouch.y);
286         locPreTouch.x = location.x;
287         locPreTouch.y = location.y;
288         return touch;
289     },
290 
291     getMouseEvent: function(location, pos, eventType){
292         var locPreMouse = this._prevMousePoint;
293         this._glView._convertMouseToLocationInView(location, pos);
294         var mouseEvent = new cc.EventMouse(eventType);
295         mouseEvent.setLocation(location.x, location.y);
296         mouseEvent._setPrevCursor(locPreMouse.x, locPreMouse.y);
297         locPreMouse.x = location.x;
298         locPreMouse.y = location.y;
299         return mouseEvent;
300     },
301 
302     getPointByEvent: function(event, pos){
303         if (event.pageX != null)  //not avalable in <= IE8
304             return {x: event.pageX, y: event.pageY};
305 
306         pos.left -= document.body.scrollLeft;
307         pos.top -= document.body.scrollTop;
308         return {x: event.clientX, y: event.clientY};
309     },
310 
311     getTouchesByEvent: function(event, pos){
312         var touchArr = [], locView = this._glView;
313         var touch_event, touch, preLocation;
314         var locPreTouch = this._preTouchPoint;
315 
316         var length = event.changedTouches.length;
317         for (var i = 0; i < length; i++) {
318             touch_event = event.changedTouches[i];
319             if (touch_event) {
320                 var location = locView.convertToLocationInView(touch_event.clientX, touch_event.clientY, pos);
321                 if (touch_event.identifier != null) {
322                     touch = new cc.Touch(location.x, location.y, touch_event.identifier);
323                     //use Touch Pool
324                     preLocation = this.getPreTouch(touch).getLocation();
325                     touch._setPrevPoint(preLocation.x, preLocation.y);
326                     this.setPreTouch(touch);
327                 } else {
328                     touch = new cc.Touch(location.x, location.y);
329                     touch._setPrevPoint(locPreTouch.x, locPreTouch.y);
330                 }
331                 locPreTouch.x = location.x;
332                 locPreTouch.y = location.y;
333                 touchArr.push(touch);
334             }
335         }
336         return touchArr;
337     },
338 
339     registerSystemEvent: function(element){
340         if(this._isRegisterEvent) return;
341 
342         var locView = this._glView = cc.view;
343         var selfPointer = this;
344         var supportMouse = ('mouse' in cc.sys.capabilities), supportTouches = ('touches' in cc.sys.capabilities);
345 
346         //register touch event
347         if (supportMouse) {
348             window.addEventListener('mousedown', function () {
349                 selfPointer._mousePressed = true;
350             }, false);
351 
352             window.addEventListener('mouseup', function (event) {
353                 var savePressed = selfPointer._mousePressed;
354                 selfPointer._mousePressed = false;
355 
356                 if(!savePressed)
357                     return;
358 
359                 var pos = selfPointer.getHTMLElementPosition(element);
360                 var location = selfPointer.getPointByEvent(event, pos);
361                 if (!cc.rectContainsPoint(new cc.Rect(pos.left, pos.top, pos.width, pos.height), location)){
362                     if(!supportTouches)
363                         selfPointer.handleTouchesEnd([selfPointer.getTouchByXY(location.x, location.y, pos)]);
364 
365                     var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.UP);
366                     mouseEvent.setButton(event.button);
367                     cc.eventManager.dispatchEvent(mouseEvent);
368                 }
369             }, false);
370 
371             //register canvas mouse event
372             element.addEventListener("mousedown", function (event) {
373                 selfPointer._mousePressed = true;
374 
375                 var pos = selfPointer.getHTMLElementPosition(element);
376                 var location = selfPointer.getPointByEvent(event, pos);
377                 if(!supportTouches)
378                     selfPointer.handleTouchesBegin([selfPointer.getTouchByXY(location.x, location.y, pos)]);
379 
380                 var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.DOWN);
381                 mouseEvent.setButton(event.button);
382                 cc.eventManager.dispatchEvent(mouseEvent);
383 
384                 event.stopPropagation();
385                 event.preventDefault();
386             }, false);
387 
388             element.addEventListener("mouseup", function (event) {
389                 selfPointer._mousePressed = false;
390 
391                 var pos = selfPointer.getHTMLElementPosition(element);
392                 var location = selfPointer.getPointByEvent(event, pos);
393 
394                 if(!supportTouches)
395                     selfPointer.handleTouchesEnd([selfPointer.getTouchByXY(location.x, location.y, pos)]);
396 
397                 var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.UP);
398                 mouseEvent.setButton(event.button);
399                 cc.eventManager.dispatchEvent(mouseEvent);
400 
401                 event.stopPropagation();
402                 event.preventDefault();
403             }, false);
404 
405             element.addEventListener("mousemove", function (event) {
406                 if(!selfPointer._mousePressed)
407                     return;
408 
409                 var pos = selfPointer.getHTMLElementPosition(element);
410                 var location = selfPointer.getPointByEvent(event, pos);
411 
412                 if(!supportTouches)
413                     selfPointer.handleTouchesMove([selfPointer.getTouchByXY(location.x, location.y, pos)]);
414 
415                 var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.MOVE);
416                 mouseEvent.setButton(event.button);
417                 cc.eventManager.dispatchEvent(mouseEvent);
418 
419                 event.stopPropagation();
420                 event.preventDefault();
421             }, false);
422 
423             element.addEventListener("mousewheel", function (event) {
424                 var pos = selfPointer.getHTMLElementPosition(element);
425                 var location = selfPointer.getPointByEvent(event, pos);
426 
427                 var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.SCROLL);
428                 mouseEvent.setButton(event.button);
429                 mouseEvent.setScrollData(0, event.wheelDelta);
430                 cc.eventManager.dispatchEvent(mouseEvent);
431 
432                 event.stopPropagation();
433                 event.preventDefault();
434             }, false);
435 
436             /* firefox fix */
437             element.addEventListener("DOMMouseScroll", function(event) {
438                 var pos = selfPointer.getHTMLElementPosition(element);
439                 var location = selfPointer.getPointByEvent(event, pos);
440 
441                 var mouseEvent = selfPointer.getMouseEvent(location,pos,cc.EventMouse.SCROLL);
442                 mouseEvent.setButton(event.button);
443                 mouseEvent.setScrollData(0, event.detail * -120);
444                 cc.eventManager.dispatchEvent(mouseEvent);
445 
446                 event.stopPropagation();
447                 event.preventDefault();
448             }, false);
449         }
450 
451         if(window.navigator.msPointerEnabled){
452             var _pointerEventsMap = {
453                 "MSPointerDown"     : "handleTouchesBegin",
454                 "MSPointerMove"     : "handleTouchesMove",
455                 "MSPointerUp"       : "handleTouchesEnd",
456                 "MSPointerCancel"   : "handleTouchesCancel"
457             };
458 
459             for(var eventName in _pointerEventsMap){
460                 (function(_pointerEvent, _touchEvent){
461                     element.addEventListener(_pointerEvent, function (event){
462                         var pos = selfPointer.getHTMLElementPosition(element);
463                         pos.left -= document.body.scrollLeft;
464                         pos.top -= document.body.scrollTop;
465 
466                         selfPointer[_touchEvent]([selfPointer.getTouchByXY(event.clientX, event.clientY, pos)]);
467                         event.stopPropagation();
468                         event.preventDefault();
469                     }, false);
470                 })(eventName, _pointerEventsMap[eventName]);
471             }
472         }
473 
474         if(supportTouches) {
475             //register canvas touch event
476             element.addEventListener("touchstart", function (event) {
477                 if (!event.changedTouches) return;
478 
479                 var pos = selfPointer.getHTMLElementPosition(element);
480                 pos.left -= document.body.scrollLeft;
481                 pos.top -= document.body.scrollTop;
482                 selfPointer.handleTouchesBegin(selfPointer.getTouchesByEvent(event, pos));
483                 event.stopPropagation();
484                 event.preventDefault();
485             }, false);
486 
487             element.addEventListener("touchmove", function (event) {
488                 if (!event.changedTouches) return;
489 
490                 var pos = selfPointer.getHTMLElementPosition(element);
491                 pos.left -= document.body.scrollLeft;
492                 pos.top -= document.body.scrollTop;
493                 selfPointer.handleTouchesMove(selfPointer.getTouchesByEvent(event, pos));
494                 event.stopPropagation();
495                 event.preventDefault();
496             }, false);
497 
498             element.addEventListener("touchend", function (event) {
499                 if (!event.changedTouches) return;
500 
501                 var pos = selfPointer.getHTMLElementPosition(element);
502                 pos.left -= document.body.scrollLeft;
503                 pos.top -= document.body.scrollTop;
504                 selfPointer.handleTouchesEnd(selfPointer.getTouchesByEvent(event, pos));
505                 event.stopPropagation();
506                 event.preventDefault();
507             }, false);
508 
509             element.addEventListener("touchcancel", function (event) {
510                 if (!event.changedTouches) return;
511 
512                 var pos = selfPointer.getHTMLElementPosition(element);
513                 pos.left -= document.body.scrollLeft;
514                 pos.top -= document.body.scrollTop;
515                 locView.handleTouchesCancel(selfPointer.getTouchesByEvent(event, pos));
516                 event.stopPropagation();
517                 event.preventDefault();
518             }, false);
519         }
520 
521         //register keyboard event
522         this._registerKeyboardEvent();
523 
524         //register Accelerometer event
525         this._registerAccelerometerEvent();
526 
527         this._isRegisterEvent = true;
528     },
529 
530     _registerKeyboardEvent: function(){
531         document.addEventListener("keydown", function (e) {
532             cc.eventManager.dispatchEvent(new cc.EventKeyboard(e.keyCode, true));
533         });
534         document.addEventListener("keyup", function (e) {
535             cc.eventManager.dispatchEvent(new cc.EventKeyboard(e.keyCode, false));
536         });
537     },
538 
539     _registerAccelerometerEvent: function(){
540         this._acceleration = new cc.Acceleration();
541         var w = window;
542         this._accelDeviceEvent = w.DeviceMotionEvent || w.DeviceOrientationEvent;
543 
544         //TODO fix DeviceMotionEvent bug on QQ Browser version 4.1 and below.
545         if (cc.sys.browserType == cc.sys.BROWSER_TYPE_MOBILE_QQ)
546             this._accelDeviceEvent = window.DeviceOrientationEvent;
547 
548         var _deviceEventType = (this._accelDeviceEvent == w.DeviceMotionEvent) ? "devicemotion" : "deviceorientation";
549         var ua = navigator.userAgent;
550         if (/Android/.test(ua) || (/Adr/.test(ua) && cc.sys.browserType == cc.BROWSER_TYPE_UC)) {
551             this._minus = -1;
552         }
553 
554         w.addEventListener(_deviceEventType, this.didAccelerate.bind(this), false);
555     },
556 
557     didAccelerate: function (eventData) {
558         if (!this._accelEnabled)
559             return;
560 
561         var mAcceleration = this._acceleration;
562         if (this._accelDeviceEvent == window.DeviceMotionEvent) {
563             var eventAcceleration = eventData["accelerationIncludingGravity"];
564             mAcceleration.x = this._accelMinus * eventAcceleration.x * 0.1;
565             mAcceleration.y = this._accelMinus * eventAcceleration.y * 0.1;
566             mAcceleration.z = eventAcceleration.z * 0.1;
567         } else {
568             mAcceleration.x = (eventData["gamma"] / 90) * 0.981;
569             mAcceleration.y = -(eventData["beta"] / 90) * 0.981;
570             mAcceleration.z = (eventData["alpha"] / 90) * 0.981;
571         }
572         mAcceleration.timestamp = eventData.timeStamp || Date.now();
573 
574         var tmpX = mAcceleration.x;
575         switch (window.orientation) {
576             case cc.UIInterfaceOrientationLandscapeRight://-90
577                 mAcceleration.x = -mAcceleration.y;
578                 mAcceleration.y = tmpX;
579                 break;
580 
581             case cc.UIInterfaceOrientationLandscapeLeft://90
582                 mAcceleration.x = mAcceleration.y;
583                 mAcceleration.y = -tmpX;
584                 break;
585 
586             case cc.UIInterfaceOrientationPortraitUpsideDown://180
587                 mAcceleration.x = -mAcceleration.x;
588                 mAcceleration.y = -mAcceleration.y;
589                 break;
590 
591             case cc.UIInterfaceOrientationPortrait://0
592                 break;
593         }
594     },
595 
596     update:function(dt){
597         if(this._accelCurTime > this._accelInterval){
598             this._accelCurTime -= this._accelInterval;
599             cc.eventManager.dispatchEvent(new cc.EventAcceleration(this._acceleration));
600         }
601         this._accelCurTime += dt;
602     }
603 };
604