Source: common/models/touch.js

define(["lib/jquery",               "lib/underscore",        "lib/sylvester",
        "common/util/log",          "common/util/dom_utils", "common/util/id_generator",
        "common/util/binding_list", "ARCore/coordinate",     "common/util/check"],
function($,                          _,                       Sylvester,
         Log,                        DOMUtils,                IDGenerator,
         BindingList,                Coordinate,              check) {

    "use strict";

    var $V = Sylvester.$V;

    /**
     * @privconstructor
     * @class
     * Represents a single touch. See {@tutorial touch} for a description of the
     * Lens multitouch system.
     *
     * @property {String}  id        The ID of this touch. These IDs are not
     *                               unique: they are shared between Touches
     *                               that represent the same touch on different
     *                               elements.
     * @property {Element} el        Element this touch is fired on.
     * @property {Element} target    Element this touch started on before
     *                               bubbling up.
     * @property {Number}  origX     Original X coordinate of the touch.
     * @property {Number}  origY     Original Y coordinate of the touch.
     * @property {Number}  pageX     Current X coordinate of the touch.
     * @property {Number}  pageY     Current Y coordinate of the touch.
     * @property {Number}  elX       Current X coordinate of the touch,
     *                               relative to the parent element
     * @property {Number}  elY       Current Y coordinate of the touch,
     *                               relative to the parent element.
     * @property {Number}  dx        X displacement: pageX-origX
     * @property {Number}  dy        Y displacement: pageY-origY
     * @property {Number}  w         Width of the touch
     * @property {Number}  h         Height of the touch
     * @name Touch
     */
    var Touch = function(id, x, y, w, h, el, target) {
        /** @alias Touch.prototype */
        var self = {};

        var pageX, pageY, elX, elY, dx, dy;

        // A list of movement subscribers. Each element is an object with four
        // properties: fn, the subscriber function; delta, the amount a finger
        // must move before calling the subscriber; and lastX and lastY, the x
        // and y positions of the finger the last time the subscriber was
        // called.
        var moveSubscribers = BindingList();

        // a list of end subscribers
        var endSubscribers = BindingList();

        /**
         * Updates this touch to a new position, informing movement subscribers.
         * @param  {Number} newX New X position
         * @param  {Number} newY New Y position
         * @private
         */
        self._update = function(newX, newY) {
            pageX = newX;
            pageY = newY;
            dx = pageX - x;
            dy = pageY - y;

            var offsets = $(el).offset();
            if(offsets) {
                elX = pageX - (offsets.left - $(document).scrollLeft());
                elY = pageY - (offsets.top  - $(document).scrollTop());
            }
            else {
                // document doens't have offsets; use 0,0
                elX = pageX;
                elY = pageY;
            }

            moveSubscribers.each(function(sub) {
                if(self._vector().distanceFrom($V([sub.lastX, sub.lastY])) > sub.delta) {
                    // touch has moved enough
                    sub.fn(self);
                    sub.lastX = pageX;
                    sub.lastY = pageY;
                }
            });
        };

        /**
         * Ends this touch, informing end subscribers.
         * @private
         */
        self._markDone = function() {
            endSubscribers.callAll(self);
        };

        /**
         * Returns the position of the touch as a Sylvester vector.
         * @private
         */
        self._vector = function() {
            return $V([pageX, pageY]);
        };

        /**
         * Registers a function to be called whenever this touch moves.
         * @param  {Function} fn      The function to call. Gets this touch as
         *                            an argument.
         * @param  {Number}   [delta] If given, the function will only be called
         *                            when the touch has moved this many pixels.
         * @return {Binding}          A Binding that allows this handler to be
         *                            cleared
         */
        self.moved = function(fn, delta) {
            check(fn, Function);
            check(delta, check.Match.Optional(Number));

            if(!delta) {
                delta = 0;
            }

            return moveSubscribers.add({
                fn: fn,
                delta: delta,
                lastX: pageX,
                lastY: pageY
            });
        };

        /**
         * Registers a function to be called when this touch ends.
         * @param  {Function} fn The function to call. Gets this touch as an
         *                       argument.
         * @return {Binding}     A Binding that allows this handler to be
         *                       cleared
         */
        self.ended = function(fn) {
            check(fn, Function);

            return endSubscribers.add(fn);
        };

        /**
         * Returns true if the touch is inside the element it started in.
         * @return {Boolean}
         */
        self.isInEl = function() {
            return ((elX > 0) && (elX < $(el).width()) &&
                    (elY > 0) && (elY < $(el).height()));
        };

        /**
         * Returns a {@link Coordinate} representing the position of this
         * Touch, in the projector coordinate system.
         * @return {Coordinate}
         */
        self.coordinate = function() {
            return Coordinate(Coordinate.PROJECTOR, pageX, pageY);
        };

        self.id     = id;
        self.origX  = x;
        self.origY  = y;
        self.el     = el;
        self.target = target;
        self.w      = w;
        self.h      = h;

        Object.defineProperty(self, "pageX", { get: function(){ return pageX; } });
        Object.defineProperty(self, "pageY", { get: function(){ return pageY; } });
        Object.defineProperty(self, "elX",   { get: function(){ return elX;   } });
        Object.defineProperty(self, "elY",   { get: function(){ return elY;   } });
        Object.defineProperty(self, "dx",    { get: function(){ return dx;    } });
        Object.defineProperty(self, "dy",    { get: function(){ return dy;    } });

        self._update(x, y);
        Object.freeze(self);
        return self;
    };

    return Touch;
});