Created
October 28, 2013 11:08
-
-
Save oodavid/7195045 to your computer and use it in GitHub Desktop.
GameClosure Patch > Additional Transition / Easing Functions - I couldn't find the proper repo to commit this, so just dumping into a gist for the time being.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// sdk/timestep/ui/backend/animate.js | |
/** | |
* @license | |
* This file is part of the Game Closure SDK. | |
* | |
* The Game Closure SDK is free software: you can redistribute it and/or modify | |
* it under the terms of the Mozilla Public License v. 2.0 as published by Mozilla. | |
* The Game Closure SDK is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* Mozilla Public License v. 2.0 for more details. | |
* You should have received a copy of the Mozilla Public License v. 2.0 | |
* along with the Game Closure SDK. If not, see <http://mozilla.org/MPL/2.0/>. | |
*/ | |
/** | |
* package ui.backend.canvas.animate; | |
* | |
* Canvas animate namespace and functions. | |
*/ | |
import event.Emitter as Emitter; | |
import animate.transitions as transitions; | |
import timer; | |
var anim_uid = 0; | |
exports = function (subject, groupId) { | |
// TODO: we have a circular import, so do the Engine import on first use | |
if (typeof Engine == 'undefined') { | |
import ui.Engine as Engine; | |
import ui.View as View; | |
} | |
var groupId = groupId || 0, | |
group = groups[groupId] || (groups[groupId] = new Group()), | |
animID = subject.__anim_id || (subject.__anim_id = '__anim_' + (++anim_uid)), | |
anim = group.get(animID); | |
if (!anim) { | |
anim = subject instanceof View | |
? new ViewAnimator(subject, group) | |
: new Animator(subject, group); | |
group.add(animID, anim); | |
} | |
return anim; | |
} | |
exports.getViewAnimator = function () { | |
return ViewAnimator; | |
}; | |
exports.setViewAnimator = function (ctor) { | |
ViewAnimator = ctor; | |
}; | |
var Group = Class(Emitter, function (supr) { | |
this.init = function () { | |
this._anims = {}; | |
this._pending = []; | |
}; | |
this.get = function (id) { return this._anims[id]; }; | |
this.add = function (id, q) { | |
this._anims[id] = q; | |
q.id = id; | |
return q; | |
}; | |
this.isActive = function () { | |
for (var id in this._anims) { | |
if (this._anims[id].hasFrames()) { return true; } | |
} | |
return false; | |
}; | |
this.onAnimationFinish = function (anim) { | |
delete this._anims[anim.id]; | |
if (!this.isActive()) { | |
// if called from a Finish event, republish it | |
this.publish('Finish'); | |
} | |
}; | |
}); | |
var groups = { | |
0: new Group() | |
}; | |
exports.getGroup = function (i) { | |
return groups[i || 0]; | |
}; | |
var TRANSITIONS = [ | |
transitions.easeInOut, // 0: default | |
transitions.linear, // 1 | |
transitions.easeIn, // 2 | |
transitions.easeOut, // 3 | |
transitions.easeInOut, // 4 | |
transitions.easeInQuad, // 5: this was previously "bounce" but that appeared unfinished | |
transitions.easeOutQuad, // 6 | |
transitions.easeInOutQuad, // 7 | |
transitions.easeInCubic, // 8 | |
transitions.easeOutCubic, // 9 | |
transitions.easeInOutCubic, // 10 | |
transitions.easeInQuart, // 11 | |
transitions.easeOutQuart, // 12 | |
transitions.easeInOutQuart, // 13 | |
transitions.easeInQuint, // 14 | |
transitions.easeOutQuint, // 15 | |
transitions.easeInOutQuint, // 16 | |
transitions.easeInSine, // 17 | |
transitions.easeOutSine, // 18 | |
transitions.easeInOutSine, // 19 | |
transitions.easeInExpo, // 20 | |
transitions.easeOutExpo, // 21 | |
transitions.easeInOutExpo, // 22 | |
transitions.easeInCirc, // 23 | |
transitions.easeOutCirc, // 24 | |
transitions.easeInOutCirc, // 25 | |
transitions.easeInElastic, // 26 | |
transitions.easeOutElastic, // 27 | |
transitions.easeInOutElastic, // 28 | |
transitions.easeInBack, // 29 | |
transitions.easeOutBack, // 30 | |
transitions.easeInOutBack, // 31 | |
transitions.easeInBounce, // 32 | |
transitions.easeOutBounce, // 33 | |
transitions.easeInOutBounce // 34 | |
]; | |
exports.linear = 1; | |
exports.easeIn = 2; | |
exports.easeOut = 3; | |
exports.easeInOut = 4; | |
exports.easeInQuad = 5; // this was previously "bounce" but that appeared unfinished | |
exports.easeOutQuad = 6; | |
exports.easeInOutQuad = 7; | |
exports.easeInCubic = 8; | |
exports.easeOutCubic = 9; | |
exports.easeInOutCubic = 10; | |
exports.easeInQuart = 11; | |
exports.easeOutQuart = 12; | |
exports.easeInOutQuart = 13; | |
exports.easeInQuint = 14; | |
exports.easeOutQuint = 15; | |
exports.easeInOutQuint = 16; | |
exports.easeInSine = 17; | |
exports.easeOutSine = 18; | |
exports.easeInOutSine = 19; | |
exports.easeInExpo = 20; | |
exports.easeOutExpo = 21; | |
exports.easeInOutExpo = 22; | |
exports.easeInCirc = 23; | |
exports.easeOutCirc = 24; | |
exports.easeInOutCirc = 25; | |
exports.easeInElastic = 26; | |
exports.easeOutElastic = 27; | |
exports.easeInOutElastic = 28; | |
exports.easeInBack = 29; | |
exports.easeOutBack = 30; | |
exports.easeInOutBack = 31; | |
exports.easeInBounce = 32; | |
exports.easeOutBounce = 33; | |
exports.easeInOutBounce = 34; | |
function getTransition(n) { | |
return (typeof n == 'function' ? n : TRANSITIONS[n | 0]); | |
} | |
var Frame = Class(function () { | |
this.init = function (opts) { | |
this.subject = opts.subject; | |
this.target = opts.target; | |
this.duration = opts.duration || 0; | |
this.transition = getTransition(opts.transition); | |
this.onTick = opts.onTick; | |
}; | |
this.exec = function () {}; | |
this.onInterrupt = function () {}; | |
}); | |
var CallbackFrame = Class(Frame, function () { | |
this.exec = function (tt, t) { | |
this.target.apply(this.subject, arguments); | |
}; | |
}); | |
// a wait frame is just a frame that does nothing... so we | |
// don't need to do anything! | |
var WaitFrame = Frame; | |
var ObjectFrame = Class(Frame, function () { | |
this.exec = function (tt, t, debug) { | |
if (!this.base) { | |
this.base = {}; | |
for (var key in this.target) { | |
this.base[key] = this.subject[key]; | |
} | |
} | |
for (var key in this.target) { | |
this.subject[key] = (this.target[key] - this.base[key]) * tt + this.base[key]; | |
} | |
if (debug) { | |
var changed = {}; | |
for (var key in this.target) { | |
changed[key] = this.subject[key] + ' -> ' + this.target[key]; | |
} | |
logger.log(this.duration, tt, JSON.stringify(changed)); | |
} | |
}; | |
}); | |
// a ViewStyleFrame updates a view's style in exec | |
var ViewStyleFrame = Class(Frame, function () { | |
this._resolvedDeltas = false; | |
this.init = function (opts) { | |
this.subject = opts.subject; | |
this.target = merge({}, opts.target); | |
this.duration = opts.duration === 0 ? 0 : (opts.duration || 500); | |
this.transition = getTransition(opts && opts.transition); | |
this.onTick = opts.onTick; | |
}; | |
this.resolveDeltas = function (againstStyle) { | |
var style = this.target; | |
this._resolvedDeltas = true; | |
for (var key in style) { | |
var baseKey = key.substring(1); | |
if (key.charAt(0) == 'd' && !(key in againstStyle) && (baseKey in againstStyle)) { | |
style[baseKey] = style[key] + againstStyle[baseKey]; | |
delete style[key]; | |
} | |
} | |
}; | |
this.exec = function (tt, t, debug) { | |
var oldStyle = this._baseStyle || (this._baseStyle = this.subject.style.copy()), | |
newStyle = this.target; | |
if (!this._resolvedDeltas) { this.resolveDeltas(this._baseStyle); } | |
var oldStyle = this._baseStyle; | |
for (var key in newStyle) { | |
if (key in oldStyle) { | |
this.subject.style[key] = (newStyle[key] - oldStyle[key]) * tt + oldStyle[key]; | |
} | |
} | |
this.subject.needsRepaint(); | |
if (debug) { | |
var changed = {}; | |
for (var key in newStyle) { | |
changed[key] = this.subject.style[key] + ' -> ' + newStyle[key]; | |
} | |
logger.log(timer.now, this.duration, tt, JSON.stringify(changed)); | |
} | |
}; | |
this.onInterrupt = function (newFrame) { | |
// var preStyle = this.subject.style.copy(); | |
// You might want to resolve any deltas against the post-committed style. | |
// But I think this should be off by default. You can commit the style, | |
// resolve your deltas, then restore the old style yourself if you want. | |
// I don't think you _always_ want this to be the behavior? | |
// | |
// this.commit(true); | |
// newFrame.resolveDeltas(this.subject.style); | |
// If you're animating multiple properties, and you've only | |
// interrupted some of them, you may want the rest to continue. | |
// This only looks good if the timing is the same too -- to do | |
// this properly, you'd have to branch animations based on the | |
// style property being animated... | |
// | |
// for (var i in ViewStyle.keys) { | |
// if (postCommitStyle[i] != preStyle[i] && !(i in newStyle)) { | |
// newStyle[i] = postCommitStyle[i]; | |
// } | |
// } | |
// this.subject.style.update(preStyle); | |
}; | |
}); | |
var Animator = exports.Animator = Class(Emitter, function () { | |
this.init = function (subject, group) { | |
this.subject = subject; | |
this._group = group; | |
this.clear(); | |
this._isPaused = false; | |
}; | |
this.clear = function () { | |
this._elapsed = 0; | |
this._queue = []; | |
this._unschedule(); | |
return this; | |
}; | |
// this.getQueue = function () { return this._queue; } | |
// Careful: pause will *not* fire the finish event, so anything pending the end of the | |
// animation will have to wait until the animation is resumed. | |
this.pause = function () { | |
if (!this._isPaused) { | |
this._isPaused = true; | |
this._unschedule(); | |
} | |
}; | |
this.resume = function () { | |
if (this._isPaused) { | |
this._isPaused = false; | |
this._schedule(); | |
} | |
}; | |
this._schedule = function () { | |
if (!this._isScheduled) { | |
this._isScheduled = true; | |
Engine.get().subscribe('Tick', this, 'onTick'); | |
} | |
}; | |
this._unschedule = function () { | |
if (this._isScheduled) { | |
this._isScheduled = false; | |
Engine.get().unsubscribe('Tick', this, 'onTick'); | |
} | |
}; | |
this.isPaused = function () { return this._isPaused; }; | |
this.hasFrames = function () { return !!this._queue[0]; }; | |
this.wait = function (duration) { | |
return this.then(new WaitFrame({duration: duration})); | |
}; | |
this.buildFrame = function (opts) { | |
if (typeof opts.target == 'function') { | |
return new CallbackFrame(opts); | |
} | |
if (typeof opts.target == 'object') { | |
return new ObjectFrame(opts); | |
} | |
return new WaitFrame(opts); | |
}; | |
this.now = function (target, duration, transition, onTick) { | |
transition = transition || (this._queue[0] ? exports.easeOut : exports.easeInOut); | |
var nextFrame = target instanceof Frame | |
? target | |
: this.buildFrame({ | |
subject: this.subject, | |
target: target, | |
duration: duration, | |
transition: transition, | |
onTick: onTick | |
}); | |
var frame = this._queue[0]; | |
frame && frame.onInterrupt(nextFrame); | |
this.clear(); | |
return this.then(nextFrame); | |
}; | |
this.then = function (target, duration, transition, onTick) { | |
var nextFrame = target instanceof Frame | |
? target | |
: this.buildFrame({ | |
subject: this.subject, | |
target: target, | |
duration: duration, | |
transition: transition, | |
onTick: onTick | |
}); | |
if (!this._queue.length) { | |
this._elapsed = 0; | |
} | |
this._queue.push(nextFrame); | |
this._schedule(); | |
return this; | |
}; | |
this.debug = function () { this._debug = true; return this; }; | |
this.commit = function () { | |
this._elapsed = 0; | |
for (var i = 0, p; p = this._queue[i]; ++i) { | |
this._elapsed += p.duration; | |
} | |
this.next(); | |
return this; | |
}; | |
this.onTick = function (dt) { | |
if (!this._isScheduled) { | |
return; | |
} | |
this._elapsed += dt; | |
this.next(); | |
if (!this._isScheduled) { | |
this._group.onAnimationFinish(this); | |
} | |
}; | |
this.next = function () { | |
var p, | |
target; | |
if (!this._queue[0]) { return; } | |
while ((p = this._queue[0])) { | |
var frameFinished = this._elapsed >= p.duration, | |
t = frameFinished ? 1 : this._elapsed / p.duration, | |
tt = p.transition(t); | |
if (frameFinished) { | |
this._elapsed -= p.duration; | |
} | |
try { | |
p.exec(tt, t, this._debug); | |
if (p.onTick) { p.onTick.call(p.subject, tt, t); } | |
} finally { | |
// if we haven't modified the queue in a callback, remove the frame if it is finished | |
if (frameFinished && p == this._queue[0]) { | |
this._queue.shift(); | |
} | |
// if we got paused during a callback or the | |
// frame is not finished yet, don't continue | |
if (!frameFinished || this._isPaused) { return; } | |
} | |
} | |
// nothing left in the queue! | |
this._unschedule(); | |
}; | |
}); | |
var ViewAnimator = Class(Animator, function (supr) { | |
this.buildFrame = function (opts) { | |
if (typeof opts.target == 'object') { | |
return new ViewStyleFrame(opts); | |
} | |
return supr(this, 'buildFrame', arguments); | |
}; | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// sdk/timestep/animate/transitions.js | |
/** | |
* @license | |
* This file is part of the Game Closure SDK. | |
* | |
* The Game Closure SDK is free software: you can redistribute it and/or modify | |
* it under the terms of the Mozilla Public License v. 2.0 as published by Mozilla. | |
* The Game Closure SDK is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* Mozilla Public License v. 2.0 for more details. | |
* You should have received a copy of the Mozilla Public License v. 2.0 | |
* along with the Game Closure SDK. If not, see <http://mozilla.org/MPL/2.0/>. | |
*/ | |
/** | |
* @module animate.transitions | |
* | |
* Transition functions for use by the animate features. These aren't referenced | |
* directly by the animate namespace, but by numerical reference. | |
* | |
* @doc http://doc.gameclosure.com/api/animate.html | |
* @docsrc https://github.com/gameclosure/doc/blob/master/api/animate.md | |
*/ | |
exports.linear = function (n) { | |
return n; | |
}; | |
exports.easeIn = exports.easeInQuad = function (n) { | |
return n*n; | |
}; | |
exports.easeOut = exports.easeOutQuad = function (n) { | |
return n*(2-n); | |
}; | |
exports.easeInOutQuad = function (n) { | |
if ((n*=2) < 1) return 0.5*n*n; | |
return -0.5 * ((--n)*(n-2) - 1); | |
}; | |
exports.easeInCubic = function (n) { | |
return n*n*n; | |
}; | |
exports.easeOutCubic = function (n) { | |
return ((n-=1)*n*n + 1); | |
}; | |
exports.easeInOut = exports.easeInOutCubic = function (n) { | |
if ((n*=2) < 1) return 0.5*n*n*n; | |
return 0.5*((n-=2)*n*n + 2); | |
}; | |
exports.easeInQuart = function (n) { | |
return n*n*n*n; | |
}; | |
exports.easeOutQuart = function (n) { | |
return -1 * ((n-=1)*n*n*n - 1); | |
}; | |
exports.easeInOutQuart = function (n) { | |
if ((n*=2) < 1) return 0.5*n*n*n*n; | |
return -0.5 * ((n-=2)*n*n*n - 2); | |
}; | |
exports.easeInQuint = function (n) { | |
return n*n*n*n*n; | |
}; | |
exports.easeOutQuint = function (n) { | |
return ((n-=1)*n*n*n*n + 1); | |
}; | |
exports.easeInOutQuint = function (n) { | |
if ((n*=2) < 1) return 0.5*n*n*n*n*n; | |
return 0.5*((n-=2)*n*n*n*n + 2); | |
}; | |
exports.easeInSine = function(n){ | |
return -1 * Math.cos(n*(Math.PI/2)) + 1; | |
}; | |
exports.easeOutSine = function (n) { | |
return Math.sin(n*(Math.PI/2)); | |
}; | |
exports.easeInOutSine = function (n) { | |
return -0.5 * (Math.cos(Math.PI*n) - 1); | |
}; | |
exports.easeInExpo = function (n) { | |
return (n==0) ? 0 : Math.pow(2, 10*(n-1)); | |
}; | |
exports.easeOutExpo = function (n) { | |
return (n==1) ? 1 : (-Math.pow(2, -10*n) + 1); | |
}; | |
exports.easeInOutExpo = function (n) { | |
if (n==0) return 0; | |
if (n==1) return 1; | |
if ((n*=2) < 1) return 0.5 * Math.pow(2, 10*(n-1)); | |
return 0.5 * (-Math.pow(2, -10*--n) + 2); | |
}; | |
exports.easeInCirc = function (n) { | |
return -1 * (Math.sqrt(1 - n*n) - 1); | |
}; | |
exports.easeOutCirc = function (n) { | |
return Math.sqrt(1 - (n-=1)*n); | |
}; | |
exports.easeInOutCirc = function (n) { | |
if ((n*=2) < 1) return -0.5 * (Math.sqrt(1 - n*n) - 1); | |
return 0.5 * (Math.sqrt(1 - (n-=2)*n) + 1); | |
}; | |
exports.easeInElastic = function (n) { | |
if (n==0) return 0; | |
if (n==1) return 1; | |
var p = 0.3; | |
var s = 0.075; // p / (2*Math.PI) * Math.asin(1) | |
return -(Math.pow(2,10*(n-=1)) * Math.sin((n-s)*(2*Math.PI)/p)); | |
}; | |
exports.easeOutElastic = function (n) { | |
if (n==0) return 0; | |
if (n==1) return 1; | |
var p = 0.3; | |
var s = 0.075; // p / (2*Math.PI) * Math.asin(1) | |
return Math.pow(2,-10*n) * Math.sin((n-s)*(2*Math.PI)/p) + 1; | |
}; | |
exports.easeInOutElastic = function (n) { | |
var s=1.70158;var p=0;var a=1; | |
if (n==0) return 0; | |
if ((n*=2)==2) return 1; | |
var p = 0.45; // 0.3 * 1.5 | |
var s = 0.1125; // p / (2*Math.PI) * Math.asin(1) | |
if (n < 1) return -.5*(Math.pow(2,10*(n-=1)) * Math.sin( (n*1-s)*(2*Math.PI)/p )); | |
return Math.pow(2,-10*(n-=1)) * Math.sin( (n*1-s)*(2*Math.PI)/p )*.5 + 1; | |
}; | |
exports.easeInBack = function (n) { | |
var s = 1.70158; | |
return n*n*((s+1)*n - s); | |
}; | |
exports.easeOutBack = function (n, s) { | |
var s = 1.70158; | |
return ((n-=1)*n*((s+1)*n + s) + 1); | |
}; | |
exports.easeInOutBack = function (n) { | |
var s = 1.70158; | |
if ((n*=2) < 1) return 0.5*(n*n*(((s*=(1.525))+1)*n - s)); | |
return 0.5*((n-=2)*n*(((s*=(1.525))+1)*n + s) + 2); | |
}; | |
exports.easeInBounce = function (n) { | |
return 1 - this.easeOutBounce(1-n); | |
}; | |
exports.easeOutBounce = function (n) { | |
if ((n) < (1/2.75)) { | |
return (7.5625*n*n); | |
} else if (n < (2/2.75)) { | |
return (7.5625*(n-=(1.5/2.75))*n + .75); | |
} else if (n < (2.5/2.75)) { | |
return (7.5625*(n-=(2.25/2.75))*n + .9375); | |
} else { | |
return (7.5625*(n-=(2.625/2.75))*n + .984375); | |
} | |
}; | |
exports.easeInOutBounce = function (n) { | |
if (n < 0.5) return this.easeInBounce (n*2) * .5; | |
return this.easeOutBounce ((n*2)-1) * .5 + .5; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment