OSDN Git Service

Version 0.6.11, add XUI_Gesture.
[pettanr/clientJs.git] / 0.6.x / js / ui / 05_XUI_Gesture.js
1 /* original:\r
2  *  Hammer.JS - v1.0.5 - 2013-04-07\r
3  *  http://eightmedia.github.com/hammer.js\r
4  *  Jorik Tangelder <j.tangelder@gmail.com>, MIT license\r
5  **/\r
6 \r
7 X.UI = X.UI || {};\r
8 \r
9 ( function( Math, window, document, undefined ){\r
10         \r
11         var ELEENT_LIST = [],\r
12                 HAMMER_LIST = [],\r
13                 POINTERS    = [],\r
14                 ABS = new Function( 'v', 'return v<0?-v:v' );\r
15         \r
16         X.UI.Gesture = Hammer;\r
17         \r
18         function Hammer( uinodeRoot, uinode, type ){\r
19                 this.uinode  = uinode;\r
20                 this.enabled = true;\r
21                 \r
22                 Hammer.startup && Hammer.startup( uinodeRoot );\r
23 \r
24                 this.options = Hammer.defaults;\r
25 \r
26                 // start detection on touchstart\r
27                 Utils.addEvents( uinode, Hammer.EVENT_TYPES_START, this );\r
28                 \r
29                 this.listen( type );\r
30         };\r
31         \r
32         Hammer.defaults = {};\r
33         \r
34         Hammer.prototype.handleEvent = function( e ){\r
35                 //var sourceEventType = e.type.toLowerCase();\r
36 \r
37                 var type       = IdToGestureID[ e.type ],\r
38                         gestures   = Detection.gestures,\r
39                         numTouches = 0,// count the total touches on the screen\r
40                         pointerType, i, l, touches, ret, active, gesture, startEv,\r
41                         deltaTime, deltaX, deltaY, velocity;\r
42                 console.log( '1 : ' + type + ' .............. ' + e.type )\r
43                 if( !type ) return;\r
44                 \r
45                 if( e.pointerType ){\r
46                         type |= POINTER;\r
47                         switch( e.pointerType ){\r
48                                 case 'touch' :\r
49                                 case e.MSPOINTER_TYPE_TOUCH :\r
50                                         type |= TOUCH; break;\r
51                                 case 'pen' :\r
52                                 case e.MSPOINTER_TYPE_PEN :\r
53                                         type |= PEN; break;\r
54                                 case 'mouse' :\r
55                                 case e.MSPOINTER_TYPE_MOUSE :\r
56                                         type |= MOUSE; break;\r
57                                 default :\r
58                                         return;\r
59                         };\r
60                 } else\r
61                 if( e.touches ){\r
62                         type |= TOUCH;\r
63                 } else {\r
64                         type |= MOUSE;\r
65                 };\r
66                 \r
67                 // onmouseup, but when touchend has been fired we do nothing.\r
68                 // this is for touchdevices which also fire a mouseup on touchend\r
69                 if( type & MOUSE && touch_triggered ){\r
70                         return X.Callback.STOP_NOW | X.Callback.STOP_PROPAGATION | X.Callback.PREVENT_DEFAULT;\r
71                 }\r
72                 // mousebutton must be down or a touch event\r
73                 else if (\r
74                         type & TOUCH || //sourceEventType.match(/touch/) || // touch events are always on screen\r
75                         ( type & POINTER && type & START ) || //sourceEventType.match(/pointerdown/) || // pointerevents touch\r
76                         ( type & MOUSE   && e.which === 1 ) //(sourceEventType.match(/mouse/) && e.which === 1) // mouse is pressed\r
77                 ){\r
78                         enable_detect = true;\r
79                 };\r
80 \r
81                 // we are in a touch event, set the touch triggered bool to true,\r
82                 // this for the conflicts that may occur on ios and android\r
83                 type & ( TOUCH | POINTER ) && ( touch_triggered = true );\r
84                 //if (sourceEventType.match(/touch|pointer/)) { touch_triggered = true;}\r
85 \r
86                 // when touch has been triggered in this detection session\r
87                 // and we are now handling a mouse event, we stop that to prevent conflicts\r
88                 if( enable_detect ){\r
89                         // update pointerevent\r
90                         if( Hammer.HAS_POINTEREVENTS ){ //eventType !== Hammer.EVENT_END ){\r
91                                 POINTERS[ e.identifier = e.pointerId ] = type & END ? null : e;\r
92                                 touches = [];\r
93                                 // we can use forEach since pointerEvents only is in IE10\r
94                                 for( i = 0, l = POINTERS.length; i < l; ++i ){\r
95                                         POINTERS[ i ] && ( touches[ touches.length ] = POINTERS[ i ] );\r
96                                 };\r
97                                 numTouches = touches.length;\r
98                         }\r
99                         // touch\r
100                         else if ( type & TOUCH ){ //sourceEventType.match(/touch/)) {\r
101                                 touches    = Hammer.DO_TOUCHES_FIX && type & END ? [] : e.touches;\r
102                                 numTouches = touches.length;\r
103                         }\r
104                         // mouse\r
105                         else if( !touch_triggered ){\r
106                                 numTouches = ( type & END ) ? 0 : 1;\r
107                                 touches    = numTouches === 0 ? [] : [{\r
108                                         identifier : 1,\r
109                                         pageX      : e.pageX,\r
110                                         pageY      : e.pageY,\r
111                                         target     : e.target\r
112                                 }];\r
113                         };\r
114 \r
115                         // if we are in a end event, but when we remove one touch and\r
116                         // we still have enough, set eventType to move\r
117                         if( 0 < numTouches && type & END ){ // eventType === Hammer.EVENT_END ){\r
118                                 type = type & POINTER_TYPE_MASK | MOVE;\r
119                                 //eventType = Hammer.EVENT_MOVE;\r
120                         } else if( !numTouches ){\r
121                         // no touches, force the end event\r
122                                 type = type & POINTER_TYPE_MASK | END;\r
123                                 //eventType = Hammer.EVENT_END;\r
124                         };\r
125 \r
126                         // because touchend has no touches, and we often want to use these in our gestures,\r
127                         // we send the last move event as our eventData in touchend\r
128                         ( !numTouches && last_move_event !== null ) ?\r
129                                 ( e = last_move_event ):\r
130                                 ( last_move_event = e ); // store the last move event\r
131 \r
132                         e = {\r
133                                 center      : Utils.getCenter( touches ),\r
134                                 timeStamp   : Date.now ? Date.now() : +new Date,\r
135                                 target      : e.target,\r
136                                 touches     : touches,\r
137                                 eventType   : type & EVENT_TYPE_MASK,\r
138                                 pointerType : type & POINTER_TYPE_MASK\r
139                         };\r
140 \r
141                         console.log( '2 : ' + type + ' .............. ' + last_move_event.type )\r
142 \r
143                         if( type & START ){\r
144                                 console.log( '2a : ' + type + ' .............. ' + last_move_event.type )\r
145                                 if( !this.enabled ) return;\r
146                                 // already busy with a Hammer.gesture detection on an element\r
147                                 console.log( '2b : ' + type + ' .............. ' + last_move_event.type )\r
148                                 if( Detection.current ) return;\r
149                                 Detection.current = {\r
150                                         hammer     : this, // reference to HammerInstance we're working for\r
151                                         startEvent : Utils.extend( {}, e ), // start eventData for distances, timing etc\r
152                                         lastEvent  : false, // last eventData\r
153                                         name       : '' // current gesture we're in/detected, can be 'tap', 'hold' etc\r
154                                 };\r
155                                 Detection.stopped = false;\r
156                                 hammer = this;\r
157                                 active = hammer.activeGesture;\r
158                         } else\r
159                         if( !Detection.current || Detection.stopped ){\r
160                                 return;\r
161                         } else {\r
162                                 hammer = Detection.current.hammer;\r
163                                 active = hammer.activeGesture;\r
164                         };\r
165                         \r
166                         console.log( '3 : ' + type + ' .............. ' + last_move_event.type )\r
167                         \r
168                         // ----------------------------------------------------------------------------------------------------------------\r
169                         // ret = Detection.detect( e );\r
170 \r
171                         // ----------------------------------------------------------------------------------------------------------------\r
172                         // extend event data with calculations about scale, distance etc\r
173                         // e = Detection.extendEventData( e );\r
174                         startEv = Detection.current.startEvent;\r
175                         center  = e.center;\r
176 \r
177                         // if the touches change, set the new touches over the startEvent touches\r
178                         // this because touchevents don't have all the touches on touchstart, or the\r
179                         // user must place his fingers at the EXACT same time on the screen, which is not realistic\r
180                         // but, sometimes it happens that both fingers are touching at the EXACT same time\r
181                         if( startEv && ( numTouches !== startEv.touches.length || touches === startEv.touches ) ){\r
182                                 // extend 1 level deep to get the touchlist with the touch objects\r
183                                 startEv.touches.length = i = 0;\r
184                                 for( ; i < numTouches; ++i ){\r
185                                         startEv.touches[ startEv.touches.length ] = Utils.extend( {}, touches[ i ] );\r
186                                 };\r
187                         };\r
188 \r
189                         deltaTime = e.timeStamp  - startEv.timeStamp;\r
190                         deltaX    = center.pageX - startEv.center.pageX;\r
191                         deltaY    = center.pageY - startEv.center.pageY;\r
192                         velocity  = Utils.getVelocity( deltaTime, deltaX, deltaY );\r
193 \r
194                         Utils.extend( e, {\r
195                                 deltaTime  : deltaTime,\r
196 \r
197                                 deltaX     : deltaX,\r
198                                 deltaY     : deltaY,\r
199 \r
200                                 velocityX  : velocity.x,\r
201                                 velocityY  : velocity.y,\r
202 \r
203                                 distance   : Utils.getDistance( startEv.center, center ),\r
204                                 angle      : Utils.getAngle( startEv.center, center ),\r
205                                 direction  : Utils.getDirection( startEv.center, center ),\r
206 \r
207                                 scale      : Utils.getScale( startEv.touches, touches ),\r
208                                 rotation   : Utils.getRotation( startEv.touches, touches ),\r
209 \r
210                                 startEvent : startEv\r
211                         });\r
212 \r
213 \r
214                         // call Hammer.gesture handlers\r
215                         for( i = 0, l = gestures.length; i < l; ++i ){\r
216                                 gesture = gestures[ i ];\r
217                                 if( Detection.stopped ) break;\r
218                                 if( active[ gesture.name ] ) console.log( gesture.name );\r
219                                 // only when the instance options have enabled this gesture\r
220                                 active[ gesture.name ] &&\r
221                                         // if a handler returns false, we stop with the detection\r
222                                         ( ret |= ( gesture.handler.call( gesture, e, hammer ) || X.Callback.NONE ) );\r
223                         };\r
224 \r
225                         // store as previous event event\r
226                         Detection.current.lastEvent = e;\r
227 \r
228                         // endevent, but not the last touch, so dont stop\r
229                         type & END && numTouches === 0 && Detection.stopDetect();\r
230                         \r
231                         // ----------------------------------------------------------------------------------------------------------------\r
232                         // trigger the handler\r
233                         //handler.call( context, HamEvent.collectEventData( element, eventType, e ) );\r
234 \r
235                         // remove pointerevent from list\r
236                         if( Hammer.HAS_POINTEREVENTS && type & END ){ // eventType === Hammer.EVENT_END ){\r
237                                 numTouches = 0;\r
238                         };\r
239                 };\r
240 \r
241                 //debug(sourceEventType +" "+ eventType);\r
242 \r
243                 // on the end we reset everything\r
244                 if( numTouches === 0 ){\r
245                         last_move_event = null;\r
246                         enable_detect   = false;\r
247                         touch_triggered = false;\r
248                         POINTERS.length = 0;\r
249                 };\r
250                 \r
251                 return ret;\r
252         };\r
253         \r
254         Hammer.startup = function( uinodeRoot ){\r
255                 // find what eventtypes we add listeners to\r
256                 /**\r
257                  * we have different events for each device/browser\r
258                  * determine what we need and set them in the Hammer.EVENT_TYPES constant\r
259                  */\r
260                 // determine the eventtype we want to set\r
261                 // for non pointer events browsers and mixed browsers,\r
262                 // like chrome on windows8 touch laptop         \r
263                 var types, name;\r
264 \r
265                 // Register all gestures inside Gestures\r
266                 for( name in Gestures ){\r
267                         Gestures.hasOwnProperty( name ) && Detection.register( Gestures[ name ] );\r
268                 };\r
269 \r
270                 if( navigator.pointerEnabled || navigator.msPointerEnabled ){\r
271                         Hammer.EVENT_TYPES_START = [ X.View.Event._POINTER_DOWN ];\r
272                         types = [ X.View.Event._POINTER_MOVE, X.View.Event._POINTER_UP, X.View.Event._POINTER_CANCEL ];\r
273                 } else\r
274                 if( window.ontouchstart !== void 0 ){\r
275                         Hammer.EVENT_TYPES_START = [ X.View.Event._TOUCH_START ];\r
276                         types = [ X.View.Event._TOUCH_MOVE, X.View.Event._MOUSE_MOVE, X.View.Event._TOUCH_END, X.View.Event._TOUCH_CANCEL ];\r
277                 } else {\r
278                         Hammer.EVENT_TYPES_START = [ X.View.Event._MOUSE_DOWN ];\r
279                         types = [ X.View.Event._MOUSE_MOVE, X.View.Event._MOUSE_UP, X.View.Event._MOUSE_CANCEL ];\r
280                 };\r
281 \r
282                 // Add touch events on the document\r
283                 Utils.addEvents( uinodeRoot, types, Hammer.prototype.handleEvent );\r
284 \r
285                 // Hammer is ready...!\r
286                 delete Hammer.startup;\r
287         };\r
288         \r
289         Hammer.prototype.trigger = function( type, gesture ){\r
290                 if( !this.types[ type ] ) return;\r
291                 var e = Utils.extend( {}, gesture );\r
292                 e.type = type;\r
293                 console.log( 'trigger : ' + type )\r
294                 return this.uinode.dispatch( e );\r
295         };\r
296         \r
297         Hammer.prototype.listen = function( type ){\r
298                 var gestures = Detection.gestures,\r
299                         i = gestures.length, g;\r
300                 for( ; i; ){\r
301                         g = gestures[ --i ];\r
302                         if( g.startID <= type && type <= g.endID ){\r
303                                 if( !this.activeGesture ) this.activeGesture = {};\r
304                                 if( !this.types ) this.types = {};\r
305                                 this.activeGesture[ g.name ] = this.types[ type ] = 1;\r
306                                 return;\r
307                         };\r
308                 };\r
309         };\r
310         \r
311         Hammer.prototype.unlisten = function( type ){\r
312                 var gestures = Detection.gestures,\r
313                         i = gestures.length, g;\r
314                 if( !this.activeGesture ) return;\r
315                 for( ; i; ){\r
316                         g = gestures[ --i ];\r
317                         if( g.startID <= type && type <= g.endID ){\r
318                                 if( this.activeGesture[ g.name ] ){\r
319                                         if( this.types[ type ] ) delete this.types[ type ];\r
320                                         for( i = g.startID; i <= g.endID; ++i ){\r
321                                                 if( this.types[ i ] ) return;\r
322                                         };\r
323                                         delete this.activeGesture[ g.name ];\r
324                                 };\r
325                                 return;\r
326                         };\r
327                 };\r
328         };\r
329         \r
330         /*\r
331          *  "Android version < 2.2" return ev.touches.length === 1 when touchend, others return ev.touches.length === 0\r
332          */\r
333         Hammer.DO_TOUCHES_FIX = Hammer.HAS_TOUCHEVENTS && ( function( ua, i ){\r
334                                 if( ( i = ua.indexOf('android') ) === -1 ) return false;\r
335                                 return ( parseFloat( ua.substr( i + 8 ) ) || 0 ) < 2.2;\r
336                         })( navigator.userAgent.toLowerCase() );\r
337         \r
338         // detect touchevents\r
339         Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;\r
340 \r
341         // eventtypes per touchevent (start, move, end)\r
342         // are filled by HamEvent.determineEventTypes on setup\r
343         Hammer.EVENT_TYPES_START = null;\r
344 \r
345         // direction defines\r
346         Hammer.DIRECTION_DOWN  = 'down';\r
347         Hammer.DIRECTION_LEFT  = 'left';\r
348         Hammer.DIRECTION_UP    = 'up';\r
349         Hammer.DIRECTION_RIGHT = 'right';\r
350 \r
351         // plugins namespace\r
352         Hammer.plugins = {};\r
353 \r
354         var POINTER     = 1,\r
355                 TOUCH       = 2,\r
356                 PEN         = 8, //4,\r
357                 MOUSE       = 8,\r
358                 START       = 16,\r
359                 MOVE        = 32,\r
360                 END         = 64,\r
361                 CANCEL      = 128,\r
362                 EVENT_TYPE_MASK   = START | MOVE | END,\r
363                 POINTER_TYPE_MASK = POINTER | TOUCH | MOUSE | PEN,\r
364                 IdToGestureID = {};\r
365         IdToGestureID[ X.View.Event._POINTER_DOWN   ] = START;\r
366         IdToGestureID[ X.View.Event._POINTER_MOVE   ] = MOVE;\r
367         IdToGestureID[ X.View.Event._POINTER_UP     ] = END;\r
368         IdToGestureID[ X.View.Event._POINTER_CANCEL ] = END;\r
369         \r
370         IdToGestureID[ X.View.Event._TOUCH_START  ] = START;\r
371         IdToGestureID[ X.View.Event._TOUCH_MOVE   ] = MOVE;\r
372         IdToGestureID[ X.View.Event._TOUCH_END    ] = END;\r
373         IdToGestureID[ X.View.Event._TOUCH_CANCEL ] = END;\r
374         \r
375         IdToGestureID[ X.View.Event._MOUSE_DOWN   ] = START;\r
376         IdToGestureID[ X.View.Event._MOUSE_MOVE   ] = MOVE;\r
377         IdToGestureID[ X.View.Event._MOUSE_UP     ] = END;\r
378         IdToGestureID[ X.View.Event._MOUSE_CANCEL ] = END;\r
379         \r
380         Utils = {\r
381                 \r
382                 /**\r
383                  * touch events with mouse fallback\r
384                  * @param   {HTMLElement}   element\r
385                  * @param   {String}        eventType        like Hammer.EVENT_MOVE\r
386                  * @param   {Function}      handler\r
387                  */\r
388                 addEvents : function( uinode, types, context ){\r
389                         for( var i = 0; i < types.length; ++i ){\r
390                                 uinode.listen( types[ i ], context );\r
391                         };\r
392                 },\r
393                 \r
394                 /**\r
395                  * extend method,\r
396                  * also used for cloning when dest is an empty object\r
397                  * @param   {Object}    dest\r
398                  * @param   {Object}    src\r
399                  * @parm        {Boolean}       merge           do a merge\r
400                  * @returns {Object}    dest\r
401                  */\r
402                 extend : function extend( dest, src, merge ){\r
403                         for( var key in src ){\r
404                                 if( dest[ key ] !== undefined && merge ) continue;\r
405                                 dest[ key ] = src[ key ];\r
406                         };\r
407                         return dest;\r
408                 },\r
409 \r
410                 /**\r
411                  * find if a node is in the given parent\r
412                  * used for event delegation tricks\r
413                  * @param   {HTMLElement}   node\r
414                  * @param   {HTMLElement}   parent\r
415                  * @returns {boolean}       has_parent\r
416                  */\r
417                 hasParent : function( node, parent ){\r
418                         while( node && node.tagName ){ /* tagName for ie */\r
419                                 if( node === parent ) return true;\r
420                                 node = node.parentNode;\r
421                         };\r
422                         return false;\r
423                 },\r
424 \r
425                 /**\r
426                  * get the center of all the touches\r
427                  * @param   {Array}     touches\r
428                  * @returns {Object}    center\r
429                  */\r
430                 getCenter : function getCenter(touches) {\r
431                         var i = 0,\r
432                                 l = touches.length,\r
433                                 valuesX, valuesY;\r
434                         switch( l ){\r
435                                 case 0 :\r
436                                         return {};\r
437                                 case 1 :\r
438                                         return {\r
439                                                 pageX : touches[ 0 ].pageX,\r
440                                                 pageY : touches[ 0 ].pageY\r
441                                         };\r
442                                 case 2 :\r
443                                         return {\r
444                                                 pageX : ( touches[ 0 ].pageX + touches[ 1 ].pageX ) / 2,\r
445                                                 pageY : ( touches[ 0 ].pageY + touches[ 1 ].pageY ) / 2\r
446                                         };\r
447                         };\r
448                         valuesX = [];\r
449                         valuesY = [];\r
450                         for( ; i < l; ++i ){\r
451                                 valuesX[ valuesX.length ] = touches[ i ].pageX;\r
452                                 valuesY[ valuesY.length ] = touches[ i ].pageY;\r
453                         };\r
454                         return {\r
455                                 pageX : ( ( Math.min.apply( null, valuesX ) + Math.max.apply( null, valuesX ) ) / 2 ),\r
456                                 pageY : ( ( Math.min.apply( null, valuesY ) + Math.max.apply( null, valuesY ) ) / 2 )\r
457                         };\r
458                 },\r
459 \r
460                 /**\r
461                  * calculate the velocity between two points\r
462                  * @param   {Number}    deltaTime\r
463                  * @param   {Number}    deltaX\r
464                  * @param   {Number}    deltaY\r
465                  * @returns {Object}    velocity\r
466                  */\r
467                 getVelocity : function getVelocity( deltaTime, deltaX, deltaY ) {\r
468                         return {\r
469                                 x : ABS( deltaX / deltaTime ) || 0,\r
470                                 y : ABS( deltaY / deltaTime ) || 0\r
471                         };\r
472                 },\r
473 \r
474                 /**\r
475                  * calculate the angle between two coordinates\r
476                  * @param   {Touch}     touch1\r
477                  * @param   {Touch}     touch2\r
478                  * @returns {Number}    angle\r
479                  */\r
480                 getAngle : function getAngle(touch1, touch2) {\r
481                         var y = touch2.pageY - touch1.pageY,\r
482                                 x = touch2.pageX - touch1.pageX;\r
483                         return Math.atan2( y, x ) * 180 / Math.PI;\r
484                 },\r
485 \r
486                 /**\r
487                  * angle to direction define\r
488                  * @param   {Touch}     touch1\r
489                  * @param   {Touch}     touch2\r
490                  * @returns {String}    direction constant, like Hammer.DIRECTION_LEFT\r
491                  */\r
492                 getDirection : function getDirection( touch1, touch2 ){\r
493                         var x = touch1.pageX - touch2.pageX,\r
494                                 y = touch1.pageY - touch2.pageY;\r
495                         return ABS( y ) <= ABS( x ) ?\r
496                                 ( x > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT ) :\r
497                                 ( y > 0 ? Hammer.DIRECTION_UP   : Hammer.DIRECTION_DOWN );\r
498                 },\r
499 \r
500                 /**\r
501                  * calculate the distance between two touches\r
502                  * @param   {Touch}     touch1\r
503                  * @param   {Touch}     touch2\r
504                  * @returns {Number}    distance\r
505                  */\r
506                 getDistance : function getDistance( touch1, touch2 ){\r
507                         var x = touch2.pageX - touch1.pageX,\r
508                                 y = touch2.pageY - touch1.pageY;\r
509                         return Math.sqrt( ( x * x ) + ( y * y ) );\r
510                 },\r
511 \r
512                 /**\r
513                  * calculate the scale factor between two touchLists (fingers)\r
514                  * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out\r
515                  * @param   {Array}     start\r
516                  * @param   {Array}     end\r
517                  * @returns {Number}    scale\r
518                  */\r
519                 getScale : function getScale( start, end ){\r
520                         // need two fingers...\r
521                         return ( 2 <= start.length && 2 <= end.length ) ?\r
522                                 Utils.getDistance( end[ 0 ], end[ 1 ] ) / Utils.getDistance( start[ 0 ], start[ 1 ] ) :\r
523                                 1;\r
524                 },\r
525 \r
526                 /**\r
527                  * calculate the rotation degrees between two touchLists (fingers)\r
528                  * @param   {Array}     start\r
529                  * @param   {Array}     end\r
530                  * @returns {Number}    rotation\r
531                  */\r
532                 getRotation : function getRotation( start, end ){\r
533                         // need two fingers\r
534                         return ( 2 <= start.length && 2 <= end.length ) ?\r
535                                 Utils.getAngle( end[ 1 ], end[ 0 ] ) - Utils.getAngle( start[ 1 ], start[ 0 ] ) :\r
536                                 0;\r
537                 },\r
538 \r
539                 /**\r
540                  * boolean if the direction is vertical\r
541                  * @param    {String}    direction\r
542                  * @returns  {Boolean}   is_vertical\r
543                  */\r
544                 isVertical : function isVertical( direction ){\r
545                         return direction === Hammer.DIRECTION_UP || direction === Hammer.DIRECTION_DOWN;\r
546                 }\r
547         };\r
548         \r
549         /**\r
550          * this holds the last move event,\r
551          * used to fix empty touchend issue\r
552          * see the onTouch event for an explanation\r
553          * @type {Object}\r
554          */\r
555         var last_move_event = null;\r
556 \r
557         /**\r
558          * when the mouse is hold down, this is true\r
559          * @type {Boolean}\r
560          */\r
561         var enable_detect = false;\r
562 \r
563         /**\r
564          * when touch events have been fired, this is true\r
565          * @type {Boolean}\r
566          */\r
567         var touch_triggered = false;\r
568         \r
569         Detection = {\r
570                 // contains all registred Gestures in the correct order\r
571                 gestures : [],\r
572 \r
573                 // data of the current Hammer.gesture detection session\r
574                 current : null,\r
575 \r
576                 // the previous Hammer.gesture session data\r
577                 // is a full clone of the previous gesture.current object\r
578                 previous : null,\r
579 \r
580                 // when this becomes true, no gestures are fired\r
581                 stopped : false,\r
582 \r
583                 /**\r
584                  * clear the Hammer.gesture vars\r
585                  * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected\r
586                  * to stop other Gestures from being fired\r
587                  */\r
588                 stopDetect : function stopDetect() {\r
589                         // clone current data to the store as the previous gesture\r
590                         // used for the double tap gesture, since this is an other gesture detect session\r
591                         Detection.previous = Utils.extend( {}, Detection.current );\r
592 \r
593                         // reset the current\r
594                         Detection.current = null;\r
595 \r
596                         // stopped!\r
597                         Detection.stopped = true;\r
598                 },\r
599 \r
600                 /**\r
601                  * register new gesture\r
602                  * @param   {Object}    gesture object, see gestures.js for documentation\r
603                  * @returns {Array}     gestures\r
604                  */\r
605                 register : function( gesture ){\r
606                         // add an enable gesture options if there is no given\r
607                         var options = gesture.defaults || {};\r
608                         if( options[ gesture.name ] === undefined ) options[ gesture.name ] = true;\r
609 \r
610                         // extend Hammer default options with the Hammer.gesture options\r
611                         Utils.extend( Hammer.defaults, options, true );\r
612 \r
613                         // set its index\r
614                         gesture.index = gesture.index || 1000;\r
615 \r
616                         // add Hammer.gesture to the list\r
617                         Detection.gestures.push( gesture );\r
618 \r
619                         // sort the list by index\r
620                         Detection.gestures.sort( function( a, b ){\r
621                                 return\r
622                                         a.index < b.index ? -1 :\r
623                                         a.index > b.index ? 1 : 0;\r
624                         });\r
625                 }\r
626         };\r
627 \r
628         var Gestures = Gestures || {};\r
629 \r
630         /**\r
631          * Custom gestures\r
632          * ==============================\r
633          *\r
634          * Gesture object\r
635          * --------------------\r
636          * The object structure of a gesture:\r
637          *\r
638          * { name: 'mygesture',\r
639          *   index: 1337,\r
640          *   defaults: {\r
641          *     mygesture_option: true\r
642          *   }\r
643          *   handler: function(type, e, inst) {\r
644          *     // trigger gesture event\r
645          *     inst.trigger(this.name, e );\r
646          *   }\r
647          * }\r
648 \r
649          * @param   {String}    name\r
650          * this should be the name of the gesture, lowercase\r
651          * it is also being used to disable/enable the gesture per instance config.\r
652          *\r
653          * @param   {Number}    [index=1000]\r
654          * the index of the gesture, where it is going to be in the stack of gestures detection\r
655          * like when you build an gesture that depends on the drag gesture, it is a good\r
656          * idea to place it after the index of the drag gesture.\r
657          *\r
658          * @param   {Object}    [defaults={}]\r
659          * the default settings of the gesture. these are added to the instance settings,\r
660          * and can be overruled per instance. you can also add the name of the gesture,\r
661          * but this is also added by default (and set to true).\r
662          *\r
663          * @param   {Function}  handler\r
664          * this handles the gesture detection of your custom gesture and receives the\r
665          * following arguments:\r
666          *\r
667          *      @param  {Object}    eventData\r
668          *      event data containing the following properties:\r
669          *          timeStamp   {Number}        time the event occurred\r
670          *          target      {HTMLElement}   target element\r
671          *          touches     {Array}         touches (fingers, pointers, mouse) on the screen\r
672          *          pointerType {String}        kind of pointer that was used. matches Hammer.POINTER_MOUSE|TOUCH\r
673          *          center      {Object}        center position of the touches. contains pageX and pageY\r
674          *          deltaTime   {Number}        the total time of the touches in the screen\r
675          *          deltaX      {Number}        the delta on x axis we haved moved\r
676          *          deltaY      {Number}        the delta on y axis we haved moved\r
677          *          velocityX   {Number}        the velocity on the x\r
678          *          velocityY   {Number}        the velocity on y\r
679          *          angle       {Number}        the angle we are moving\r
680          *          direction   {String}        the direction we are moving. matches Hammer.DIRECTION_UP|DOWN|LEFT|RIGHT\r
681          *          distance    {Number}        the distance we haved moved\r
682          *          scale       {Number}        scaling of the touches, needs 2 touches\r
683          *          rotation    {Number}        rotation of the touches, needs 2 touches *\r
684          *          eventType   {String}        matches Hammer.EVENT_START|MOVE|END\r
685          *          srcEvent    {Object}        the source event, like TouchStart or MouseDown *\r
686          *          startEvent  {Object}        contains the same properties as above,\r
687          *                                      but from the first touch. this is used to calculate\r
688          *                                      distances, deltaTime, scaling etc\r
689          *\r
690          *      @param  {Hammer.Instance}    inst\r
691          *      the instance we are doing the detection for. you can get the options from\r
692          *      the inst.options object and trigger the gesture event by calling inst.trigger\r
693          *\r
694          *\r
695          * Handle gestures\r
696          * --------------------\r
697          * inside the handler you can get/set Detection.current. This is the current\r
698          * detection session. It has the following properties\r
699          *      @param  {String}    name\r
700          *      contains the name of the gesture we have detected. it has not a real function,\r
701          *      only to check in other gestures if something is detected.\r
702          *      like in the drag gesture we set it to 'drag' and in the swipe gesture we can\r
703          *      check if the current gesture is 'drag' by accessing Detection.current.name\r
704          *\r
705          *      @readonly\r
706          *      @param  {Hammer.Instance}    inst\r
707          *      the instance we do the detection for\r
708          *\r
709          *      @readonly\r
710          *      @param  {Object}    startEvent\r
711          *      contains the properties of the first gesture detection in this session.\r
712          *      Used for calculations about timing, distance, etc.\r
713          *\r
714          *      @readonly\r
715          *      @param  {Object}    lastEvent\r
716          *      contains all the properties of the last gesture detect in this session.\r
717          *\r
718          * after the gesture detection session has been completed (user has released the screen)\r
719          * the Detection.current object is copied into Detection.previous,\r
720          * this is usefull for gestures like doubletap, where you need to know if the\r
721          * previous gesture was a tap\r
722          *\r
723          * options that have been set by the instance can be received by calling inst.options\r
724          *\r
725          * You can trigger a gesture event by calling inst.trigger("mygesture", event).\r
726          * The first param is the name of your gesture, the second the event argument\r
727          *\r
728          *\r
729          * Register gestures\r
730          * --------------------\r
731          * When an gesture is added to the Gestures object, it is auto registered\r
732          * at the setup of the first Hammer instance. You can also call Detection.register\r
733          * manually and pass your gesture object as a param\r
734          *\r
735          */\r
736 \r
737         /**\r
738          * Hold\r
739          * Touch stays at the same place for x time\r
740          * @events  hold holdend\r
741          */\r
742         Gestures.Hold = {\r
743                 name  : 'hold',\r
744                 index : 10,\r
745                 startID : X.View.Event.HOLD,\r
746                 endID   : X.View.Event.HOLD_END,\r
747                 defaults : {\r
748                         hold_timeout   : 500,\r
749                         hold_threshold : 1\r
750                 },\r
751                 timerID : null,\r
752                 holding : false,\r
753                 handler : function holdGesture( e, hammer ){\r
754                         switch( e.eventType ){\r
755                                 case START :\r
756                                         // clear any running timers\r
757                                         this.timerID && X.Timer.remove( this.timerID );\r
758 \r
759                                         // set the gesture so we can check in the timeout if it still is\r
760                                         Detection.current.name = this.name;\r
761                                         Gestures.Hold.holding = false;\r
762                                         \r
763                                         // set timer and if after the timeout it still is hold,\r
764                                         // we trigger the hold event\r
765                                         this.timerID = X.Timer.add( hammer.options.hold_timeout, 1, Gestures.Hold._onTimer, [ e, hammer ] );\r
766                                         return;\r
767 \r
768                                 // when you move or end we clear the timer\r
769                                 case MOVE :\r
770                                         if( e.distance <= hammer.options.hold_threshold ) return;\r
771                                 case END :\r
772                                         this.timerID && X.Timer.remove( this.timerID );\r
773                                         if( Gestures.Hold.holding === true ){\r
774                                                 Gestures.Hold.holding = false;\r
775                                                 return hammer.trigger( X.View.Event.HOLD_END, e );\r
776                                         };\r
777                                         break;\r
778                         };\r
779                 },\r
780                 _onTimer : function( e, hammer ){\r
781                         if( Detection.current.name === 'hold' ){\r
782                                 hammer.trigger( X.View.Event.HOLD, e );\r
783                                 Gestures.Hold.holding = true;\r
784                         };\r
785                 }\r
786         };\r
787 \r
788         /**\r
789          * Tap/DoubleTap\r
790          * Quick touch at a place or double at the same place\r
791          * @events  tap, doubletap\r
792          */\r
793         Gestures.Tap = {\r
794                 name     : 'tap',\r
795                 index    : 100,\r
796                 startID  : X.View.Event.TAP,\r
797                 endID    : X.View.Event.DOUBLE_TAP,\r
798                 defaults : {\r
799                         tap_max_touchtime  : 250,\r
800                         tap_max_distance   : 10,\r
801                         tap_always         : true,\r
802                         doubletap_distance : 20,\r
803                         doubletap_interval : 300\r
804                 },\r
805                 handler : function tapGesture( e, hammer ){\r
806                         // previous gesture, for the double tap since these are two different gesture detections\r
807                         var prev = Detection.previous;\r
808                         if( e.eventType === END ){\r
809                                 // when the touchtime is higher then the max touch time\r
810                                 // or when the moving distance is too much\r
811                                 if( hammer.options.tap_max_touchtime < e.deltaTime || hammer.options.tap_max_distance < e.distance ) return;\r
812 \r
813                                 // check if double tap\r
814                                 if( prev && prev.name === 'tap' && ( e.timeStamp - prev.lastEvent.timeStamp ) < hammer.options.doubletap_interval && e.distance < hammer.options.doubletap_distance ){\r
815                                         return hammer.trigger( X.View.Event.DOUBLE_TAP, e );\r
816                                 } else\r
817                                 // do a single tap\r
818                                 if( hammer.options.tap_always ){\r
819                                         Detection.current.name = 'tap';\r
820                                         return hammer.trigger(  X.View.Event.TAP, e );\r
821                                 };\r
822                         };\r
823                 }\r
824         };\r
825 \r
826         /**\r
827          * Swipe\r
828          * triggers swipe events when the end velocity is above the threshold\r
829          * @events  swipe, swipeleft, swiperight, swipeup, swipedown\r
830          */\r
831         Gestures.Swipe = {\r
832                 name : 'swipe',\r
833                 index : 40,\r
834                 startID  : X.View.Event.SWIP,\r
835                 endID    : X.View.Event.SWIP_DOWN,\r
836                 defaults : {\r
837                         // set 0 for unlimited, but this can conflict with transform\r
838                         swipe_max_touches : 1,\r
839                         swipe_velocity : 0.7\r
840                 },\r
841                 handler : function swipeGesture(e, hammer) {\r
842                         if( e.eventType === END ){\r
843                                 // max touches\r
844                                 if( 0 < hammer.options.swipe_max_touches && hammer.options.swipe_max_touches < e.touches.length ) return;\r
845 \r
846                                 // when the distance we moved is too small we skip this gesture\r
847                                 // or we can be already in dragging\r
848                                 if( hammer.options.swipe_velocity < e.velocityX || hammer.options.swipe_velocity < e.velocityY ){\r
849                                         // trigger swipe events\r
850                                         hammer.trigger( X.View.Event.SWIP, e );\r
851                                         hammer.trigger(\r
852                                                 e.direction === Hammer.DIRECTION_UP ?\r
853                                                         X.View.Event.SWIP_UP :\r
854                                                 e.direction === Hammer.DIRECTION_DOWN ?\r
855                                                         X.View.Event.SWIP_DOWN :\r
856                                                 e.direction === Hammer.DIRECTION_LEFT ?\r
857                                                         X.View.Event.SWIP_LEFT :\r
858                                                         X.View.Event.SWIP_RIGHT,\r
859                                                 e\r
860                                         );\r
861                                 };\r
862                         };\r
863                 }\r
864         };\r
865 \r
866         /**\r
867          * Drag\r
868          * Move with x fingers (default 1) around on the page. Blocking the scrolling when\r
869          * moving left and right is a good practice. When all the drag events are blocking\r
870          * you disable scrolling on that area.\r
871          * @events  drag, dragstart, dragend, drapleft, dragright, dragup, dragdown\r
872          */\r
873         Gestures.Drag = {\r
874                 name : 'drag',\r
875                 index : 50,\r
876                 startID  : X.View.Event.DRAG,\r
877                 endID    : X.View.Event.DRAG_DOWN,\r
878                 defaults : {\r
879                         drag_min_distance : 10,\r
880                         // set 0 for unlimited, but this can conflict with transform\r
881                         drag_max_touches : 1,\r
882                         // prevent default browser behavior when dragging occurs\r
883                         // be careful with it, it makes the element a blocking element\r
884                         // when you are using the drag gesture, it is a good practice to set this true\r
885                         drag_block_horizontal : false,\r
886                         drag_block_vertical : false,\r
887                         // drag_lock_to_axis keeps the drag gesture on the axis that it started on,\r
888                         // It disallows vertical directions if the initial direction was horizontal, and vice versa.\r
889                         drag_lock_to_axis : false,\r
890                         // drag lock only kicks in when distance > drag_lock_min_distance\r
891                         // This way, locking occurs only when the distance has become large enough to reliably determine the direction\r
892                         drag_lock_min_distance : 25\r
893                 },\r
894                 triggered : false,\r
895                 handler : function dragGesture( e, hammer ){\r
896                         var last_direction;\r
897                         // current gesture isnt drag, but dragged is true\r
898                         // this means an other gesture is busy. now call dragend\r
899                         if( Detection.current.name !== this.name && this.triggered ){\r
900                                 hammer.trigger( X.View.Event.DRAG_END, e );\r
901                                 this.triggered = false;\r
902                                 return;\r
903                         };\r
904 \r
905                         // max touches\r
906                         if( 0 < hammer.options.drag_max_touches && hammer.options.drag_max_touches < e.touches.length ) return;\r
907 \r
908                         switch( e.eventType ){\r
909                                 case START:\r
910                                         this.triggered = false;\r
911                                         break;\r
912 \r
913                                 case MOVE :\r
914                                         // when the distance we moved is too small we skip this gesture\r
915                                         // or we can be already in dragging\r
916                                         if( e.distance < hammer.options.drag_min_distance && Detection.current.name !== this.name ) return;\r
917 \r
918                                         // we are dragging!\r
919                                         Detection.current.name = this.name;\r
920 \r
921                                         // lock drag to axis?\r
922                                         if( Detection.current.lastEvent.drag_locked_to_axis || ( hammer.options.drag_lock_to_axis && hammer.options.drag_lock_min_distance <= e.distance ) ){\r
923                                                 e.drag_locked_to_axis = true;\r
924                                         };\r
925                                         last_direction = Detection.current.lastEvent.direction;\r
926                                         if( e.drag_locked_to_axis && last_direction !== e.direction ){\r
927                                                 // keep direction on the axis that the drag gesture started on\r
928                                                 e.direction = Utils.isVertical( last_direction ) ?\r
929                                                         ( e.deltaY < 0 ? Hammer.DIRECTION_UP   : Hammer.DIRECTION_DOWN ) :\r
930                                                         ( e.deltaX < 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT );\r
931                                         };\r
932 \r
933                                         // first time, trigger dragstart event\r
934                                         if( !this.triggered ){\r
935                                                 hammer.trigger( X.View.Event.DRAG_START, e );\r
936                                                 this.triggered = true;\r
937                                         };\r
938 \r
939                                         // trigger normal event\r
940                                         hammer.trigger( X.View.Event.DRAG, e );\r
941 \r
942                                         // direction event, like dragdown\r
943                                         hammer.trigger(\r
944                                                 e.direction === Hammer.DIRECTION_UP ?\r
945                                                         X.View.Event.DRAG_UP :\r
946                                                 e.direction === Hammer.DIRECTION_DOWN ?\r
947                                                         X.View.Event.DRAG_DOWN :\r
948                                                 e.direction === Hammer.DIRECTION_LEFT ?\r
949                                                         X.View.Event.DRAG_LEFT :\r
950                                                         X.View.Event.DRAG_RIGHT,\r
951                                                 e\r
952                                         );\r
953 \r
954                                         // block the browser events\r
955                                         (\r
956                                                 ( hammer.options.drag_block_vertical   &&  Utils.isVertical( e.direction ) ) ||\r
957                                                 ( hammer.options.drag_block_horizontal && !Utils.isVertical( e.direction ) )\r
958                                         ) && e.preventDefault();\r
959                                         break;\r
960 \r
961                                 case END:\r
962                                         // trigger dragend\r
963                                         this.triggered && hammer.trigger( X.View.Event.DRAG_END, e );\r
964                                         this.triggered = false;\r
965                                         break;\r
966                         }\r
967                 }\r
968         };\r
969 \r
970         /**\r
971          * Transform\r
972          * User want to scale or rotate with 2 fingers\r
973          * @events  transform, transformstart, transformend, pinch, pinchin, pinchout, rotate\r
974          */\r
975         Gestures.Transform = {\r
976                 name : 'transform',\r
977                 index : 45,\r
978                 startID  : X.View.Event.TRANSFORM,\r
979                 endID    : X.View.Event.ROTATE,\r
980                 defaults : {\r
981                         // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1\r
982                         transform_min_scale : 0.01,\r
983                         // rotation in degrees\r
984                         transform_min_rotation : 1,\r
985                         // prevent default browser behavior when two touches are on the screen\r
986                         // but it makes the element a blocking element\r
987                         // when you are using the transform gesture, it is a good practice to set this true\r
988                         transform_always_block : false\r
989                 },\r
990                 triggered : false,\r
991                 handler : function transformGesture( e, hammer ){\r
992                         // current gesture isnt drag, but dragged is true\r
993                         // this means an other gesture is busy. now call dragend\r
994                         if( Detection.current.name !== this.name && this.triggered ){\r
995                                 hammer.trigger( X.View.Event.TRANSFORM_END, e );\r
996                                 this.triggered = false;\r
997                                 return;\r
998                         };\r
999 \r
1000                         // atleast multitouch\r
1001                         if( e.touches.length < 2 ) return;\r
1002 \r
1003                         // prevent default when two fingers are on the screen\r
1004                         hammer.options.transform_always_block && e.preventDefault();\r
1005 \r
1006                         switch(e.eventType) {\r
1007                                 case START:\r
1008                                         this.triggered = false;\r
1009                                         break;\r
1010 \r
1011                                 case MOVE:\r
1012                                         var scale_threshold    = ABS( 1 - e.scale ),\r
1013                                                 rotation_threshold = ABS( e.rotation );\r
1014 \r
1015                                         // when the distance we moved is too small we skip this gesture\r
1016                                         // or we can be already in dragging\r
1017                                         if( scale_threshold < hammer.options.transform_min_scale && rotation_threshold < hammer.options.transform_min_rotation ) return;\r
1018 \r
1019                                         // we are transforming!\r
1020                                         Detection.current.name = this.name;\r
1021 \r
1022                                         // first time, trigger dragstart event\r
1023                                         if( !this.triggered ){\r
1024                                                 hammer.trigger( X.View.Event.TRANSFORM_START, e );\r
1025                                                 this.triggered = true;\r
1026                                         };\r
1027 \r
1028                                         hammer.trigger( X.View.Event.TRANSFORM, e );\r
1029                                         // basic transform event\r
1030 \r
1031                                         // trigger rotate event\r
1032                                         hammer.options.transform_min_rotation < rotation_threshold && hammer.trigger( X.View.Event.ROTATE, e );\r
1033 \r
1034                                         // trigger pinch event\r
1035                                         if( scale_threshold > hammer.options.transform_min_scale ){\r
1036                                                 hammer.trigger( X.View.Event.PINCH, e );\r
1037                                                 hammer.trigger( e.scale < 1 ? X.View.Event.PINCH_IN : X.View.Event.PINCH_OUT, e );\r
1038                                         };\r
1039                                         break;\r
1040 \r
1041                                 case END:\r
1042                                         // trigger dragend\r
1043                                         this.triggered && hammer.trigger( X.View.Event.TRANSFORM_END, e );\r
1044                                         this.triggered = false;\r
1045                                         break;\r
1046                         };\r
1047                 }\r
1048         };\r
1049 \r
1050         /**\r
1051          * Touch\r
1052          * Called as first, tells the user has touched the screen\r
1053          * @events  touch\r
1054          */\r
1055         Gestures.Touch = {\r
1056                 name : 'touch',\r
1057                 index : -Infinity,\r
1058                 defaults : {\r
1059                         // call preventDefault at touchstart, and makes the element blocking by\r
1060                         // disabling the scrolling of the page, but it improves gestures like\r
1061                         // transforming and dragging.\r
1062                         // be careful with using this, it can be very annoying for users to be stuck\r
1063                         // on the page\r
1064                         prevent_default : false,\r
1065 \r
1066                         // disable mouse events, so only touch (or pen!) input triggers events\r
1067                         prevent_mouseevents : false\r
1068                 },\r
1069                 handler : function touchGesture( e, hammer ){\r
1070                         if( hammer.options.prevent_mouseevents && e.pointerType === MOUSE ){\r
1071                                 Detection.stopDetect();\r
1072                                 return;\r
1073                         };\r
1074 \r
1075                         hammer.options.prevent_default && e.preventDefault();\r
1076 \r
1077                         e.eventType === START && hammer.trigger( this.name, e );\r
1078                 }\r
1079         };\r
1080 \r
1081         /**\r
1082          * Release\r
1083          * Called as last, tells the user has released the screen\r
1084          * @events  release\r
1085          */\r
1086         Gestures.Release = {\r
1087                 name : 'release',\r
1088                 index : Infinity,\r
1089                 handler : function releaseGesture( e, hammer ){\r
1090                         e.eventType === END && hammer.trigger( this.name, e );\r
1091                 }\r
1092         };\r
1093         \r
1094 })( Math, window, document );