blob: 1e94984d7ce6ca942f71a0e08ea55db33198e464 [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.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, <code>playheadTime</code> determines
* which child effect should be the active one and sets the
* appropriate <code>playheadTime</code> in that effect.
* Previous effects in the sequence will be ended (if playing) or
* skipped (if not yet started but <code>playheadTime</code> 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 <code>onEffectEnd()</code> 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;
}
}
}