//////////////////////////////////////////////////////////////////////////////// // // 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.effectClasses { import mx.core.UIComponent; import mx.core.mx_internal; import mx.effects.Effect; import mx.effects.EffectInstance; import mx.effects.IEffectInstance; import mx.effects.Sequence; import mx.effects.Tween; use namespace mx_internal; /** * The SequenceInstance class implements the instance class * for the Sequence effect. * Flex creates an instance of this class when it plays a Sequence effect; * you do not create one yourself. * * @see mx.effects.Sequence * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class SequenceInstance extends CompositeEffectInstance { include "../../core/Version.as"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @param target This argument is ignored for Sequence effects. * It is included only for consistency with other types of effects. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function SequenceInstance(target:Object) { super(target); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private */ private var activeChildCount:Number; /** * @private * Used internally to store the sum of all previously playing effects. */ private var currentInstanceDuration:Number = 0; /** * @private * Used internally to track the set of effect instances * that the Sequence is currently playing. */ private var currentSet:Array; /** * @private * Used internally to track the index number of the current set * of playing effect instances */ private var currentSetIndex:int = -1; /** * @private */ private var startTime:Number = 0; /** * @private * Used internally to track when the effect is paused */ private var isPaused:Boolean = false; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // durationWithoutRepeat //---------------------------------- /** * @private */ override mx_internal function get durationWithoutRepeat():Number { var _duration:Number = 0; var n:int = childSets.length; for (var i:int = 0; i < n; i++) { var instances:Array = childSets[i]; _duration += instances[0].actualDuration; } return _duration; } /** * @inheritDoc * * In a Sequence effect, playheadTime determines * which child effect should be the active one and sets the * appropriate playheadTime in that effect. * Previous effects in the sequence will be ended (if playing) or * skipped (if not yet started but playheadTime is past the time * when they would have ended). Note that 'skipping' a child effect may * entail playing it with zero duration to avoid side-effects that may * occur from not playing the effect at all. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ override public function set playheadTime(value:Number):void { var prevPlayheadTime:Number = playheadTime; // Seek in the SequenceInstance itself, which advances the // Tween timer running on the effect super.playheadTime = value; /* * Behavior of this command depends on what state we're in: * - if we're in a startDelay, then cut down the playheadTime by * the amount of time we have left to sleep * - if the playheadTime doesn't get us past the startDelay, then * reduce the amount of startDelay appropriately and return * - if the playheadTime is still in the currently playing effects * just set the appropriate playheadTime in that effect * - if the playheadTime puts the sequence into a later * child effect, end the currently playing child effect, * play and end the intervening ones, * start the ones that should be active and set their * playheadTime appropriately * - else the playheadTime puts the sequence into an earlier child * effect. Set the playhead time to 0 in the currently playing child * effect and stop it. Play/stop earlier child effects until we get * to the one we should be playing. Play it and set playheadTime * appropriately */ var i:int, j:int, k:int, l:int; var compositeDur:Number = Sequence(effect).compositeDuration; var firstCycleDur:Number = compositeDur + startDelay + repeatDelay; var laterCycleDur:Number = compositeDur + repeatDelay; // totalDur is only sensible/used when repeatCount != 0 var totalDur:Number = firstCycleDur + laterCycleDur * (repeatCount - 1); var iterationPlayheadTime:Number; if (value <= firstCycleDur) { iterationPlayheadTime = Math.min(value - startDelay, compositeDur); playCount = 1; } else { if (value >= totalDur && repeatCount != 0) { iterationPlayheadTime = compositeDur; playCount = repeatCount; } else { var valueAfterFirstCycle:Number = value - firstCycleDur; iterationPlayheadTime = valueAfterFirstCycle % laterCycleDur; iterationPlayheadTime = Math.min(iterationPlayheadTime, compositeDur); playCount = 1 + valueAfterFirstCycle / laterCycleDur; } } if (activeEffectQueue && activeEffectQueue.length > 0) { // cumulativeDuration is the duration of child effects in this // iteration thus far as we walk through the set of child effects var cumulativeDuration:Number = 0; // Step through the child effects in the sequence until we find the // set that the requested playheadTime is in var activeLength:Number = activeEffectQueue.length; for (i = 0; i < activeLength; ++i) { var setToCompare:int = playReversed ? (activeLength - 1 - i) : i; // temp holder for instances that we fast-forward or rewind var childEffectInstances:Array; // start/end times of current child effect we're looking at var startTime:Number = cumulativeDuration; var endTime:Number = cumulativeDuration + childSets[setToCompare][0].actualDuration; cumulativeDuration = endTime; // If iterationPlayheadTime is in between the start and end time for this // effect, this must be the one we need to seek into if (startTime <= iterationPlayheadTime && iterationPlayheadTime <= endTime) { // seting endEffectCalled to true keeps the next effect from // being started when we cause one to end. We'll start effects // manually when seeking, instead. endEffectCalled = true; // We're already playing the effect we should seek into if (currentSetIndex == setToCompare) { // Since we're already playing the right effect, just seek for (j = 0; j < currentSet.length; j++) currentSet[j].playheadTime = (iterationPlayheadTime - startTime); } else if (setToCompare < currentSetIndex) { if (playReversed) { // We're currently playing a child effect later than the one we // should seek into. First, rewind and stop the current effect for (j = 0; j < currentSet.length; j++) currentSet[j].end(); // Next, play(), then stop() the previous effects back to // the one we want. This will cause these effects to set // values for their target properties at the start // of their animations, which is what we want when seeking // backwards // Next, play/end all child effects before the one we want // This will set the animated properties to their end values. for (j = currentSetIndex - 1; j > setToCompare; --j) { childEffectInstances = activeEffectQueue[j]; for (k = 0; k < childEffectInstances.length; k++) { if (playReversed) childEffectInstances[k].playReversed = true; childEffectInstances[k].play(); childEffectInstances[k].end(); } } } else { // We're currently playing a child effect later than the one we // should seek into. First, rewind and stop the current effect for (j = 0; j < currentSet.length; j++) { currentSet[j].playheadTime = 0; currentSet[j].stop(); } // Next, play(), then stop() the previous effects back to // the one we want. This will cause these effects to set // values for their target properties at the start // of their animations, which is what we want when seeking // backwards for (j = currentSetIndex - 1; j > setToCompare; --j) { childEffectInstances = activeEffectQueue[j]; for (k = 0; k < childEffectInstances.length; k++) { childEffectInstances[k].play(); childEffectInstances[k].stop(); } } } // Now, play the right effect and seek into it currentSetIndex = setToCompare; playCurrentChildSet(); for (k = 0; k < currentSet.length; k++) { currentSet[k].playheadTime = (iterationPlayheadTime - startTime); if (isPaused) currentSet[k].pause(); } //break; } else // setToCompare > currentSetIndex { if (playReversed) { // We're currently playing a child effect later than the one we // should seek into. First, rewind and stop the current effect for (j = 0; j < currentSet.length; j++) { currentSet[j].playheadTime = 0; currentSet[j].stop(); } // Next, play/end all child effects before the one we want // This will set the animated properties to their end values. for (k = currentSetIndex + 1; k < setToCompare; k++) { childEffectInstances = activeEffectQueue[k]; for (l = 0; l < childEffectInstances.length; l++) { childEffectInstances[l].playheadTime = 0; childEffectInstances[l].stop(); } } } else { // We need to seek into a child effect later than the // one we're currently playing. First, end the current effect. var currentEffectInstances:Array = currentSet.concat(); for (j = 0; j < currentEffectInstances.length; j++) currentEffectInstances[j].end(); // Next, play/end all child effects before the one we want // This will set the animated properties to their end values. for (k = currentSetIndex + 1; k < setToCompare; k++) { childEffectInstances = activeEffectQueue[k]; for (l = 0; l < childEffectInstances.length; l++) { childEffectInstances[l].play(); childEffectInstances[l].end(); } } } // Finally, set the current child effect and seek into it currentSetIndex = setToCompare; playCurrentChildSet(); for (k = 0; k < currentSet.length; k++) { currentSet[k].playheadTime = (iterationPlayheadTime - startTime); if (isPaused) currentSet[k].pause(); } } endEffectCalled = false; // We're done, break out of the loop break; } } } } //-------------------------------------------------------------------------- // // Overridden methods // //-------------------------------------------------------------------------- /** * @private */ override public function play():void { isPaused = false; // Create a new queue. activeEffectQueue = []; // Start at the beginning or the end // depending if we are playing backwards. currentSetIndex = playReversed ? childSets.length : -1; var n:int; var i:int; var m:int; var j:int; // Each childSets contains an instance of an effect for each target. // Flatten these instances into the effectQueue. // Put a null object between each effect so that the sequence knows // when to stop and wait for the previous instances to finish. n = childSets.length; for (i = 0; i < n; i++) { var instances:Array = childSets[i]; activeEffectQueue.push(instances); } // Dispatch an effectStart event from the target. super.play(); startTime = Tween.intervalTime; if (activeEffectQueue.length == 0) { finishRepeat(); return; } playNextChildSet(); } /** * @private */ override public function pause():void { super.pause(); isPaused = true; if (currentSet && currentSet.length > 0) { var n:int = currentSet.length; for (var i:int = 0; i < n; i++) { currentSet[i].pause(); } } } /** * @private */ override public function stop():void { isPaused = false; if (activeEffectQueue && activeEffectQueue.length > 0) { var queueCopy:Array = activeEffectQueue.concat(); activeEffectQueue = null; // Call stop on the currently playing set var currentInstances:Array = queueCopy[currentSetIndex]; if (currentInstances) { var currentCount:int = currentInstances.length; for (var i:int = 0; i < currentCount; i++) currentInstances[i].stop(); } // For instances that have yet to run, we will delete them // without dispatching events. // (Another alternative would have been add them into // currentInstances and currentSet, then just stop them // along with the others. In this case, they would have // dispatched effectEnd events). var n:int = queueCopy.length; for (var j:int = currentSetIndex + 1; j < n; j++) { var waitingInstances:Array = queueCopy[j]; var m:int = waitingInstances.length; for (var k:int = 0; k < m; k++) { var instance:IEffectInstance = waitingInstances[k]; instance.effect.deleteInstance(instance); } } } super.stop(); } /** * @private */ override public function resume():void { super.resume(); isPaused = false; if (currentSet && currentSet.length > 0) { var n:int = currentSet.length; for (var i:int = 0; i < n; i++) { currentSet[i].resume(); } } } /** * @private */ override public function reverse():void { super.reverse(); if (currentSet && currentSet.length > 0) { // PlayNextChildSet handles the logic of playing previously completed effects var n:int = currentSet.length; for (var i:int = 0; i < n; i++) { currentSet[i].reverse(); } } } /** * Interrupts any effects that are currently playing, skips over * any effects that haven't started playing, and jumps immediately * to the end of the composite effect. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ override public function end():void { endEffectCalled = true; // activeEffectQueue are all effects to play // currentSetIndex is where we want to start // if play() hasn't been called on us yet (b/c of startDelay), // activeEffectQueue will have nothing in it. In this case, // we leave the component in it's current state, rather than // call .playWithNoDuration() on all the effects that haven't // been run (or even added to the activeEffectQueue yet) if (activeEffectQueue && activeEffectQueue.length > 0) { var queueCopy:Array = activeEffectQueue.concat(); activeEffectQueue = null; // Call end on the currently playing set var currentInstances:Array = queueCopy[currentSetIndex]; if (currentInstances) { var currentCount:int = currentInstances.length; for (var i:int = 0; i < currentCount; i++) { currentInstances[i].end(); } } var n:int = queueCopy.length; for (var j:int = currentSetIndex + 1; j < n; j++) { var waitingInstances:Array = queueCopy[j]; var m:int = waitingInstances.length; for (var k:int = 0; k < m; k++) { EffectInstance(waitingInstances[k]).playWithNoDuration(); } } } isPaused = false; super.end(); } /** * Each time a child effect of SequenceInstance finishes, * Flex calls the onEffectEnd() method. * For SequenceInstance, it plays the next effect. * This method implements the method of the superclass. * * @param childEffect The child effect. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ override protected function onEffectEnd(childEffect:IEffectInstance):void { // Each child effect notifies us when it is finished. // Remove the notifying child from childSets, // so that the end() method doesn't call it. // When the last child notifies us that it's finished, // notify our listener that we're finished. // Resume the background processing that was suspended earlier if (Object(childEffect).suspendBackgroundProcessing) UIComponent.resumeBackgroundProcessing(); for (var i:int = 0; i < currentSet.length; i++) { if (childEffect == currentSet[i]) { currentSet.splice(i, 1); break; } } // See endEffect, above. if (endEffectCalled) return; if (currentSet.length == 0) { if (false == playNextChildSet()) finishRepeat(); } } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- private function playCurrentChildSet():void { var childEffect:EffectInstance; var instances:Array = activeEffectQueue[currentSetIndex]; currentSet = []; for (var i:int = 0; i < instances.length; i++) { childEffect = instances[i]; currentSet.push(childEffect); childEffect.playReversed = playReversed; // Block all layout, responses from web services, and other // background processing until the effect finishes executing. if (childEffect.suspendBackgroundProcessing) UIComponent.suspendBackgroundProcessing(); childEffect.startEffect(); } currentInstanceDuration += childEffect.actualDuration; } /** * @private */ private function playNextChildSet(offset:Number = 0):Boolean { if (!playReversed) { if (!activeEffectQueue || currentSetIndex++ >= activeEffectQueue.length - 1) { return false; } } else { if (currentSetIndex-- <= 0) return false; } playCurrentChildSet(); return true; } } }