File:OffClickPlugin.js

/**
 * @module EaselJS Learning
 * @namespace springroll
 * @requires Core, Learning
 */
(function()
{
	// Include classes
	var ApplicationPlugin = include('springroll.ApplicationPlugin'),
		Point = include('createjs.Point'),
		Debug;

	/**
	 * @class Application
	 */
	var plugin = new ApplicationPlugin();

	/**
	 * The last interactive position
	 * @property {createjs.Point} _currentPosition
	 * @private
	 */
	var _currentPosition = new Point();

	/**
	 * Helper point for normalizing position
	 * @property {createjs.Point} _helperPoint
	 * @private
	 */
	var _helperPoint = new Point();

	// Init the animator
	plugin.setup = function()
	{
		if (!Debug)
		{
			Debug = include('springroll.Debug', false);
		}

		/**
		 * Some games need to send additional parameters to the tracker's
		 * offClick event. They may set them here as needed. These parameters are appended
		 * to the normal offClick data.
		 * @property {Array} offClickParams
		 */
		this.offClickParams = [];

		/**
		 * Keep track of the last Pointer ID
		 * @property {int} _lastPointerID
		 * @private
		 */
		this._lastPointerID = null;

		/**
		 * For learning events, we want to send consistent data when sending
		 * positions. This helper method generates that data.
		 * In the future, we may return an object with known properties,
		 * but for now we are returning an object of {x:int, y:int,
		 * stage_width:int, stage_height:int} in unscaled numbers.
		 *
		 * @method normalizePosition
		 * @param {createjs.DisplayObject|createjs.Point} pos A display object or point to use.
		 * @param {createjs.DisplayObject} [coordSpace] The coordinate space the position is in, so
		 *                                            it can be converted to global space. If
		 *                                            omitted and <code>pos</code> is a
		 *                                            DisplayObject, <code>pos.parent</code> will
		 *                                            be used.
		 * @return {Object} {x:int, y:int, stage_width:int, stage_height:int}
		 */

		/**
		 * For learning events, we want to send consistent data when sending
		 * positions. This helper method generates that data.
		 * In the future, we may return an object with known properties,
		 * but for now we are returning an object of {x:int, y:int,
		 * stage_width:int, stage_height:int} in unscaled numbers.
		 *
		 * @method normalizePosition
		 * @param {Number} x The x position
		 * @param {Number} y The y position
		 * @param {createjs.DisplayObject} [coordSpace] The coordinate space the position is in, so
		 *                                            it can be converted to global space.
		 * @return {Object} {x:int, y:int, stage_width:int, stage_height:int}
		 */
		this.normalizePosition = function(x, y, coordSpace)
		{
			//detect Points and DisplayObjects
			if (x.hasOwnProperty("x"))
			{
				coordSpace = y || x.parent;
				y = x.y;
				x = x.x;
			}

			if (coordSpace && coordSpace.localToGlobal)
			{
				var globalPoint = coordSpace.localToGlobal(x, y, _helperPoint);
				x = globalPoint.x;
				y = globalPoint.y;
			}

			var display = this.display;
			return {
				x: x | 0,
				y: y | 0,
				stage_width: display.width,
				stage_height: display.height
			};
		};

		/**
		 * For learning events, we want to send consistent data when sending
		 * Position. This helper method generates that data for the
		 * current stage position of mouse or touch. We are returning an object of
		 * `{x:int, y:int, stage_width:int, stage_height:int}` in unscaled numbers.
		 *
		 * @method currentPosition
		 * @return {Object} `{x:int, y:int, stage_width:int, stage_height:int}`
		 */
		this.currentPosition = function()
		{
			return this.normalizePosition(_currentPosition);
		};
	};

	// Check for dependencies
	plugin.preload = function(done)
	{
		if (!this.learning)
		{
			if (DEBUG)
			{
				throw "Missing learning module. Is a requirement of easeljs-learning";
			}
			else
			{
				throw "No learning";
			}
		}

		//Provide convenience handling of stage off click progress events
		var display = this.display;
		if (display)
		{
			var stage = display.stage;
			if (stage)
			{
				onStageMouseDown = onStageMouseDown.bind(this);
				onStageMouseMove = onStageMouseMove.bind(this);
				stage.addEventListener("stagemousedown", onStageMouseDown);
				stage.addEventListener("stagemousemove", onStageMouseMove);
			}
		}
		done();
	};

	/**
	 * Fires event whenever the mouse is moved
	 * @method onStateMouseMove
	 * @private
	 * @param {createjs.MouseEvent} ev The mouse event
	 */
	var onStageMouseMove = function(ev)
	{
		if (ev.pointerID === this._lastPointerID)
		{
			_currentPosition.x = ev.stageX;
			_currentPosition.y = ev.stageY;
		}
	};

	/**
	 * Fires OffClick event if click on unhandled object
	 * @method onStageMouseDown
	 * @private
	 * @param {createjs.MouseEvent} ev The mouse event
	 */
	var onStageMouseDown = function(ev)
	{
		// Keep track of the last pointer ID
		// this allows us to remember the last position
		this._lastPointerID = ev.pointerID;

		var stage = ev.target;
		var target = stage._getObjectsUnderPoint(ev.stageX, ev.stageY, null, true);

		var foundListener = false;

		if (target)
		{
			while (target && target != stage)
			{
				if (target.hasEventListener("mousedown") || target.hasEventListener("click"))
				{
					foundListener = true;
					break;
				}
				target = target.parent;
			}
		}

		if (!foundListener) //no interactive objects found
		{
			//duplicate the array of optional offClick parameters
			var arr = this.offClickParams.slice(0);

			//make sure we are sending the default parameter (position)
			//as the first parameter
			arr.unshift(this.normalizePosition(ev.stageX, ev.stageY));

			//send the entire array of parameters
			if (this.learning.offClick)
			{
				this.learning.offClick.apply(this, arr);
			}
			else if (DEBUG && Debug)
			{
				Debug.info("Learning doesn't have an offClick event");
			}
		}
	};

	// Destroy the animator
	plugin.teardown = function()
	{
		//Remove stage listener
		var display = this.display;
		if (display && display.stage)
		{
			var stage = display.stage;
			if (stage)
			{
				stage.removeEventListener("stagemousedown", onStageMouseDown);
				stage.removeEventListener("stagemousemove", onStageMouseMove);
			}
		}
		this.offClickParams = null;
	};

}());