| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 spark.effects |
| { |
| import flash.geom.Vector3D; |
| import flash.utils.Dictionary; |
| |
| import mx.core.ILayoutElement; |
| import mx.core.IUIComponent; |
| import mx.core.mx_internal; |
| import mx.effects.CompositeEffect; |
| import mx.effects.Effect; |
| import mx.effects.IEffectInstance; |
| import mx.effects.Parallel; |
| import mx.effects.Sequence; |
| import mx.events.EffectEvent; |
| import mx.geom.TransformOffsets; |
| import mx.styles.IStyleClient; |
| |
| import spark.core.IGraphicElement; |
| import spark.effects.animation.Keyframe; |
| import spark.effects.animation.MotionPath; |
| import spark.effects.easing.IEaser; |
| import spark.effects.easing.Linear; |
| import spark.effects.supportClasses.AnimateTransformInstance; |
| |
| use namespace mx_internal; |
| |
| //-------------------------------------- |
| // Excluded APIs |
| //-------------------------------------- |
| |
| [Exclude(name="repeatCount", kind="property")] |
| [Exclude(name="repeatBehavior", kind="property")] |
| [Exclude(name="repeatDelay", kind="property")] |
| |
| /** |
| * The AnimateTransform effect controls all transform-related animations on target |
| * objects. Transform operations, such as translation, scale, and |
| * rotation, are combined into single operations that act |
| * in parallel to avoid any conflict when modifying overlapping property values. |
| * This effect works by combining all current transform effects |
| * on a target into one single effect instance for that target. That is, multiple |
| * transform effects within the same Parallel effect are combined (transform |
| * effects within a Sequence run separately). |
| * |
| * <p>While this combination of multiple transform effects happens |
| * internally, |
| * it does force certain constraints that should be considered:</p> |
| * |
| * <ul> |
| * <li>The <code>transformCenter</code> for the target object is |
| * globally applied to all transform effects on that target, so it |
| * should be set to the same value on all targets.</li> |
| * <li>Transform effects ignore repeat parameters, |
| * since the effects of any single Transform effect |
| * impact all other Transform effects running on the same target. |
| * Effects can still be repeated by encapsulating them in a |
| * CompositeEffect.</li> |
| * <li>The subclasses of the AnimateTransform class provide an |
| * easy way for simple manipulations of the transform effect, but for |
| * full control and fine-grained manipulation of the underlying keyframe |
| * times and values, use the AnimateTransform effect directly.</li> |
| * </ul> |
| * |
| * <p>An additional constraint of this effect and its subclasses is that |
| * the target must be of type UIComponent or GraphicElement (or a subclass |
| * of those classes), or any other object which has similarly |
| * defined and implemented <code>transformAround()</code> and |
| * <code>transformPointToParent()</code> functions.</p> |
| * |
| * <p>This effect is not intended to be used directly, but rather exposes |
| * common functionality used by its subclasses. To use transform effects, |
| * use the subclass effects (Move, Move3D, Scale, Scale3D, Rotate, and |
| * Rotate3D).</p> |
| * |
| * @mxml |
| * |
| * <p>The <code><s:AnimateTransform></code> tag |
| * inherits all of the tag attributes of its superclass, |
| * and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:AnimateTransform |
| * <b>Properties</b> |
| * id="ID" |
| * applyChangesPostLayout="false" |
| * autoCenterTransform="false" |
| * transformX="0" |
| * transformY="0" |
| * transformZ="0" |
| * /> |
| * </pre> |
| * |
| * @see spark.effects.supportClasses.AnimateTransformInstance |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public class AnimateTransform extends Animate |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class constants |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private static var AFFECTED_PROPERTIES:Array = |
| ["translationX", "translationY", "translationZ", |
| "rotationX", "rotationY", "rotationZ", |
| "scaleX", "scaleY", "scaleZ", |
| "postLayoutTranslationX","postLayoutTranslationY","postLayoutTranslationZ", |
| "postLayoutRotationX","postLayoutRotationY","postLayoutRotationZ", |
| "postLayoutScaleX","postLayoutScaleY","postLayoutScaleZ", |
| "left", "right", "top", "bottom", |
| "horizontalCenter", "verticalCenter", "baseline", |
| "width", "height"]; |
| |
| /** |
| * @private |
| */ |
| private static var RELEVANT_STYLES:Array = |
| ["left", "right", "top", "bottom", "horizontalCenter", "verticalCenter", "baseline"]; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @param target The Object to animate with this effect. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function AnimateTransform(target:Object=null) |
| { |
| super(target); |
| instanceClass = AnimateTransformInstance; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| // By default, the overall AnimateTransform effect (when instantiated by |
| // the leaf-node subclasses Move, Move3D, Rotate, etc.) uses Linear easing, |
| // but each keyframe pair uses the regular Sine(.5) easing. This mimics the |
| // behavior of other effects that just have Sine(.5) easing for their motion. |
| private static var linearEaser:Linear = new Linear(); |
| |
| // This variable is used to detect whether the transform effect was created |
| // via one of the known subclasses (Move, Move3D, Rotate, etc.) or whether it |
| // was created directly (AnimateTransform or AnimateTransform3D). If created |
| // directly, assume the developer knows what they're doing and pass the |
| // easer onto the effect instance. Otherwise, use the easing approach |
| // described in the comment for linearEaser above. |
| mx_internal var transformEffectSubclass:Boolean = false; |
| |
| // TODO (chaase): consider putting the three per-target maps into one |
| // single structure |
| /** |
| * @private |
| * These maps hold information about whether values have already been applied |
| * to a target as a part of the transition start or end process |
| */ |
| static private var appliedStartValuesPerTarget:Dictionary = new Dictionary(true); |
| /** |
| * @private |
| */ |
| static private var appliedEndValuesPerTarget:Dictionary = new Dictionary(true); |
| |
| /** |
| * The sharedObjectDepot holds shared transform effect instances on a |
| * per-toplevel-Parallel basis. That is, transform effects running in |
| * parallel shares a common effect instance with other effects |
| * grouped in the same Parallel tree. Effects running individually, or |
| * running inside a Sequence, does not share instances. We manage this |
| * by having a map-of-maps inside our sharedObjectDepot. The top-level |
| * map manages the per-toplevel-Parallel maps (maps keyd from particular |
| * Parallel effects), whose entries are maps that are keyed off of |
| * instance targets. The reason for the separate utility class is to |
| * simplify managing the map-of-maps; the depot uses a reference counter |
| * so that we know, when removing the shared-instance map entries, whether |
| * we can remove the top level map for a particular Paralle effect. |
| */ |
| static private var sharedObjectDepot:SharedObjectDepot |
| = new SharedObjectDepot(); |
| |
| /** |
| * @private |
| * Helper structures to hold values used in applyValues() |
| */ |
| private static var scale:Vector3D = new Vector3D(); |
| private static var rotation:Vector3D = new Vector3D(); |
| private static var position:Vector3D = new Vector3D(); |
| |
| private static var offsetRotation:Vector3D = new Vector3D(); |
| private static var offsetTranslation:Vector3D = new Vector3D(); |
| private static var offsetScale:Vector3D = new Vector3D(); |
| |
| private static var xformPosition:Vector3D = new Vector3D(); |
| private static var postLayoutPosition:Vector3D = new Vector3D(); |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // applyChangesPostLayout |
| //---------------------------------- |
| |
| private var _applyChangesPostLayout:Boolean = false; |
| |
| [Inspectable(category="General", enumeration="true,false")] |
| /** |
| * Subclasses of AnimateTransform use this flag to specify |
| * whether the effect changes transform values used by the layout |
| * manager, or whether it changes values used after layout is run. |
| * |
| * @default false |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get applyChangesPostLayout():Boolean |
| { |
| return _applyChangesPostLayout; |
| } |
| public function set applyChangesPostLayout(value:Boolean):void |
| { |
| _applyChangesPostLayout = value; |
| } |
| |
| //---------------------------------- |
| // autoCenterTransform |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="false")] |
| |
| /** |
| * Specifies whether the transform effect occurs |
| * around the center of the target, <code>(width/2, height/2)</code> |
| * when the effect begins playing. |
| * If the flag is not set, the transform center is determined by |
| * the transform center of the object (<code>transformX, transformY, |
| * transformZ</code>) and the <code>transformX, transformY, |
| * transformZ</code> properties in this effect. That is, the |
| * transform center is the transform center of the target object, |
| * where any of the <code>transformX, transformY, |
| * transformZ</code> properties are overridden by those |
| * values in the effect, if set. |
| * |
| * @default false |
| * |
| * @see mx.core.UIComponent#transformX |
| * @see mx.core.UIComponent#transformY |
| * @see mx.core.UIComponent#transformZ |
| * @see #transformX |
| * @see #transformY |
| * @see #transformZ |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public var autoCenterTransform:Boolean = false; |
| |
| //---------------------------------- |
| // transformX |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="NaN")] |
| |
| /** |
| * Sets the x coordinate for the transform center, unless overridden |
| * by the <code>autoCenterTransform</code> property. |
| * |
| * <p>If <code>autoCenterTransform</code> is <code>false</code>, the transform |
| * center is determined by the <code>transformX</code>, |
| * <code>transformY</code>, and <code>transformZ</code> properties |
| * of the target object, but each of those properties can be |
| * overridden by setting the respective properties in this effect.</p> |
| * |
| * @see mx.core.UIComponent#transformX |
| * @see #autoCenterTransform |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public var transformX:Number; |
| |
| //---------------------------------- |
| // transformY |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="NaN")] |
| |
| /** |
| * Sets the y coordinate for the transform center, unless overridden |
| * by the <code>autoCenterTransform</code> property. |
| * |
| * <p>If <code>autoCenterTransform</code> is <code>false</code>, the transform |
| * center is determined by the <code>transformX</code>, |
| * <code>transformY</code>, and <code>transformZ</code> properties |
| * of the target object, but each of those properties can be |
| * overridden by setting the respective properties in this effect.</p> |
| * |
| * @see mx.core.UIComponent#transformY |
| * @see #autoCenterTransform |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public var transformY:Number; |
| |
| //---------------------------------- |
| // transformZ |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="NaN")] |
| |
| /** |
| * Sets the z coordinate for the transform center, unless overridden |
| * by the <code>autoCenterTransform</code> property. |
| * |
| * <p>If <code>autoCenterTransform</code> is <code>false</code>, the transform |
| * center is determined by the <code>transformX</code>, |
| * <code>transformY</code>, and <code>transformZ</code> properties |
| * of the target object, but each of those properties can be |
| * overridden by setting the respective properties in this effect.</p> |
| * |
| * @see mx.core.UIComponent#transformZ |
| * @see #autoCenterTransform |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public var transformZ:Number; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| private function getOwningParallelEffect():Parallel |
| { |
| var prevParent:Parallel = null; |
| var parent:Effect = parentCompositeEffect; |
| // Only share instance for children of parallel effects |
| while (parent) |
| { |
| if (parent is Sequence) |
| break; |
| prevParent = Parallel(parent); |
| parent = parent.parentCompositeEffect; |
| } |
| return prevParent; |
| } |
| |
| /** |
| * @private |
| * |
| * Creates the instance for this effect. Unlike other effects which operate |
| * autonomously, this effect uses only a single effect instance per target. |
| * So all objects of type AnimateTransform, or its subclasses, share this |
| * one global instance. If there is already an instance created for the effect, |
| * the values from the new effect is inserted as animation values into |
| * the existing instance. |
| * |
| * @param target The Object to animate with this effect. |
| * |
| * @return The effect instance object for the effect. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| override public function createInstance(target:Object = null):IEffectInstance |
| { |
| if (!target) |
| target = this.target; |
| |
| var parent:Effect = parentCompositeEffect; |
| var sharedInstance:IEffectInstance = null; |
| var topmostParallel:Parallel = getOwningParallelEffect(); |
| if (topmostParallel != null) |
| sharedInstance = IEffectInstance( |
| sharedObjectDepot.getSharedObject(topmostParallel, target)); |
| if (!sharedInstance) |
| { |
| var newInstance:IEffectInstance = super.createInstance(target); |
| if (topmostParallel) |
| sharedObjectDepot.storeSharedObject(topmostParallel, |
| target, newInstance); |
| return newInstance; |
| } |
| else |
| { |
| initInstance(sharedInstance); |
| // return null to indicate that there is no 'new' instance. This |
| // keeps it from being redundantly added to composite effects |
| return null; |
| } |
| } |
| |
| /** |
| * @private |
| * |
| * We handle this event in order to remove the single effect instance per |
| * target. We cannot correctly add values to a playing effect, so we only |
| * allow combining values into the single instance before that instance |
| * begins. |
| */ |
| override protected function effectStartHandler(event:EffectEvent):void |
| { |
| super.effectStartHandler(event); |
| // Delete any cached info about the effect instance, such as the |
| // shared transform effect instance |
| var topmostParallel:Parallel = getOwningParallelEffect(); |
| if (topmostParallel != null) |
| sharedObjectDepot.removeSharedObject(topmostParallel, |
| event.effectInstance.target); |
| delete appliedStartValuesPerTarget[event.effectInstance.target]; |
| delete appliedEndValuesPerTarget[event.effectInstance.target]; |
| } |
| |
| /** |
| * @private |
| * Used internally to grab the values of the relevant properties. Note |
| * that some properties are fake - translationX, translationY, |
| * and translationZ do not exist on the targets, |
| * but are manufactured from a combination of the object position |
| * and the transform center. |
| */ |
| override mx_internal function captureValues(propChanges:Array, |
| setStartValues:Boolean, |
| targetsToCapture:Array = null):Array |
| { |
| propChanges = super.captureValues(propChanges, setStartValues, |
| targetsToCapture); |
| var valueMap:Object; |
| var i:int; |
| var n:int; |
| var target:Object; |
| |
| n = propChanges.length; |
| for (i = 0; i < n; i++) |
| { |
| target = propChanges[i].target; |
| if (targetsToCapture == null || targetsToCapture.length == 0 || |
| targetsToCapture.indexOf(target) >= 0) |
| { |
| // For now, just make sure that we can transform the current target |
| // and that the target exists on the display tree, |
| // then go ahead and capture its transform values |
| if (!(target is ILayoutElement) || (!target.parent && setStartValues)) |
| continue; |
| |
| valueMap = setStartValues ? propChanges[i].start : propChanges[i].end; |
| |
| var computedTransformCenter:Vector3D = |
| computeTransformCenterForTarget(target, valueMap); |
| |
| if (valueMap.translationX === undefined || |
| valueMap.translationY === undefined || |
| valueMap.translationZ === undefined) |
| { |
| // TODO (chaase): do we really need this? |
| propChanges[i].stripUnchangedValues = false; |
| |
| target.transformPointToParent(computedTransformCenter, xformPosition, |
| null); |
| valueMap.translationX = xformPosition.x; |
| valueMap.translationY = xformPosition.y; |
| valueMap.translationZ = xformPosition.z; |
| } |
| |
| |
| // if someone has asked for the motionpaths to affect offsets, |
| // they might not have explcitly defined any offsets. If that's the case, |
| // we still need to capture default start values, so let's initialize the offsets |
| // to a default set anyway. |
| if ((applyChangesPostLayout || postLayoutTransformPropertiesSet) && |
| target.postLayoutTransformOffsets == null) |
| { |
| target.postLayoutTransformOffsets = new TransformOffsets(); |
| } |
| |
| // if the target doesn't have any offsets, there's no need to capture |
| // offset values. |
| if (target.postLayoutTransformOffsets != null) |
| { |
| var postLayoutTransformOffsets:TransformOffsets = target.postLayoutTransformOffsets; |
| valueMap.postLayoutRotationX = postLayoutTransformOffsets.rotationX; |
| valueMap.postLayoutRotationY = postLayoutTransformOffsets.rotationY; |
| valueMap.postLayoutRotationZ = postLayoutTransformOffsets.rotationZ; |
| |
| valueMap.postLayoutScaleX = postLayoutTransformOffsets.scaleX; |
| valueMap.postLayoutScaleY = postLayoutTransformOffsets.scaleY; |
| valueMap.postLayoutScaleZ = postLayoutTransformOffsets.scaleZ; |
| |
| if (valueMap.postLayoutTranslationX === undefined || |
| valueMap.postLayoutTranslationY === undefined || |
| valueMap.postLayoutTranslationZ === undefined) |
| { |
| // TODO (chaase): do we really need this? |
| propChanges[i].stripUnchangedValues = false; |
| |
| target.transformPointToParent(computedTransformCenter, null, |
| postLayoutPosition); |
| valueMap.postLayoutTranslationX = postLayoutPosition.x; |
| valueMap.postLayoutTranslationY = postLayoutPosition.y; |
| valueMap.postLayoutTranslationZ = postLayoutPosition.z; |
| } |
| } |
| } |
| } |
| return propChanges; |
| } |
| |
| /** |
| * @private |
| * |
| * Calculates the transformCenter used by the effect instance for this |
| * target. The center is calculated as: |
| * - if autoCenterTransform, center around the target's x/y center |
| * - else if none of transformX, transformY, transformZ are set on the |
| * effect, return null. This will result in the effect simply using |
| * the target's transform center |
| * - else get the transformXYZ properties in the target and override |
| * these values by the effects transformXYZ properties |
| */ |
| private function computeTransformCenterForTarget(target:Object, |
| valueMap:Object = null):Vector3D |
| { |
| var computedTransformCenter:Vector3D; |
| |
| if (autoCenterTransform) |
| { |
| var w:Number = (valueMap != null && valueMap["width"] !== undefined) ? |
| valueMap["width"] : |
| target.width; |
| var h:Number = (valueMap != null && valueMap["height"] !== undefined) ? |
| valueMap["height"] : |
| target.height; |
| computedTransformCenter = new Vector3D(w/2, h/2, 0); |
| } |
| else |
| { |
| // Always create a transform center, even if we do not override |
| // the object's transform XYZ properties. Theoretically, a null transform |
| // center should be the same as one that uses the tXYZ properties on |
| // the target, but that's not the case currently. |
| computedTransformCenter = new Vector3D(target.transformX, |
| target.transformY, target.transformZ); |
| if (!isNaN(transformX)) |
| computedTransformCenter.x = transformX; |
| if (!isNaN(transformY)) |
| computedTransformCenter.y = transformY; |
| if (!isNaN(transformZ)) |
| computedTransformCenter.z = transformZ; |
| } |
| return computedTransformCenter; |
| } |
| |
| /** |
| * @private |
| * |
| * Utility function called by applyStartValues() and |
| * applyEndValues(). We override these functions from Effect |
| * because we need to apply all transform-related properties |
| * at the same time, rather than one by one, because they are |
| * all interdependent. |
| */ |
| private function applyValues(propChanges:Array, targets:Array, |
| start:Boolean):void |
| { |
| var appliedValuesPerTarget:Dictionary = |
| start ? |
| appliedStartValuesPerTarget : |
| appliedEndValuesPerTarget; |
| 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; |
| |
| if (appliedValuesPerTarget[target]) |
| continue; |
| |
| m = targets.length; |
| for (j = 0; j < m; j++) |
| { |
| if (targets[j] == target) |
| { |
| apply = filterInstance(propChanges, target); |
| break; |
| } |
| } |
| |
| if (apply) |
| { |
| var effectProps:Array = AFFECTED_PROPERTIES; |
| var valueMap:Object = start ? propChanges[i].start : propChanges[i].end; |
| var otherValueMap:Object = start ? propChanges[i].end : propChanges[i].start; |
| var transitionValues:Object = { |
| rotationX:NaN, rotationY:NaN, rotation:NaN, |
| scaleX:NaN, scaleY:NaN, scaleZ:NaN, |
| translationX:NaN, translationY:NaN, translationZ:NaN |
| }; |
| |
| // Walk the properties in the target |
| m = effectProps.length; |
| for (j = 0; j < m; j++) |
| { |
| var propName:String = effectProps[j]; |
| // Only record and apply values if they change between states |
| // Special case for tx/ty because they may change implicitly due |
| // to transform center changes |
| if (propName in valueMap && |
| (propName == "translationX" || propName == "translationY" || |
| propName.indexOf("postLayout") == 0 || |
| valueMap[propName] != otherValueMap[propName])) |
| { |
| transitionValues[propName] = valueMap[propName]; |
| } |
| } |
| // Now transform it |
| var xformCenter:Vector3D = computeTransformCenterForTarget(target, valueMap); |
| var tmpScale:Vector3D = null; |
| var tmpPosition:Vector3D = null; |
| var tmpRotation:Vector3D = null; |
| |
| var tmpOffsetTranslation:Vector3D = null; |
| var tmpOffsetRotation:Vector3D = null; |
| var tmpOffsetScale:Vector3D = null; |
| |
| var currentXFormPositionComputed:Boolean = false; |
| |
| if (!isNaN(transitionValues.scaleX) || |
| !isNaN(transitionValues.scaleY) || |
| !isNaN(transitionValues.scaleZ)) |
| { |
| scale.x = !isNaN(transitionValues.scaleX) ? |
| transitionValues.scaleX : target["scaleX"]; |
| scale.y = !isNaN(transitionValues.scaleY) ? |
| transitionValues.scaleY : target["scaleY"]; |
| scale.z = !isNaN(transitionValues.scaleZ) ? |
| transitionValues.scaleZ : target["scaleZ"]; |
| tmpScale = scale; |
| } |
| |
| if (!isNaN(transitionValues.rotationX) || |
| !isNaN(transitionValues.rotationY) || |
| !isNaN(transitionValues.rotationZ)) |
| { |
| rotation.x = !isNaN(transitionValues.rotationX) ? |
| transitionValues.rotationX : target["rotationX"]; |
| rotation.y = !isNaN(transitionValues.rotationY) ? |
| transitionValues.rotationY : target["rotationY"]; |
| rotation.z = !isNaN(transitionValues.rotationZ) ? |
| transitionValues.rotationZ : target["rotationZ"]; |
| tmpRotation = rotation; |
| } |
| |
| position.x = transitionValues.translationX; |
| position.y = transitionValues.translationY; |
| position.z = transitionValues.translationZ; |
| if (isNaN(position.x) || isNaN(position.y) || isNaN(position.z)) |
| { |
| target.transformPointToParent(xformCenter, |
| xformPosition, postLayoutPosition); |
| currentXFormPositionComputed = true; |
| if (isNaN(position.x)) |
| position.x = xformPosition.x; |
| if (isNaN(position.y)) |
| position.y = xformPosition.y; |
| if (isNaN(position.z)) |
| position.z = xformPosition.z; |
| } |
| |
| if (target.postLayoutTransformOffsets != null) |
| { |
| offsetRotation.x = !isNaN(transitionValues.postLayoutRotationX) ? |
| transitionValues.postLayoutRotationX : 0; |
| offsetRotation.y = !isNaN(transitionValues.postLayoutRotationY) ? |
| transitionValues.postLayoutRotationY : 0; |
| offsetRotation.z = !isNaN(transitionValues.postLayoutRotationZ) ? |
| transitionValues.postLayoutRotationZ : 0; |
| tmpOffsetRotation = offsetRotation; |
| |
| offsetScale.x = !isNaN(transitionValues.postLayoutScaleX) ? |
| transitionValues.postLayoutScaleX : 1; |
| offsetScale.y = !isNaN(transitionValues.postLayoutScaleY) ? |
| transitionValues.postLayoutScaleY : 1; |
| offsetScale.z = !isNaN(transitionValues.postLayoutScaleZ) ? |
| transitionValues.postLayoutScaleZ : 1; |
| tmpOffsetScale = offsetScale; |
| |
| offsetTranslation.x = transitionValues.postLayoutTranslationX; |
| offsetTranslation.y = transitionValues.postLayoutTranslationY; |
| offsetTranslation.z = transitionValues.postLayoutTranslationZ; |
| |
| if (isNaN(offsetTranslation.x) || |
| isNaN(offsetTranslation.y) || |
| isNaN(offsetTranslation.z)) |
| { |
| |
| if (currentXFormPositionComputed == false) |
| { |
| target.transformPointToParent(xformCenter, |
| xformPosition, postLayoutPosition); |
| currentXFormPositionComputed = true; |
| } |
| |
| if (isNaN(offsetTranslation.x)) |
| offsetTranslation.x = postLayoutPosition.x; |
| if (isNaN(offsetTranslation.y)) |
| offsetTranslation.y = postLayoutPosition.y; |
| if (isNaN(offsetTranslation.z)) |
| offsetTranslation.z = postLayoutPosition.z; |
| } |
| |
| tmpOffsetTranslation = offsetTranslation; |
| } |
| |
| target.transformAround(xformCenter, tmpScale, tmpRotation, |
| position,tmpOffsetScale,tmpOffsetRotation,tmpOffsetTranslation); |
| appliedValuesPerTarget[target] = true; |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * |
| * Applies the start values found in the array of PropertyChanges |
| * to the relevant targets. Overriding because we need to set |
| * transform-related properties together, not one-by-one. |
| */ |
| mx_internal override function applyStartValues(propChanges:Array, |
| targets:Array):void |
| { |
| applyValues(propChanges, targets, true); |
| super.applyStartValues(propChanges, targets); |
| } |
| |
| /** |
| * @private |
| * |
| * Applies the end values found in the array of PropertyChanges |
| * to the relevant targets. Overriding because we need to set |
| * transform-related properties together, not one-by-one. |
| */ |
| mx_internal override 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) |
| { |
| applyValues(propChanges, targets, false); |
| super.applyEndValues(propChanges, targets); |
| } |
| } |
| |
| /** |
| * @private |
| * |
| * Called by Effect.applyStartValues() and Effect.applyEndValues(). Overriding |
| * to noop some property setting |
| */ |
| override protected function applyValueToTarget(target:Object, property:String, |
| value:*, props:Object):void |
| { |
| // We've already set most of these properties in applyStartValues() or |
| // applyEndValues() override; don't set them again here. Skip width/height |
| // because they are only used to track the size for autoCenterTransform; |
| // we should not apply those values as a side effect of this transform effect |
| if (property == "translationX" || property == "translationY" || |
| property == "translationZ" || property == "rotationX" || |
| property == "rotationY" || property == "rotationZ" || |
| property == "scaleX" || property == "scaleY" || |
| property == "scaleZ" || |
| property == "postLayoutTranslationX" || property == "postLayoutTranslationY" || |
| property == "postLayoutTranslationZ" || property == "postLayoutRotationX" || |
| property == "postLayoutRotationY" || property == "postLayoutRotationZ" || |
| property == "postLayoutScaleX" || property == "postLayoutScaleY" || |
| property == "postLayoutScaleZ" || |
| property == "width" || property == "height" |
| ) |
| { |
| return; |
| } |
| else |
| { |
| super.applyValueToTarget(target, property, value, props); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override public function getAffectedProperties():Array /* of String */ |
| { |
| return AFFECTED_PROPERTIES; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function get relevantStyles():Array /* of String */ |
| { |
| return RELEVANT_STYLES; |
| } |
| |
| /** |
| * Inserts a keyframe into an existing set of keyframes according to |
| * its time. Keyframes are sorted in increasing time order. |
| */ |
| private function insertKeyframe(keyframes:Vector.<Keyframe>, newKF:Keyframe):void |
| { |
| for (var i:int = 0; i < keyframes.length; i++) |
| { |
| if (keyframes[i].time > newKF.time) |
| { |
| keyframes.splice(i, 0, newKF); |
| return; |
| } |
| } |
| keyframes.push(newKF); |
| } |
| |
| /** |
| * @private |
| * |
| * Adds a MotionPath object to the transform effect with the |
| * given parameters. |
| * If a MotionPath object |
| * on the same property already exists, adds the keyframes from the |
| * new MotionPath object into the existing MotionPath object, sorted |
| * by the time values. |
| * |
| * @param property The name of the property being animated. |
| * |
| * @param valueFrom The initial value of the property. |
| * |
| * @param valueTo The final value of the property. |
| * |
| * @param valueBy An optional parameter that specifies the delta with |
| * which to calculate either the from or to values, if one is omitted. |
| */ |
| mx_internal function addMotionPath(property:String, |
| valueFrom:Number = NaN, valueTo:Number = NaN, valueBy:Number = NaN):void |
| { |
| // First, nail down the from value with to/by, if possible |
| if (isNaN(valueFrom)) |
| { |
| if (!isNaN(valueTo) && !isNaN(valueBy)) |
| valueFrom = valueTo - valueBy; |
| } |
| // Now create a MotionPath from the result |
| var mp:MotionPath = new MotionPath(property); |
| mp.keyframes = new <Keyframe>[new Keyframe(0, valueFrom), |
| new Keyframe(duration, valueTo, valueBy)]; |
| // For transform effect subclasses (Move, Move3D, Rotate, etc.), we |
| // set the easing on the keyframes and leave the overall effect easing |
| // Linear. Otherwise, we end up with artifacts like including the |
| // startDelay in the easing because startDelay happens via keyframes |
| mp.keyframes[1].easer = easer; |
| |
| // Finally, integrate this MotionPath in with the existing |
| // MotionPath objects, if there are any |
| if (motionPaths) |
| { |
| var n:int = motionPaths.length; |
| for (var i:int = 0; i < n; i++) |
| { |
| var prop:MotionPath = MotionPath(motionPaths[i]); |
| if (prop.property == mp.property) |
| { |
| for (var j:int = 0; j < mp.keyframes.length; j++) |
| { |
| insertKeyframe(prop.keyframes, mp.keyframes[j]); |
| } |
| return; |
| } |
| } |
| } |
| else |
| { |
| motionPaths = new Vector.<MotionPath>(); |
| } |
| motionPaths.push(mp); |
| } |
| |
| // TODO (chaase): This function appears in multiple places. Maybe |
| // put it in some util class instead? |
| /** |
| * @private |
| * |
| * Utility function to determine whether a given value is 'valid', |
| * which means it's either a Number and it's not NaN, or it's not |
| * a Number and it's not null |
| */ |
| private function isValidValue(value:Object):Boolean |
| { |
| return ((value is Number && !isNaN(Number(value))) || |
| (!(value is Number) && value !== null)); |
| } |
| |
| /** |
| * @private |
| * Determine whether we are dealing with any post-layout properties |
| */ |
| private function get postLayoutTransformPropertiesSet():Boolean |
| { |
| if (motionPaths) |
| for (var i:int = 0; i < motionPaths.length; ++i) |
| { |
| if (motionPaths[i].property.indexOf("postLayout", 0) == 0) |
| return true; |
| } |
| return false; |
| } |
| |
| |
| |
| /** |
| * @private |
| * |
| * This is where we create the single instance and/or feed extra |
| * MotionPath information (from other transform-related effects) into the |
| * single transform effect instance. |
| */ |
| override protected function initInstance(instance:IEffectInstance):void |
| { |
| var i:int; |
| var adjustedDuration:Number = duration; |
| var target:Object = instance.target; |
| |
| var transformInstance:AnimateTransformInstance = |
| AnimateTransformInstance(instance); |
| |
| |
| if (postLayoutTransformPropertiesSet) |
| { |
| // there are two ways we can be affecting post-layout values. |
| // first, if the user has explicity asked the motion paths to be post layout by setting the motionPathsAffectLayout |
| // flag. In that case, we can assume that they need an offsets object if one doesn't already exist. |
| // Second, if we captured post-layout changes from a state change. In that case, we can assume that since values were captured, |
| // offsets must already exist. |
| if (target.postLayoutTransformOffsets == null) |
| target.postLayoutTransformOffsets = new TransformOffsets(); |
| } |
| // Feed startDelay directly into keyframe times |
| if (motionPaths) |
| { |
| var instanceAnimProps:Array = []; |
| for (i = 0; i < motionPaths.length; ++i) |
| { |
| instanceAnimProps[i] = motionPaths[i].clone(); |
| var mp:MotionPath = MotionPath(instanceAnimProps[i]); |
| if (mp.keyframes) |
| { |
| for (var j:int = 0; j < mp.keyframes.length; ++j) |
| { |
| var kf:Keyframe = Keyframe(mp.keyframes[j]); |
| // NaN for the time is used by SimpleMotionPath as a |
| // placeholder for the end time of the effect |
| if (isNaN(kf.time)) |
| kf.time = duration; |
| if (startDelay != 0) |
| kf.time += startDelay; |
| } |
| adjustedDuration = Math.max(adjustedDuration, |
| mp.keyframes[mp.keyframes.length - 1].time); |
| } |
| } |
| var globalStartTime:Number = getGlobalStartTime(); |
| for (i = 0; i < instanceAnimProps.length; ++i) |
| transformInstance.addMotionPath(instanceAnimProps[i], globalStartTime); |
| } |
| // Tell the instance which properties/styles to check for state |
| // transition changes |
| var s:String; |
| for each (s in getAffectedProperties()) |
| if (relevantStyles.indexOf(s) < 0) |
| transformInstance.affectedProperties[s] = s; |
| for each (s in relevantStyles) |
| transformInstance.layoutConstraints[s] = s; |
| |
| // Multiple effects can feed into this one instance, so only init |
| // it once |
| if (transformInstance.initialized) |
| return; |
| transformInstance.initialized = true; |
| |
| // Only use transformCenter in instance if not auto-centering |
| if (!autoCenterTransform) |
| transformInstance.transformCenter = |
| computeTransformCenterForTarget(instance.target); |
| transformInstance.autoCenterTransform = autoCenterTransform; |
| |
| // Need to hide these properties from the superclass, as they are |
| // already handled in our single instance. But restore them afterwards |
| // so that they are still available for reuse upon re-playing the effect |
| var tmpStartDelay:Number = startDelay; |
| startDelay = 0; |
| var tmpAnimProps:Vector.<MotionPath> = motionPaths; |
| motionPaths = null; |
| super.initInstance(instance); |
| startDelay = tmpStartDelay; |
| motionPaths = tmpAnimProps; |
| transformInstance.duration = Math.max(duration, adjustedDuration); |
| // For transform effect subclasses (Move, Move3D, Rotate, etc.), |
| // override default easer on the instance. We want the overall easing |
| // to be Linear, with the Keyframes controlling the per-interval easing |
| if (transformEffectSubclass) |
| transformInstance.easer = linearEaser; |
| } |
| |
| /** |
| * @private |
| * |
| * Utility method used by subclasses which returns the time, |
| * in milliseconds, when this effect starts, relative to |
| * the outermost CompositeEffect. This value does not include the |
| * <code>startDelay</code> on the effect itself (which should be figured |
| * into the effect's start time locally), but does include that delay |
| * on any parent effects. This global start time is needed by AnimateTransform |
| * and subclass effects which need to calculate the times in their keyframes |
| * according to when they happen relative to each other (because of the |
| * single-instance-per-target nature of this effect). |
| */ |
| private function getGlobalStartTime():Number |
| { |
| var globalStartTime:Number = 0; |
| var parent:Effect = parentCompositeEffect; |
| while (parent) |
| { |
| globalStartTime += parent.startDelay; |
| if (parent is Sequence) |
| { |
| var sequence:Sequence = Sequence(parent); |
| for (var i:int = 0; i < sequence.children.length; ++i) |
| { |
| var child:Effect = sequence.children[i]; |
| if (child == this) |
| break; |
| if (child is CompositeEffect) |
| globalStartTime += CompositeEffect(child).compositeDuration; |
| else |
| globalStartTime += child.startDelay + |
| (child.duration * child.repeatCount) + |
| (child.repeatDelay + (child.repeatCount - 1)); |
| } |
| } |
| parent = parent.parentCompositeEffect; |
| } |
| return globalStartTime; |
| } |
| |
| } |
| } |