| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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; |
| } |
| } |
| } |
| |
| } |