////////////////////////////////////////////////////////////////////////////////
//
// 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 flash.events.TimerEvent;
import flash.system.ApplicationDomain;
import flash.utils.Timer;
import mx.core.FlexVersion;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.effects.EffectInstance;
import mx.effects.IEffectInstance;
import mx.effects.Parallel;
use namespace mx_internal;
/**
* The ParallelInstance class implements the instance class
* for the Parallel effect.
* Flex creates an instance of this class when it plays a Parallel effect;
* you do not create one yourself.
*
* @see mx.effects.Parallel
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class ParallelInstance extends CompositeEffectInstance
{
include "../../core/Version.as";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @param target This argument is ignored for Parallel 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 ParallelInstance(target:Object)
{
super(target);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
* Holds the child effect instances that have already completed.
*/
private var doneEffectQueue:Array /* of EffectInstance */;
/**
* @private
* Holds the child effect instances that are waiting to be replayed.
*/
private var replayEffectQueue:Array /* of EffectInstance */;
/**
* @private
*/
private var isReversed:Boolean = false;
/**
* @private
*/
private var timer:Timer;
//--------------------------------------------------------------------------
//
// Overridden properties
//
//--------------------------------------------------------------------------
//----------------------------------
// durationWithoutRepeat
//----------------------------------
/**
* @private
*/
override mx_internal function get durationWithoutRepeat():Number
{
var _duration:Number = 0;
// Get the largest actualDuration of all of our children
var n:int = childSets.length;
for (var i:int = 0; i < n; i++)
{
var instances:Array = childSets[i];
_duration = Math.max(instances[0].actualDuration, _duration);
}
return _duration;
}
//----------------------------------
// playheadTime
//----------------------------------
/**
* @inheritDoc
*
* In a Parallel effect, setting playheadTime
* will cause all child effects to go to that same
* playheadTime
. This may cause child effects to
* lessen their startDelay
(if they are currently
* waiting to be played), start playing (if the
* playheadTime
is greater than their
* startDelay
), or end (if the playheadTime
* is greater than their startDelay
plus
* their duration
).
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override public function set playheadTime(value:Number):void
{
// This will seek in the Parallel's instance
super.playheadTime = value;
var compositeDur:Number = Parallel(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 childPlayheadTime:Number;
if (value <= firstCycleDur) {
childPlayheadTime = Math.min(value - startDelay, compositeDur);
playCount = 1;
}
else
{
if (value >= totalDur && repeatCount != 0)
{
childPlayheadTime = compositeDur;
playCount = repeatCount;
}
else
{
var valueAfterFirstCycle:Number = value - firstCycleDur;
childPlayheadTime = Math.min(childPlayheadTime, compositeDur);
childPlayheadTime = valueAfterFirstCycle % laterCycleDur;
playCount = 1 + valueAfterFirstCycle / laterCycleDur;
}
}
// Tell all of our children to set their playheadTime
for (var i:int = 0; i < childSets.length; i++)
{
var instances:Array = childSets[i];
var m:int = instances.length;
for (var j:int = 0; j < m; j++)
instances[j].playheadTime = playReversed ?
Math.max(0, (childPlayheadTime -
(durationWithoutRepeat - instances[j].actualDuration))) :
childPlayheadTime;
}
// Add any effects waiting on the replayQueue, which holds the
// effects waiting to be played at the right time when reversing
if (playReversed && replayEffectQueue != null && replayEffectQueue.length > 0)
{
var position:Number = durationWithoutRepeat - playheadTime;
var numDone:int = replayEffectQueue.length;
for (i = numDone - 1; i >= 0; i--)
{
var childEffect:EffectInstance = replayEffectQueue[i];
// If the position is less than the child duration, then
// we must be either in or past the child effect, so we
// should start that effect
if (position <= childEffect.actualDuration)
{
// Move the effect from the done queue back onto the active one
if (activeEffectQueue == null)
activeEffectQueue = [];
activeEffectQueue.push(childEffect);
replayEffectQueue.splice(i,1);
childEffect.playReversed = playReversed;
childEffect.startEffect();
// Note that we've already set the playheadTime on these
// queued effects in the previous loop on childSets
}
}
}
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
private static var resizeEffectType:Class;
private static var resizeEffectLoaded:Boolean = false;
/**
* @private
*/
override public function addChildSet(childSet:Array):void
{
super.addChildSet(childSet);
// Make sure that spark Resize and mx Rotate are in the beginning of the
// array because they need to be before any Move effects
// TODO: There is logic at the Animation level to handle processing
// width/height values before any others for any animation frame. We
// maybe be able to remove this Resize-reordering logic altogether.
if (childSets.length > 1 && childSet.length > 0)
{
var compChild:CompositeEffectInstance = childSet[0] as CompositeEffectInstance;
// Resize effects must run before Move effects for autoCenterTransform to
// work correctly
if (!resizeEffectLoaded)
{
resizeEffectLoaded = true;
if (ApplicationDomain.currentDomain.hasDefinition("spark.effects.supportClasses.ResizeInstance"))
resizeEffectType = Class(ApplicationDomain.currentDomain.
getDefinition("spark.effects.supportClasses.ResizeInstance"));
}
if (resizeEffectType && (childSet[0] is resizeEffectType ||
(compChild != null && compChild.hasResizeInstance())))
{
// Remove item we just added to the end and place it at the beginning,
// but after any other Resize effects
var resizeChildSet:Array = childSets.pop();
var insertionIndex:int = 0;
for (var i:int = 0; i < childSets.length; ++i)
{
var currentChildSet:Array = childSets[i];
var currentChildSetComposite:CompositeEffectInstance =
currentChildSet[0] as CompositeEffectInstance;
if (!(currentChildSet[0] is resizeEffectType) &&
(!currentChildSetComposite || !currentChildSetComposite.hasResizeInstance()))
{
break;
}
++insertionIndex;
}
childSets.splice(insertionIndex, 0, resizeChildSet);
}
// Check if the child is a Rotate and also check if it is a composite effect that has a Rotate
else if (childSet[0] is RotateInstance || (compChild != null && compChild.hasRotateInstance()))
{
// Remove item we just added to the end and place it at the beginning
childSets.unshift(childSets.pop());
}
}
}
/**
* @private
*/
override public function play():void
{
doneEffectQueue = [];
activeEffectQueue = [];
replayEffectQueue = [];
// Create a timer tween used to keep track of the playheadTime
var checkQueueLength:Boolean = false;
// Dispatch an effectStart event from the target.
super.play();
var n:int;
var i:int;
n = childSets.length;
for (i = 0; i < n; i++)
{
var instances:Array = childSets[i];
var m:int = instances.length;
for (var j:int = 0; j < m && activeEffectQueue != null; j++)
{
var childEffect:EffectInstance = instances[j];
// Check if the effect should play right away
// or should be put in the replay queue.
if (playReversed &&
childEffect.actualDuration < durationWithoutRepeat)
{
replayEffectQueue.push(childEffect);
startTimer();
}
else
{
childEffect.playReversed = playReversed;
activeEffectQueue.push(childEffect);
}
// Block all layout, responses from web services,
// and other background processing until the effect
// finishes executing.
if (childEffect.suspendBackgroundProcessing)
UIComponent.suspendBackgroundProcessing();
}
}
if (activeEffectQueue.length > 0)
{
// Start all of the effects in the active queue.
// Need to make a copy of the queue first since some effects
// (like Action effects) are immediate and will be removed
// from the activeEffectQueue before returning from startEffect().
var queueCopy:Array = activeEffectQueue.slice(0);
for (i = 0; i < queueCopy.length; i++)
{
queueCopy[i].startEffect();
}
}
}
/**
* @private
*/
override public function pause():void
{
super.pause();
// Pause every currently playing effect instance.
if (activeEffectQueue)
{
var n:int = activeEffectQueue.length;
for (var i:int = 0; i < n; i++)
{
activeEffectQueue[i].pause();
}
}
}
/**
* @private
*/
override public function stop():void
{
stopTimer();
if (activeEffectQueue)
{
var queueCopy:Array = activeEffectQueue.concat();
activeEffectQueue = null;
var n:int = queueCopy.length;
for (var i:int = 0; i < n; i++)
{
if (queueCopy[i])
queueCopy[i].stop();
}
}
super.stop();
}
/**
* @private
*/
override public function resume():void
{
super.resume();
// Resume every currently playing effect instance.
if (activeEffectQueue)
{
var n:int = activeEffectQueue.length;
for (var i:int = 0; i < n; i++)
{
activeEffectQueue[i].resume();
}
}
}
/**
* @private
*/
override public function reverse():void
{
super.reverse();
var n:int;
var i:int;
if (isReversed)
{
// We don't care about anything in the done queue.
// Just reverse all the active ones.
n = activeEffectQueue.length;
for (i = 0; i < n; i++)
{
activeEffectQueue[i].reverse();
}
stopTimer();
}
else
{
replayEffectQueue = doneEffectQueue.splice(0);
// Reverse all of the active ones.
// Plus, setup a timer to check if we reach any done ones.
n = activeEffectQueue.length;
for (i = 0; i < n; i++)
{
activeEffectQueue[i].reverse();
}
startTimer();
}
isReversed = !isReversed;
}
/**
* 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;
stopTimer();
if (activeEffectQueue)
{
var queueCopy:Array = activeEffectQueue.concat();
activeEffectQueue = null;
var n:int = queueCopy.length;
for (var i:int = 0; i < n; i++)
{
if (queueCopy[i])
queueCopy[i].end();
}
}
super.end();
}
/**
* Each time a child effect of SequenceInstance or ParallelInstance
* finishes, Flex calls the onEffectEnd()
method.
* For SequenceInstance, it plays the next effect.
* In ParallelInstance, it keeps track of all the
* effects until all of them have finished playing.
* If you create a subclass of CompositeEffect, you must implement this method.
*
* @param childEffect A child effect that has finished.
*
* @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();
// See endEffect, above.
if (endEffectCalled || activeEffectQueue == null)
return;
var n:int = activeEffectQueue.length;
for (var i:int = 0; i < n; i++)
{
if (childEffect == activeEffectQueue[i])
{
doneEffectQueue.push(childEffect);
activeEffectQueue.splice(i, 1);
break;
}
}
if (n == 1)
{
// Note: This event must be dispatched *before* checking for
// actualCachePolicy below so that the listener has a chance to set
// it to "on".
finishRepeat();
}
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function startTimer():void
{
if (!timer)
{
timer = new Timer(10);
timer.addEventListener(TimerEvent.TIMER, timerHandler);
}
timer.start();
}
/**
* @private
*/
private function stopTimer():void
{
if (timer)
timer.reset();
}
/**
* @private
* Used internally to figure out if we should be playing an effect
* from the replay queue.
*/
private function timerHandler(event:TimerEvent):void
{
// Assume we are playing in reverse
// invert the playheadTime
var position:Number = durationWithoutRepeat - playheadTime;
var numDone:int = replayEffectQueue.length;
if (numDone == 0)
{
stopTimer();
return;
}
for (var i:int = numDone - 1; i >= 0; i--)
{
var childEffect:EffectInstance = replayEffectQueue[i];
if (position <= childEffect.actualDuration)
{
// Move the effect from the done queue back onto the active one
if (activeEffectQueue == null)
activeEffectQueue = [];
activeEffectQueue.push(childEffect);
replayEffectQueue.splice(i,1);
childEffect.playReversed =playReversed;
childEffect.startEffect();
}
}
}
}
}