blob: 83f17544e57f467238661030b1bdbd4fb4742712 [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.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.utils.getTimer;
import mx.core.UIComponentGlobals;
import mx.core.mx_internal;
import mx.events.TweenEvent;
use namespace mx_internal;
/**
* Tween is the underlying animation class for the effects in Flex 3. As of Flex 4, the Spark
* effects use the spark.effects.animation.Animation class to provide similar functionality.
*/
[Alternative(replacement="spark.effects.animation.Animation", since="4.0")]
/**
* The Tween class defines a tween, a property animation performed
* on a target object over a period of time.
* That animation can be a change in position, such as performed by
* the Move effect; a change in size, as performed by the Resize or
* Zoom effects; a change in visibility, as performed by the Fade or
* Dissolve effects; or other types of animations.
*
* <p>When defining tween effects, you typically create an instance
* of the Tween class within your override of the
* <code>EffectInstance.play()</code> method.
* A Tween instance accepts the <code>startValue</code>,
* <code>endValue</code>, and <code>duration</code> properties,
* and an optional easing function to define the animation.</p>
*
* <p>The Tween object invokes the
* <code>mx.effects.effectClasses.TweenEffectInstance.onTweenUpdate()</code>
* callback function on a regular interval on the effect instance
* for the duration of the effect, passing to the
* <code>onTweenUpdate()</code> method an interpolated value
* between the <code>startValue</code> and <code>endValue</code>.
* Typically, the callback function updates some property of the target object,
* causing that object to animate over the duration of the effect.</p>
*
* <p>When the effect ends, the Tween objects invokes the
* <code>mx.effects.effectClasses.TweenEffectInstance.onTweenEnd()</code>
* callback function, if you defined one. </p>
*
* @see mx.effects.TweenEffect
* @see mx.effects.effectClasses.TweenEffectInstance
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class Tween extends EventDispatcher
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
mx_internal static var activeTweens:Array = [];
/**
* @private
*/
private static var interval:Number = 10;
/**
* @private
*/
private static var timer:Timer = null;
//--------------------------------------------------------------------------
//
// Class properties
//
//--------------------------------------------------------------------------
//----------------------------------
// intervalTime
//----------------------------------
/**
* @private
* Used by effects to get the current effect time tick.
*/
mx_internal static var intervalTime:Number = NaN;
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
private static function addTween(tween:Tween):void
{
tween.id = activeTweens.length;
activeTweens.push(tween);
if (!timer)
{
timer = new Timer(interval);
timer.addEventListener(TimerEvent.TIMER, timerHandler);
timer.start();
}
else
{
timer.start();
}
if (isNaN(intervalTime))
intervalTime = getTimer();
tween.startTime = tween.previousUpdateTime = intervalTime;
}
/**
* @private
*/
private static function removeTweenAt(index:int):void
{
var length:int = activeTweens.length;
if (index >= length || index < 0)
return;
activeTweens.splice(index, 1);
length--;
for (var i:int = index; i < length; i++)
{
var curTween:Tween = Tween(activeTweens[i]);
curTween.id--;
}
if (length == 0)
{
intervalTime = NaN;
timer.reset();
}
}
/**
* @private
*/
mx_internal static function removeTween(tween:Tween):void
{
removeTweenAt(tween.id);
tween.id = -1;
}
/**
* @private
*/
private static function timerHandler(event:TimerEvent):void
{
var needToLayout:Boolean = false;
var oldTime:Number = intervalTime;
var length:int = activeTweens.length;
intervalTime = getTimer();
for (var i:int = length-1; i >= 0; i--)
{
var tween:Tween = Tween(activeTweens[i]);
if (tween)
{
tween.needToLayout = false;
tween.doInterval();
if (tween.needToLayout)
needToLayout = true;
}
}
// If one of the effects requested a layout, do it now.
if (needToLayout)
{
UIComponentGlobals.layoutManager.validateNow();
}
event.updateAfterEvent();
}
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* <p>When the constructor is called, the animation automatically
* starts playing.</p>
*
* @param listener Object that is notified at each interval
* of the animation. You typically pass the <code>this</code>
* keyword as the value.
* The <code>listener</code> must define the
* <code>onTweenUpdate()</code> method and optionally the
* <code>onTweenEnd()</code> method.
* The former method is invoked for each interval of the animation,
* and the latter is invoked just after the animation finishes.
*
* @param startValue Initial value(s) of the animation.
* Either a number or an array of numbers.
* If a number is passed, the Tween interpolates
* between this number and the number passed
* in the <code>endValue</code> parameter.
* If an array of numbers is passed,
* each number in the array is interpolated.
*
* @param endValue Final value(s) of the animation.
* The type of this argument must match the <code>startValue</code>
* parameter.
*
* @param duration Duration of the animation, expressed in milliseconds.
*
* @param minFps Minimum number of times that the
* <code>onTweenUpdate()</code> method should be called every second.
* The tween code tries to call the <code>onTweenUpdate()</code>
* method as frequently as possible (up to 100 times per second).
* However, if the frequency falls below <code>minFps</code>,
* the duration of the animation automatically increases.
* As a result, an animation that temporarily freezes
* (because it is not getting any CPU cycles) begins again
* where it left off, instead of suddenly jumping ahead.
*
* @param updateFunction Specifies an alternative update callback
* function to be used instead of <code>listener.OnTweenUpdate()</code>
*
* @param endFunction Specifies an alternative end callback function
* to be used instead of <code>listener.OnTweenEnd()</code>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function Tween(listener:Object,
startValue:Object, endValue:Object,
duration:Number = -1, minFps:Number = -1,
updateFunction:Function = null,
endFunction:Function = null)
{
super();
if (!listener)
return;
if (startValue is Array)
arrayMode = true;
this.listener = listener;
this.startValue = startValue;
this.endValue = endValue;
if (!isNaN(duration) && duration != -1)
this.duration = duration;
// If user has specified a minimum number of frames per second,
// remember the maximum allowable milliseconds between frames.
if (!isNaN(minFps) && minFps != -1)
maxDelay = 1000 / minFps;
this.updateFunction = updateFunction;
this.endFunction = endFunction;
if (duration == 0)
{
id = -1; // use -1 to indicate that this tween was never added
endTween();
}
else
Tween.addTween(this);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
mx_internal var needToLayout:Boolean = false;
/**
* @private
*/
private var id:int = -1;
/**
* @private
*/
private var maxDelay:Number = 87.5; // Min frames per second is 12
/**
* @private
*/
private var arrayMode:Boolean;
/**
* @private
*/
private var _doSeek:Boolean = false;
/**
* @private
*/
private var _isPlaying:Boolean = true;
/**
* @private
*/
private var _doReverse:Boolean = false;
/**
* @private
*/
mx_internal var startTime:Number;
/**
* @private
*/
private var previousUpdateTime:Number;
/**
* @private
*/
private var userEquation:Function = defaultEasingFunction;
/**
* @private
*/
private var updateFunction:Function;
/**
* @private
*/
private var endFunction:Function;
/**
* @private
* Final value(s) of the animation.
* This can be a Number of an Array of Numbers.
*/
private var endValue:Object;
/**
* @private
* Initial value(s) of the animation.
* This can be a Number of an Array of Numbers.
*/
private var startValue:Object;
/**
* @private
*/
private var started:Boolean = false;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// duration
//----------------------------------
/**
* Duration of the animation, in milliseconds.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var duration:Number = 3000;
//----------------------------------
// listener
//----------------------------------
/**
* Object that is notified at each interval of the animation.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var listener:Object;
//----------------------------------
// playheadTime
//----------------------------------
/**
* @private
* Storage for the playheadTime property.
*/
private var _playheadTime:Number = 0;
/**
* @private
* The current millisecond position in the tween.
* This value is between 0 and duration.
* Use the seek() method to change the position of the tween.
*/
mx_internal function get playheadTime():Number
{
return _playheadTime;
}
//----------------------------------
// playReversed
//----------------------------------
/**
* @private
* Storage for the playReversed property.
*/
private var _invertValues:Boolean = false;
/**
* @private
* Starts playing reversed from start of tween.
* Setting this property to <code>true</code>
* inverts the values returned by the tween.
* Using reverse inverts the values and only plays
* for as much time that has already elapsed.
*/
mx_internal function get playReversed():Boolean
{
return _invertValues;
}
/**
* @private
*/
mx_internal function set playReversed(value:Boolean):void
{
_invertValues = value;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* By default, the Tween class invokes the
* <code>mx.effects.effectClasses.TweenEffectInstance.onTweenUpdate()</code>
* callback function on a regular interval on the effect instance
* for the duration of the effect, and the optional
* <code>mx.effects.effectClasses.TweenEffectInstance.onTweenEnd()</code>
* callback function at the end of the effect duration.
*
* <p>This method lets you specify different methods
* as the update and the end callback functions.</p>
*
* @param updateFunction Specifies the update callback function.
*
* @param endFunction Specifies the end callback function.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function setTweenHandlers(updateFunction:Function,
endFunction:Function):void
{
this.updateFunction = updateFunction;
this.endFunction = endFunction;
}
/**
* Sets the easing function for the animation.
* The easing function is used to interpolate between
* the <code>startValue</code> value and the <code>endValue</code>.
* A trivial easing function does linear interpolation,
* but more sophisticated easing functions create the illusion of
* acceleration and deceleration, which makes the animation seem
* more natural.
*
* <p>If no easing function is specified, an easing function based
* on the <code>Math.sin()</code> method is used.</p>
*
* <p>The easing function follows the function signature
* popularized by Robert Penner.
* The function accepts four arguments.
* The first argument is the "current time",
* where the animation start time is 0.
* The second argument is a the initial value
* at the beginning of the animation (a Number).
* The third argument is the ending value
* minus the initial value.
* The fourth argument is the duration of the animation.
* The return value is the interpolated value for the current time
* (usually a value between the initial value and the ending value).</p>
*
* <p>Flex includes a set of easing functions
* in the mx.effects.easing package.</p>
*
* @param easingFunction Function that implements the easing equation.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set easingFunction(value:Function):void
{
userEquation = value;
}
/**
* Interrupt the tween, jump immediately to the end of the tween,
* and invoke the <code>onTweenEnd()</code> callback function.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function endTween():void
{
var event:TweenEvent = new TweenEvent(TweenEvent.TWEEN_END);
var value:Object = getCurrentValue(duration);
event.value = value;
dispatchEvent(event);
if (endFunction != null)
endFunction(value);
else
listener.onTweenEnd(value);
// If tween has been added, id >= 0
// but if duration = 0, this might not be the case.
if (id >= 0) {
Tween.removeTweenAt(id);
id = -1;
}
}
/**
* @private
* Returns true if the tween has ended.
*/
mx_internal function doInterval():Boolean
{
var tweenEnded:Boolean = false;
// If user specified a minimum frames per second, we can't guarantee
// that we'll be called often enough to satisfy that request.
// However, we can avoid skipping over part of the animation.
// If this callback arrives too late, adjust the animation startTime,
// so that the animation starts up 'maxDelay' milliseconds
// after its last update.
/*
if (intervalTime - previousUpdateTime > maxDelay)
{
startTime += intervalTime - previousUpdateTime - maxDelay;
}
*/
previousUpdateTime = intervalTime;
if (_isPlaying || _doSeek)
{
var currentTime:Number = intervalTime - startTime;
_playheadTime = currentTime;
var currentValue:Object =
getCurrentValue(currentTime);
if (currentTime >= duration && !_doSeek)
{
endTween();
tweenEnded = true;
}
else
{
if (!started)
{
var startEvent:TweenEvent = new TweenEvent(TweenEvent.TWEEN_START);
dispatchEvent(startEvent);
started = true;
}
var event:TweenEvent =
new TweenEvent(TweenEvent.TWEEN_UPDATE);
event.value = currentValue;
dispatchEvent(event);
if (updateFunction != null)
updateFunction(currentValue);
else
listener.onTweenUpdate(currentValue);
}
_doSeek = false;
}
return tweenEnded;
}
/**
* @private
*/
mx_internal function getCurrentValue(currentTime:Number):Object
{
if (duration == 0)
{
return endValue;
}
if (_invertValues)
currentTime = duration - currentTime;
if (arrayMode)
{
var returnArray:Array = [];
var n:int = startValue.length;
for (var i:int = 0; i < n; i++)
{
returnArray[i] = userEquation(currentTime, startValue[i],
endValue[i] - startValue[i],
duration);
}
return returnArray;
}
else
{
return userEquation(currentTime, startValue,
Number(endValue) - Number(startValue),
duration);
}
}
/**
* @private
*/
private function defaultEasingFunction(t:Number, b:Number,
c:Number, d:Number):Number
{
return c / 2 * (Math.sin(Math.PI * (t / d - 0.5)) + 1) + b;
}
/**
* Advances the tween effect to the specified position.
*
* @param playheadTime The position, in milliseconds, between 0
* and the value of the <code>duration</code> property.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function seek(playheadTime:Number):void
{
// Set value between 0 and duration
//playheadTime = Math.min(Math.max(playheadTime, 0), duration);
var clockTime:Number = intervalTime;
// Reset the previous update time
previousUpdateTime = clockTime;
// Reset the start time
startTime = clockTime - playheadTime;
_doSeek = true;
doInterval();
}
/**
* Plays the effect in reverse,
* starting from the current position of the effect.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function reverse():void
{
if (_isPlaying)
{
_doReverse = false;
seek(duration - _playheadTime);
_invertValues = !_invertValues;
}
else
{
_doReverse = !_doReverse;
}
}
/**
* Pauses the effect until you call the <code>resume()</code> method.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function pause():void
{
_isPlaying = false;
}
/**
* Stops the tween, ending it without dispatching an event or calling
* the Tween's endFunction or <code>onTweenEnd()</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function stop():void
{
if (id >= 0) {
Tween.removeTweenAt(id);
id = -1;
}
}
/**
* Resumes the effect after it has been paused
* by a call to the <code>pause()</code> method.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function resume():void
{
_isPlaying = true;
startTime = intervalTime - _playheadTime;
if (_doReverse)
{
reverse();
_doReverse = false;
}
}
}
}