1////////////////////////////////////////////////////////////////////////////////
2//
3//  ADOBE SYSTEMS INCORPORATED
4//  Copyright 2008 Adobe Systems Incorporated
5//  All Rights Reserved.
6//
7//  NOTICE: Adobe permits you to use, modify, and distribute this file
8//  in accordance with the terms of the license agreement accompanying it.
9//
10////////////////////////////////////////////////////////////////////////////////
11package spark.effects
12{
13import mx.core.mx_internal;
14import mx.effects.Effect;
15import mx.effects.IEffectInstance;
16import mx.events.EffectEvent;
17
18import spark.effects.animation.Animation;
19import spark.effects.animation.MotionPath;
20import spark.effects.animation.RepeatBehavior;
21import spark.effects.easing.IEaser;
22import spark.effects.easing.Sine;
23import spark.effects.interpolation.IInterpolator;
24import spark.effects.supportClasses.AnimateInstance;
25
26use namespace mx_internal;
27
28//--------------------------------------
29//  Excluded APIs
30//--------------------------------------
31
32// Exclude suspendBackgroundProcessing for now because the Flex 4
33// effects depend on the layout validation work that the flag suppresses
34[Exclude(name="suspendBackgroundProcessing", kind="property")]
35
36
37[DefaultProperty("motionPaths")]
38
39/**
40 * Dispatched every time the effect updates the target.
41 *
42 * @eventType mx.events.EffectEvent.EFFECT_UPDATE
43 *
44 *  @langversion 3.0
45 *  @playerversion Flash 10
46 *  @playerversion AIR 1.5
47 *  @productversion Flex 4
48 */
49[Event(name="effectUpdate", type="mx.events.EffectEvent")]
50
51/**
52 * Dispatched when the effect begins a new repetition, for
53 * any effect that is repeated more than once.
54 * Flex also dispatches an <code>effectUpdate</code> event
55 * for the effect at the same time.
56 *
57 * @eventType mx.events.EffectEvent.EFFECT_REPEAT
58 *
59 *  @langversion 3.0
60 *  @playerversion Flash 10
61 *  @playerversion AIR 1.5
62 *  @productversion Flex 4
63 */
64[Event(name="effectRepeat", type="mx.events.EffectEvent")]
65
66
67/**
68 * This Animate effect animates an arbitrary set of properties between values.
69 * Specify the properties and values to animate by setting the <code>motionPaths</code> property.
70 *
71 *  @mxml
72 *
73 *  <p>The <code>&lt;s:Animate&gt;</code> tag
74 *  inherits all of the tag attributes of its superclass,
75 *  and adds the following tag attributes:</p>
76 *
77 *  <pre>
78 *  &lt;s:Animate
79 *    <b>Properties</b>
80 *    id="ID"
81 *    disableLayout="false"
82 *    easer="{spark.effects.easing.Sine(.5)}"
83 *    interpolator="NumberInterpolator"
84 *    motionPaths="no default"
85 *    repeatBehavior="loop"
86 *  /&gt;
87 *  </pre>
88 *
89 *  @see spark.effects.supportClasses.AnimateInstance
90 *
91 *  @includeExample examples/AnimateEffectExample.mxml
92 *
93 *  @langversion 3.0
94 *  @playerversion Flash 10
95 *  @playerversion AIR 1.5
96 *  @productversion Flex 4
97 */
98public class Animate extends Effect
99{
100    include "../core/Version.as";
101
102    //--------------------------------------------------------------------------
103    //
104    //  Constructor
105    //
106    //--------------------------------------------------------------------------
107
108    /**
109     *  Constructor.
110     *
111     *  @param target The Object to animate with this effect.
112     *
113     *  @langversion 3.0
114     *  @playerversion Flash 10
115     *  @playerversion AIR 1.5
116     *  @productversion Flex 4
117     */
118    public function Animate(target:Object = null)
119    {
120        super(target);
121
122        instanceClass = AnimateInstance;
123    }
124
125    //--------------------------------------------------------------------------
126    //
127    //  Variables
128    //
129    //--------------------------------------------------------------------------
130
131    // Cached version of the affected properties. By default, we simply return
132    // the list of properties specified in the motionPaths Vector.
133    // Subclasses should override getAffectedProperties() if they wish to
134    // specify a different set.
135    private var affectedProperties:Array = null;
136
137    // Cached version of the relevant styles. By default, we simply return
138    // the list of properties specified in the motionPaths Vector.
139    // Subclasses should override relevantStyles() if they wish to
140    // specify a different set.
141    private var _relevantStyles:Array = null;
142
143    // Cached default easer. We only need one of these, so we cache this static
144    // object to be reused by any Animate instances that do not specify
145    // a custom easer.
146    private static var defaultEaser:IEaser = new Sine(.5);
147
148    // Used to optimize event dispatching: only send out updated events if
149    // there is someone listening
150    private var numUpdateListeners:int = 0;
151
152
153    //--------------------------------------------------------------------------
154    //
155    // Properties
156    //
157    //--------------------------------------------------------------------------
158
159    //----------------------------------
160    //  motionPaths
161    //----------------------------------
162    /**
163     * @private
164     * Storage for the motionPaths property.
165     */
166    private var _motionPaths:Vector.<MotionPath>;
167    /**
168     * A Vector of MotionPath objects, each of which holds the
169     * name of a property being animated and the values that the property
170     * takes during the animation.
171     * This Vector takes precedence over
172     * any properties declared in subclasses of Animate.
173     * For example, if this Array is set directly on a Move effect,
174     * then any properties of the Move effect, such as <code>xFrom</code>, are ignored.
175     *
176     *  @langversion 3.0
177     *  @playerversion Flash 10
178     *  @playerversion AIR 1.5
179     *  @productversion Flex 4
180     */
181    public function get motionPaths():Vector.<MotionPath>
182    {
183        return _motionPaths;
184    }
185    /**
186     * @private
187     */
188    public function set motionPaths(value:Vector.<MotionPath>):void
189    {
190        _motionPaths = value;
191    }
192
193    //----------------------------------
194    //  easer
195    //----------------------------------
196    /**
197     * @private
198     * Storage for the easer property.
199     */
200    private var _easer:IEaser = defaultEaser;
201    /**
202     * The easing behavior for this effect.
203     * This IEaser object is used to convert the elapsed fraction of
204     * the animation into an eased fraction, which is then used to
205     * calculate the value at that eased elapsed fraction.
206     *
207     * <p>Note that it is possible to have easing at both the effect
208     * level and the Keyframe level (where Keyframes hold the values/times
209     * used in the MotionPath structures).
210     * These easing behaviors build on each other.
211     * The <code>easer</code> controls the easing of the overall effect.
212     * The Keyframe controls the easing in any particular interval of the animation.
213     * By default, the easing for Animate is non-linear (Sine(.5)).
214     * The easing for Keyframes is linear. If you desire an effect with easing
215     * at the keyframe level instead, you can set the easing of the
216     * effect to linear, and then set the easing specifically on the Keyframes.</p>
217     *
218     * @default spark.effects.easing.Sine(.5)
219     *
220     * @see spark.effects.easing.Sine
221     *
222     *  @langversion 3.0
223     *  @playerversion Flash 10
224     *  @playerversion AIR 1.5
225     *  @productversion Flex 4
226     */
227    public function get easer():IEaser
228    {
229        return _easer;
230    }
231    /**
232     * @private
233     */
234    public function set easer(value:IEaser):void
235    {
236        _easer = value;
237    }
238
239    //----------------------------------
240    //  interpolator
241    //----------------------------------
242    /**
243     * @private
244     * Storage for the interpolator property.
245     */
246    private var _interpolator:IInterpolator = null;
247    /**
248     * The interpolator used by this effect to calculate values between
249     * the start and end values of a property.
250     * By default, the NumberInterpolator class handles interpolation
251     * or, in the case of the start
252     * and end values being Arrays or Vectors, by the
253     * MultiValueInterpolator class.
254     * Interpolation of other types, or of Numbers that should be interpolated
255     * differently, such as <code>uint</code> values that hold color
256     * channel information, can be handled by supplying a different
257     * interpolator.
258     *
259     *  @see spark.effects.interpolation.NumberInterpolator
260     *  @see spark.effects.interpolation.MultiValueInterpolator
261     *
262     *  @langversion 3.0
263     *  @playerversion Flash 10
264     *  @playerversion AIR 1.5
265     *  @productversion Flex 4
266     */
267    public function get interpolator():IInterpolator
268    {
269        return _interpolator;
270    }
271    /**
272     * @private
273     */
274    public function set interpolator(value:IInterpolator):void
275    {
276        _interpolator = value;
277    }
278
279    //----------------------------------
280    //  repeatBehavior
281    //----------------------------------
282    /**
283     * @private
284     * Storage for the repeatBehavior property.
285     */
286    private var _repeatBehavior:String = RepeatBehavior.LOOP;
287
288    [Inspectable(category="General", enumeration="loop,reverse", defaultValue="loop" )]
289
290    /**
291     * The behavior of a repeating effect, which means an effect
292     * with <code>repeatCount</code> equal to either 0 or &gt; 1. This
293     * value should be either <code>RepeatBehavior.LOOP</code>, which means the animation
294     * repeats in the same order each time, or <code>RepeatBehavior.REVERSE</code>,
295     * which means the animation reverses direction on each iteration.
296     *
297     * @default RepeatBehavior.LOOP
298     *
299     *  @langversion 3.0
300     *  @playerversion Flash 10
301     *  @playerversion AIR 1.5
302     *  @productversion Flex 4
303     */
304    public function get repeatBehavior():String
305    {
306        return _repeatBehavior;
307    }
308    /**
309     * @private
310     */
311    public function set repeatBehavior(value:String):void
312    {
313        _repeatBehavior = value;
314    }
315
316    //----------------------------------
317    //  disableLayout
318    //----------------------------------
319    /**
320     * @private
321     * Storage for the disableLayout property.
322     */
323    private var _disableLayout:Boolean = false;
324    /**
325     * If <code>true</code>, the effect disables layout on its
326     * targets' parent containers, setting the containers <code>autoLayout</code>
327     * property to false, and also disables any layout constraints on the
328     * target objects. These properties are restored when the effect
329     * finishes.
330     *
331     * @default false
332     *
333     * @langversion 3.0
334     * @playerversion Flash 10
335     * @playerversion AIR 1.5
336     * @productversion Flex 4
337     */
338    public function get disableLayout():Boolean
339    {
340        return _disableLayout;
341    }
342    /**
343     * @private
344     */
345    public function set disableLayout(value:Boolean):void
346    {
347        _disableLayout = value;
348    }
349
350    //--------------------------------------------------------------------------
351    //
352    // Methods
353    //
354    //--------------------------------------------------------------------------
355
356    /**
357     *  @private
358     */
359    override public function getAffectedProperties():Array /* of String */
360    {
361        if (!affectedProperties)
362        {
363            if (motionPaths)
364            {
365                var horizontalMove:Boolean;
366                var verticalMove:Boolean;
367                affectedProperties = new Array();
368                for (var i:int = 0; i < motionPaths.length; ++i)
369                {
370                    var effectHolder:MotionPath = MotionPath(motionPaths[i]);
371                    affectedProperties.push(effectHolder.property);
372                    // Some properties side-affect others: add them to the list
373                    switch (effectHolder.property)
374                    {
375                        // left/right/top/bottom side-effect x/y/width/height
376                        case "left":
377                        case "right":
378                        case "horizontalCenter":
379                            horizontalMove = true;
380                            break;
381                        case "top":
382                        case "bottom":
383                        case "verticalCenter":
384                            verticalMove = true;
385                            break;
386                        // The next two let us capture the explicit values, so that we
387                        // can restore them if necessary when the effect ends
388                        case "width":
389                            if (affectedProperties.indexOf("explicitWidth") < 0)
390                                affectedProperties.push("explicitWidth");
391                            if (affectedProperties.indexOf("percentWidth") < 0)
392                                affectedProperties.push("percentWidth");
393                            break;
394                        case "height":
395                            if (affectedProperties.indexOf("explicitHeight") < 0)
396                                affectedProperties.push("explicitHeight");
397                            if (affectedProperties.indexOf("percentHeight") < 0)
398                                affectedProperties.push("percentHeight");
399                            break;
400                    }
401                }
402                if (horizontalMove)
403                {
404                    if (affectedProperties.indexOf("x") < 0)
405                        affectedProperties.push("x");
406                    if (affectedProperties.indexOf("width") < 0)
407                        affectedProperties.push("width");
408                    if (affectedProperties.indexOf("explicitWidth") < 0)
409                        affectedProperties.push("explicitWidth");
410                }
411                if (verticalMove)
412                {
413                    if (affectedProperties.indexOf("y") < 0)
414                        affectedProperties.push("y");
415                    if (affectedProperties.indexOf("height") < 0)
416                        affectedProperties.push("height");
417                    if (affectedProperties.indexOf("explicitHeight") < 0)
418                        affectedProperties.push("explicitHeight");
419                }
420            }
421            else
422            {
423                affectedProperties = [];
424            }
425        }
426        return affectedProperties;
427    }
428
429    /**
430     *  @private
431     */
432    override public function get relevantStyles():Array /* of String */
433    {
434        if (!_relevantStyles)
435        {
436            if (motionPaths)
437            {
438                _relevantStyles = new Array(motionPaths.length);
439                for (var i:int = 0; i < motionPaths.length; ++i)
440                {
441                    var effectHolder:MotionPath = MotionPath(motionPaths[i]);
442                    _relevantStyles[i] = effectHolder.property;
443                }
444            }
445            else
446            {
447                _relevantStyles = [];
448            }
449        }
450        return _relevantStyles;
451    }
452
453    /**
454     * @private
455     */
456    override protected function initInstance(instance:IEffectInstance):void
457    {
458        super.initInstance(instance);
459
460        var animateInstance:AnimateInstance = AnimateInstance(instance);
461
462        animateInstance.addEventListener(EffectEvent.EFFECT_REPEAT, animationEventHandler);
463        // Optimization: don't bother listening for update events if we don't have
464        // any listeners for that event
465        if (numUpdateListeners > 0)
466            animateInstance.addEventListener(EffectEvent.EFFECT_UPDATE, animationEventHandler);
467
468        if (easer)
469            animateInstance.easer = easer;
470
471        if (interpolator)
472            animateInstance.interpolator = interpolator;
473
474        if (isNaN(repeatCount))
475            animateInstance.repeatCount = repeatCount;
476
477        animateInstance.repeatBehavior = repeatBehavior;
478        animateInstance.disableLayout = disableLayout;
479
480        if (motionPaths)
481        {
482            animateInstance.motionPaths = new Vector.<MotionPath>();
483            for (var i:int = 0; i < motionPaths.length; ++i)
484                animateInstance.motionPaths[i] = motionPaths[i].clone();
485        }
486    }
487
488
489    /**
490     * @private
491     */
492    override protected function applyValueToTarget(target:Object, property:String,
493                                          value:*, props:Object):void
494    {
495        if (property in target)
496        {
497            // The "property in target" test only tells if the property exists
498            // in the target, but does not distinguish between read-only and
499            // read-write properties. Put a try/catch around the setter and
500            // ignore any errors.
501            try
502            {
503                target[property] = value;
504            }
505            catch(e:Error)
506            {
507                // Ignore errors
508            }
509        }
510    }
511
512    /**
513     * @private
514     * Track number of listeners to update event for optimization purposes
515     */
516    override public function addEventListener(type:String, listener:Function,
517        useCapture:Boolean=false, priority:int=0,
518        useWeakReference:Boolean=false):void
519    {
520        super.addEventListener(type, listener, useCapture, priority,
521            useWeakReference);
522        if (type == EffectEvent.EFFECT_UPDATE)
523            ++numUpdateListeners;
524    }
525
526    /**
527     * @private
528     * Track number of listeners to update event for optimization purposes
529     */
530    override public function removeEventListener(type:String, listener:Function,
531        useCapture:Boolean=false):void
532    {
533        super.removeEventListener(type, listener, useCapture);
534        if (type == EffectEvent.EFFECT_UPDATE)
535            --numUpdateListeners;
536    }
537
538    /**
539     * @private
540     * Called when the AnimateInstance object dispatches an EffectEvent.
541     *
542     * @param event An event object of type EffectEvent.
543     *
544     *  @langversion 3.0
545     *  @playerversion Flash 10
546     *  @playerversion AIR 1.5
547     *  @productversion Flex 4
548     */
549    private function animationEventHandler(event:EffectEvent):void
550    {
551        dispatchEvent(event);
552    }
553
554    /**
555     * @private
556     * Tell the propertyChanges array to keep all values, unchanged or not.
557     * This enables us to check later, when the effect is finished, whether
558     * we need to restore explicit height/width values.
559     */
560    override mx_internal function captureValues(propChanges:Array,
561                                                setStartValues:Boolean,
562                                                targetsToCapture:Array = null):Array
563    {
564        var propertyChanges:Array =
565            super.captureValues(propChanges, setStartValues, targetsToCapture);
566
567        // If we're capturing explicitWidth/Height values, don't strip unchanging
568        // values from propertyChanges; we want to know whether these values should
569        // be restored when the effect ends
570        var explicitValuesCaptured:Boolean =
571            getAffectedProperties().indexOf("explicitWidth") >= 0 ||
572            getAffectedProperties().indexOf("explicitHeight") >= 0;
573        if (explicitValuesCaptured && setStartValues)
574        {
575            var n:int = propertyChanges.length;
576            for (var i:int = 0; i < n; i++)
577            {
578                if (targetsToCapture == null || targetsToCapture.length == 0 ||
579                    targetsToCapture.indexOf(propertyChanges[i].target) >= 0)
580                {
581                    propertyChanges[i].stripUnchangedValues = false;
582                }
583            }
584        }
585        return propertyChanges;
586    }
587
588    /**
589     *  @private
590     *  After applying start values, check to see whether the values
591     *  contain percentWidth/percentHeight, which should be applied
592     *  after any width/height/explicitWidth/explicitHeight values.
593     */
594    override mx_internal function applyStartValues(propChanges:Array,
595                                                   targets:Array):void
596    {
597        super.applyStartValues(propChanges, targets);
598        // Special case for percentWidth/Height properties. If we are watching
599        // width/explicitWidth or height/explicitHeight values and also
600        // percentWidth/Height values, we have to make sure to apply the
601        // percent values last to avoid having them get clobbered by
602        // applying the width/height values. We could either sort the
603        // propChanges array or just make sure the re-apply the percent
604        // values here, after we're done with the rest.
605        if (propChanges)
606        {
607            var n:int = propChanges.length;
608            for (var i:int = 0; i < n; i++)
609            {
610                var target:Object = propChanges[i].target;
611                if (propChanges[i].start["percentWidth"] !== undefined &&
612                    "percentWidth" in target)
613                {
614                    target.percentWidth = propChanges[i].start["percentWidth"];
615                }
616                if (propChanges[i].start["percentHeight"] !== undefined &&
617                    "percentHeight" in target)
618                {
619                    target.percentHeight = propChanges[i].start["percentHeight"];
620                }
621            }
622        }
623    }
624
625    /**
626     * @private
627     * When we're done, check to see whether explicitWidth/Height values
628     * for the target are NaN in the end state. If so, we should restore
629     * them to that value. This ensures that the target will be sized by
630     * its layout manager instead of by the width/height set during
631     * the effect.
632     */
633    override mx_internal function applyEndValues(propChanges:Array,
634                                                 targets:Array):void
635    {
636        super.applyEndValues(propChanges, targets);
637        // Special case for animating width/height during a transition, because
638        // we may have clobbered the explicitWidth/Height values which otherwise
639        // would not have been set. We need to restore these values plus any
640        // associated layout constraint values (percentWidth/Height)
641        if (propChanges)
642        {
643            var n:int = propChanges.length;
644            for (var i:int = 0; i < n; i++)
645            {
646                var target:Object = propChanges[i].target;
647                if (propChanges[i].end["explicitWidth"] !== undefined)
648                {
649                    if (isNaN(propChanges[i].end["explicitWidth"]) &&
650                        "explicitWidth" in target)
651                    {
652                        target.explicitWidth = NaN;
653                        if (propChanges[i].end["percentWidth"] !== undefined &&
654                            "percentWidth" in target)
655                        {
656                            target.percentWidth = propChanges[i].end["percentWidth"];
657                        }
658                    }
659                }
660                if (propChanges[i].end["explicitHeight"] !== undefined)
661                {
662                    if (isNaN(propChanges[i].end["explicitHeight"]) &&
663                        "explicitHeight" in target)
664                    {
665                        target.explicitHeight = NaN;
666                        if (propChanges[i].end["percentHeight"] !== undefined &&
667                            "percentHeight" in target)
668                        {
669                            target.percentHeight = propChanges[i].end["percentHeight"];
670                        }
671                    }
672                }
673            }
674        }
675    }
676}
677}
678