Checkpoint on animation.
authordsc <david.schoonover@gmail.com>
Wed, 8 Dec 2010 08:07:13 +0000 (00:07 -0800)
committerdsc <david.schoonover@gmail.com>
Wed, 8 Dec 2010 08:07:13 +0000 (00:07 -0800)
bin/deploy.sh
src/ezl/loop/animation.cjs [new file with mode: 0644]

index be07bb0..7452616 100755 (executable)
@@ -1,5 +1,11 @@
 #! /bin/bash
 
+VERBOSE=""
+SRC="src/Y src/ezl src/tanks"
+EXCLUDE="--exclude=$(join ' --exclude=' 'tmp' 'src' 'bin' $*)"
+GIT_VERSION=$(git show --oneline . | head -n1 | cut -d ' ' -f 1)
+
+
 function halp () {
     cat >&2 <<-HALP
 The Littlest Deployer of Battletanks
@@ -20,35 +26,33 @@ TODO: Optionally minify using Google Closure Compiler.
 
 Options:
     -h              Displays this help
-    -v              Verbose logging
+    -v              Verbose
     -n              Dry-run
     -C              Clean before rebuilding
-    -G              Only emit the deploy version (Git commit abbreviation)
+    -G              Only emit the deploy version (Git commit abbreviation: $GIT_VERSION)
 HALP
 }
 
 SHIFT=0
 function incshift () { SHIFT=$(( $SHIFT + ${1:-1} )); }
 function fail () { echo; echo "PREDICTABLE FAILURE. $1" >&2; exit 1; }
-function join () { sep="$1"; out="$2"; shift 2; for a in $*; do out="${out}${sep}${a}"; done; echo "$out"; }
+function join () { sep="$1"; printf "$2"; shift 2; for a in $*; do printf "$sep$a"; done; echo; }
+function log () { test "$VERBOSE" && echo && echo "$*"; }
 
 for opt in $*; do
     echo $opt | egrep -xq -e '--?h(e(lp?)?)?' && { halp; exit 0; }
 done
-while getopts "nvCG" opt; do
+while getopts ":vnCG" opt; do
     case $opt in
-        n ) DRY_RUN="--dry-run"; incshift ;;
         v ) VERBOSE="-v";        incshift ;;
+        n ) DRY_RUN="--dry-run"; incshift ;;
         C ) CLEAN="--clean";     incshift ;;
         G ) GIT_VERSION_ONLY=1;  incshift ;;
+        * ) fail "Unknown option: $OPTARG" ;;
     esac
 done
 shift $SHIFT
 
-SRC="src/Y src/ezl src/tanks"
-EXCLUDE="--exclude=$(join ' --exclude=' 'tmp' 'src' 'bin' $*)"
-GIT_VERSION=$(git show --oneline . | head -n1 | cut -d ' ' -f 1)
-
 if test "$GIT_VERSION_ONLY"; then
     echo "$GIT_VERSION"
     exit
diff --git a/src/ezl/loop/animation.cjs b/src/ezl/loop/animation.cjs
new file mode 100644 (file)
index 0000000..46589ea
--- /dev/null
@@ -0,0 +1,168 @@
+var Y   = require('Y').Y
+,   evt = require('evt')
+,
+
+/**
+ * All easing functions adhere to the following interface.
+ * 
+ * @param {Float} p Proportion completed [0-1].
+ * @param {Number} elapsed Miliseconds elapsed since start.
+ * @param {Number} start Starting value.
+ * @param {Number} span Delta value (finish - start) to animate.
+ * @param {Number} duration Total duration in miliseconds of the animation.
+ */
+Easing =
+exports['Easing'] = {
+    'linear' : function linearEasing( p, elapsed, start, span, duration ) {
+        return start + span * p;
+    },
+    
+    'swing'  : function swingEasing( p, elapsed, start, span, duration ) {
+        return ((-Math.cos(p*Math.PI)/2) + 0.5) * span + start;
+    }
+}
+,
+
+
+lookupEasing =
+exports['lookupEasing'] =
+function lookupEasing(name, prop){
+    if ( Y.isFunction(name) )
+        return name;
+    if (name in Easing)
+        return Easing[name];
+    if ( !(Y.isPlainObject(name) && prop in name) )
+        throw new Error('Unknown easing function '+name+'!');
+    return lookupEasing(name[prop]);
+}
+,
+
+
+Fx =
+exports['Fx'] =
+Y.subclass('Fx', function setupFx(Fx){
+    var VAL_PATTERN = /^([+\-]=)?([\d+.\-]+)(.*)$/;
+    
+    this['init'] =
+    function initFx(obj, duration, prop, val, easing){
+        var parts = VAL_PATTERN.exec(val)
+        ,   rel   = parts[1]
+        ,   val   = parseFloat(parts[2])
+        ,   unit  = parts[3] || null
+        ;
+        
+        this.obj      = obj;
+        this.duration = duration;
+        this.prop     = prop;
+        this.unit     = unit;
+        this.easing   = Easing[easing];
+        
+        this.start      = this.cur = Y.attr(obj, prop);
+        if (rel) {
+            this.span   = val*(rel === '-=' ? -1 : 1);
+            this.finish = this.start + this.span;
+        } else {
+            this.span   = val - this.start;
+            this.finish = val;
+        }
+    };
+    
+    this['start'] =
+    function start(now){
+        if (this.running) return;
+        this.startTime = now || new Date().getTime();
+        this.running = true;
+    };
+    
+    this['stop'] =
+    function stop(){
+        this.running = false;
+    };
+    
+    this['tick'] =
+    function tick(elapsed, now){
+        if (!this.running)
+            return false;
+        
+        var p = (now - this.startTime) / this.duration
+        ,   v = this.cur = this.start + this.easing(p, 0, this.span, this.duration) ;
+        Y.attr(this.obj, this.prop, ( this.unit !== null ? v+this.unit : v ));
+        
+        return true;
+    };
+})
+,
+
+Animation =
+exports['Animation'] =
+new evt.Class('Animation', function setupAnimation(Animation){
+    Y.extend(this, {
+        'started': 0,
+        'running': false
+    });
+    
+    var defOptions = {
+        'delay'         : 0,
+        // 'queue'         : undefined,
+        'easing'        : 'linear'
+        // 'complete'      : null,
+        // 'step'          : null
+    };
+    
+    /**
+     * @param {Object} obj Object to animate.
+     * @param {Number} duration Duration (ms) of animation.
+     * @param {Object} props Object whose keys are the property-names to animate
+     *  paired with the target value. Animated properties can also be relative.
+     *  If a value is supplied with a leading += or -= sequence of characters,
+     *  then the target value is computed by adding or subtracting the given
+     *  number from the current value of the property.
+     * @param {Object} [options] Animation options:
+     *      * {Number} [delay=0] Milliseconds to wait before beginning animation.
+     *      * {Boolean} [queue=true] Indicates whether to place the animation 
+     *          in the effects queue to run when other animations have finished.
+     *          If false, the animation will begin after `delay` milliseconds;
+     *          likewise, if `delay` is non-zero `queue` defaults to `false`
+     *          instead.
+     *      * {String|Function|Object} [easing='linear'] Name of easing function
+     *          used to calculate each step value, or a function, or a map of
+     *          properties to either of the above.
+     */
+    this['init'] =
+    function initAnimation(obj, duration, props, options){
+        this.obj      = obj;
+        this.duration = duration;
+        this.props    = props;
+        
+        this.options  = options = Y.extend({}, defOptions, options);
+        if ( !('queue' in options) )
+            options.queue = !options.delay;
+        
+        this.fx = Y(this.props)
+            .reduce(function(fx, val, prop){
+                var easing = lookupEasing(options.easing, prop);
+                return fx.push(new Fx(obj, duration, prop, val, easing));
+            }, Y([]) );
+        
+        // TODO: Handle Queuing -- should happen in target objects who provide the API
+    };
+    
+    this['start'] =
+    function start(now){
+        if (this.running) return;
+        this.started = now || new Date().getTime();
+        this.running = true;
+        this.waiting = !!this.options.delay;
+    };
+    
+    this['tick'] =
+    function tick(elapsed, now){
+        if (this.waiting && (now < this.started+this.options.delay))
+            return true;
+        
+        
+        this.waiting = false;
+        return true;
+    };
+    
+});