src/cm/media/js/lib/yui/yui_3.10.3/build/gesture-simulate/gesture-simulate-debug.js
changeset 525 89ef5ed3c48b
equal deleted inserted replaced
524:322d0feea350 525:89ef5ed3c48b
       
     1 /*
       
     2 YUI 3.10.3 (build 2fb5187)
       
     3 Copyright 2013 Yahoo! Inc. All rights reserved.
       
     4 Licensed under the BSD License.
       
     5 http://yuilibrary.com/license/
       
     6 */
       
     7 
       
     8 YUI.add('gesture-simulate', function (Y, NAME) {
       
     9 
       
    10 /**
       
    11  * Simulate high-level user gestures by generating a set of native DOM events.
       
    12  *
       
    13  * @module gesture-simulate
       
    14  * @requires event-simulate, async-queue, node-screen
       
    15  */
       
    16 
       
    17 var NAME = "gesture-simulate",
       
    18 
       
    19     // phantomjs check may be temporary, until we determine if it really support touch all the way through, like it claims to (http://code.google.com/p/phantomjs/issues/detail?id=375)
       
    20     SUPPORTS_TOUCH = ((Y.config.win && ("ontouchstart" in Y.config.win)) && !(Y.UA.phantomjs) && !(Y.UA.chrome && Y.UA.chrome < 6)),
       
    21 
       
    22     gestureNames = {
       
    23         tap: 1,
       
    24         doubletap: 1,
       
    25         press: 1,
       
    26         move: 1,
       
    27         flick: 1,
       
    28         pinch: 1,
       
    29         rotate: 1
       
    30     },
       
    31 
       
    32     touchEvents = {
       
    33         touchstart: 1,
       
    34         touchmove: 1,
       
    35         touchend: 1,
       
    36         touchcancel: 1
       
    37     },
       
    38 
       
    39     document = Y.config.doc,
       
    40     emptyTouchList,
       
    41 
       
    42     EVENT_INTERVAL = 20,        // 20ms
       
    43     START_PAGEX,                // will be adjusted to the node element center
       
    44     START_PAGEY,                // will be adjusted to the node element center
       
    45 
       
    46     // defaults that user can override.
       
    47     DEFAULTS = {
       
    48         // tap gestures
       
    49         HOLD_TAP: 10,           // 10ms
       
    50         DELAY_TAP: 10,          // 10ms
       
    51 
       
    52         // press gesture
       
    53         HOLD_PRESS: 3000,       // 3sec
       
    54         MIN_HOLD_PRESS: 1000,   // 1sec
       
    55         MAX_HOLD_PRESS: 60000,  // 1min
       
    56 
       
    57         // move gesture
       
    58         DISTANCE_MOVE: 200,     // 200 pixels
       
    59         DURATION_MOVE: 1000,    // 1sec
       
    60         MAX_DURATION_MOVE: 5000,// 5sec
       
    61 
       
    62         // flick gesture
       
    63         MIN_VELOCITY_FLICK: 1.3,
       
    64         DISTANCE_FLICK: 200,     // 200 pixels
       
    65         DURATION_FLICK: 1000,    // 1sec
       
    66         MAX_DURATION_FLICK: 5000,// 5sec
       
    67 
       
    68         // pinch/rotation
       
    69         DURATION_PINCH: 1000     // 1sec
       
    70     },
       
    71 
       
    72     TOUCH_START = 'touchstart',
       
    73     TOUCH_MOVE = 'touchmove',
       
    74     TOUCH_END = 'touchend',
       
    75 
       
    76     GESTURE_START = 'gesturestart',
       
    77     GESTURE_CHANGE = 'gesturechange',
       
    78     GESTURE_END = 'gestureend',
       
    79 
       
    80     MOUSE_UP    = 'mouseup',
       
    81     MOUSE_MOVE  = 'mousemove',
       
    82     MOUSE_DOWN  = 'mousedown',
       
    83     MOUSE_CLICK = 'click',
       
    84     MOUSE_DBLCLICK = 'dblclick',
       
    85 
       
    86     X_AXIS = 'x',
       
    87     Y_AXIS = 'y';
       
    88 
       
    89 
       
    90 function Simulations(node) {
       
    91     if(!node) {
       
    92         Y.error(NAME+': invalid target node');
       
    93     }
       
    94     this.node = node;
       
    95     this.target = Y.Node.getDOMNode(node);
       
    96 
       
    97     var startXY = this.node.getXY(),
       
    98         dims = this._getDims();
       
    99 
       
   100     START_PAGEX = startXY[0] + (dims[0])/2;
       
   101     START_PAGEY = startXY[1] + (dims[1])/2;
       
   102 }
       
   103 
       
   104 Simulations.prototype = {
       
   105 
       
   106     /**
       
   107      * Helper method to convert a degree to a radian.
       
   108      * 
       
   109      * @method _toRadian
       
   110      * @private
       
   111      * @param {Number} deg A degree to be converted to a radian.
       
   112      * @return {Number} The degree in radian. 
       
   113      */
       
   114     _toRadian: function(deg) {
       
   115         return deg * (Math.PI/180);
       
   116     },
       
   117 
       
   118     /**
       
   119      * Helper method to get height/width while accounting for 
       
   120      * rotation/scale transforms where possible by using the 
       
   121      * bounding client rectangle height/width instead of the 
       
   122      * offsetWidth/Height which region uses.
       
   123      * @method _getDims
       
   124      * @private
       
   125      * @return {Array} Array with [height, width]
       
   126      */
       
   127     _getDims : function() {
       
   128         var region,
       
   129             width,
       
   130             height;
       
   131 
       
   132         // Ideally, this should be in DOM somewhere.
       
   133         if (this.target.getBoundingClientRect) {
       
   134             region = this.target.getBoundingClientRect();
       
   135 
       
   136             if ("height" in region) {
       
   137                 height = region.height;
       
   138             } else {
       
   139                 // IE7,8 has getBCR, but no height.
       
   140                 height = Math.abs(region.bottom - region.top);
       
   141             }
       
   142 
       
   143             if ("width" in region) {
       
   144                 width = region.width;
       
   145             } else {
       
   146                 // IE7,8 has getBCR, but no width.
       
   147                 width = Math.abs(region.right - region.left);
       
   148             }
       
   149         } else {
       
   150             region = this.node.get("region");
       
   151             width = region.width;
       
   152             height = region.height;
       
   153         }
       
   154 
       
   155         return [width, height];
       
   156     },
       
   157 
       
   158     /**
       
   159      * Helper method to convert a point relative to the node element into 
       
   160      * the point in the page coordination.
       
   161      * 
       
   162      * @method _calculateDefaultPoint
       
   163      * @private
       
   164      * @param {Array} point A point relative to the node element.
       
   165      * @return {Array} The same point in the page coordination. 
       
   166      */
       
   167     _calculateDefaultPoint: function(point) {
       
   168 
       
   169         var height;
       
   170 
       
   171         if(!Y.Lang.isArray(point) || point.length === 0) {
       
   172             point = [START_PAGEX, START_PAGEY];
       
   173         } else {
       
   174             if(point.length == 1) {
       
   175                 height = this._getDims[1];
       
   176                 point[1] = height/2;
       
   177             }
       
   178             // convert to page(viewport) coordination
       
   179             point[0] = this.node.getX() + point[0];
       
   180             point[1] = this.node.getY() + point[1];
       
   181         }
       
   182 
       
   183         return point;
       
   184     },
       
   185 
       
   186     /**
       
   187      * The "rotate" and "pinch" methods are essencially same with the exact same 
       
   188      * arguments. Only difference is the required parameters. The rotate method 
       
   189      * requires "rotation" parameter while the pinch method requires "startRadius" 
       
   190      * and "endRadius" parameters.
       
   191      *
       
   192      * @method rotate
       
   193      * @param {Function} cb The callback to execute when the gesture simulation 
       
   194      *      is completed.
       
   195      * @param {Array} center A center point where the pinch gesture of two fingers
       
   196      *      should happen. It is relative to the top left corner of the target 
       
   197      *      node element.
       
   198      * @param {Number} startRadius A radius of start circle where 2 fingers are 
       
   199      *      on when the gesture starts. This is optional. The default is a fourth of 
       
   200      *      either target node width or height whichever is smaller.
       
   201      * @param {Number} endRadius A radius of end circle where 2 fingers will be on when
       
   202      *      the pinch or spread gestures are completed. This is optional. 
       
   203      *      The default is a fourth of either target node width or height whichever is less.
       
   204      * @param {Number} duration A duration of the gesture in millisecond.
       
   205      * @param {Number} start A start angle(0 degree at 12 o'clock) where the 
       
   206      *      gesture should start. Default is 0.  
       
   207      * @param {Number} rotation A rotation in degree. It is required.
       
   208      */
       
   209     rotate: function(cb, center, startRadius, endRadius, duration, start, rotation) {
       
   210         var radius,
       
   211             r1 = startRadius,   // optional
       
   212             r2 = endRadius;     // optional
       
   213 
       
   214         if(!Y.Lang.isNumber(r1) || !Y.Lang.isNumber(r2) || r1<0 || r2<0) {
       
   215             radius = (this.target.offsetWidth < this.target.offsetHeight)? 
       
   216                 this.target.offsetWidth/4 : this.target.offsetHeight/4;
       
   217             r1 = radius;
       
   218             r2 = radius;
       
   219         }
       
   220 
       
   221         // required
       
   222         if(!Y.Lang.isNumber(rotation)) {
       
   223             Y.error(NAME+'Invalid rotation detected.');
       
   224         }
       
   225 
       
   226         this.pinch(cb, center, r1, r2, duration, start, rotation);
       
   227     },
       
   228 
       
   229     /**
       
   230      * The "rotate" and "pinch" methods are essencially same with the exact same 
       
   231      * arguments. Only difference is the required parameters. The rotate method 
       
   232      * requires "rotation" parameter while the pinch method requires "startRadius" 
       
   233      * and "endRadius" parameters.
       
   234      *
       
   235      * The "pinch" gesture can simulate various 2 finger gestures such as pinch, 
       
   236      * spread and/or rotation. The "startRadius" and "endRadius" are required.
       
   237      * If endRadius is larger than startRadius, it becomes a spread gesture 
       
   238      * otherwise a pinch gesture. 
       
   239      *
       
   240      * @method pinch
       
   241      * @param {Function} cb The callback to execute when the gesture simulation 
       
   242      *      is completed.
       
   243      * @param {Array} center A center point where the pinch gesture of two fingers
       
   244      *      should happen. It is relative to the top left corner of the target 
       
   245      *      node element.
       
   246      * @param {Number} startRadius A radius of start circle where 2 fingers are 
       
   247      *      on when the gesture starts. This paramenter is required.
       
   248      * @param {Number} endRadius A radius of end circle where 2 fingers will be on when
       
   249      *      the pinch or spread gestures are completed. This parameter is required.
       
   250      * @param {Number} duration A duration of the gesture in millisecond.
       
   251      * @param {Number} start A start angle(0 degree at 12 o'clock) where the 
       
   252      *      gesture should start. Default is 0.  
       
   253      * @param {Number} rotation If rotation is desired during the pinch or 
       
   254      *      spread gestures, this parameter can be used. Default is 0 degree.  
       
   255      */
       
   256     pinch: function(cb, center, startRadius, endRadius, duration, start, rotation) {
       
   257         var eventQueue,
       
   258             i,
       
   259             interval = EVENT_INTERVAL,
       
   260             touches,
       
   261             id = 0,
       
   262             r1 = startRadius,   // required
       
   263             r2 = endRadius,     // required
       
   264             radiusPerStep,
       
   265             centerX, centerY,
       
   266             startScale, endScale, scalePerStep,
       
   267             startRot, endRot, rotPerStep,
       
   268             path1 = {start: [], end: []}, // paths for 1st and 2nd fingers. 
       
   269             path2 = {start: [], end: []},
       
   270             steps,
       
   271             touchMove;
       
   272 
       
   273         center = this._calculateDefaultPoint(center);
       
   274 
       
   275         if(!Y.Lang.isNumber(r1) || !Y.Lang.isNumber(r2) || r1<0 || r2<0) {
       
   276             Y.error(NAME+'Invalid startRadius and endRadius detected.');
       
   277         }
       
   278 
       
   279         if(!Y.Lang.isNumber(duration) || duration <= 0) {
       
   280             duration = DEFAULTS.DURATION_PINCH;
       
   281         }
       
   282 
       
   283         if(!Y.Lang.isNumber(start)) {
       
   284             start = 0.0;
       
   285         } else {
       
   286             start = start%360;
       
   287             while(start < 0) {
       
   288                 start += 360;
       
   289             }
       
   290         }
       
   291 
       
   292         if(!Y.Lang.isNumber(rotation)) {
       
   293             rotation = 0.0;
       
   294         }
       
   295 
       
   296         Y.AsyncQueue.defaults.timeout = interval;
       
   297         eventQueue = new Y.AsyncQueue();
       
   298 
       
   299         // range determination
       
   300         centerX = center[0];
       
   301         centerY = center[1];
       
   302 
       
   303         startRot = start;
       
   304         endRot = start + rotation;
       
   305 
       
   306         // 1st finger path
       
   307         path1.start = [
       
   308             centerX + r1*Math.sin(this._toRadian(startRot)), 
       
   309             centerY - r1*Math.cos(this._toRadian(startRot))
       
   310         ];
       
   311         path1.end   = [
       
   312             centerX + r2*Math.sin(this._toRadian(endRot)), 
       
   313             centerY - r2*Math.cos(this._toRadian(endRot))
       
   314         ];
       
   315         
       
   316         // 2nd finger path
       
   317         path2.start = [
       
   318             centerX - r1*Math.sin(this._toRadian(startRot)), 
       
   319             centerY + r1*Math.cos(this._toRadian(startRot))
       
   320         ];
       
   321         path2.end   = [
       
   322             centerX - r2*Math.sin(this._toRadian(endRot)), 
       
   323             centerY + r2*Math.cos(this._toRadian(endRot))
       
   324         ];
       
   325 
       
   326         startScale = 1.0;
       
   327         endScale = endRadius/startRadius;
       
   328 
       
   329         // touch/gesture start
       
   330         eventQueue.add({
       
   331             fn: function() {
       
   332                 var coord1, coord2, coord, touches;
       
   333 
       
   334                 // coordinate for each touch object.
       
   335                 coord1 = {
       
   336                     pageX: path1.start[0], 
       
   337                     pageY: path1.start[1],
       
   338                     clientX: path1.start[0], 
       
   339                     clientY: path1.start[1]
       
   340                 };
       
   341                 coord2 = {
       
   342                     pageX: path2.start[0], 
       
   343                     pageY: path2.start[1],
       
   344                     clientX: path2.start[0], 
       
   345                     clientY: path2.start[1]
       
   346                 };
       
   347                 touches = this._createTouchList([Y.merge({
       
   348                     identifier: (id++)   
       
   349                 }, coord1), Y.merge({
       
   350                     identifier: (id++)
       
   351                 }, coord2)]);
       
   352 
       
   353                 // coordinate for top level event
       
   354                 coord = {
       
   355                     pageX: (path1.start[0] + path2.start[0])/2,
       
   356                     pageY: (path1.start[0] + path2.start[1])/2,
       
   357                     clientX: (path1.start[0] + path2.start[0])/2,
       
   358                     clientY: (path1.start[0] + path2.start[1])/2
       
   359                 };
       
   360 
       
   361                 this._simulateEvent(this.target, TOUCH_START, Y.merge({
       
   362                     touches: touches,
       
   363                     targetTouches: touches,
       
   364                     changedTouches: touches,
       
   365                     scale: startScale,
       
   366                     rotation: startRot
       
   367                 }, coord));
       
   368 
       
   369                 if(Y.UA.ios >= 2.0) {
       
   370                     /* gesture starts when the 2nd finger touch starts.
       
   371                     * The implementation will fire 1 touch start event for both fingers,
       
   372                     * simulating 2 fingers touched on the screen at the same time.
       
   373                     */
       
   374                     this._simulateEvent(this.target, GESTURE_START, Y.merge({
       
   375                         scale: startScale,
       
   376                         rotation: startRot
       
   377                     }, coord));
       
   378                 }
       
   379             },
       
   380             timeout: 0,
       
   381             context: this
       
   382         });
       
   383 
       
   384         // gesture change
       
   385         steps = Math.floor(duration/interval);
       
   386         radiusPerStep = (r2 - r1)/steps;
       
   387         scalePerStep = (endScale - startScale)/steps;
       
   388         rotPerStep = (endRot - startRot)/steps;
       
   389         
       
   390         touchMove = function(step) {
       
   391             var radius = r1 + (radiusPerStep)*step,
       
   392                 px1 = centerX + radius*Math.sin(this._toRadian(startRot + rotPerStep*step)),
       
   393                 py1 = centerY - radius*Math.cos(this._toRadian(startRot + rotPerStep*step)),
       
   394                 px2 = centerX - radius*Math.sin(this._toRadian(startRot + rotPerStep*step)),
       
   395                 py2 = centerY + radius*Math.cos(this._toRadian(startRot + rotPerStep*step)),
       
   396                 px = (px1+px2)/2,
       
   397                 py = (py1+py2)/2,
       
   398                 coord1, coord2, coord, touches;
       
   399 
       
   400             // coordinate for each touch object.    
       
   401             coord1 = {
       
   402                 pageX: px1,
       
   403                 pageY: py1,
       
   404                 clientX: px1,
       
   405                 clientY: py1
       
   406             };
       
   407             coord2 = {
       
   408                 pageX: px2,
       
   409                 pageY: py2,
       
   410                 clientX: px2,
       
   411                 clientY: py2
       
   412             };
       
   413             touches = this._createTouchList([Y.merge({
       
   414                 identifier: (id++)   
       
   415             }, coord1), Y.merge({
       
   416                 identifier: (id++)
       
   417             }, coord2)]);
       
   418 
       
   419             // coordinate for top level event
       
   420             coord = {
       
   421                 pageX: px,
       
   422                 pageY: py,
       
   423                 clientX: px,
       
   424                 clientY: py
       
   425             };
       
   426 
       
   427             this._simulateEvent(this.target, TOUCH_MOVE, Y.merge({
       
   428                 touches: touches,
       
   429                 targetTouches: touches,
       
   430                 changedTouches: touches,
       
   431                 scale: startScale + scalePerStep*step,
       
   432                 rotation: startRot + rotPerStep*step
       
   433             }, coord));
       
   434 
       
   435             if(Y.UA.ios >= 2.0) {
       
   436                 this._simulateEvent(this.target, GESTURE_CHANGE, Y.merge({
       
   437                     scale: startScale + scalePerStep*step,
       
   438                     rotation: startRot + rotPerStep*step
       
   439                 }, coord));
       
   440             }
       
   441         };
       
   442 
       
   443         for (i=0; i < steps; i++) {
       
   444             eventQueue.add({
       
   445                 fn: touchMove,
       
   446                 args: [i],
       
   447                 context: this
       
   448             });
       
   449         }
       
   450 
       
   451         // gesture end
       
   452         eventQueue.add({
       
   453             fn: function() {
       
   454                 var emptyTouchList = this._getEmptyTouchList(),
       
   455                     coord1, coord2, coord, touches;
       
   456 
       
   457                 // coordinate for each touch object.
       
   458                 coord1 = {
       
   459                     pageX: path1.end[0], 
       
   460                     pageY: path1.end[1],
       
   461                     clientX: path1.end[0], 
       
   462                     clientY: path1.end[1]
       
   463                 };
       
   464                 coord2 = {
       
   465                     pageX: path2.end[0], 
       
   466                     pageY: path2.end[1],
       
   467                     clientX: path2.end[0], 
       
   468                     clientY: path2.end[1]
       
   469                 };
       
   470                 touches = this._createTouchList([Y.merge({
       
   471                     identifier: (id++)   
       
   472                 }, coord1), Y.merge({
       
   473                     identifier: (id++)
       
   474                 }, coord2)]);
       
   475 
       
   476                 // coordinate for top level event
       
   477                 coord = {
       
   478                     pageX: (path1.end[0] + path2.end[0])/2,
       
   479                     pageY: (path1.end[0] + path2.end[1])/2,
       
   480                     clientX: (path1.end[0] + path2.end[0])/2,
       
   481                     clientY: (path1.end[0] + path2.end[1])/2
       
   482                 };  
       
   483 
       
   484                 if(Y.UA.ios >= 2.0) {
       
   485                     this._simulateEvent(this.target, GESTURE_END, Y.merge({
       
   486                         scale: endScale,
       
   487                         rotation: endRot
       
   488                     }, coord));
       
   489                 }
       
   490 
       
   491                 this._simulateEvent(this.target, TOUCH_END, Y.merge({
       
   492                     touches: emptyTouchList,
       
   493                     targetTouches: emptyTouchList,
       
   494                     changedTouches: touches,
       
   495                     scale: endScale,
       
   496                     rotation: endRot
       
   497                 }, coord));
       
   498             },
       
   499             context: this
       
   500         });
       
   501 
       
   502         if(cb && Y.Lang.isFunction(cb)) {
       
   503             eventQueue.add({
       
   504                 fn: cb,
       
   505 
       
   506                 // by default, the callback runs the node context where 
       
   507                 // simulateGesture method is called.
       
   508                 context: this.node
       
   509 
       
   510                 //TODO: Use args to pass error object as 1st param if there is an error.
       
   511                 //args: 
       
   512             });
       
   513         }
       
   514 
       
   515         eventQueue.run();
       
   516     },
       
   517 
       
   518     /**
       
   519      * The "tap" gesture can be used for various single touch point gestures 
       
   520      * such as single tap, N number of taps, long press. The default is a single 
       
   521      * tap.
       
   522      * 
       
   523      * @method tap
       
   524      * @param {Function} cb The callback to execute when the gesture simulation 
       
   525      *      is completed.
       
   526      * @param {Array} point A point(relative to the top left corner of the 
       
   527      *      target node element) where the tap gesture should start. The default 
       
   528      *      is the center of the taget node.
       
   529      * @param {Number} times The number of taps. Default is 1.
       
   530      * @param {Number} hold The hold time in milliseconds between "touchstart" and
       
   531      *      "touchend" event generation. Default is 10ms.
       
   532      * @param {Number} delay The time gap in millisecond between taps if this
       
   533      *      gesture has more than 1 tap. Default is 10ms.
       
   534      */
       
   535     tap: function(cb, point, times, hold, delay) {           
       
   536         var eventQueue = new Y.AsyncQueue(),
       
   537             emptyTouchList = this._getEmptyTouchList(),
       
   538             touches,
       
   539             coord,
       
   540             i,
       
   541             touchStart,
       
   542             touchEnd;
       
   543 
       
   544         point = this._calculateDefaultPoint(point);
       
   545 
       
   546         if(!Y.Lang.isNumber(times) || times < 1) {
       
   547             times = 1;
       
   548         }
       
   549 
       
   550         if(!Y.Lang.isNumber(hold)) {
       
   551             hold = DEFAULTS.HOLD_TAP;
       
   552         }
       
   553 
       
   554         if(!Y.Lang.isNumber(delay)) {
       
   555             delay = DEFAULTS.DELAY_TAP;
       
   556         }
       
   557 
       
   558         coord = {
       
   559             pageX: point[0], 
       
   560             pageY: point[1],
       
   561             clientX: point[0], 
       
   562             clientY: point[1]
       
   563         };
       
   564 
       
   565         touches = this._createTouchList([Y.merge({identifier: 0}, coord)]);
       
   566 
       
   567         touchStart = function() {
       
   568             this._simulateEvent(this.target, TOUCH_START, Y.merge({
       
   569                 touches: touches,
       
   570                 targetTouches: touches,
       
   571                 changedTouches: touches
       
   572             }, coord));
       
   573         };
       
   574         
       
   575         touchEnd = function() {
       
   576             this._simulateEvent(this.target, TOUCH_END, Y.merge({
       
   577                 touches: emptyTouchList,
       
   578                 targetTouches: emptyTouchList,
       
   579                 changedTouches: touches
       
   580             }, coord));
       
   581         };
       
   582         
       
   583         for (i=0; i < times; i++) {
       
   584             eventQueue.add({
       
   585                 fn: touchStart,
       
   586                 context: this,
       
   587                 timeout: (i === 0)? 0 : delay
       
   588             });
       
   589 
       
   590             eventQueue.add({
       
   591                 fn: touchEnd,
       
   592                 context: this,
       
   593                 timeout: hold
       
   594             });
       
   595         }
       
   596 
       
   597         if(times > 1 && !SUPPORTS_TOUCH) {
       
   598             eventQueue.add({
       
   599                 fn: function() {
       
   600                     this._simulateEvent(this.target, MOUSE_DBLCLICK, coord);
       
   601                 },
       
   602                 context: this
       
   603             });
       
   604         }
       
   605 
       
   606         if(cb && Y.Lang.isFunction(cb)) {
       
   607             eventQueue.add({
       
   608                 fn: cb,
       
   609 
       
   610                 // by default, the callback runs the node context where 
       
   611                 // simulateGesture method is called.
       
   612                 context: this.node
       
   613 
       
   614                 //TODO: Use args to pass error object as 1st param if there is an error.
       
   615                 //args: 
       
   616             });
       
   617         }
       
   618 
       
   619         eventQueue.run();
       
   620     },
       
   621 
       
   622     /**
       
   623      * The "flick" gesture is a specialized "move" that has some velocity 
       
   624      * and the movement always runs either x or y axis. The velocity is calculated
       
   625      * with "distance" and "duration" arguments. If the calculated velocity is 
       
   626      * below than the minimum velocity, the given duration will be ignored and 
       
   627      * new duration will be created to make a valid flick gesture.
       
   628      *   
       
   629      * @method flick
       
   630      * @param {Function} cb The callback to execute when the gesture simulation 
       
   631      *      is completed.
       
   632      * @param {Array} point A point(relative to the top left corner of the 
       
   633      *      target node element) where the flick gesture should start. The default 
       
   634      *      is the center of the taget node.
       
   635      * @param {String} axis Either "x" or "y".
       
   636      * @param {Number} distance A distance in pixels to flick.
       
   637      * @param {Number} duration A duration of the gesture in millisecond.
       
   638      * 
       
   639      */
       
   640     flick: function(cb, point, axis, distance, duration) {
       
   641         var path;
       
   642 
       
   643         point = this._calculateDefaultPoint(point);
       
   644 
       
   645         if(!Y.Lang.isString(axis)) {
       
   646             axis = X_AXIS;
       
   647         } else {
       
   648             axis = axis.toLowerCase();
       
   649             if(axis !== X_AXIS && axis !== Y_AXIS) {
       
   650                 Y.error(NAME+'(flick): Only x or y axis allowed');
       
   651             }
       
   652         }
       
   653 
       
   654         if(!Y.Lang.isNumber(distance)) { 
       
   655             distance = DEFAULTS.DISTANCE_FLICK; 
       
   656         }
       
   657 
       
   658         if(!Y.Lang.isNumber(duration)){
       
   659             duration = DEFAULTS.DURATION_FLICK; // ms
       
   660         } else {
       
   661             if(duration > DEFAULTS.MAX_DURATION_FLICK) {
       
   662                 duration = DEFAULTS.MAX_DURATION_FLICK;
       
   663             }
       
   664         }
       
   665 
       
   666         /*
       
   667          * Check if too slow for a flick.
       
   668          * Adjust duration if the calculated velocity is less than 
       
   669          * the minimum velcocity to be claimed as a flick.
       
   670          */
       
   671         if(Math.abs(distance)/duration < DEFAULTS.MIN_VELOCITY_FLICK) {
       
   672             duration = Math.abs(distance)/DEFAULTS.MIN_VELOCITY_FLICK;
       
   673         }
       
   674 
       
   675         path = {
       
   676             start: Y.clone(point),
       
   677             end: [
       
   678                 (axis === X_AXIS) ? point[0]+distance : point[0],
       
   679                 (axis === Y_AXIS) ? point[1]+distance : point[1]
       
   680             ]
       
   681         };
       
   682 
       
   683         this._move(cb, path, duration);
       
   684     },
       
   685 
       
   686     /**
       
   687      * The "move" gesture simulate the movement of any direction between 
       
   688      * the straight line of start and end point for the given duration.
       
   689      * The path argument is an object with "point", "xdist" and "ydist" properties.
       
   690      * The "point" property is an array with x and y coordinations(relative to the
       
   691      * top left corner of the target node element) while "xdist" and "ydist" 
       
   692      * properties are used for the distance along the x and y axis. A negative 
       
   693      * distance number can be used to drag either left or up direction. 
       
   694      * 
       
   695      * If no arguments are given, it will simulate the default move, which
       
   696      * is moving 200 pixels from the center of the element to the positive X-axis 
       
   697      * direction for 1 sec.
       
   698      * 
       
   699      * @method move
       
   700      * @param {Function} cb The callback to execute when the gesture simulation 
       
   701      *      is completed.
       
   702      * @param {Object} path An object with "point", "xdist" and "ydist".
       
   703      * @param {Number} duration A duration of the gesture in millisecond.
       
   704      */
       
   705     move: function(cb, path, duration) {
       
   706         var convertedPath;
       
   707 
       
   708         if(!Y.Lang.isObject(path)) {
       
   709             path = {
       
   710                 point: this._calculateDefaultPoint([]),
       
   711                 xdist: DEFAULTS.DISTANCE_MOVE,
       
   712                 ydist: 0
       
   713             };
       
   714         } else {
       
   715             // convert to the page coordination
       
   716             if(!Y.Lang.isArray(path.point)) {
       
   717                 path.point = this._calculateDefaultPoint([]);
       
   718             } else {
       
   719                 path.point = this._calculateDefaultPoint(path.point);
       
   720             }
       
   721 
       
   722             if(!Y.Lang.isNumber(path.xdist)) {
       
   723                 path.xdist = DEFAULTS.DISTANCE_MOVE;
       
   724             }
       
   725 
       
   726             if(!Y.Lang.isNumber(path.ydist)) {
       
   727                 path.ydist = 0;
       
   728             }
       
   729         }
       
   730 
       
   731         if(!Y.Lang.isNumber(duration)){
       
   732             duration = DEFAULTS.DURATION_MOVE; // ms
       
   733         } else {
       
   734             if(duration > DEFAULTS.MAX_DURATION_MOVE) {
       
   735                 duration = DEFAULTS.MAX_DURATION_MOVE;
       
   736             }
       
   737         }
       
   738 
       
   739         convertedPath = {
       
   740             start: Y.clone(path.point),
       
   741             end: [path.point[0]+path.xdist, path.point[1]+path.ydist]
       
   742         };
       
   743 
       
   744         this._move(cb, convertedPath, duration);
       
   745     },
       
   746 
       
   747     /**
       
   748      * A base method on top of "move" and "flick" methods. The method takes
       
   749      * the path with start/end properties and duration to generate a set of 
       
   750      * touch events for the movement gesture. 
       
   751      *
       
   752      * @method _move
       
   753      * @private
       
   754      * @param {Function} cb The callback to execute when the gesture simulation 
       
   755      *      is completed.
       
   756      * @param {Object} path An object with "start" and "end" properties. Each 
       
   757      *      property should be an array with x and y coordination (e.g. start: [100, 50])
       
   758      * @param {Number} duration A duration of the gesture in millisecond. 
       
   759      */
       
   760     _move: function(cb, path, duration) {
       
   761         var eventQueue,
       
   762             i,
       
   763             interval = EVENT_INTERVAL,
       
   764             steps, stepX, stepY,
       
   765             id = 0,
       
   766             touchMove;
       
   767 
       
   768         if(!Y.Lang.isNumber(duration)){
       
   769             duration = DEFAULTS.DURATION_MOVE; // ms
       
   770         } else {
       
   771             if(duration > DEFAULTS.MAX_DURATION_MOVE) {
       
   772                 duration = DEFAULTS.MAX_DURATION_MOVE;
       
   773             }
       
   774         }
       
   775 
       
   776         if(!Y.Lang.isObject(path)) {
       
   777             path = {
       
   778                 start: [
       
   779                     START_PAGEX, 
       
   780                     START_PAGEY
       
   781                 ], 
       
   782                 end: [
       
   783                     START_PAGEX + DEFAULTS.DISTANCE_MOVE, 
       
   784                     START_PAGEY
       
   785                 ]
       
   786             };
       
   787         } else {
       
   788             if(!Y.Lang.isArray(path.start)) {
       
   789                 path.start = [
       
   790                     START_PAGEX, 
       
   791                     START_PAGEY
       
   792                 ];
       
   793             }
       
   794             if(!Y.Lang.isArray(path.end)) {
       
   795                 path.end = [
       
   796                     START_PAGEX + DEFAULTS.DISTANCE_MOVE, 
       
   797                     START_PAGEY
       
   798                 ];
       
   799             }
       
   800         }
       
   801 
       
   802         Y.AsyncQueue.defaults.timeout = interval;
       
   803         eventQueue = new Y.AsyncQueue();
       
   804 
       
   805         // start
       
   806         eventQueue.add({
       
   807             fn: function() {
       
   808                 var coord = {
       
   809                         pageX: path.start[0], 
       
   810                         pageY: path.start[1],
       
   811                         clientX: path.start[0], 
       
   812                         clientY: path.start[1]
       
   813                     }, 
       
   814                     touches = this._createTouchList([
       
   815                         Y.merge({identifier: (id++)}, coord)
       
   816                     ]);
       
   817 
       
   818                 this._simulateEvent(this.target, TOUCH_START, Y.merge({
       
   819                     touches: touches,
       
   820                     targetTouches: touches,
       
   821                     changedTouches: touches
       
   822                 }, coord));
       
   823             },
       
   824             timeout: 0,
       
   825             context: this
       
   826         });
       
   827 
       
   828         // move
       
   829         steps = Math.floor(duration/interval);
       
   830         stepX = (path.end[0] - path.start[0])/steps;
       
   831         stepY = (path.end[1] - path.start[1])/steps;
       
   832 
       
   833         touchMove = function(step) {
       
   834             var px = path.start[0]+(stepX * step),
       
   835                 py = path.start[1]+(stepY * step), 
       
   836                 coord = {
       
   837                     pageX: px, 
       
   838                     pageY: py,
       
   839                     clientX: px,
       
   840                     clientY: py
       
   841                 }, 
       
   842                 touches = this._createTouchList([
       
   843                     Y.merge({identifier: (id++)}, coord)
       
   844                 ]);
       
   845 
       
   846             this._simulateEvent(this.target, TOUCH_MOVE, Y.merge({
       
   847                 touches: touches,
       
   848                 targetTouches: touches,
       
   849                 changedTouches: touches
       
   850             }, coord));
       
   851         };
       
   852 
       
   853         for (i=0; i < steps; i++) {
       
   854             eventQueue.add({
       
   855                 fn: touchMove,
       
   856                 args: [i],
       
   857                 context: this
       
   858             });
       
   859         }
       
   860 
       
   861         // last move
       
   862         eventQueue.add({
       
   863             fn: function() {
       
   864                 var coord = {
       
   865                         pageX: path.end[0], 
       
   866                         pageY: path.end[1],
       
   867                         clientX: path.end[0], 
       
   868                         clientY: path.end[1]
       
   869                     },
       
   870                     touches = this._createTouchList([
       
   871                         Y.merge({identifier: id}, coord)
       
   872                     ]);
       
   873 
       
   874                 this._simulateEvent(this.target, TOUCH_MOVE, Y.merge({
       
   875                     touches: touches,
       
   876                     targetTouches: touches,
       
   877                     changedTouches: touches
       
   878                 }, coord));
       
   879             },
       
   880             timeout: 0,
       
   881             context: this
       
   882         });
       
   883 
       
   884         // end
       
   885         eventQueue.add({
       
   886             fn: function() {
       
   887                 var coord = {
       
   888                     pageX: path.end[0], 
       
   889                     pageY: path.end[1],
       
   890                     clientX: path.end[0], 
       
   891                     clientY: path.end[1]
       
   892                 },
       
   893                 emptyTouchList = this._getEmptyTouchList(),
       
   894                 touches = this._createTouchList([
       
   895                     Y.merge({identifier: id}, coord)
       
   896                 ]);
       
   897 
       
   898                 this._simulateEvent(this.target, TOUCH_END, Y.merge({
       
   899                     touches: emptyTouchList,
       
   900                     targetTouches: emptyTouchList,
       
   901                     changedTouches: touches
       
   902                 }, coord));
       
   903             },
       
   904             context: this
       
   905         });
       
   906         
       
   907         if(cb && Y.Lang.isFunction(cb)) {
       
   908             eventQueue.add({
       
   909                 fn: cb,
       
   910 
       
   911                 // by default, the callback runs the node context where 
       
   912                 // simulateGesture method is called.
       
   913                 context: this.node
       
   914 
       
   915                 //TODO: Use args to pass error object as 1st param if there is an error.
       
   916                 //args: 
       
   917             });
       
   918         }
       
   919         
       
   920         eventQueue.run();
       
   921     },
       
   922 
       
   923     /**
       
   924      * Helper method to return a singleton instance of empty touch list.
       
   925      * 
       
   926      * @method _getEmptyTouchList
       
   927      * @private
       
   928      * @return {TouchList | Array} An empty touch list object.
       
   929      */
       
   930     _getEmptyTouchList: function() {
       
   931         if(!emptyTouchList) {
       
   932             emptyTouchList = this._createTouchList([]);
       
   933         }
       
   934 
       
   935         return emptyTouchList;
       
   936     },
       
   937 
       
   938     /**
       
   939      * Helper method to convert an array with touch points to TouchList object as
       
   940      * defined in http://www.w3.org/TR/touch-events/
       
   941      * 
       
   942      * @method _createTouchList
       
   943      * @private
       
   944      * @param {Array} touchPoints 
       
   945      * @return {TouchList | Array} If underlaying platform support creating touch list
       
   946      *      a TouchList object will be returned otherwise a fake Array object 
       
   947      *      will be returned.
       
   948      */
       
   949     _createTouchList: function(touchPoints) {
       
   950         /*
       
   951         * Android 4.0.3 emulator:
       
   952         * Native touch api supported starting in version 4.0 (Ice Cream Sandwich).
       
   953         * However the support seems limited. In Android 4.0.3 emulator, I got
       
   954         * "TouchList is not defined".
       
   955         */
       
   956         var touches = [],
       
   957             touchList,
       
   958             self = this;
       
   959 
       
   960         if(!!touchPoints && Y.Lang.isArray(touchPoints)) {
       
   961             if(Y.UA.android && Y.UA.android >= 4.0 || Y.UA.ios && Y.UA.ios >= 2.0) {
       
   962                 Y.each(touchPoints, function(point) {
       
   963                     if(!point.identifier) {point.identifier = 0;}
       
   964                     if(!point.pageX) {point.pageX = 0;}
       
   965                     if(!point.pageY) {point.pageY = 0;}
       
   966                     if(!point.screenX) {point.screenX = 0;}
       
   967                     if(!point.screenY) {point.screenY = 0;}
       
   968 
       
   969                     touches.push(document.createTouch(Y.config.win, 
       
   970                         self.target,
       
   971                         point.identifier, 
       
   972                         point.pageX, point.pageY, 
       
   973                         point.screenX, point.screenY));
       
   974                 });
       
   975 
       
   976                 touchList = document.createTouchList.apply(document, touches);
       
   977             } else if(Y.UA.ios && Y.UA.ios < 2.0) { 
       
   978                 Y.error(NAME+': No touch event simulation framework present.');
       
   979             } else {
       
   980                 // this will inclide android(Y.UA.android && Y.UA.android < 4.0) 
       
   981                 // and desktops among all others. 
       
   982 
       
   983                 /*
       
   984                  * Touch APIs are broken in androids older than 4.0. We will use 
       
   985                  * simulated touch apis for these versions. 
       
   986                  */
       
   987                 touchList = [];
       
   988                 Y.each(touchPoints, function(point) {
       
   989                     if(!point.identifier) {point.identifier = 0;}
       
   990                     if(!point.clientX)  {point.clientX = 0;}
       
   991                     if(!point.clientY)  {point.clientY = 0;}
       
   992                     if(!point.pageX)    {point.pageX = 0;}
       
   993                     if(!point.pageY)    {point.pageY = 0;}
       
   994                     if(!point.screenX)  {point.screenX = 0;}
       
   995                     if(!point.screenY)  {point.screenY = 0;}
       
   996 
       
   997                     touchList.push({
       
   998                         target: self.target,
       
   999                         identifier: point.identifier,
       
  1000                         clientX: point.clientX,
       
  1001                         clientY: point.clientY,
       
  1002                         pageX: point.pageX,
       
  1003                         pageY: point.pageY,
       
  1004                         screenX: point.screenX,
       
  1005                         screenY: point.screenY
       
  1006                     });
       
  1007                 });
       
  1008 
       
  1009                 touchList.item = function(i) {
       
  1010                     return touchList[i];
       
  1011                 };
       
  1012             }
       
  1013         } else {
       
  1014             Y.error(NAME+': Invalid touchPoints passed');
       
  1015         }
       
  1016 
       
  1017         return touchList;
       
  1018     },
       
  1019 
       
  1020     /**
       
  1021      * @method _simulateEvent
       
  1022      * @private
       
  1023      * @param {HTMLElement} target The DOM element that's the target of the event.
       
  1024      * @param {String} type The type of event or name of the supported gesture to simulate 
       
  1025      *      (i.e., "click", "doubletap", "flick").
       
  1026      * @param {Object} options (Optional) Extra options to copy onto the event object. 
       
  1027      *      For gestures, options are used to refine the gesture behavior.
       
  1028      * @return {void}
       
  1029      */
       
  1030     _simulateEvent: function(target, type, options) {
       
  1031         var touches;
       
  1032 
       
  1033         if (touchEvents[type]) {
       
  1034             if(SUPPORTS_TOUCH) {
       
  1035                 Y.Event.simulate(target, type, options);
       
  1036             } else {
       
  1037                 // simulate using mouse events if touch is not applicable on this platform.
       
  1038                 // but only single touch event can be simulated.
       
  1039                 if(this._isSingleTouch(options.touches, options.targetTouches, options.changedTouches)) {
       
  1040                     type = {
       
  1041                         touchstart: MOUSE_DOWN,
       
  1042                         touchmove: MOUSE_MOVE,
       
  1043                         touchend: MOUSE_UP
       
  1044                     }[type];
       
  1045 
       
  1046                     options.button = 0;
       
  1047                     options.relatedTarget = null; // since we are not using mouseover event.
       
  1048 
       
  1049                     // touchend has none in options.touches.
       
  1050                     touches = (type === MOUSE_UP)? options.changedTouches : options.touches;
       
  1051 
       
  1052                     options = Y.mix(options, {
       
  1053                         screenX: touches.item(0).screenX,
       
  1054                         screenY: touches.item(0).screenY,
       
  1055                         clientX: touches.item(0).clientX,
       
  1056                         clientY: touches.item(0).clientY
       
  1057                     }, true);
       
  1058 
       
  1059                     Y.Event.simulate(target, type, options);
       
  1060 
       
  1061                     if(type == MOUSE_UP) {
       
  1062                         Y.Event.simulate(target, MOUSE_CLICK, options);
       
  1063                     }
       
  1064                 } else {
       
  1065                     Y.error("_simulateEvent(): Event '" + type + "' has multi touch objects that can't be simulated in your platform.");
       
  1066                 }
       
  1067             }
       
  1068         } else {
       
  1069             // pass thru for all non touch events
       
  1070             Y.Event.simulate(target, type, options);
       
  1071         }
       
  1072     },
       
  1073 
       
  1074     /**
       
  1075      * Helper method to check the single touch.
       
  1076      * @method _isSingleTouch
       
  1077      * @private
       
  1078      * @param {TouchList} touches
       
  1079      * @param {TouchList} targetTouches
       
  1080      * @param {TouchList} changedTouches
       
  1081      */
       
  1082     _isSingleTouch: function(touches, targetTouches, changedTouches) {
       
  1083         return (touches && (touches.length <= 1)) && 
       
  1084             (targetTouches && (targetTouches.length <= 1)) &&
       
  1085             (changedTouches && (changedTouches.length <= 1));
       
  1086     }
       
  1087 };
       
  1088 
       
  1089 /*
       
  1090  * A gesture simulation class.
       
  1091  */
       
  1092 Y.GestureSimulation = Simulations;
       
  1093 
       
  1094 /*
       
  1095  * Various simulation default behavior properties. If user override 
       
  1096  * Y.GestureSimulation.defaults, overriden values will be used and this 
       
  1097  * should be done before the gesture simulation.  
       
  1098  */
       
  1099 Y.GestureSimulation.defaults = DEFAULTS;
       
  1100 
       
  1101 /*
       
  1102  * The high level gesture names that YUI knows how to simulate.
       
  1103  */
       
  1104 Y.GestureSimulation.GESTURES = gestureNames;
       
  1105 
       
  1106 /**
       
  1107  * Simulates the higher user level gesture of the given name on a target. 
       
  1108  * This method generates a set of low level touch events(Apple specific gesture 
       
  1109  * events as well for the iOS platforms) asynchronously. Note that gesture  
       
  1110  * simulation is relying on `Y.Event.simulate()` method to generate 
       
  1111  * the touch events under the hood. The `Y.Event.simulate()` method
       
  1112  * itself is a synchronous method.
       
  1113  * 
       
  1114  * Users are suggested to use `Node.simulateGesture()` method which 
       
  1115  * basically calls this method internally. Supported gestures are `tap`, 
       
  1116  * `doubletap`, `press`, `move`, `flick`, `pinch` and `rotate`.
       
  1117  * 
       
  1118  * The `pinch` gesture is used to simulate the pinching and spreading of two
       
  1119  * fingers. During a pinch simulation, rotation is also possible. Essentially
       
  1120  * `pinch` and `rotate` simulations share the same base implementation to allow
       
  1121  * both pinching and rotation at the same time. The only difference is `pinch`
       
  1122  * requires `start` and `end` option properties while `rotate` requires `rotation` 
       
  1123  * option property.
       
  1124  * 
       
  1125  * The `pinch` and `rotate` gestures can be described as placing 2 fingers along a
       
  1126  * circle. Pinching and spreading can be described by start and end circles while 
       
  1127  * rotation occurs on a single circle. If the radius of the start circle is greater 
       
  1128  * than the end circle, the gesture becomes a pinch, otherwise it is a spread spread.
       
  1129  * 
       
  1130  * @example
       
  1131  *
       
  1132  *     var node = Y.one("#target");
       
  1133  *       
       
  1134  *     // double tap example
       
  1135  *     node.simulateGesture("doubletap", function() {
       
  1136  *         // my callback function
       
  1137  *     });
       
  1138  *     
       
  1139  *     // flick example from the center of the node, move 50 pixels down for 50ms)
       
  1140  *     node.simulateGesture("flick", {
       
  1141  *         axis: y,
       
  1142  *         distance: -100
       
  1143  *         duration: 50
       
  1144  *     }, function() {
       
  1145  *         // my callback function
       
  1146  *     });
       
  1147  *     
       
  1148  *     // simulate rotating a node 75 degrees counter-clockwise 
       
  1149  *     node.simulateGesture("rotate", {
       
  1150  *         rotation: -75
       
  1151  *     });
       
  1152  *
       
  1153  *     // simulate a pinch and a rotation at the same time. 
       
  1154  *     // fingers start on a circle of radius 100 px, placed at top/bottom
       
  1155  *     // fingers end on a circle of radius 50px, placed at right/left 
       
  1156  *     node.simulateGesture("pinch", {
       
  1157  *         r1: 100,
       
  1158  *         r2: 50,
       
  1159  *         start: 0
       
  1160  *         rotation: 90
       
  1161  *     });
       
  1162  *     
       
  1163  * @method simulateGesture
       
  1164  * @param {HTMLElement|Node} node The YUI node or HTML element that's the target 
       
  1165  *      of the event.
       
  1166  * @param {String} name The name of the supported gesture to simulate. The 
       
  1167  *      supported gesture name is one of "tap", "doubletap", "press", "move", 
       
  1168  *      "flick", "pinch" and "rotate". 
       
  1169  * @param {Object} [options] Extra options used to define the gesture behavior:
       
  1170  * 
       
  1171  *      Valid options properties for the `tap` gesture:
       
  1172  *      
       
  1173  *      @param {Array} [options.point] (Optional) Indicates the [x,y] coordinates 
       
  1174  *        where the tap should be simulated. Default is the center of the node 
       
  1175  *        element.
       
  1176  *      @param {Number} [options.hold=10] (Optional) The hold time in milliseconds. 
       
  1177  *        This is the time between `touchstart` and `touchend` event generation.
       
  1178  *      @param {Number} [options.times=1] (Optional) Indicates the number of taps.
       
  1179  *      @param {Number} [options.delay=10] (Optional) The number of milliseconds 
       
  1180  *        before the next tap simulation happens. This is valid only when `times` 
       
  1181  *        is more than 1. 
       
  1182  *        
       
  1183  *      Valid options properties for the `doubletap` gesture:
       
  1184  *      
       
  1185  *      @param {Array} [options.point] (Optional) Indicates the [x,y] coordinates 
       
  1186  *        where the doubletap should be simulated. Default is the center of the 
       
  1187  *        node element.
       
  1188  * 
       
  1189  *      Valid options properties for the `press` gesture:
       
  1190  *      
       
  1191  *      @param {Array} [options.point] (Optional) Indicates the [x,y] coordinates 
       
  1192  *        where the press should be simulated. Default is the center of the node 
       
  1193  *        element.
       
  1194  *      @param {Number} [options.hold=3000] (Optional) The hold time in milliseconds. 
       
  1195  *        This is the time between `touchstart` and `touchend` event generation. 
       
  1196  *        Default is 3000ms (3 seconds).
       
  1197  * 
       
  1198  *      Valid options properties for the `move` gesture:
       
  1199  *      
       
  1200  *      @param {Object} [options.path] (Optional) Indicates the path of the finger 
       
  1201  *        movement. It's an object with three optional properties: `point`, 
       
  1202  *        `xdist` and  `ydist`.
       
  1203  *        @param {Array} [options.path.point] A starting point of the gesture.
       
  1204  *          Default is the center of the node element.
       
  1205  *        @param {Number} [options.path.xdist=200] A distance to move in pixels  
       
  1206  *          along the X axis. A negative distance value indicates moving left.
       
  1207  *        @param {Number} [options.path.ydist=0] A distance to move in pixels  
       
  1208  *          along the Y axis. A negative distance value indicates moving up.
       
  1209  *      @param {Number} [options.duration=1000] (Optional) The duration of the 
       
  1210  *        gesture in milliseconds.
       
  1211  * 
       
  1212  *      Valid options properties for the `flick` gesture:
       
  1213  *      
       
  1214  *      @param {Array} [options.point] (Optional) Indicates the [x, y] coordinates 
       
  1215  *        where the flick should be simulated. Default is the center of the 
       
  1216  *        node element.
       
  1217  *      @param {String} [options.axis='x'] (Optional) Valid values are either 
       
  1218  *        "x" or "y". Indicates axis to move along. The flick can move to one of 
       
  1219  *        4 directions(left, right, up and down).
       
  1220  *      @param {Number} [options.distance=200] (Optional) Distance to move in pixels
       
  1221  *      @param {Number} [options.duration=1000] (Optional) The duration of the 
       
  1222  *        gesture in milliseconds. User given value could be automatically 
       
  1223  *        adjusted by the framework if it is below the minimum velocity to be 
       
  1224  *        a flick gesture.
       
  1225  * 
       
  1226  *      Valid options properties for the `pinch` gesture:
       
  1227  *      
       
  1228  *      @param {Array} [options.center] (Optional) The center of the circle where 
       
  1229  *        two fingers are placed. Default is the center of the node element.
       
  1230  *      @param {Number} [options.r1] (Required) Pixel radius of the start circle 
       
  1231  *        where 2 fingers will be on when the gesture starts. The circles are 
       
  1232  *        centered at the center of the element.
       
  1233  *      @param {Number} [options.r2] (Required) Pixel radius of the end circle 
       
  1234  *        when this gesture ends.
       
  1235  *      @param {Number} [options.duration=1000] (Optional) The duration of the 
       
  1236  *        gesture in milliseconds.
       
  1237  *      @param {Number} [options.start=0] (Optional) Starting degree of the first 
       
  1238  *        finger. The value is relative to the path of the north. Default is 0 
       
  1239  *        (i.e., 12:00 on a clock).
       
  1240  *      @param {Number} [options.rotation=0] (Optional) Degrees to rotate from 
       
  1241  *        the starting degree. A negative value means rotation to the 
       
  1242  *        counter-clockwise direction.
       
  1243  * 
       
  1244  *      Valid options properties for the `rotate` gesture:
       
  1245  *      
       
  1246  *      @param {Array} [options.center] (Optional) The center of the circle where 
       
  1247  *        two fingers are placed. Default is the center of the node element.
       
  1248  *      @param {Number} [options.r1] (Optional) Pixel radius of the start circle 
       
  1249  *        where 2 fingers will be on when the gesture starts. The circles are 
       
  1250  *        centered at the center of the element. Default is a fourth of the node 
       
  1251  *        element width or height, whichever is smaller.
       
  1252  *      @param {Number} [options.r2] (Optional) Pixel radius of the end circle 
       
  1253  *        when this gesture ends. Default is a fourth of the node element width or 
       
  1254  *        height, whichever is smaller.
       
  1255  *      @param {Number} [options.duration=1000] (Optional) The duration of the 
       
  1256  *        gesture in milliseconds.
       
  1257  *      @param {Number} [options.start=0] (Optional) Starting degree of the first 
       
  1258  *        finger. The value is relative to the path of the north. Default is 0 
       
  1259  *        (i.e., 12:00 on a clock).
       
  1260  *      @param {Number} [options.rotation] (Required) Degrees to rotate from 
       
  1261  *        the starting degree. A negative value means rotation to the 
       
  1262  *        counter-clockwise direction.
       
  1263  * 
       
  1264  * @param {Function} [cb] The callback to execute when the asynchronouse gesture  
       
  1265  *      simulation is completed. 
       
  1266  *      @param {Error} cb.err An error object if the simulation is failed.  
       
  1267  * @return {void}
       
  1268  * @for Event
       
  1269  * @static
       
  1270  */
       
  1271 Y.Event.simulateGesture = function(node, name, options, cb) {
       
  1272 
       
  1273     node = Y.one(node);    
       
  1274 
       
  1275     var sim = new Y.GestureSimulation(node);
       
  1276     name = name.toLowerCase();
       
  1277 
       
  1278     if(!cb && Y.Lang.isFunction(options)) {
       
  1279         cb = options;
       
  1280         options = {};
       
  1281     }
       
  1282 
       
  1283     options = options || {};
       
  1284 
       
  1285     if (gestureNames[name]) {
       
  1286         switch(name) {
       
  1287             // single-touch: point gestures 
       
  1288             case 'tap':
       
  1289                 sim.tap(cb, options.point, options.times, options.hold, options.delay);
       
  1290                 break;
       
  1291             case 'doubletap':
       
  1292                 sim.tap(cb, options.point, 2);
       
  1293                 break;
       
  1294             case 'press':
       
  1295                 if(!Y.Lang.isNumber(options.hold)) {
       
  1296                     options.hold = DEFAULTS.HOLD_PRESS;
       
  1297                 } else if(options.hold < DEFAULTS.MIN_HOLD_PRESS) {
       
  1298                     options.hold = DEFAULTS.MIN_HOLD_PRESS;
       
  1299                 } else if(options.hold > DEFAULTS.MAX_HOLD_PRESS) {
       
  1300                     options.hold = DEFAULTS.MAX_HOLD_PRESS;
       
  1301                 }
       
  1302                 sim.tap(cb, options.point, 1, options.hold);
       
  1303                 break;
       
  1304 
       
  1305             // single-touch: move gestures 
       
  1306             case 'move':
       
  1307                 sim.move(cb, options.path, options.duration);
       
  1308                 break;
       
  1309             case 'flick':
       
  1310                 sim.flick(cb, options.point, options.axis, options.distance, 
       
  1311                     options.duration);
       
  1312                 break;
       
  1313 
       
  1314             // multi-touch: pinch/rotation gestures
       
  1315             case 'pinch':
       
  1316                 sim.pinch(cb, options.center, options.r1, options.r2, 
       
  1317                     options.duration, options.start, options.rotation);
       
  1318                 break;    
       
  1319             case 'rotate':
       
  1320                 sim.rotate(cb, options.center, options.r1, options.r2, 
       
  1321                     options.duration, options.start, options.rotation);
       
  1322                 break;
       
  1323         }
       
  1324     } else {
       
  1325         Y.error(NAME+': Not a supported gesture simulation: '+name);
       
  1326     }
       
  1327 };
       
  1328 
       
  1329 
       
  1330 }, '3.10.3', {"requires": ["async-queue", "event-simulate", "node-screen"]});