Home Reference Source Test

src/gestures/Swipe.js

/**
 * @file Swipe.js
 * Contains the Swipe class
 */

import Gesture from './Gesture.js';
import util from './../core/util.js';

const DEFAULT_INPUTS = 1;
const DEFAULT_MAX_REST_TIME = 100;
const DEFAULT_ESCAPE_VELOCITY = 0.2;
const DEFAULT_TIME_DISTORTION = 100;
const DEFAULT_MAX_PROGRESS_STACK = 10;

/**
 * A swipe is defined as input(s) moving in the same direction in an relatively increasing velocity
 * and leaving the screen at some point before it drops below it's escape velocity.
 * @class Swipe
 */
class Swipe extends Gesture {

  /**
   * Constructor function for the Swipe class.
   * @param {Object} [options] - The options object.
   * @param {Number} [options.numInputs] - The number of inputs to trigger a Swipe can be variable,
   *  and the maximum number being a factor of the browser.
   *  move and current move events.
   * @param {Number} [options.maxRestTime] - The maximum resting time a point has between it's last
   * @param {Number} [options.escapeVelocity] - The minimum velocity the input has to be at to emit a swipe.
   * @param {Number} [options.timeDistortion] - (EXPERIMENTAL) A value of time in milliseconds to distort between events.
   * @param {Number} [options.maxProgressStack] - (EXPERIMENTAL)The maximum amount of move events to keep
   * track of for a swipe.
   */
  constructor(options) {
    super();
    /**
     * The type of the Gesture
     * @type {String}
     */
    this.type = 'swipe';

    /**
     * The number of inputs to trigger a Swipe can be variable, and the maximum number being
     * a factor of the browser.
     * @type {Number}
     */
    this.numInputs = (options && options.numInputs) ? options.numInputs : DEFAULT_INPUTS;

    /**
     * The maximum resting time a point has between it's last move and current move events.
     * @type {Number}
     */
    this.maxRestTime = (options && options.maxRestTime) ? options.maxRestTime : DEFAULT_MAX_REST_TIME;

    /**
     * The minimum velocity the input has to be at to emit a swipe. This is useful for determining
     * the difference between
     * a swipe and a pan gesture.
     * @type {number}
     */
    this.escapeVelocity = (options && options.escapeVelocity) ? options.escapeVelocity : DEFAULT_ESCAPE_VELOCITY;

    /**
     * (EXPERIMENTAL) A value of time in milliseconds to distort between events. Browsers do not accurately
     * measure time with the Date constructor in milliseconds, so consecutive events sometimes
     * display the same timestamp but different x/y coordinates. This will distort a previous time
     * in such cases by the timeDistortion's value.
     * @type {number}
     */
    this.timeDistortion = (options && options.timeDistortion) ? options.timeDistortion : DEFAULT_TIME_DISTORTION;

    /**
     * (EXPERIMENTAL) The maximum amount of move events to keep track of for a swipe. This helps give a more
     * accurate estimate of the user's velocity.
     * @type {number}
     */
    this.maxProgressStack = (options && options.maxProgressStack) ? options.maxProgressStack : DEFAULT_MAX_PROGRESS_STACK;
  }

  /**
   * Event hook for the move of a gesture. Captures an input's x/y coordinates and the time of
   * it's event on a stack.
   * @param {Array} inputs - The array of Inputs on the screen.
   * @param {Object} state - The state object of the current region.
   * @returns {null} - Swipe does not emit from a move.
   */
  move(inputs, state) {
    if (this.numInputs === inputs.length) {
      for (var i = 0; i < inputs.length; i++) {
        var progress = inputs[i].getGestureProgress(this.getId());
        if (!progress.moves) {
          progress.moves = [];
        }

        progress.moves.push({
          time: new Date().getTime(),
          x: inputs[i].current.x,
          y: inputs[i].current.y
        });

        if (progress.length > this.maxProgressStack) {
          progress.moves.shift();
        }
      }

    }

    return null;
  }

  /*move*/

  /**
   * Determines if the input's history validates a swipe motion. Determines if it did not come to
   * a complete stop (maxRestTime), and if it had enough of a velocity to be considered
   * (ESCAPE_VELOCITY).
   * @param {Array} inputs - The array of Inputs on the screen
   * @returns {null|Object} - null if the gesture is not to be emitted, Object with information otherwise.
   */
  end(inputs) {

    if (this.numInputs === inputs.length) {

      var output = {
        data: []
      };

      for (var i = 0; i < inputs.length; i++) {

        //Determine if all input events are on the 'end' event.
        if (inputs[i].current.type !== 'end') {
          return;
        }

        var progress = inputs[i].getGestureProgress(this.getId());
        if (progress.moves && progress.moves.length > 2) {
          //CHECK : Return if the input has not moved in maxRestTime ms.

          var currentMove = progress.moves.pop();
          if ((new Date().getTime()) - currentMove.time > this.maxRestTime) {
            return null;
          }

          var lastMove;
          var index = progress.moves.length - 1;

          //CHECK : Date is unreliable, so we retrieve the last move event where the time is not the same.
          while (index !== -1) {
            if (progress.moves[index].time !== currentMove.time) {
              lastMove = progress.moves[index];
              break;
            }

            index--;
          }

          //If the date is REALLY unreliable, we apply a time distortion to the last event.
          if (!lastMove) {
            lastMove = progress.moves.pop();
            lastMove.time += this.timeDistortion;
          }

          var velocity = util.getVelocity(lastMove.x, lastMove.y, lastMove.time,
            currentMove.x, currentMove.y, currentMove.time);

          output.data[i] = {
            velocity: velocity,
            currentDirection: util.getAngle(lastMove.x, lastMove.y, currentMove.x, currentMove.y)
          };
        }
      }

      for (var i = 0; i < output.data.length; i++) {
        if (velocity < this.escapeVelocity) {
          return null;
        }
      }

      if (output.data.length > 0) {
        return output;
      }

    }

    return null;

  }

  /*end*/
}

export default Swipe;