Source: animator.js

/**
 * @module  animator
 * @author 	Flavio De Stefano <flavio.destefano@caffeinalab.com>
 */

var Matrix = require('T/matrix');

/**
 * Animate a view from `A` to `B` properties. 
 * It puts the `A` properties before the animations starts, then it animates to `B`.
 * @param  {Ti.UI.View}   	view 						The view to animate
 * @param  {Object}   		a 							Initial state
 * @param  {Object}   		b        				Final state
 * @param  {Function} 		[callback=null] 		Callback to execute when animation ends
 */
exports.animateFromTo = function(view, a, b, callback) {
	callback = callback || Alloy.Globals.noop;
	view.applyProperties(a);
	_.defer(function(){
		view.animate(b, callback);
	});
};

/**
 * Fade in the view
 * @param {Object} 			opt
 * @param {Ti.UI.View} 		opt.view 					The view to animate
 * @param {Number} 			[opt.duration=400] 		The duration in ms
 * @param {Function} 		[opt.callback=null]		Callback to execute when animation ends
 */
exports.fadeIn = function(opt) {
	_.defaults(opt, {
		duration: 400
	});

	return exports.animateFromTo(opt.view,
	{ 
		opacity: 0 
	},
	{ 
		opacity: 1, 
		duration: opt.duration
	},
	opt.callback);
};

/**
 * Fade out the view
 * @param {Object} 			opt
 * @param {Ti.UI.View} 		opt.view 					The view to animate
 * @param {Number} 			[opt.duration=400] 		The duration in ms
 * @param {Function} 		[opt.callback=null]		Callback to execute when animation ends
 */
exports.fadeOut = function(opt) {
	_.defaults(opt, {
		duration: 400
	});

	return  exports.animateFromTo(opt.view,
	{ 
		opacity: 1 
	},
	{ 
		opacity: 0, 
		duration: opt.duration
	},
	opt.callback);
};

/**
 * Fade in the view applying a matrix transformation
 * @param {Object} 				opt
 * @param {Ti.UI.View} 			opt.view 					The view to animate
 * @param {Ti.UI.2DMatrix} 	opt.startTransform 		The initial matrix
 * @param {Ti.UI.2DMatrix}		opt.endTransform			The final matrix
 * @param {Number} 				[opt.duration=400] 		The duration in ms
 * @param {Function} 			[opt.callback=null]		Callback to execute when animation ends
 */
exports.fadeInWithTransform = function(opt) {
	_.defaults(opt, {
		duration: 400,
	});

	return exports.animateFromTo(opt.view,
	{ 
		opacity: 0, 
		transform: opt.startTransform
	},
	{ 
		opacity: 1, 
		transform: opt.endTransform,
		duration: opt.duration
	},
	opt.callback);
};

/**
 * Fade in the view with an offset from up
 * @param {Object} 			opt
 * @param {Ti.UI.View} 		opt.view 					The view to animate
 * @param {Number} 			[duration=400] 			The duration in ms
 * @param {Number} 			[opt.offset=50] 			The offset to apply when animation starts
 * @param {Function} 		[opt.callback=null]		Callback to execute when animation ends
 */
exports.fadeInUp = function(opt) {
	_.defaults(opt, {
		duration: 400,
		offset: 50
	});

	return exports.fadeInWithTransform({
		view: opt.view,
		startTransform: Matrix.i().t(0, -opt.offset).matrix,
		endTransform: Matrix.i().matrix,
		duration: opt.duration
	}, opt.callback);
};

/**
 * Fade in the view with an offset from left
 * @param {Object}			opt
 * @param {Ti.UI.View} 		opt.view 					The view to animate
 * @param {Number} 			[opt.duration=400] 		The duration in ms
 * @param {Number} 			[opt.offset=50] 			The offset to apply when animation starts
 * @param {Function} 		[opt.callback=null]		Callback to execute when animation ends
 */
exports.fadeInLeft = function(opt) {
	_.defaults(opt, {
		duration: 400,
		offset: 50
	});

	return exports.fadeInWithTransform({
		view: opt.view,
		startTransform: Matrix.i().t(-opt.offset, 0).matrix,
		endTransform: Matrix.i().matrix,
		duration: opt.duration
	}, opt.callback);
};

/**
 * Fade in the view with an offset from bottom
 * @param {Object}			opt
 * @param {Ti.UI.View} 		opt.view 					The view to animate
 * @param {Number} 			[opt.duration=400] 		The duration in ms
 * @param {Number} 			[opt.offset=50] 			The offset to apply when animation starts
 * @param {Function} 		[opt.callback=null]		Callback to execute when animation ends
 */
exports.fadeInBottom = function(opt) {
	_.defaults(opt, {
		duration: 400,
		offset: 50
	});

	return exports.fadeInWithTransform({
		view: opt.view,
		startTransform: Matrix.i().t(0, opt.offset).matrix,
		endTransform: Matrix.i().matrix,
		duration: opt.duration
	}, opt.callback);
};

/**
 * Fade in the view with an offset from right
 * @param {Object}			opt
 * @param {Ti.UI.View} 		opt.view 					The view to animate
 * @param {Number} 			[opt.duration=400] 		The duration in ms
 * @param {Number} 			[opt.offset=50] 			The offset to apply when animation starts
 * @param {Function} 		[opt.callback=null]		Callback to execute when animation ends
 */
exports.fadeInRight = function(opt) {
	_.defaults(opt, {
		duration: 400,
		offset: 50
	});

	return exports.fadeInWithTransform({
		view: opt.view,
		startTransform: Matrix.i().t(opt.offset, 0).matrix,
		endTransform: Matrix.i().matrix,
		duration: opt.duration
	}, opt.callback);
};

/**
 * Indefinitely move up and down a view
 * @param {Object} 				opt
 * @param {Ti.UI.View} 			opt.view 					The view to animate
 * @param {Number} 				[opt.duration=1000] 		The duration of the animation
 * @param {Number} 				[opt.y=10] 					How many pixel move up and down
 * @return {Function} 			A function that can be used to stop the animation calling its `stop()` method
 */
exports.upAndDown = function(opt) {
	var self = {};
	var run = true;

	// This is the return value, and it's a function
	// that simply set the internal flag `run` to false,
	// to stop the animation
	self.stop = function() {
		run = false;
	};

	_.defaults(opt, {
		duration: 1000,
		y: 10
	});

	var index = 0;
	(function loop() {
		if (run === false) return;

		// Switch 1 to 0 and 0 to 1
		index = (index + 1) % 2;

		_.defer(function(){	
			var newY = index ? opt.y : 0;

			opt.view.animate({
				transform: Matrix.i().t(0, newY).matrix,
				duration: opt.duration
			}, loop);
		});
	})();

	return self;
};

/**
 * Treat the view to animate like a normal falling object
 * @param {Object} 				opt
 * @param {Ti.UI.View} 			opt.view 							The view to animate
 * @param {Number} 				[opt.friction=0.6] 				The friction to apply when the object touch the ground
 * @param {Number} 				[opt.potentialEnergy=10] 		The initial potential energy of the object
 * @param {Number} 				[opt.y=60] 							The initial height of the object
 * @param {Number}				[opt.gravity=9.81]				The world gravity
 * @param {Function} 			[opt.callback=null]				Callback to execute when animation ends
 * @return {Function} 			A function that can be used to stop the animation calling its `stop()` method
 */
exports.fallDownForGravity = function(opt) {
	var self = {};
	var run = true;
	var timeout = null;

	// This is the return value, and it's a function
	// that simply set the internal flag `run` to false and clear the timeout
	// to stop the animation
	self.stop = function() {
		clearTimeout(timeout);
		run = false;
	};

	_.defaults(opt, {
		friction: 0.6,
		potentialEnergy: 10,
		y: 60,
		gravity: 9.81,
		callback: Alloy.Globals.noop
	});

	// Initial potential energy
	var U = Number(opt.potentialEnergy);

	// Calculate the animation duration based on the gravity
	var animationDuration = 1000 * (2 / opt.gravity);
	
	// Index var represents how many time the object reached the floor
	var index = -1;
	var y = null;

	(function loop() {
		if (run === false) return;

		// If our potential energy is zero, stop everything
		if (U === 0) {
			opt.callback();
			return;
		}

		_.defer(function() {
			index++;

			if (index % 2 === 1) {
				U =  Math.floor( U - (U * opt.friction) );
				y = opt.y - ((U / opt.potentialEnergy) * opt.y);
			} else {
				y = opt.y;
			}

			// If we are contrasting the gravity, ease-in. Out otherwise
			var curve = Titanium.UI[ "ANIMATION_CURVE_EASE_" + (index % 2 === 0 ? "IN" : "OUT") ];
			
			opt.view.animate({
				duration: animationDuration,
				curve: curve,
				transform: Matrix.i().t(0, y).matrix
			}, loop);

		});
	})();

	return self;
};