1////////////////////////////////////////////////////////////////////////////////
2//
3//  ADOBE SYSTEMS INCORPORATED
4//  Copyright 2003-2007 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////////////////////////////////////////////////////////////////////////////////
11
12package mx.effects
13{
14
15import flash.display.DisplayObject;
16import flash.display.DisplayObjectContainer;
17import flash.events.Event;
18import flash.events.EventDispatcher;
19import flash.events.FocusEvent;
20import flash.system.ApplicationDomain;
21import flash.utils.Dictionary;
22
23import mx.core.EventPriority;
24import mx.core.FlexGlobals;
25import mx.core.IDeferredInstantiationUIComponent;
26import mx.core.IFlexDisplayObject;
27import mx.core.IUIComponent;
28import mx.core.UIComponent;
29import mx.core.UIComponentCachePolicy;
30import mx.core.mx_internal;
31import mx.events.EffectEvent;
32import mx.events.FlexEvent;
33import mx.events.MoveEvent;
34import mx.events.ResizeEvent;
35import mx.managers.ISystemManager;
36import mx.managers.SystemManager;
37import mx.resources.IResourceManager;
38import mx.resources.ResourceManager;
39import mx.core.IVisualElementContainer;
40import mx.core.IVisualElement;
41
42use namespace mx_internal;
43
44[ResourceBundle("effects")]
45
46/**
47 *  The EffectManager class listens for events, such as the <code>show</code>
48 *  and <code>move</code> events, dispatched by objects in a Flex application.
49 *  For each event, corresponding to an event trigger, it determines if
50 *  there is an effect assigned to the object.
51 *  If an effect is defined, it plays the effect.
52 *
53 *  @langversion 3.0
54 *  @playerversion Flash 9
55 *  @playerversion AIR 1.1
56 *  @productversion Flex 3
57 */
58public class EffectManager extends EventDispatcher
59{
60    include "../core/Version.as";
61
62    //--------------------------------------------------------------------------
63    //
64    //  Class variables
65    //
66    //--------------------------------------------------------------------------
67
68    /**
69     *  @private
70     *  Keeps track of all the triggered effects that are currently playing.
71     */
72    mx_internal static var effectsPlaying:Array /* of EffectNode */ = [];
73
74    /**
75     *  @private
76     *  Map with event type as key and effectTrigger as value.
77     */
78    private static var effectTriggersForEvent:Object = {};
79
80    /**
81     *  @private
82     *  Map with effectTrigger as key and event type as value.
83     */
84    private static var eventsForEffectTriggers:Object = {};
85
86    /**
87     *  @private
88     *  Array containing miscellaneous info about effect targets.
89     *  An element in the array is an Object with three fields:
90     *  target - reference to the target
91     *  bitmapEffectsCount - number of bitmap effects
92     *                       currently playing on the target
93     *  vectorEffectsCount - number of vector effects
94     *                       currently playing on the target
95     */
96    private static var targetsInfo:Array /* of Object */ = [];
97
98    /**
99     *  @private
100     *  Remember when suspendEventHandling() has been called
101     *  without a matching resumeEventHandling().
102     */
103    private static var eventHandlingSuspendCount:Number = 0;
104
105    /**
106     *  @private
107     *  Weak backing storage for the lastEffectCreated instance.
108     */
109    private static var weakKeys:Dictionary;
110
111    /**
112     *  @private
113     *  This internal property is currently only used by the ViewStack
114     *  component in order to coordinate the hideEffect completion with
115     *  ViewStack change logic.  It would be best to find a better way
116     *  to do this (localized to ViewStack) in the future however.
117     */
118    mx_internal static function set lastEffectCreated(effect:Effect):void
119    {
120        // We just regenerate our dictionary instead of finding and deleting
121        // the previous key.  Faster.
122        weakKeys = new Dictionary(true);
123        weakKeys[effect] = true;
124    }
125
126    /**
127     *  @private
128     */
129    mx_internal static function get lastEffectCreated():Effect
130    {
131        for (var item:* in weakKeys)
132            return item;
133
134        return null;
135    }
136
137    /**
138     *  @private
139     *  Storage for the resourceManager getter.
140     *  This gets initialized on first access,
141     *  not at static initialization time, in order to ensure
142     *  that the Singleton registry has already been initialized.
143     */
144    private static var _resourceManager:IResourceManager;
145
146    /**
147     *  @private
148     *  A reference to the object which manages
149     *  all of the application's localized resources.
150     *  This is a singleton instance which implements
151     *  the IResourceManager interface.
152     */
153    private static function get resourceManager():IResourceManager
154    {
155        if (!_resourceManager)
156            _resourceManager = ResourceManager.getInstance();
157
158        return _resourceManager;
159    }
160
161    //--------------------------------------------------------------------------
162    //
163    //  Class methods
164    //
165    //--------------------------------------------------------------------------
166
167    /**
168     *  After this method is called, the EffectManager class ignores
169     *  all events, and no effects are triggered, until a call to
170     *  <code>resumeEventHandling()</code>.
171     *  Used internally so that an effect that is updating the screen
172     *  does not cause another effect to be triggered.
173     *
174     *  @langversion 3.0
175     *  @playerversion Flash 9
176     *  @playerversion AIR 1.1
177     *  @productversion Flex 3
178     */
179    public static function suspendEventHandling():void
180    {
181        eventHandlingSuspendCount++;
182    }
183
184    /**
185     *  Allows the EffectManager class to resume processing events
186     *  after a call to the <code>suspendEventHandling()</code> method.
187     *  Used internally in conjunction with the
188     *  <code>suspendEventHandling()</code> method
189     *  so that an effect that is updating the screen
190     *  does not cause another effect to be triggered.
191     *
192     *  @langversion 3.0
193     *  @playerversion Flash 9
194     *  @playerversion AIR 1.1
195     *  @productversion Flex 3
196     */
197    public static function resumeEventHandling():void
198    {
199        eventHandlingSuspendCount--;
200    }
201
202    /**
203     *  Immediately ends any effects currently playing on a target.
204     *
205     *  @param target The target component on which to end all effects.
206     *
207     *  @langversion 3.0
208     *  @playerversion Flash 9
209     *  @playerversion AIR 1.1
210     *  @productversion Flex 3
211     */
212    public static function endEffectsForTarget(target:IUIComponent):void
213    {
214        // Iterate through the array backward, because calling end()
215        // may cause the element to be removed from the array.
216        var n:int = effectsPlaying.length;
217        for (var i:int = n - 1; i >= 0; i--)
218        {
219            var otherInst:EffectInstance =
220                effectsPlaying[i].instance;
221            if (otherInst.target == target)
222                otherInst.end();
223        }
224    }
225
226    /**
227     *  @private
228     */
229    mx_internal static function setStyle(styleProp:String, target:*):void
230    {
231        // Anytime that any UIComponent's style is changed,
232        // check to see if the styleProp that's changing
233        // is an effect trigger (e.g., showEffect).
234
235        var eventName:String = eventsForEffectTriggers[styleProp];
236        if (eventName != null && eventName != "")
237        {
238            target.addEventListener(eventName,
239                                    EffectManager.eventHandler,
240                                    false, EventPriority.EFFECT);
241        }
242    }
243
244    /**
245     *  @private
246     *  Internal function used to instantiate an effect
247     */
248    mx_internal static function createEffectForType(target:Object,
249                                               type:String):Effect
250    {
251        var trigger:String = effectTriggersForEvent[type];
252
253        if (trigger == "")
254            trigger = type + "Effect"; // For backwards compatibility
255
256        var value:Object = target.getStyle(trigger);
257
258        if (!value)
259            return null;
260
261        if (value is Class)
262        {
263            var cls:Class = Class(value);
264            return new cls(target);
265        }
266
267        // If we don't find the ID on the parent document, then just move on.
268        try
269        {
270            var effectObj:Effect;
271            if (value is String)
272            {
273                var doc:Object = target.parentDocument;
274                // The main Application doesn't have a parentDocument.
275                if (!doc)
276                    doc = FlexGlobals.topLevelApplication;
277                effectObj = doc[value];
278            }
279            else if (value is Effect)
280            {
281                effectObj = Effect(value);
282            }
283
284            if (effectObj)
285            {
286                effectObj.target = target;
287                return effectObj;
288            }
289        }
290        catch(e:Error)
291        {
292        }
293
294        var effectClass:Class;
295        if (target is UIComponent && target.moduleFactory)
296        {
297            // only UIComponents have moduleFactories
298            var appDomain:ApplicationDomain =
299                target.moduleFactory.info()["currentDomain"];
300            if (appDomain.hasDefinition("mx.effects." + value))
301                effectClass = Class(appDomain.getDefinition("mx.effects." + value));
302        }
303        if (!effectClass)
304            effectClass = Class(target.systemManager.getDefinitionByName(
305                "mx.effects." + value));
306
307        if (effectClass)
308            return new effectClass(target);
309
310        return null;
311    }
312
313    /**
314     *  @private
315     *  Internal function used while playing effects
316     */
317    private static function animateSameProperty(a:Effect, b:Effect,
318                                                c:EffectInstance):Boolean
319    {
320        // This function returns true if "a" and "b" animate
321        // the same property of the same object.
322
323        if (a.target == c.target)
324        {
325            var aProps:Array = a.getAffectedProperties();
326            var bProps:Array = b.getAffectedProperties();
327
328            var n:int = aProps.length;
329            var m:int = bProps.length;
330
331            for (var i:int = 0; i < n; i++)
332            {
333                for (var j:int = 0; j < m; j++)
334                {
335                    if (aProps[i] == bProps[j])
336                        return true;
337                }
338            }
339        }
340
341        return false;
342    }
343
344    /**
345     *  @private
346     *  Should be called by an effect instance before it starts playing,
347     *  to suggest bitmap caching on the target.
348     *  E.g. Fade calls this function in its play().
349     */
350    mx_internal static function startBitmapEffect(target:IUIComponent):void
351    {
352        cacheOrUncacheTargetAsBitmap(target, true, true);
353    }
354
355    /**
356     *  @private
357     *  Should be called by an effect instance after it has finished playing,
358     *  to suggest that the cached bitmap for the target can be freed.
359     *  E.g. Fade calls this function in its onTweenEnd().
360     */
361    mx_internal static function endBitmapEffect(target:IUIComponent):void
362    {
363        cacheOrUncacheTargetAsBitmap(target, false, true);
364    }
365
366    /**
367     *  @private
368     *  Should be called by an effect instance before it starts playing, to
369     *  suggest that bitmap caching should be turned off on the target.
370     *  E.g. Resize calls this function in its play().
371     */
372    mx_internal static function startVectorEffect(target:IUIComponent):void
373    {
374        cacheOrUncacheTargetAsBitmap(target, true, false);
375    }
376
377    /**
378     *  @private
379     *  Should be called by an effect instance after it has finished playing,
380     *  to suggest that bitmap caching may be turned back on on the target.
381     *  E.g. Resize calls this function in its onTweenEnd().
382     */
383    mx_internal static function endVectorEffect(target:IUIComponent):void
384    {
385        cacheOrUncacheTargetAsBitmap(target, false, false);
386    }
387
388    /**
389     *  @private
390     *  Cache or uncache the target as a bitmap depending on which effects are
391     *  currently playing on the target.
392     *
393     *  @param target The effect target.
394     *
395     *  @param effectStart Whether this is the starting of the effect.
396     *  false means it's the ending of the effect.
397     *
398     *  @param bitmapEffect Whether this is a bitmap effect.
399     *  false means it's a vector effect (like resize, zoom, etc.)
400     *  that wants the target object to be uncached.
401     */
402    private static function cacheOrUncacheTargetAsBitmap(
403                                target:IUIComponent,
404                                effectStart:Boolean = true,
405                                bitmapEffect:Boolean = true):void
406    {
407        var n:int;
408        var i:int;
409
410        // Object containing information about the target.
411        var info:Object = null;
412
413        n = targetsInfo.length;
414        for (i = 0; i < n; i++)
415        {
416            if (targetsInfo[i].target == target)
417            {
418                info = targetsInfo[i];
419                break;
420            }
421        }
422
423        // If no info object is available, create an object and push it
424        // into the array.
425        if (!info)
426        {
427            info =
428            {
429                target: target,
430                bitmapEffectsCount: 0,
431                vectorEffectsCount: 0
432            };
433
434            targetsInfo.push(info);
435        }
436
437        if (effectStart)
438        {
439            if (bitmapEffect)
440            {
441                info.bitmapEffectsCount++;
442
443                // If no vector effects are currently playing,
444                // cache the target.
445                if (info.vectorEffectsCount == 0 &&
446                    target is IDeferredInstantiationUIComponent)
447                {
448                    IDeferredInstantiationUIComponent(target).cacheHeuristic = true;
449                }
450            }
451            else
452            {
453                // If a vector effect started playing, forcibly uncache
454                // the target regardless of anything else.
455                if (info.vectorEffectsCount++ == 0 &&
456                    target is IDeferredInstantiationUIComponent &&
457                    IDeferredInstantiationUIComponent(target).cachePolicy == UIComponentCachePolicy.AUTO)
458                {
459                    target.cacheAsBitmap = false;
460                }
461            }
462        }
463        else // effect end
464        {
465            if (bitmapEffect)
466            {
467                if (info.bitmapEffectsCount != 0)
468                    info.bitmapEffectsCount--;
469
470                if (target is IDeferredInstantiationUIComponent)
471                    IDeferredInstantiationUIComponent(target).cacheHeuristic = false;
472            }
473            else
474            {
475                if (info.vectorEffectsCount != 0)
476                {
477                    // If no more vector effects are playing but bitmap
478                    // effects are still playing, cache the target.
479                    if (--info.vectorEffectsCount == 0 &&
480                        info.bitmapEffectsCount != 0)
481                    {
482                        // Crank up the counter.
483                        n = info.bitmapEffectsCount;
484                        for (i = 0; i < n; i++)
485                        {
486                            if (target is IDeferredInstantiationUIComponent)
487                            IDeferredInstantiationUIComponent(target).cacheHeuristic = true;
488                        }
489                    }
490                }
491            }
492
493            if (info.bitmapEffectsCount == 0 && info.vectorEffectsCount == 0)
494            {
495                // No more effects are playing on this target, so discard the
496                // info object (should speed up lookups).
497                n = targetsInfo.length;
498                for (i = 0; i < n; i++)
499                {
500                    if (targetsInfo[i].target == target)
501                    {
502                        targetsInfo.splice(i, 1);
503                        break;
504                    }
505                }
506            }
507        }
508    }
509
510    /**
511     *  @private
512     *  Called in code generated by MXML compiler.
513     */
514    mx_internal static function registerEffectTrigger(name:String,
515                                                 event:String):void
516    {
517        if (name != "")
518        {
519            if (event == "")
520            {
521                // For backwards compatibility.
522                var strLen:Number = name.length;
523                if (strLen > 6 && name.substring(strLen - 6) == "Effect")
524                    event = name.substring(0, strLen - 6);
525            }
526
527            if (event != "")
528            {
529                effectTriggersForEvent[event] = name;
530                eventsForEffectTriggers[name] = event;
531            }
532        }
533    }
534
535    /**
536     *  @private
537     */
538    mx_internal static function getEventForEffectTrigger(effectTrigger:String):String
539    {
540        if (eventsForEffectTriggers)
541        {
542            try
543            {
544                return eventsForEffectTriggers[effectTrigger];
545            }
546            catch(e:Error)
547            {
548                return "";
549            }
550        }
551
552        return "";
553    }
554
555    //--------------------------------------------------------------------------
556    //
557    //  Class event handlers
558    //
559    //--------------------------------------------------------------------------
560
561    /**
562     *  @private
563     */
564    mx_internal static function eventHandler(eventObj:Event):void
565    {
566        // If this event fired because an effect is currently playing
567        // (in other words, if an effect was the source of this event),
568        // then don't listen to the effect.
569        if (!(eventObj.currentTarget is IFlexDisplayObject))
570            return;
571
572        if (eventHandlingSuspendCount > 0)
573            return;
574
575        if (eventObj is FocusEvent &&
576            (eventObj.type == FocusEvent.FOCUS_OUT ||
577             eventObj.type == FocusEvent.FOCUS_IN))
578        {
579            var focusEventObj:FocusEvent = FocusEvent(eventObj);
580            if (focusEventObj.relatedObject &&
581                (focusEventObj.currentTarget.contains(focusEventObj.relatedObject) ||
582                 focusEventObj.currentTarget == focusEventObj.relatedObject))
583            {
584                return;
585            }
586        }
587
588        // Only trigger the event for added and removed if the current target is the same as the target.
589        if ((eventObj.type == Event.ADDED || eventObj.type == Event.REMOVED) && eventObj.target != eventObj.currentTarget)
590            return;
591
592        if (eventObj.type == Event.REMOVED)
593        {
594            if (eventObj.target is UIComponent)
595            {
596                if (UIComponent(eventObj.target).initialized == false)
597                {
598                    return;
599                }
600                else if (UIComponent(eventObj.target).isEffectStarted)
601                {
602                    for (var i:int = 0; i < UIComponent(eventObj.target)._effectsStarted.length; i++)
603                    {
604                        // Don't allow removedEffect to trigger more than one effect at a time
605                        if (UIComponent(eventObj.target)._effectsStarted[i].triggerEvent.type == Event.REMOVED)
606                            return;
607                    }
608                }
609            }
610
611            var targ:DisplayObject = eventObj.target as DisplayObject;
612
613            if (targ != null)
614            {
615                var parent:DisplayObjectContainer = targ.parent as DisplayObjectContainer;
616
617                if (parent != null)
618                {
619                    var index:int = parent.getChildIndex(targ);
620                    if (index >= 0)
621                    {
622                        if (targ is UIComponent)
623                        {
624                            // Since we get the "removed" event before the child is actually removed,
625                            // we need to delay adding back the child. We must exit the current
626                            // script block must exit before the child can be removed.
627                            UIComponent(targ).callLater(removedEffectHandler, [targ, parent, index, eventObj]);
628                        }
629                    }
630                }
631            }
632        }
633        else
634        {
635            createAndPlayEffect(eventObj, eventObj.currentTarget);
636        }
637    }
638
639    /**
640     *  @private
641     */
642    private static function createAndPlayEffect(eventObj:Event, target:Object):void
643    {
644
645        var effectInst:Effect = createEffectForType(target, eventObj.type);
646        if (!effectInst)
647            return;
648
649        if (effectInst is Zoom && eventObj.type == MoveEvent.MOVE)
650        {
651            var message:String = resourceManager.getString(
652                "effects", "incorrectTrigger");
653            throw new Error(message);
654        }
655
656        // If this is a "move" or "resize" event that was caused by
657        // the layout manager doing an initial layout, then don't
658        // play any effects.
659        // Ditto for "show" or "hide" effects triggered by ViewStack.doLayout.
660        if (target.initialized == false)
661        {
662            var type:String = eventObj.type;
663            if (type == MoveEvent.MOVE ||
664                type == ResizeEvent.RESIZE ||
665                type == FlexEvent.SHOW ||
666                type == FlexEvent.HIDE ||
667                type == Event.CHANGE)
668            {
669                effectInst = null;
670                return;
671            }
672        }
673
674        var n:int;
675        var i:int;
676        var m:int;
677        var j:int;
678
679        // Some components contain built-in tweens, which are not managed by
680        // the EffectManager.  If one of those tweens is currently playing,
681        // and if it's animating a conflicting property, then don't play this
682        // tween.
683        if (effectInst.target is IUIComponent)
684        {
685            var tweeningProperties:Array =
686                IUIComponent(effectInst.target).tweeningProperties;
687            if (tweeningProperties && tweeningProperties.length > 0)
688            {
689                var effectProperties:Array = effectInst.getAffectedProperties();
690
691                n = tweeningProperties.length;
692                m = effectProperties.length;
693
694                for (i = 0; i < n; i++)
695                {
696                    for (j = 0; j < m; j++)
697                    {
698                        if (tweeningProperties[i] == effectProperties[j])
699                        {
700                            effectInst = null;
701                            return;
702                        }
703                    }
704                }
705            }
706        }
707
708        // At any given time, only one effect may be animating a given
709        // property of a given target object.  If some other effect was
710        // previously animating the same properties of my target object,
711        // then finish the other effect before starting this new one.
712        //
713        if (effectInst.target is UIComponent &&
714            UIComponent(effectInst.target).isEffectStarted)
715        {
716            var affectedProps:Array = effectInst.getAffectedProperties();
717            for (i = 0; i < affectedProps.length; i++)
718            {
719                var runningInstances:Array =
720                    effectInst.target.getEffectsForProperty(affectedProps[i]);
721                if (runningInstances.length > 0)
722                {
723                    if (eventObj.type == ResizeEvent.RESIZE)
724                        return;
725
726                    for (j = 0; j < runningInstances.length; j++)
727                    {
728                        var otherInst:EffectInstance = runningInstances[j];
729                        if (eventObj.type == FlexEvent.SHOW && otherInst.hideOnEffectEnd)
730                        {
731                            otherInst.target.removeEventListener(
732                                FlexEvent.SHOW, otherInst.eventHandler);
733                            otherInst.hideOnEffectEnd = false;
734
735                        }
736
737                        /*
738                        if (eventObj.type == MoveEvent.MOVE &&
739                            ((affectedProps[i] == "width") ||
740                             (affectedProps[i] == "height") ||
741                             (affectedProps[i] == "x") ||
742                             (affectedProps[i] == "y")) &&
743                             effectInst.target.getStyle("moveEffect") != undefined)
744                        {
745                            trace("EM Got Move and ignoring");
746                            return;
747                        }
748
749                        if (eventObj.type == ResizeEvent.RESIZE &&
750                            ((affectedProps[i] == "width") ||
751                             (affectedProps[i] == "height")) &&
752                             effectInst.target.getStyle("resizeEffect") != undefined)
753                        {
754                            return;
755                        }
756                        */
757
758                        otherInst.end();
759                    }
760                }
761            }
762        }
763
764        // Pass in event data for effect initialization
765        effectInst.triggerEvent = eventObj;
766
767        // Tell the effectInst that I'm the listener, so that my "onEffectEnd"
768        // method is called when the effect finishes playing.  The
769        // onEffectEnd handler will remove this effect from the effectsPlaying
770        // array.
771        effectInst.addEventListener(EffectEvent.EFFECT_END,
772                                    EffectManager.effectEndHandler);
773
774        lastEffectCreated = effectInst;
775
776        var instances:Array = effectInst.play();
777        n = instances.length;
778        for (i = 0; i < n; i++)
779        {
780            effectsPlaying.push(
781                new EffectNode(effectInst, instances[i]));
782        }
783
784        // Block all layout, responses from web services, and other background
785        // processing until the effect finishes executing.
786        if (effectInst.suspendBackgroundProcessing)
787            UIComponent.suspendBackgroundProcessing();
788    }
789
790    /**
791     *  @private
792     *  Delayed function call when effect is triggered by "removed" event
793     */
794    private static function removedEffectHandler(target:DisplayObject, parent:DisplayObjectContainer, index:int, eventObj:Event):void
795    {
796        suspendEventHandling();
797        // Add the child back to the parent so the effect can play upon it
798        if (parent is IVisualElementContainer && target is IVisualElement)
799            (IVisualElementContainer(parent).addElementAt(target as IVisualElement, index));
800        else
801            parent.addChildAt(target, index);
802        resumeEventHandling();
803        // Use target because the player assigns the Stage to the currentTarget when we leave the scope of the event handler function
804        createAndPlayEffect(eventObj, target);
805    }
806
807    /**
808     *  @private
809     *  Internal function used while playing effects
810     */
811    mx_internal static function effectEndHandler(event:EffectEvent):void
812    {
813        var effectInst:IEffectInstance = event.effectInstance;
814        // This function is called when an effect, which was started
815        // earlier by this effect manager, finishes playing.  Remove
816        // this effect from the "effectPlaying" list
817        var n:int = effectsPlaying.length;
818        for (var i:int = n - 1; i >= 0; i--)
819        {
820            if (effectsPlaying[i].instance == effectInst)
821            {
822                effectsPlaying.splice(i, 1);
823                break;
824            }
825        }
826
827        // If the event that caused this effect was "hide", then the
828        // eventHandler() method set the object's visiblity to true.
829        // Now that the effect is finished playing, set visiblity to false.
830        if (Object(effectInst).hideOnEffectEnd == true)
831        {
832            effectInst.target.removeEventListener(
833                FlexEvent.SHOW, Object(effectInst).eventHandler);
834            effectInst.target.setVisible(false, true);
835        }
836
837        if (effectInst.triggerEvent && effectInst.triggerEvent.type == Event.REMOVED)
838        {
839            var targ:DisplayObject = effectInst.target as DisplayObject;
840
841            if (targ != null)
842            {
843                var parent:DisplayObjectContainer = targ.parent as DisplayObjectContainer;
844
845                if (parent != null)
846                {
847                    // Since we added the child back to the parent when the effect began,
848                    // we need to remove it once the effect has finished.
849                    suspendEventHandling();
850                    if (parent is IVisualElementContainer && targ is IVisualElement)
851                        (IVisualElementContainer(parent).removeElement(targ as IVisualElement));
852                    else
853                        parent.removeChild(targ);
854                    resumeEventHandling();
855                }
856            }
857        }
858
859        // Resume the background processing that was suspended earlier
860        if (effectInst.suspendBackgroundProcessing)
861            UIComponent.resumeBackgroundProcessing();
862    }
863
864    //--------------------------------------------------------------------------
865    //
866    //  Diagnostics
867    //
868    //--------------------------------------------------------------------------
869    private static var effects:Dictionary = new Dictionary(true);
870
871    mx_internal static function effectStarted(effect:EffectInstance):void
872    {
873        effects[effect] = 1;
874    }
875
876    mx_internal static function effectFinished(effect:EffectInstance):void
877    {
878        delete effects[effect];
879    }
880
881    mx_internal static function effectsInEffect():Boolean
882    {
883        for (var i:* in effects)
884        {
885            return true;
886        }
887        return false;
888    }
889}
890
891}
892
893////////////////////////////////////////////////////////////////////////////////
894//
895//  Helper class: EffectNode
896//
897////////////////////////////////////////////////////////////////////////////////
898
899import mx.effects.Effect;
900import mx.effects.EffectInstance;
901
902/**
903 *  @private
904 */
905class EffectNode
906{
907    //--------------------------------------------------------------------------
908    //
909    //  Constructor
910    //
911    //--------------------------------------------------------------------------
912
913    /**
914     *  Constructor.
915     *
916     *  @langversion 3.0
917     *  @playerversion Flash 9
918     *  @playerversion AIR 1.1
919     *  @productversion Flex 3
920     */
921    public function EffectNode(factory:Effect, instance:EffectInstance)
922    {
923        super();
924
925        this.factory = factory;
926        this.instance = instance;
927    }
928
929    //--------------------------------------------------------------------------
930    //
931    //  Properties
932    //
933    //--------------------------------------------------------------------------
934
935    /**
936     *  @private
937     */
938    public var factory:Effect;
939
940    /**
941     *  @private
942     */
943    public var instance:EffectInstance;
944}
945