blob: f55a5bd123f1e883aa65195ee61d0f113398ce68 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package mx.effects
{
import flash.events.Event;
import flash.events.EventDispatcher;
import mx.core.FlexVersion;
import mx.core.IFlexDisplayObject;
import mx.core.mx_internal;
import mx.effects.effectClasses.AddRemoveEffectTargetFilter;
import mx.effects.effectClasses.HideShowEffectTargetFilter;
import mx.effects.effectClasses.PropertyChanges;
import mx.events.EffectEvent;
import mx.managers.LayoutManager;
import mx.styles.IStyleClient;
import mx.utils.NameUtil;
use namespace mx_internal;
/**
* Dispatched when one of the effect's instances finishes playing,
* either when the instance finishes playing or when the effect
* is interrupted by a call to the <code>end()</code> method.
*
* @eventType mx.events.EffectEvent.EFFECT_END
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="effectEnd", type="mx.events.EffectEvent")]
/**
* Dispatched when the effect has been stopped,
* which only occurs when the effect is
* interrupted by a call to the <code>stop()</code> method.
* The EFFECT_END event is also dispatched to indicate that
* the effect has ended. This extra event is sent first, as an
* indicator to listeners that the effect did not reach its
* end state.
*
* @eventType mx.events.EffectEvent.EFFECT_STOP
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="effectStop", type="mx.events.EffectEvent")]
/**
* Dispatched when the effect starts playing.
*
* @eventType mx.events.EffectEvent.EFFECT_START
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="effectStart", type="mx.events.EffectEvent")]
/**
* The Effect class is an abstract base class that defines the basic
* functionality of all Flex effects.
* The Effect class defines the base factory class for all effects.
* The EffectInstance class defines the base class for all effect
* instance subclasses.
*
* <p>You do not create an instance of the Effect class itself
* in an application.
* Instead, you create an instance of one of the subclasses,
* such as Fade or WipeLeft.</p>
*
* @mxml
*
* <p>The Effect class defines the following properties,
* which all of its subclasses inherit:</p>
*
* <pre>
* &lt;mx:<i>tagname</i>
* <b>Properties</b>
* customFilter=""
* duration="500"
* filter=""
* hideFocusRing="false"
* perElementOffset="0"
* repeatCount="1"
* repeatDelay="0"
* startDelay="0"
* suspendBackgroundProcessing="false|true"
* target="<i>effect target</i>"
* targets="<i>array of effect targets</i>"
*
* <b>Events</b>
* effectEnd="<i>No default</i>"
* efectStart="<i>No default</i>"
* /&gt;
* </pre>
*
* @see mx.effects.EffectInstance
*
* @includeExample examples/SimpleEffectExample.mxml
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class Effect extends EventDispatcher implements IEffect
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
private static function mergeArrays(a1:Array, a2:Array):Array
{
if (a2)
{
for (var i2:int = 0; i2 < a2.length; i2++)
{
var addIt:Boolean = true;
for (var i1:int = 0; i1 < a1.length; i1++)
{
if (a1[i1] == a2[i2])
{
addIt = false;
break;
}
}
if (addIt)
a1.push(a2[i2]);
}
}
return a1;
}
/**
* @private
*/
private static function stripUnchangedValues(propChanges:Array):Array
{
// Go through and remove any before/after values that are the same.
for (var i:int = 0; i < propChanges.length; i++)
{
if (propChanges[i].stripUnchangedValues == false)
continue;
for (var prop:Object in propChanges[i].start)
{
if ((propChanges[i].start[prop] ==
propChanges[i].end[prop]) ||
(typeof(propChanges[i].start[prop]) == "number" &&
typeof(propChanges[i].end[prop])== "number" &&
isNaN(propChanges[i].start[prop]) &&
isNaN(propChanges[i].end[prop])))
{
delete propChanges[i].start[prop];
delete propChanges[i].end[prop];
}
}
}
return propChanges;
}
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* <p>Starting an effect is usually a three-step process:</p>
*
* <ul>
* <li>Create an instance of the effect object
* with the <code>new</code> operator.</li>
* <li>Set properties on the effect object,
* such as <code>duration</code>.</li>
* <li>Call the <code>play()</code> method
* or assign the effect to a trigger.</li>
* </ul>
*
* @param target The Object to animate with this effect.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function Effect(target:Object = null)
{
super();
this.target = target;
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
// Transitions will force the values animated by this effect to get set to
// their final values, as specified in the end state, when the effect finishes.
// This flag is set to true when the effect is being played by a transition,
// but applying the values is still gated by the value of the
// applyTransitionEndProperties flag, which is set on a per-Effect basis.
mx_internal var applyEndValuesWhenDone:Boolean = false;
// This property tracks whether the effect is playing in interruption mode.
// Normally, effects pick up starting values (when not provided explicitly)
// from the current values of the target objects. But when playing in interruption
// mode, the effect should get its starting values from the propertyChanges
// array instead.
private var _transitionInterruption:Boolean = false;
mx_internal function get transitionInterruption():Boolean
{
return _transitionInterruption;
}
mx_internal function set transitionInterruption(value:Boolean):void
{
_transitionInterruption = value;
}
// This is new behavior for the Flex4 effects; previously, we would not
// set the end-state values automatically. Because of this switch, the
// default value is hinged on a compatibility check, so that applications
// can choose to have the older behavior instead.
/**
* This flag controls whether the effect, when run in a transition,
* automatically applies the property values according to the end
* state, as opposed to leaving values as set by the effect itself.
*
* @default true
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var applyTransitionEndProperties:Boolean =
(FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_0) ?
false : true;
/**
* @private
*/
private var _instances:Array /* of EffectInstance */ = [];
/**
* @private
*/
private var _callValidateNow:Boolean = false;
/**
* @private
*/
private var isPaused:Boolean = false;
/**
* @private
*/
mx_internal var filterObject:EffectTargetFilter;
/**
* @private
* Used in applyValueToTarget()
*/
mx_internal var applyActualDimensions:Boolean = true;
/**
* @private
* Holds the init object passed in by the Transition.
*/
mx_internal var propertyChangesArray:Array;
/**
* @private
* Track the property set in the play() function
*/
mx_internal var playReversed:Boolean;
private var effectStopped:Boolean;
/**
* @private
* Pointer back to the CompositeEffect that created this instance.
* Value is null if we are not the child of a CompositeEffect
*/
mx_internal var parentCompositeEffect:Effect;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// className
//----------------------------------
/**
* @copy mx.effects.IEffect#className
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get className():String
{
return NameUtil.getUnqualifiedClassName(this);
}
//----------------------------------
// customFilter
//----------------------------------
/**
* @private
* Storage for the customFilter property.
*/
private var _customFilter:EffectTargetFilter;
/**
* @copy mx.effects.IEffect#customFilter
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get customFilter():EffectTargetFilter
{
return _customFilter;
}
/**
* @private
*/
public function set customFilter(value:EffectTargetFilter):void
{
_customFilter = value;
filterObject = value;
}
//----------------------------------
// duration
//----------------------------------
/**
* @private
* Storage for the duration property.
*/
private var _duration:Number = 500;
/**
* @private
*/
mx_internal var durationExplicitlySet:Boolean = false;
[Inspectable(category="General", defaultValue="500", minValue="0.0")]
/**
* @copy mx.effects.IEffect#duration
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get duration():Number
{
if (!durationExplicitlySet &&
parentCompositeEffect)
{
return parentCompositeEffect.duration;
}
else
{
return _duration;
}
}
/**
* @private
*/
public function set duration(value:Number):void
{
durationExplicitlySet = true;
_duration = value;
}
//----------------------------------
// effectTargetHost
//----------------------------------
/**
* @private
* Storage for the effectTargetHost property.
*/
private var _effectTargetHost:IEffectTargetHost;
/**
* @copy mx.effects.IEffect#effectTargetHost
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get effectTargetHost():IEffectTargetHost
{
return _effectTargetHost;
}
/**
* @private
*/
public function set effectTargetHost(value:IEffectTargetHost):void
{
_effectTargetHost = value;
}
//----------------------------------
// endValuesCaptured
//----------------------------------
/**
* A flag containing <code>true</code> if the end values
* of an effect have already been determined,
* or <code>false</code> if they should be acquired from the
* current properties of the effect targets when the effect runs.
* This property is required by data effects because the sequence
* of setting up the data effects, such as DefaultListEffect
* and DefaultTileListEffect, is more complicated than for
* normal effects.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var endValuesCaptured:Boolean = false;
//----------------------------------
// filter
//----------------------------------
/**
* @private
* Storage for the filter property.
*/
private var _filter:String;
[Inspectable(category="General", enumeration="add,remove,show,hide,move,resize,addItem,removeItem,replacedItem,replacementItem,none", defaultValue="none")]
/**
* @copy mx.effects.IEffect#filter
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get filter():String
{
return _filter;
}
/**
* @private
*/
public function set filter(value:String):void
{
if (!customFilter)
{
_filter = value;
switch (value)
{
case "add":
case "remove":
{
filterObject =
new AddRemoveEffectTargetFilter();
AddRemoveEffectTargetFilter(filterObject).add =
(value == "add");
break;
}
case "hide":
case "show":
{
filterObject =
new HideShowEffectTargetFilter();
HideShowEffectTargetFilter(filterObject).show =
(value == "show");
break;
}
case "move":
{
filterObject =
new EffectTargetFilter();
filterObject.filterProperties =
[ "x", "y" ];
break;
}
case "resize":
{
filterObject =
new EffectTargetFilter();
filterObject.filterProperties =
[ "width", "height" ];
break;
}
case "addItem":
{
filterObject = new EffectTargetFilter();
filterObject.requiredSemantics = {added:true};
break;
}
case "removeItem":
{
filterObject = new EffectTargetFilter();
filterObject.requiredSemantics = {removed:true};
break;
}
case "replacedItem":
{
filterObject = new EffectTargetFilter();
filterObject.requiredSemantics = {replaced:true};
break;
}
case "replacementItem":
{
filterObject = new EffectTargetFilter();
filterObject.requiredSemantics = {replacement:true};
break;
}
default:
{
filterObject = null;
break;
}
}
}
}
//----------------------------------
// hideFocusRing
//----------------------------------
/**
* @private
* Storage for the hideFocusRing property.
*/
private var _hideFocusRing:Boolean = false;
/**
* @copy mx.effects.IEffect#hideFocusRing
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get hideFocusRing():Boolean
{
return _hideFocusRing;
}
/**
* @private
*/
public function set hideFocusRing(value:Boolean):void
{
_hideFocusRing = value;
}
//----------------------------------
// instanceClass
//----------------------------------
/**
* An object of type Class that specifies the effect
* instance class class for this effect class.
*
* <p>All subclasses of the Effect class must set this property
* in their constructor.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var instanceClass:Class = IEffectInstance;
//----------------------------------
// isPlaying
//----------------------------------
/**
* @copy mx.effects.IEffect#isPlaying
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get isPlaying():Boolean
{
return _instances && _instances.length > 0;
}
//----------------------------------
// perElementOffset
//----------------------------------
/**
* @private
* Storage for the perElementOffset property.
*/
private var _perElementOffset:Number = 0;
[Inspectable(defaultValue="0", category="General", verbose="0", minValue="0.0")]
/**
* @copy mx.effects.IEffect#perElementOffset
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get perElementOffset():Number
{
return _perElementOffset;
}
/**
* @private
*/
public function set perElementOffset(value:Number):void
{
_perElementOffset = value;
}
//----------------------------------
// relevantProperties
//----------------------------------
/**
* @private
* Storage for the relevantProperties property.
*/
private var _relevantProperties:Array /* of String */;
/**
* @copy mx.effects.IEffect#relevantProperties
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get relevantProperties():Array /* of String */
{
if (_relevantProperties)
return _relevantProperties;
else
return getAffectedProperties();
}
/**
* @private
*/
public function set relevantProperties(value:Array /* of String */):void
{
_relevantProperties = value;
}
//----------------------------------
// relevantStyles
//----------------------------------
/**
* @private
* Storage for the relevantStyles property.
*/
private var _relevantStyles:Array /* of String */ = [];
/**
* @copy mx.effects.IEffect#relevantStyles
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get relevantStyles():Array /* of String */
{
return _relevantStyles;
}
/**
* @private
*/
public function set relevantStyles(value:Array /* of String */):void
{
_relevantStyles = value;
}
//----------------------------------
// repeatCount
//----------------------------------
[Inspectable(category="General", defaultValue="1", minValue="0")]
/**
* Number of times to repeat the effect.
* Possible values are any integer greater than or equal to 0.
* A value of 1 means to play the effect once.
* A value of 0 means to play the effect indefinitely
* until stopped by a call to the <code>end()</code> method.
*
* @default 1
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var repeatCount:int = 1;
//----------------------------------
// repeatDelay
//----------------------------------
[Inspectable(category="General", defaultValue="0", minValue="0")]
/**
* Amount of time, in milliseconds, to wait before repeating the effect.
* Possible values are any integer greater than or equal to 0.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var repeatDelay:int = 0;
//----------------------------------
// startDelay
//----------------------------------
[Inspectable(category="General", defaultValue="0", minValue="0")]
/**
* Amount of time, in milliseconds, to wait before starting the effect.
* Possible values are any int greater than or equal to 0.
* If the effect is repeated by using the <code>repeatCount</code>
* property, the <code>startDelay</code> is only applied
* to the first time the effect is played.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var startDelay:int = 0;
//----------------------------------
// suspendBackgroundProcessing
//----------------------------------
/**
* If <code>true</code>, blocks all background processing
* while the effect is playing.
* Background processing includes measurement, layout, and
* processing responses that have arrived from the server.
* The default value is <code>false</code>.
*
* <p>You are encouraged to set this property to
* <code>true</code> in most cases, because it improves
* the performance of the application.
* However, the property should be set to <code>false</code>
* if either of the following is true:</p>
* <ul>
* <li>User input can arrive while the effect is playing,
* and the application must respond to the user input
* before the effect finishes playing.</li>
* <li>A response can arrive from the server while the effect
* is playing, and the application must process the response
* while the effect is still playing.</li>
* </ul>
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var suspendBackgroundProcessing:Boolean = false;
//----------------------------------
// target
//----------------------------------
/**
* @copy mx.effects.IEffect#target
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get target():Object
{
if (_targets.length > 0)
return _targets[0];
else
return null;
}
/**
* @private
*/
public function set target(value:Object):void
{
_targets.splice(0);
if (value)
_targets[0] = value;
}
//----------------------------------
// targets
//----------------------------------
/**
* @private
* Storage for the targets property.
*/
private var _targets:Array = [];
[Inspectable(arrayType="Object")]
[ArrayElementType("Object")]
/**
* @copy mx.effects.IEffect#targets
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get targets():Array
{
return _targets;
}
/**
* @private
*/
public function set targets(value:Array):void
{
// Strip out null values.
// Binding will trigger again when the null targets are created.
var n:int = value.length;
for (var i:int = n - 1; i >= 0; i--)
{
if (value[i] == null)
value.splice(i,1);
}
_targets = value;
}
//----------------------------------
// triggerEvent
//----------------------------------
/**
* @private
* Storage for the triggerEvent property.
*/
private var _triggerEvent:Event;
/**
* @copy mx.effects.IEffect#triggerEvent
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get triggerEvent():Event
{
return _triggerEvent;
}
/**
* @private
*/
public function set triggerEvent(value:Event):void
{
_triggerEvent = value;
}
//----------------------------------
// playheadTime
//----------------------------------
/**
* @private
* Backing storage for the playheadTime property. Note that this
* value is just a backup, used if the effect is not currently running.
* A running effect will query its effect instance for the value
* instead.
*/
private var _playheadTime:Number = 0;
[Inspectable(minValue="0.0")]
/**
* Current time position of the effect.
* This property has a value between 0 and the total duration,
* which includes the Effect's <code>startDelay</code>,
* <code>repeatCount</code>, and <code>repeatDelay</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get playheadTime():Number
{
for (var i:int = 0; i < _instances.length; i++)
{
if (_instances[i])
return IEffectInstance(_instances[i]).playheadTime;
}
// Effect isn't running: return the cached value
return _playheadTime;
}
public function set playheadTime(value:Number):void
{
// If the effect is not yet playing, it should still be possible
// to seek into it. playing and then pausing it provides that
// capability
// TODO (chaase): Need better overall mechanism to seek into a
// non-playing effect. The internals of seeking in Animation
// are complicated and don't end up giving us the behavior we
// want, especially for successive seeks.
var started:Boolean = false;
if (_instances.length == 0)
{
play();
started = true;
}
for (var i:int = 0; i < _instances.length; i++)
{
if (_instances[i])
EffectInstance(_instances[i]).playheadTime = value;
}
if (started)
pause();
_playheadTime = value;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @copy mx.effects.IEffect#getAffectedProperties()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getAffectedProperties():Array /* of String */
{
// Every subclass should override this method.
return [];
}
/**
* @copy mx.effects.IEffect#createInstances()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function createInstances(targets:Array = null):Array /* of EffectInstance */
{
if (!targets)
targets = this.targets;
var newInstances:Array = [];
// Multiple target support
var n:int = targets.length;
var offsetDelay:Number = 0;
for (var i:int = 0; i < n; i++)
{
var newInstance:IEffectInstance = createInstance(targets[i]);
if (newInstance)
{
newInstance.startDelay += offsetDelay;
offsetDelay += perElementOffset;
newInstances.push(newInstance);
}
}
triggerEvent = null;
return newInstances;
}
/**
* @copy mx.effects.IEffect#createInstance()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function createInstance(target:Object = null):IEffectInstance
{
if (!target)
target = this.target;
var newInstance:IEffectInstance = null;
var props:PropertyChanges = null;
var create:Boolean = true;
var setPropsArray:Boolean = false;
if (propertyChangesArray)
{
setPropsArray = true;
create = filterInstance(propertyChangesArray,
target);
}
if (create)
{
newInstance = IEffectInstance(new instanceClass(target));
initInstance(newInstance);
if (setPropsArray)
{
var n:int = propertyChangesArray.length;
for (var i:int = 0; i < n; i++)
{
if (propertyChangesArray[i].target == target)
{
newInstance.propertyChanges =
propertyChangesArray[i];
}
}
}
EventDispatcher(newInstance).addEventListener(EffectEvent.EFFECT_START, effectStartHandler);
EventDispatcher(newInstance).addEventListener(EffectEvent.EFFECT_STOP, effectStopHandler);
EventDispatcher(newInstance).addEventListener(EffectEvent.EFFECT_END, effectEndHandler);
_instances.push(newInstance);
if (triggerEvent)
newInstance.initEffect(triggerEvent);
}
return newInstance;
}
/**
* Copies properties of the effect to the effect instance.
*
* <p>Flex calls this method from the <code>Effect.createInstance()</code>
* method; you do not have to call it yourself. </p>
*
* <p>When you create a custom effect, override this method to
* copy properties from the Effect class to the effect instance class.
* In your override, call <code>super.initInstance()</code>. </p>
*
* @param EffectInstance The effect instance to initialize.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function initInstance(instance:IEffectInstance):void
{
instance.duration = duration;
Object(instance).durationExplicitlySet = durationExplicitlySet;
instance.effect = this;
instance.effectTargetHost = effectTargetHost;
instance.hideFocusRing = hideFocusRing;
instance.repeatCount = repeatCount;
instance.repeatDelay = repeatDelay;
instance.startDelay = startDelay;
instance.suspendBackgroundProcessing = suspendBackgroundProcessing;
}
/**
* @copy mx.effects.IEffect#deleteInstance()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function deleteInstance(instance:IEffectInstance):void
{
EventDispatcher(instance).removeEventListener(
EffectEvent.EFFECT_START, effectStartHandler);
EventDispatcher(instance).removeEventListener(
EffectEvent.EFFECT_STOP, effectStopHandler);
EventDispatcher(instance).removeEventListener(
EffectEvent.EFFECT_END, effectEndHandler);
var n:int = _instances.length;
for (var i:int = 0; i < n; i++)
{
if (_instances[i] === instance)
_instances.splice(i, 1);
}
}
/**
* @copy mx.effects.IEffect#play()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function play(targets:Array = null,
playReversedFromEnd:Boolean = false):
Array /* of EffectInstance */
{
effectStopped = false;
isPaused = false;
playReversed = playReversedFromEnd;
// If we have a propertyChangesArray, capture the current values
// if they haven't been captured already, strip out any unchanged
// values, then apply the start values.
if (targets == null && propertyChangesArray != null)
{
if (_callValidateNow)
LayoutManager.getInstance().validateNow();
if (!endValuesCaptured)
propertyChangesArray =
captureValues(propertyChangesArray, false);
propertyChangesArray =
stripUnchangedValues(propertyChangesArray);
applyStartValues(propertyChangesArray,
this.targets);
if (playReversedFromEnd)
{
for (var j:int = 0; j < propertyChangesArray.length; ++j)
{
var tmpStart:Object = propertyChangesArray[j].start;
propertyChangesArray[j].start = propertyChangesArray[j].end;
propertyChangesArray[j].end = tmpStart;
}
}
// Revalidate after applying the start values, to get everything
// back the way it should be before starting the animation
LayoutManager.getInstance().validateNow();
applyEndValuesWhenDone = true;
}
var newInstances:Array = createInstances(targets);
var n:int = newInstances.length;
for (var i:int = 0; i < n; i++)
{
var newInstance:IEffectInstance = IEffectInstance(newInstances[i]);
Object(newInstance).playReversed = playReversedFromEnd;
newInstance.startEffect();
}
return newInstances;
}
/**
* @copy mx.effects.IEffect#pause()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function pause():void
{
if (isPlaying && !isPaused)
{
isPaused = true;
var n:int = _instances.length;
for (var i:int = 0; i < n; i++)
{
IEffectInstance(_instances[i]).pause();
}
}
}
/**
* @copy mx.effects.IEffect#stop()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function stop():void
{
var n:int = _instances.length - 1;
for (var i:int = n; i >= 0; i--)
{
var instance:IEffectInstance = IEffectInstance(_instances[i]);
if (instance)
instance.stop();
}
}
/**
* @copy mx.effects.IEffect#resume()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function resume():void
{
if (isPlaying && isPaused)
{
isPaused = false;
var n:int = _instances.length;
for (var i:int = 0; i < n; i++)
{
IEffectInstance(_instances[i]).resume();
}
}
}
/**
* @copy mx.effects.IEffect#reverse()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function reverse():void
{
if (isPlaying)
{
var n:int = _instances.length;
for (var i:int = 0; i < n; i++)
{
IEffectInstance(_instances[i]).reverse();
}
}
}
/**
* @copy mx.effects.IEffect#end()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function end(effectInstance:IEffectInstance = null):void
{
if (effectInstance)
{
effectInstance.end();
}
else
{
var n:int = _instances.length;
for (var i:int = n - 1; i >= 0; i--)
{
var instance:IEffectInstance = IEffectInstance(_instances[i]);
if (instance)
instance.end();
}
}
}
/**
* Determines the logic for filtering out an effect instance.
* The CompositeEffect class overrides this method.
*
* @param propChanges The properties modified by the effect.
*
* @param targ The effect target.
*
* @return Returns <code>true</code> if the effect instance should play.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function filterInstance(propChanges:Array, target:Object):Boolean
{
if (filterObject)
return filterObject.filterInstance(propChanges, effectTargetHost, target);
return true;
}
/**
* @copy mx.effects.IEffect#captureStartValues()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function captureStartValues():void
{
if (targets.length > 0)
{
// Reset the PropertyChanges array by passing in 'null'
propertyChangesArray = captureValues(null, true);
_callValidateNow = true;
}
endValuesCaptured = false;
}
/**
* @copy mx.effects.IEffect#captureMoreStartValues()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function captureMoreStartValues(targets:Array):void
{
if (targets.length > 0)
{
// make temporary PropertyChangesArray
var additionalPropertyChangesArray:Array = captureValues(null, true);
propertyChangesArray = (propertyChangesArray != null) ?
propertyChangesArray.concat(additionalPropertyChangesArray) :
additionalPropertyChangesArray;
}
}
/**
* @copy mx.effects.IEffect#captureEndValues()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function captureEndValues():void
{
// captureValues will create propertyChangesArray if it does not
// yet exist
propertyChangesArray =
captureValues(propertyChangesArray, false);
endValuesCaptured = true;
}
/**
* @private
* Used internally to grab the values of the relevant properties
*/
mx_internal function captureValues(propChanges:Array,
setStartValues:Boolean,
targetsToCapture:Array = null):Array
{
var n:int;
var i:int;
if (!propChanges)
{
propChanges = [];
// Create a new PropertyChanges object for the sum of all targets.
n = targets.length;
for (i = 0; i < n; i++)
propChanges.push(new PropertyChanges(targets[i]));
}
// Merge Effect.filterProperties and filterObject.filterProperties
var effectProps:Array = !filterObject ?
relevantProperties :
mergeArrays(relevantProperties,
filterObject.filterProperties);
var valueMap:Object;
var target:Object;
var m:int;
var j:int;
// For each target, grab the property's value
// and put it into the propChanges Array.
// Walk the targets.
if (effectProps && effectProps.length > 0)
{
n = propChanges.length;
for (i = 0; i < n; i++)
{
target = propChanges[i].target;
if (targetsToCapture == null || targetsToCapture.length == 0 ||
targetsToCapture.indexOf(target) >= 0)
{
valueMap = setStartValues ? propChanges[i].start : propChanges[i].end;
// Walk the properties in the target
m = effectProps.length;
for (j = 0; j < m; j++)
{
// Don't clobber values already set
if (valueMap[effectProps[j]] === undefined)
{
valueMap[effectProps[j]] =
getValueFromTarget(target,effectProps[j]);
}
}
}
}
}
var styles:Array = !filterObject ?
relevantStyles :
mergeArrays(relevantStyles,
filterObject.filterStyles);
if (styles && styles.length > 0)
{
n = propChanges.length;
for (i = 0; i < n; i++)
{
target = propChanges[i].target;
if (targetsToCapture == null || targetsToCapture.length == 0 ||
targetsToCapture.indexOf(target) >= 0)
{
if (!(target is IStyleClient))
continue;
valueMap = setStartValues ? propChanges[i].start : propChanges[i].end;
// Walk the properties in the target.
m = styles.length;
for (j = 0; j < m; j++)
{
// Don't clobber values set by relevantProperties
if (valueMap[styles[j]] === undefined)
{
var value:* = target.getStyle(styles[j]);
valueMap[styles[j]] = value;
}
}
}
}
}
return propChanges;
}
/**
* Called by the <code>captureStartValues()</code> method to get the value
* of a property from the target.
* This function should only be called internally
* by the effects framework.
* The default behavior is to simply return <code>target[property]</code>.
* Effect developers can override this function
* if you need a different behavior.
*
* @param target The effect target.
*
* @param property The target property.
*
* @return The value of the target property.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function getValueFromTarget(target:Object, property:String):*
{
if (property in target)
return target[property];
return undefined;
}
/**
* @private
* Applies the start values found in the array of PropertyChanges
* to the relevant targets.
*/
mx_internal function applyStartValues(propChanges:Array,
targets:Array):void
{
var effectProps:Array = relevantProperties;
var n:int = propChanges.length;
for (var i:int = 0; i < n; i++)
{
var m:int;
var j:int;
var target:Object = propChanges[i].target;
var apply:Boolean = false;
m = targets.length;
for (j = 0; j < m; j++)
{
if (targets[j] == target)
{
apply = filterInstance(propChanges, target);
break;
}
}
if (apply)
{
// Walk the properties in the target
m = effectProps.length;
for (j = 0; j < m; j++)
{
var propName:String = effectProps[j];
var startVal:* = propChanges[i].start[propName];
var endVal:* = propChanges[i].end[propName];
if (propName in propChanges[i].start &&
endVal != startVal &&
(!(startVal is Number) ||
!(isNaN(endVal) && isNaN(startVal))))
{
applyValueToTarget(target, effectProps[j],
propChanges[i].start[effectProps[j]],
propChanges[i].start);
}
}
// Walk the styles in the target
m = relevantStyles.length;
for (j = 0; j < m; j++)
{
var styleName:String = relevantStyles[j];
var startStyle:* = propChanges[i].start[styleName];
var endStyle:* = propChanges[i].end[styleName];
if (styleName in propChanges[i].start &&
endStyle != startStyle &&
(!(startStyle is Number) ||
!(isNaN(endStyle) && isNaN(startStyle))) &&
target is IStyleClient)
{
if (propChanges[i].end[relevantStyles[j]] !== undefined)
target.setStyle(relevantStyles[j], propChanges[i].start[relevantStyles[j]]);
else
target.clearStyle(relevantStyles[j]);
}
}
}
}
}
/**
* @private
* Applies the start values found in the array of PropertyChanges
* to the relevant targets.
*/
mx_internal function applyEndValues(propChanges:Array,
targets:Array):void
{
// For now, only new Flex4 effects will apply end values when transitions
// are over, to preserve the previous behavior of Flex3 effects
if (!applyTransitionEndProperties)
return;
var effectProps:Array = relevantProperties;
var n:int = propChanges.length;
for (var i:int = 0; i < n; i++)
{
var m:int;
var j:int;
var target:Object = propChanges[i].target;
var apply:Boolean = false;
m = targets.length;
for (j = 0; j < m; j++)
{
if (targets[j] == target)
{
apply = filterInstance(propChanges, target);
break;
}
}
if (apply)
{
// Walk the properties in the target
m = effectProps.length;
for (j = 0; j < m; j++)
{
var propName:String = effectProps[j];
var startVal:* = propChanges[i].start[propName];
var endVal:* = propChanges[i].end[propName];
if (propName in propChanges[i].end &&
(!(endVal is Number) ||
!(isNaN(endVal) && isNaN(startVal))))
{
applyValueToTarget(target, propName,
propChanges[i].end[propName],
propChanges[i].end);
}
}
// Walk the styles in the target
m = relevantStyles.length;
for (j = 0; j < m; j++)
{
var styleName:String = relevantStyles[j];
var startStyle:* = propChanges[i].start[styleName];
var endStyle:* = propChanges[i].end[styleName];
if (styleName in propChanges[i].end &&
(!(endStyle is Number) ||
!(isNaN(endStyle) && isNaN(startStyle))) &&
target is IStyleClient)
{
if (propChanges[i].end[styleName] !== undefined)
target.setStyle(styleName, propChanges[i].end[styleName]);
else
target.clearStyle(styleName);
}
}
}
}
}
/**
* Used internally by the Effect infrastructure.
* If <code>captureStartValues()</code> has been called,
* then when Flex calls the <code>play()</code> method, it uses this function
* to set the targets back to the starting state.
* The default behavior is to take the value captured
* using the <code>getValueFromTarget()</code> method
* and set it directly on the target's property. For example: <pre>
*
* target[property] = value;</pre>
*
* <p>Only override this method if you need to apply
* the captured values in a different way.
* Note that style properties of a target are set
* using a different mechanism.
* Use the <code>relevantStyles</code> property to specify
* which style properties to capture and apply. </p>
*
* @param target The effect target.
*
* @param property The target property.
*
* @param value The value of the property.
*
* @param props Array of Objects, where each Array element contains a
* <code>start</code> and <code>end</code> Object
* for the properties that the effect is monitoring.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function applyValueToTarget(target:Object, property:String,
value:*, props:Object):void
{
if (property in target)
{
// The "property in target" test only tells if the property exists
// in the target, but does not distinguish between read-only and
// read-write properties. Put a try/catch around the setter and
// ignore any errors.
try
{
if (applyActualDimensions &&
target is IFlexDisplayObject &&
property == "height")
{
target.setActualSize(target.width,value);
}
else if (applyActualDimensions &&
target is IFlexDisplayObject &&
property == "width")
{
target.setActualSize(value,target.height);
}
else
{
target[property] = value;
}
}
catch(e:Error)
{
// Ignore errors
}
}
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* This method is called when the effect instance starts playing.
* If you override this method, ensure that you call the super method.
*
* @param event An event object of type EffectEvent.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function effectStartHandler(event:EffectEvent):void
{
dispatchEvent(event);
}
/**
* Called when an effect instance has stopped by a call
* to the <code>stop()</code> method.
* If you override this method, ensure that you call the super method.
*
* @param event An event object of type EffectEvent.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function effectStopHandler(event:EffectEvent):void
{
dispatchEvent(event);
// We use this event to determine whether we should set final
// state values in the ensuing endHandler() call
effectStopped = true;
}
/**
* Called when an effect instance has finished playing.
* If you override this method, ensure that you call the super method.
*
* @param event An event object of type EffectEvent.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function effectEndHandler(event:EffectEvent):void
{
if (playReversed && propertyChangesArray != null)
{
for (var j:int = 0; j < propertyChangesArray.length; ++j)
{
var tmpStart:Object = propertyChangesArray[j].start;
propertyChangesArray[j].start = propertyChangesArray[j].end;
propertyChangesArray[j].end = tmpStart;
}
}
var lastTime:Boolean = !_instances || _instances.length == 1;
// Transitions should set the end values when done
if (applyEndValuesWhenDone && !effectStopped && lastTime)
applyEndValues(propertyChangesArray, targets);
var instance:IEffectInstance = IEffectInstance(event.effectInstance);
deleteInstance(instance);
dispatchEvent(event);
if (lastTime)
{
propertyChangesArray = null;
applyEndValuesWhenDone = false;
}
}
}
}