package flare.animate { import flare.util.IValueProxy; import flare.util.Property; import flash.display.DisplayObject; import flash.geom.Rectangle; import flash.utils.Dictionary; /** * Parallel transition with convenience methods for adding new * object tweens, helping to incrementally construct a group of transitions. * A Transitioner will automatically generate tweens for any number of * items and properties, simplifying the task of creating animated * transitions. * *

For example, the following code creates a 1 second * animation for two items. The first item is translated to the point * (50,50) and the second item is scaled along the x dimension to twice the * normal size.

*
	 * var item1:Sprite, item2:Sprite; // assume these are two drawn sprites
	 * var t:Transitioner = new Transitioner(1); // create 1-second transition
	 * t.$(item1).x = 50;
	 * t.$(item1).y = 50;
	 * t.$(item2).scaleX = 2;
	 * t.play();
	 * 
* *

In the code above, the $ method takes an item (this * can be any ActionScript object, but is often a DisplayObject * instance) and returns an Object which stores the names of * the properties to animate and their target values. Behind the scenes, * the Transitioner automatically creates Tween * objects as needed.

* *

The object returned by the $ method is a proxy object * that passes the values to underlying tweens as needed. This same proxy * object is reused across calls to the $ method so do * not attempt to use multiple return values from the * $ method simultaneously. The following example shows * what you should not do!

*
	 * var o1:Object = t.$(item1);
	 * var o2:Object = t.$(item2); // o2==o1, now configured for item2
	 * o1.x = 5; // actually sets the value 5 to item2, NOT item1
	 * 
* *

* A transitioner can also be set to "immediate" mode, either by setting * the immediate property to true, or by passing in * NaN as the duration value to the constructor. When in * immediate mode, a transitioner will NOT generate * Tween instances to animate the properties. Instead, the * transitioner will set the values of the target objects immediately. * For example, when in immediate mode, the $ operator is * equivalent to directly setting the property: * t.$(item1).x = 50 has exactly the same result at * t.x = 50. The static property * Transitioner.DEFAULT provides a default instance of an * immediate-mode transitioner. *

* *

* With these features, transitioners provide a highly flexible way to * update values in your application. You can write layout and other * methods once, using a transitioner to update all the property values. * When animation is desired, a standard transitioner can be passed in * to your routines. When immediate updates are desired, you can reuse * the same code, but just pass in a transitioner in immediate mode * instead. Whether or not value updates are animated or immediate then * becomes easy to control. *

* *

* Transitioners also provide optimizations to improve animation * performance. However, they are not enabled by default, as the * optimizations make some assumptions about how the transitioner will * be used. See the optimize property and * dispose method for more information. *

*/ public class Transitioner extends Parallel implements IValueProxy { /** The default, immediate-mode transitioner instance. */ public static const DEFAULT:Transitioner = new Transitioner(NaN); /** * Gets a transitioner instance depending upon the input value. * @param t input determining the transitioner instance to return. If * the input is a transitioner, it is simply returned. If the input is * a number, a new Transitioner with duration set to the input value * is returned, unless the number is less than zero, in which case the * default immediate-mode transitioner is returned. If the input is * null, Transitioner.DEFAULT is returned. * @return a Transitioner instance determined by the input */ public static function instance(t:*):Transitioner { if (t is Number) { var dur:Number = Number(t); return dur<0 ? Transitioner.DEFAULT : new Transitioner(dur); } else if (t == null) { return Transitioner.DEFAULT; } else { return t as Transitioner; } } // -------------------------------------------------------------------- private var _immediate:Boolean; private var _lookup:/*Object->Tween*/Dictionary = new Dictionary(); private var _proxy:ValueProxy; private var _optimize:Boolean = false; private var _subdur:Number; /** @private */ public override function get duration():Number { return _trans.length==0 ? _subdur : super.duration; } /** Immediate mode flag, used to bypass tween generation and perform * immediate updates of target object values. */ public function get immediate():Boolean { return _immediate; } public function set immediate(b:Boolean):void { _immediate = b; if (!immediate && _proxy == null) _proxy = new ValueProxy(this); } /** * Flag indicating if aggressive optimization should be applied. * This can significantly decrease processing time when large numbers * of elements are involved. However, the optimization process makes a * few assumptions about how the transitioner will be used. If these * assumptions are not met, the animations may exhibit unexpected * behaviors. * *

The assumptions made for optimized transitioners are: *

    *
  1. The property values of tweened objects will not change between * the time their target values are set and the transition is * played. This allows the transitioner to avoid creating tweens * when properties have the same starting and ending values, and * immediately set values for DisplayObjects that are not visible. * However, this means the starting value must stay the same. In * particular, this means that optimized transitioners are often * inappropriate for use within a Sequence.
  2. *
  3. The transitioner will only be played once, then discarded. * This allows the transitioner to automatically recycle all * generated Tween and Interpolator * instances, reducing initialization time across transitioners by * reusing objects.
  4. *
*

*/ public function get optimize():Boolean { return _optimize; } public function set optimize(b:Boolean):void { _optimize = b; } // -------------------------------------------------------------------- /** * Creates a new Transitioner with specified duration. * @param duration the length of the transition. If this value is NaN, * the transitioner will be in immediate mode, in which all changes * are immediately applied and no tweens are generated. * @param easing the easing function to use for this transition. If * null, the function Easing.none will be used. * @param optimize boolean flag indicating if the transitioner should * attempt to optimize tween construction. See the documentation * for the optimize property for mode details. * @param id an optional id. If non-null, any other running transition * with the same id will be canceled when this transitioner is played. */ public function Transitioner(duration:Number=1, easing:Function=null, optimize:Boolean=false, id:String=null) { super.easing = easing==null ? DEFAULT_EASING : easing; _subdur = duration; _optimize = optimize; _immediate = isNaN(duration); if (id!=null) this.id = id; if (!_immediate) _proxy = new ValueProxy(this); } /** * Indicates if the Transitioner contains a Tween for the given object. * @param o the object to test for * @return true if there is a Tween for the object, false otherwise */ public function hasTweenFor(o:Object):Boolean { return _immediate ? false : (_lookup[o] != undefined); } /** * Returns the Tween for the given object, creating a new tween if no * tween is yet associated with the object. This method returns null if * the transitioner is in immediate mode. * @param o the target object * @return a tween for the input target object, or null if this * transitioner is in immediate mode. */ public function _(o:Object):Tween { if (_immediate) return null; var tw:Tween = _lookup[o]; if (tw == null) { add(tw = getTween(o, _subdur)); tw.easing = Easing.none; _lookup[o] = tw; } return tw; } /** * Returns the values object of the Tween for the given object. If no * tween is associated with the object, a new one is created. * * If the transitioner is in immediate mode, then no Tween will be * created. Instead, the input object will be returned. This allows * value updates to be set immediately, rather than animated by a * tween. * @param o the target object * @return the values object for the target object's * tween, or the target object itself if this transitioner is in * immediate mode. */ public function $(o:Object):Object { return _immediate ? o : _proxy.init(o); } /** * Sets property values for a target object. This method has the same * effect as setting a property on the object returned by the * $ method. * *

If the transitioner is in immediate mode, the property name will * be parsed and the value set at the end of the property chain. If * the transitioner is not in immediate mode, the property name and * values will simply be added to a Tween. If no Tween is associated * with the input object, a new one will be created.

* * @param o the target object * @param name the property name string * @param value the property value to set */ public function setValue(o:Object, name:String, value:*):void { if (_immediate) { // set the object property Property.$(name).setValue(o, value); } else if (optimize && getValue(o, name) == value) { // do nothing, optimize the call away... } else if (optimize && o is DisplayObject && !getValue(o, "visible")) { Property.$(name).setValue(o, value); } else { // add to a tween _(o).values[name] = value; } } /** * Retrieves property values for a target object. This method has the * same effect as accessing a property using the object returned by the * $ method. * *

If the transitioner is in immediate mode, the property name will * be parsed and the value retrieved diretly from the target object. If * the transitioner is not in immediate mode, this method will first * try to lookup the value in the tween values for the * target object. If this does not succeed, the property value will be * retrieved directly from the target object itself as in the immediate * mode case.

* * @param o the target object * @param name the property name string * @return the property value for the target object, either from the * target object's tween values, or, failing that, the object itself. */ public function getValue(o:Object, name:String):* { if (!_immediate) { var tw:Tween = _lookup[o]; if (tw != null && tw.values[name] != undefined) { return tw.values[name]; } } return Property.$(name).getValue(o); } /** * Sets the delay of the tween for the given object. If the * transitioner is in immediate mode, this method has no effect. * @param o the object to set the delay for * @param delay the delay, in seconds */ public function setDelay(o:Object, delay:Number):void { if (!_immediate) { _(o).delay = delay; } } /** * Gets the delay of the tween for the given object. If the * transitioner is in immediate mode or no tween has been created for * the input object, this method returns zero. * @param o the object to get the delay for * @return the delay of the tween, or zero if there is no tween */ public function getDelay(o:Object):Number { if (_immediate) return 0; var tw:Tween = _lookup[o]; return tw==null ? 0 : tw.delay; } /** * Sets the removal status of a display object with this transition. * If true, the display object will be removed from its parent in the * display list when the transition completes. If this transitioner is * in immediate mode, any removals are performed immediately. * @param dobj a display object * @param b true to remove the display object from the display list * at the end of the transition (the default). If false, the removal * status will be updated so that the display object will not be * removed (not applicable in immediate mode). */ public function removeChild(dobj:DisplayObject, b:Boolean=true):void { if (_immediate && b) { if (dobj.parent) dobj.parent.removeChild(dobj); } else if (!_immediate) { _(dobj).remove = b; } } /** * Indicates if a display object is scheduled to be removed from the * display list when this transition completes. This method always * returns false if this transitioner is in immediate mode. * @param d a display object * @return true if the display object will be removed from the display * list at the end of this transition, false otherwise. This method * always returns false if the transitioner is in immediate mode. */ public function willRemove(d:DisplayObject):Boolean { if (_immediate) return false; var tw:Tween = _lookup[d]; return (tw != null && tw.remove); } /** @inheritDoc */ protected override function end():void { super.end(); if (_optimize) dispose(); } /** * Disposes of the internal state of this transitioner. * Contained tweens and their interpolators will be collected and * recycled for future reuse, improving initialization times for * subsequent transitioners. This method is automatically called at the * end of the transition if the optimize flag is true. * Otherwise, this method can be invoked manually when a transitioner * is no longer needed. */ public override function dispose():void { while (_trans.length > 0) { var t:Transition = _trans.pop(); t.dispose(); if (t is Tween) reclaimTween(t as Tween); } } /** * Computes the approximate size of the given object after this * transitioner has been run. This calculation is performed by * applying the final scaleX, scaleY, and * size values of the object. * @param d the display object to compute the size for * @param r a rectangle for storing the results * @return a rectangle whose width and height * properties contain the end size values. */ public function endSize(d:DisplayObject, r:Rectangle=null):Rectangle { if (r==null) r = new Rectangle(); var t:Tween, v:Object, o:Object = Object(d); var scaleX:Number, scaleY:Number, size:Number; if (_immediate || (t=_lookup[d])==null) { r.width = d.width; r.height = d.height; } else { v = t.values; if (v.hasOwnProperty("scaleX")) { scaleX = d.scaleX; d.scaleX = v.scaleX; } if (v.hasOwnProperty("scaleY")) { scaleY = d.scaleY; d.scaleY = v.scaleY; } if (v.hasOwnProperty("size")) { size = o.size; o.size = v.size; } r.width = d.width; r.height = d.height; if (v.hasOwnProperty("scaleX")) d.scaleX = scaleX; if (v.hasOwnProperty("scaleY")) d.scaleY = scaleY; if (v.hasOwnProperty("size")) o.size = size; } return r; } /** * Computes the approximate bounds of the given object after this * transitioner has been run. This calculation is performed by * applying the final scaleX, scaleY, * size, x, and y values of * the object. * @param d the display object to compute the size for * @param coords the target coordinate space for the bounds * @return a rectangle whose width and height * properties contain the end size values. */ public function endBounds(d:DisplayObject, coords:DisplayObject):Rectangle { var r:Rectangle = new Rectangle(); var t:Tween, v:Object, o:Object = Object(d); var scaleX:Number, scaleY:Number, size:Number, x:Number, y:Number; if (_immediate || (t=_lookup[d])==null) { r = d.getBounds(coords); } else { v = t.values; if (v.hasOwnProperty("scaleX")) { scaleX = d.scaleX; d.scaleX = v.scaleX; } if (v.hasOwnProperty("scaleY")) { scaleY = d.scaleY; d.scaleY = v.scaleY; } if (v.hasOwnProperty("size")) { size = o.size; o.size = v.size; } if (v.hasOwnProperty("x")) { x = d.x; d.x = v.x; } if (v.hasOwnProperty("y")) { y = d.y; d.y = v.y; } r = d.getBounds(coords); if (v.hasOwnProperty("scaleX")) d.scaleX = scaleX; if (v.hasOwnProperty("scaleY")) d.scaleY = scaleY; if (v.hasOwnProperty("size")) o.size = size; if (v.hasOwnProperty("x")) d.x = x; if (v.hasOwnProperty("y")) d.y = y; } return r; } // -------------------------------------------------------------------- private static var _maxPoolSize:int = 10000; private static var _tweenPool:Vector. = new Vector.(); private static var _count:int = 0; private static function getTween(o:Object, duration:Number):Tween { var tw:Tween; if (_tweenPool.length > 0) { tw = _tweenPool.pop() as Tween; tw.target = o; tw.duration = duration; tw.enabled = true; } else { tw = new Tween(o, duration); } return tw; } private static function reclaimTween(tw:Tween):void { if (_tweenPool.length < _maxPoolSize) { _tweenPool.push(tw); } } } // end of class Transitioner } import flare.animate.Transitioner; import flash.utils.Proxy; import flash.utils.flash_proxy; /** * Helper class that gets/sets values for a Transitioner. * This layer of indirection allows us to perform "behind-the-scenes" * handling while maintaining simple property assignment syntax. */ dynamic class ValueProxy extends Proxy { private var _trans:Transitioner; private var _object:Object; public function ValueProxy(trans:Transitioner) { _trans = trans; } public function init(obj:Object):Object { _object = obj; return this; } override flash_proxy function callProperty(methodName:*, ... args):* { return null; } override flash_proxy function getProperty(name:*):* { return _trans.getValue(_object, name); } override flash_proxy function setProperty(name:*, value:*):void { _trans.setValue(_object, name, value); } } // end of class ValueProxy