From JLM at justinfront.net Fri Mar 6 02:35:13 2009
From: JLM at justinfront.net (Justin Lawerance Mills)
Date: Fri, 6 Mar 2009 10:35:13 +0000
Subject: [Golist] addAsynchronousAction??
In-Reply-To: Using these Docs Protected methods and properties have been excluded in almost all
* cases, but are documented in the classes. Exceptions include key
protected
* methods or properties that are integral for writing subclasses or
understanding
* the basic mechanics of the system. Many Go classes can be used as is
without
* subclassing, so the documentation offers an uncluttered view of their
public
* usage. Introduction to Go [This section
updated recently!] The Go ActionScript Animation Platform ("GOASAP") is a lightweight,
portable
* set of generic base classes for buliding AS3 animation tools. It
provides structure
* and core functionality, but does not define the specifics of
animation-handling
* classes like tweens. GoASAP could be broken up into the following general layers:
* update calls to IUpdatable instances
on their specified pulseInterval.
*
*
*
*
GoASAP provides an intentionally loose standard, in that it does not intend to limit * the possibilities of what can be built with it. Its primary benefits are compatibility and * synchronicity between animation systems, absolute extensibility into any time-based process, * and a much faster and easier way to build your own animation tools from scratch.
* *Important: Store your custom Go classes in a package bearing your own classpath, not * in the core package! This will help avoid confusion with other authors' work.
* *You may modify any class in the goasap package to suit your project's needs. Your input * is valuable! Please join the mailing list and share your Go-based animation tools at the * GoPlayground repository. The GoASAP initiative is led by Moses Gunesch at * MosesSupposes.com. Please visit the * Go website for more information.
* * *GoEngine [This section updated recently!]
* *GoEngine sits at the center of the Go system, and along with the IUpdatable * interface is the only required element for using GoASAP. GoEngine manages tightly * synchronized item lists, since updating items in groups enhances efficiency. An * advantage of GoASAP is that wildly different animation systems can be used together * in the same project. Their synchronous updates will remain as efficient as possible, * instead of fighting one another for processor cycles.
* *GoEngine's default pulse rate is ENTER_FRAME which yields the smoothest processing in the * Flash Player. However, it does not run on any one specific pulse. Instead, any object that is * IUpdatable may specify its own pulse rate, and items with matching pulses are automatically * grouped into update lists for efficiency. On a fine-tuning level, GoEngine uses a few other * tricks to try and provide the tightest possible visual synchronization for larger batches of * animation items. It passes the clock time at the start of each update cycle to each item in * that list, which can be used in place of realtime to counteract any offset due to processing * lag during the cycle. Additionally, items that get added during an update cycle are * queued until the next update.
* *GoASAP's management layer is made up of three interfaces that are
referenced by GoEngine:
* IManager, ILiveManager and IManageable. Managers are always optional in
GoASAP, and are only
* activated by calling GoEngine.addManager(). Managers can
automate processes
* as items are added and removed, such as the included OverlapMonitor
class which prevents
* property conflicts between items, or they can automate "live" processes
that occur on each
* pulse. No live managers are included but an example might be a class
that re-renders a 3D
* viewport after all 3D tweens have been processed. This can of course be
done without a custom
* manager, but by using GoASAP you gain a unique ability to very cleanly
and simply tie any
* custom routines in your project right into your animation processing,
in perfect sync and
* with maximum efficiency.
{In the game of Go, the wooden playing board, or Goban, features a grid * on which black & white go-ishi stones are laid at its intersections.}
* * @see org.goasap.items.LinearGo LinearGo * @see org.goasap.interfaces.IManager IManager * @author Moses Gunesch */ public class GoEngine { // -== Constants ==- public static const INFO:String = "GoASAP 0.5.1e (c) Moses Gunesch, MIT Licensed."; // -== Settable Class Defaults ==- /** * A pulseInterval that runs on the player's natural framerate, * which is often most efficient. */ public static const ENTER_FRAME : int = -1; // -== Protected Properties ==- // Note: Various formats for item data have been experimented with including breaking the item lists out into // a GoEngineList class, which was nicer-looking but did not perform well. Since GoEngine doesn't normally // require active work, this less-pretty but efficient flat-data format was opted for. A minor weakness of this // format is its use of a Dictionary, which means update calls are not ordered like they would be with an Array. // The Dictionary stores items' pulseInterval values, which is safer than relying on items to not change them. // Tests also show that Dictionary performs faster than Array for accessing and deleting items. private static var managerTable : Object = new Object(); // registration list of IManager instances private static var managers : Array = new Array(); // ordered registration list of IManager instances private static var liveManagers : uint = 0; private static var timers : Dictionary = new Dictionary(false); // key: pulseInterval, value: Timer for that pulse private static var items : Dictionary = new Dictionary(false); // key: IUpdatable item, value: pulseInterval at add. private static var itemCounts : Dictionary = new Dictionary(false); // key: pulseInterval, value: item count for that pulse private static var pulseSprite : Sprite; // used for ENTER_FRAME pulse private static var paused : Boolean = false; // These additional lists enables caching of items that are added during the update cycle for the same pulse. // This prevents groups & sequences from going out of sync by ensuring that each cycle completes before new items are added. private static var lockedPulses : Dictionary = new Dictionary(false); // key: pulseInterval, value: true private static var delayedPulses : Dictionary = new Dictionary(false); // key: pulseInterval, value: true private static var addQueue : Dictionary = new Dictionary(false); // key: IUpdatable item, value: true // -== Public Class Methods ==- /** * @param className A string naming the manager class, such as "OverlapMonitor". * @return The manager instance, if registered. * @see #addManager() * @see #removeManager() */ public static function getManager(className:String) : IManager { return managerTable[ className ]; } /** * Enables the extending of this class' functionality with a tight * coupling to an IManager. * *Tight coupling is crucial in such a time-sensitive context; * standard events are too asynchronous. All items that implement * IManageable are reported to registered managers as they add and * remove themselves from GoEngine.
* *Managers normally act as singletons within the Go system (which * you are welcome to modify). This method throws a DuplicateManagerError * if an instance of the same manager class is already registered. Use a * try/catch block when calling this method if your program might duplicate * managers, or use getManager() to check for prior registration.
* * @param instance An instance of a manager you wish to add. * @see #getManager() * @see #removeManager() */ public static function addManager( instance:IManager ):void { var className:String = getQualifiedClassName(instance); className = className.slice(className.lastIndexOf("::")+2); if (managerTable[ className ]) { throw new DuplicateManagerError( className ); return; } managerTable[ className ] = instance; managers.push(instance); if (instance is ILiveManager) liveManagers++; } /** * Unregisters any manager set inaddManager.
*
* @param className A string naming the manager class, such
as "OverlapMonitor".
* @see #getManager()
* @see #addManager()
*/
public static function removeManager( className:String ):void
{
managers.splice(managers.indexOf(managerTable[ className ]), 1);
if (managerTable[ className ] is ILiveManager)
liveManagers--;
delete managerTable[ className ]; // leave last
}
/**
* Test whether an item is currently stored and being updated by the
engine.
*
* @param item Any object implementing IUpdatable
* @return Whether the IUpdatable is in the engine
*/
public static function hasItem( item:IUpdatable ):Boolean
{
return (items[ item ]!=null);
}
/**
* Adds an IUpdatable instance to an update-queue corresponding to
* the item's pulseInterval property.
*
* @param item Any object implementing IUpdatable that wishes
* to receive update calls on a pulse.
*
* @return Returns false only if this item was already in the
* engine under the same pulse. (If an existing item is added
* but the pulseInterval has changed it will be removed,
* re-added, and true will be returned.)
*
* @see #removeItem()
*/
public static function addItem( item:IUpdatable ):Boolean
{
// Group items by pulse for efficient update cycles.
var interval:int = item.pulseInterval;
if (items[ item ]) {
if (items[ item ] == item.pulseInterval)
return false;
else
removeItem(item);
}
if (lockedPulses[ interval ]==true) { // this prevents items from being
added during an update loop in progress.
delayedPulses[ interval ] = true; // flags update to clear the queue
when the in-progress loop completes.
addQueue[ item ] = true; // for tightest syncing of item groups, read
the documentation under GoItem.update().
}
items[ item ] = interval; // Tether item to original pulseint. Used in
removeItem & setPaused(false).
if (!timers[ interval ]) {
addPulse( interval );
itemCounts[ interval ] = 1;
}
else {
itemCounts[ interval ] ++;
}
// Report IManageable instances to registered managers
if (item is IManageable) {
for each (var manager:IManager in managers)
manager.reserve( item as IManageable );
}
return true;
}
/**
* Removes an item from the queue and removes its pulse timer if
* the queue is depleted.
*
* @param item Any IUpdatable previously added that wishes
* to stop receiving update calls.
*
* @return Returns false if the item was not in the engine.
*
* @see #addItem()
*/
public static function removeItem( item:IUpdatable ):Boolean
{
if (items[ item ]==null)
return false;
var interval: int = items[ item ];
if ( -- itemCounts[ interval ] == 0 ) {
removePulse( interval );
delete itemCounts[ interval ];
}
delete items[ item ];
delete addQueue[ item ]; // * see note following update
// Report IManageable item removal to registered managers.
if (item is IManageable) {
for each (var manager:IManager in managers)
manager.release( item as IManageable );
}
return true;
}
/**
* Removes all items and resets the engine,
* or removes just items running on a specific pulse.
*
* @param pulseInterval Optionally filter by a specific pulse
* such as ENTER_FRAME or a number of milliseconds.
* @return The number of items successfully removed.
* @see #removeItem()
*/
public static function clear(pulseInterval:Number = NaN) : uint
{
var all:Boolean = (isNaN(pulseInterval));
var n:Number = 0;
for (var item:Object in items) {
if (all || items[ item ]==pulseInterval)
if (removeItem(item as IUpdatable)==true)
n++;
}
return n;
}
/**
* Retrieves number of active items in the engine
* or active items running on a specific pulse.
*
* @param pulseInterval Optionally filter by a specific pulseInterval
* such as ENTER_FRAME or a number of milliseconds.
*
* @return Number of active items in the Engine.
*/
public static function getCount(pulseInterval:Number = NaN) : uint
{
if (!isNaN(pulseInterval))
return (itemCounts[pulseInterval]);
var n:Number = 0;
for each (var count: int in itemCounts)
n += count;
return n;
}
/**
* @return The paused state of engine.
* @see #setPaused()
*/
public static function getPaused() : Boolean {
return paused;
}
/**
* Pauses or resumes all animation globally by suspending processing,
* and calls pause() or resume() on each item with those methods.
*
* The return value only reflects how many items had pause() or resume() * called on them, but the GoEngine.getPaused() state will change if any * pulses are suspended or resumed.
* * @param pause Pass false to resume if currently paused. * @param pulseInterval Optionally filter by a specific pulse * such as ENTER_FRAME or a number of milliseconds. * @return The number of items on which a pause() or resume() * method was called (0 doesn't necessarily reflect * whether the GoEngine.getPaused() state changed, it * may simply indicate that no items had that method). * @see #resume() */ public static function setPaused(pause:Boolean=true, pulseInterval:Number = NaN) : uint { if (paused==pause) return 0; var n:Number = 0; var pulseChanged:Boolean = false; var all:Boolean = (isNaN(pulseInterval)); var method:String = (pause ? "pause" : "resume"); for (var item:Object in items) { var pulse:int = (items[item] as int); if (all || pulse==pulseInterval) { pulseChanged = (pulseChanged || (pause ? removePulse(pulse) : addPulse(pulse))); // call pause or resume on the item if it has such a method. if (item.hasOwnProperty(method)) { if (item[method] is Function) { item[method].apply(item); n++; } } } } if (pulseChanged) paused = pause; return n; } // -== Private Class Methods ==- /** * Executes the update queue corresponding to the dispatcher's interval. * * @param event TimerEvent or Sprite ENTER_FRAME Event */ private static function update(event:Event) : void { var currentTime:Number = getTimer(); var pulse:int = (event is TimerEvent ? ( event.target as Timer ).delay : ENTER_FRAME); lockedPulses[ pulse ] = true; var doLiveUpdate:Boolean = (liveManagers > 0); var updated:Array; if (doLiveUpdate) updated = []; // syncs the live manager list to items actually updated for (var item:* in items) { if (items[ item ]==pulse && !addQueue[ item ]) { (item as IUpdatable).update(currentTime); if (doLiveUpdate) updated.push(item); } } lockedPulses[ pulse ] = false; if (delayedPulses[ pulse ]) { for (item in addQueue) delete addQueue[ item ]; delete delayedPulses[ pulse ]; } // updateAfterEvent() should not be needed as long as items follow tight-syncing instructions in GoItem.update() documentation. // if (pulse!=ENTER_FRAME) (event as TimerEvent).updateAfterEvent(); if (doLiveUpdate) for each (var manager:Object in managers) if (manager is ILiveManager) (manager as ILiveManager).onUpdate(pulse, updated, currentTime); // * see note } // * note: In one rare case that has not been reported yet but is theoretically possible, the 'updated' list // passed could contain already-released items. This could only happen if the item is removed & released // just after the main update cycle but before the the doLiveUpdate() routine runs. If you encounter this issue // please report it to the GoASAP mailing list, it's too involved to bother with before it's a problem. /** * Creates new timers when a previously unused interval is specified, * and tracks the number of items associated with that interval. * * @param pulse The pulseInterval requested * @return Whether a pulse was added */ private static function addPulse(pulse : int) : Boolean { if (pulse==ENTER_FRAME) { if (!pulseSprite) { timers[ENTER_FRAME] = pulseSprite = new Sprite(); pulseSprite.addEventListener(Event.ENTER_FRAME, update); } return true; } var t:Timer = timers[ pulse ] as Timer; if (!t) { t = timers[ pulse ] = new Timer(pulse); (timers[ pulse ] as Timer).addEventListener(TimerEvent.TIMER, update); t.start(); return true; } return false; } /** * Tracks whether a removed item was the last one using a timer * and if so, removes that timer. * * @param pulse The pulseInterval corresponding to an item being removed. * @return Whether a pulse was removed */ private static function removePulse(pulse : int) : Boolean { if (pulse==ENTER_FRAME) { if (pulseSprite) { pulseSprite.removeEventListener(Event.ENTER_FRAME, update); delete timers[ ENTER_FRAME ]; pulseSprite = null; return true; } } var t:Timer = timers[ pulse ] as Timer; if (t) { t.stop(); t.removeEventListener(TimerEvent.TIMER, update); delete timers[ pulse ]; return true; } return false; } } } \ No newline at end of file +?/** * Copyright (c) 2007 Moses Gunesch * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.goasap { import flash.display.Sprite; import flash.events.Event; import flash.events.TimerEvent; import flash.utils.Dictionary; import flash.utils.Timer; import flash.utils.getQualifiedClassName; import flash.utils.getTimer; import org.goasap.errors.DuplicateManagerError; import org.goasap.interfaces.IManageable; import org.goasap.interfaces.IManager; import org.goasap.interfaces.IUpdatable; import org.goasap.interfaces.ILiveManager; /** * Providesupdate calls to IUpdatable instances
on their specified pulseInterval.
*
* * **Using these Docs
* *Protected methods and properties have been excluded in almost all * cases, but are documented in the classes. Exceptions include key protected * methods or properties that are integral for writing subclasses or understanding * the basic mechanics of the system. Many Go classes can be used as is without * subclassing, so the documentation offers an uncluttered view of their public * usage.
* *Introduction to Go [This section updated recently!]
* *The Go ActionScript Animation Platform ("GOASAP") is a lightweight, portable * set of generic base classes for buliding AS3 animation tools. It provides structure * and core functionality, but does not define the specifics of animation-handling * classes like tweens.
* *GoASAP could be broken up into the following general layers: *
*
* *- Compatibility: In general, this layer can be used in about any animation * system. GoEngine, GoEvent, PlayStates and IPlayable.
*- Items: Base classes for utilities and animation items: PlayableBase, GoItem, * LinearGo and PhysicsGo.
*- Utilities: You can write utility classes to manage items. Go ships with a * few common ones: a parallel item class called PlayableGroup, and several Sequence classes.
*- Automation/Management: GoEngine provides a simple, centralized and fully extensible * way for you to automate any time-based process and manage multiple items at once.
*GoASAP provides an intentionally loose standard, in that it does not intend to limit * the possibilities of what can be built with it. Its primary benefits are compatibility and * synchronicity between animation systems, absolute extensibility into any time-based process, * and a much faster and easier way to build your own animation tools from scratch.
* *Important: Store your custom Go classes in a package bearing your own classpath, not * in the core package! This will help avoid confusion with other authors' work.
* *You may modify any class in the goasap package to suit your project's needs. Your input * is valuable! Please join the mailing list and share your Go-based animation tools at the * GoPlayground repository. The GoASAP initiative is led by Moses Gunesch at * MosesSupposes.com. Please visit the * Go website for more information.
*
GoEngine [This section updated recently!]
* *GoEngine sits at the center of the Go system, and along with the IUpdatable * interface is the only required element for using GoASAP. GoEngine manages tightly * synchronized item lists, since updating items in groups enhances efficiency. An * advantage of GoASAP is that wildly different animation systems can be used together * in the same project. Their synchronous updates will remain as efficient as possible, * instead of fighting one another for processor cycles.
* *GoEngine's default pulse rate is ENTER_FRAME which yields the smoothest processing in the * Flash Player. However, it does not run on any one specific pulse. Instead, any object that is * IUpdatable may specify its own pulse rate, and items with matching pulses are automatically * grouped into update lists for efficiency. On a fine-tuning level, GoEngine uses a few other * tricks to try and provide the tightest possible visual synchronization for larger batches of * animation items. It passes the clock time at the start of each update cycle to each item in * that list, which can be used in place of realtime to counteract any offset due to processing * lag during the cycle. Additionally, items that get added during an update cycle are * queued until the next update.
* *GoASAP's management layer is made up of three interfaces that are
referenced by GoEngine:
* IManager, ILiveManager and IManageable. Managers are always optional in
GoASAP, and are only
* activated by calling GoEngine.addManager(). Managers can
automate processes
* as items are added and removed, such as the included OverlapMonitor
class which prevents
* property conflicts between items, or they can automate "live" processes
that occur on each
* pulse. No live managers are included but an example might be a class
that re-renders a 3D
* viewport after all 3D tweens have been processed. This can of course be
done without a custom
* manager, but by using GoASAP you gain a unique ability to very cleanly
and simply tie any
* custom routines in your project right into your animation processing,
in perfect sync and
* with maximum efficiency.
{In the game of Go, the wooden playing board, or Goban, features a grid * on which black & white go-ishi stones are laid at its intersections.}
* * @see org.goasap.items.LinearGo LinearGo * @see org.goasap.interfaces.IManager IManager * @author Moses Gunesch */ public class GoEngine { // -== Constants ==- public static const INFO:String = "GoASAP 0.5.2 (c) Moses Gunesch, MIT Licensed."; // -== Settable Class Defaults ==- /** * A pulseInterval that runs on the player's natural framerate, * which is often most efficient. */ public static const ENTER_FRAME : int = -1; // -== Protected Properties ==- // Note: Various formats for item data have been experimented with including breaking the item lists out into // a GoEngineList class, which was nicer-looking but did not perform well. Since GoEngine doesn't normally // require active work, this less-pretty but efficient flat-data format was opted for. A minor weakness of this // format is its use of a Dictionary, which means update calls are not ordered like they would be with an Array. // The Dictionary stores items' pulseInterval values, which is safer than relying on items to not change them. // Tests also show that Dictionary performs faster than Array for accessing and deleting items. private static var managerTable : Object = new Object(); // registration list of IManager instances private static var managers : Array = new Array(); // ordered registration list of IManager instances private static var liveManagers : uint = 0; private static var timers : Dictionary = new Dictionary(false); // key: pulseInterval, value: Timer for that pulse private static var items : Dictionary = new Dictionary(false); // key: IUpdatable item, value: pulseInterval at add. private static var itemCounts : Dictionary = new Dictionary(false); // key: pulseInterval, value: item count for that pulse private static var pulseSprite : Sprite; // used for ENTER_FRAME pulse private static var paused : Boolean = false; // These additional lists enables caching of items that are added during the update cycle for the same pulse. // This prevents groups & sequences from going out of sync by ensuring that each cycle completes before new items are added. private static var lockedPulses : Dictionary = new Dictionary(false); // key: pulseInterval, value: true private static var delayedPulses : Dictionary = new Dictionary(false); // key: pulseInterval, value: true private static var addQueue : Dictionary = new Dictionary(false); // key: IUpdatable item, value: true // -== Public Class Methods ==- /** * @param className A string naming the manager class, such as "OverlapMonitor". * @return The manager instance, if registered. * @see #addManager() * @see #removeManager() */ public static function getManager(className:String) : IManager { return managerTable[ className ]; } /** * Enables the extending of this class' functionality with a tight * coupling to an IManager. * *Tight coupling is crucial in such a time-sensitive context; * standard events are too asynchronous. All items that implement * IManageable are reported to registered managers as they add and * remove themselves from GoEngine.
* *Managers normally act as singletons within the Go system (which * you are welcome to modify). This method throws a DuplicateManagerError * if an instance of the same manager class is already registered. Use a * try/catch block when calling this method if your program might duplicate * managers, or use getManager() to check for prior registration.
* * @param instance An instance of a manager you wish to add. * @see #getManager() * @see #removeManager() */ public static function addManager( instance:IManager ):void { var className:String = getQualifiedClassName(instance); className = className.slice(className.lastIndexOf("::")+2); if (managerTable[ className ]) { throw new DuplicateManagerError( className ); return; } managerTable[ className ] = instance; managers.push(instance); if (instance is ILiveManager) liveManagers++; } /** * Unregisters any manager set inaddManager.
*
* @param className A string naming the manager class, such
as "OverlapMonitor".
* @see #getManager()
* @see #addManager()
*/
public static function removeManager( className:String ):void
{
managers.splice(managers.indexOf(managerTable[ className ]), 1);
if (managerTable[ className ] is ILiveManager)
liveManagers--;
delete managerTable[ className ]; // leave last
}
/**
* Test whether an item is currently stored and being updated by the
engine.
*
* @param item Any object implementing IUpdatable
* @return Whether the IUpdatable is in the engine
*/
public static function hasItem( item:IUpdatable ):Boolean
{
return (items[ item ]!=null);
}
/**
* Adds an IUpdatable instance to an update-queue corresponding to
* the item's pulseInterval property.
*
* @param item Any object implementing IUpdatable that wishes
* to receive update calls on a pulse.
*
* @return Returns false only if this item was already in the
* engine under the same pulse. (If an existing item is added
* but the pulseInterval has changed it will be removed,
* re-added, and true will be returned.)
*
* @see #removeItem()
*/
public static function addItem( item:IUpdatable ):Boolean
{
// Group items by pulse for efficient update cycles.
var interval:int = item.pulseInterval;
if (items[ item ]) {
if (items[ item ] == item.pulseInterval)
return false;
else
removeItem(item);
}
if (lockedPulses[ interval ]==true) { // this prevents items from being
added during an update loop in progress.
delayedPulses[ interval ] = true; // flags update to clear the queue
when the in-progress loop completes.
addQueue[ item ] = true; // for tightest syncing of item groups, read
the documentation under GoItem.update().
}
items[ item ] = interval; // Tether item to original pulseint. Used in
removeItem & setPaused(false).
if (!timers[ interval ]) {
addPulse( interval );
itemCounts[ interval ] = 1;
}
else {
itemCounts[ interval ] ++;
}
// Report IManageable instances to registered managers
if (item is IManageable) {
for each (var manager:IManager in managers)
manager.reserve( item as IManageable );
}
return true;
}
/**
* Removes an item from the queue and removes its pulse timer if
* the queue is depleted.
*
* @param item Any IUpdatable previously added that wishes
* to stop receiving update calls.
*
* @return Returns false if the item was not in the engine.
*
* @see #addItem()
*/
public static function removeItem( item:IUpdatable ):Boolean
{
if (items[ item ]==null)
return false;
var interval: int = items[ item ];
if ( -- itemCounts[ interval ] == 0 ) {
removePulse( interval );
delete itemCounts[ interval ];
}
delete items[ item ];
delete addQueue[ item ]; // * see note following update
// Report IManageable item removal to registered managers.
if (item is IManageable) {
for each (var manager:IManager in managers)
manager.release( item as IManageable );
}
return true;
}
/**
* Removes all items and resets the engine,
* or removes just items running on a specific pulse.
*
* @param pulseInterval Optionally filter by a specific pulse
* such as ENTER_FRAME or a number of milliseconds.
* @return The number of items successfully removed.
* @see #removeItem()
*/
public static function clear(pulseInterval:Number = NaN) : uint
{
var all:Boolean = (isNaN(pulseInterval));
var n:Number = 0;
for (var item:Object in items) {
if (all || items[ item ]==pulseInterval)
if (removeItem(item as IUpdatable)==true)
n++;
}
return n;
}
/**
* Retrieves number of active items in the engine
* or active items running on a specific pulse.
*
* @param pulseInterval Optionally filter by a specific pulseInterval
* such as ENTER_FRAME or a number of milliseconds.
*
* @return Number of active items in the Engine.
*/
public static function getCount(pulseInterval:Number = NaN) : uint
{
if (!isNaN(pulseInterval))
return (itemCounts[pulseInterval]);
var n:Number = 0;
for each (var count: int in itemCounts)
n += count;
return n;
}
/**
* @return The paused state of engine.
* @see #setPaused()
*/
public static function getPaused() : Boolean {
return paused;
}
/**
* Pauses or resumes all animation globally by suspending processing,
* and calls pause() or resume() on each item with those methods.
*
* The return value only reflects how many items had pause() or resume() * called on them, but the GoEngine.getPaused() state will change if any * pulses are suspended or resumed.
* * @param pause Pass false to resume if currently paused. * @param pulseInterval Optionally filter by a specific pulse * such as ENTER_FRAME or a number of milliseconds. * @return The number of items on which a pause() or resume() * method was called (0 doesn't necessarily reflect * whether the GoEngine.getPaused() state changed, it * may simply indicate that no items had that method). * @see #resume() */ public static function setPaused(pause:Boolean=true, pulseInterval:Number = NaN) : uint { if (paused==pause) return 0; var n:Number = 0; var pulseChanged:Boolean = false; var all:Boolean = (isNaN(pulseInterval)); var method:String = (pause ? "pause" : "resume"); for (var item:Object in items) { var pulse:int = (items[item] as int); if (all || pulse==pulseInterval) { pulseChanged = (pulseChanged || (pause ? removePulse(pulse) : addPulse(pulse))); // call pause or resume on the item if it has such a method. if (item.hasOwnProperty(method)) { if (item[method] is Function) { item[method].apply(item); n++; } } } } if (pulseChanged) paused = pause; return n; } // -== Private Class Methods ==- /** * Executes the update queue corresponding to the dispatcher's interval. * * @param event TimerEvent or Sprite ENTER_FRAME Event */ private static function update(event:Event) : void { var currentTime:Number = getTimer(); var pulse:int = (event is TimerEvent ? ( event.target as Timer ).delay : ENTER_FRAME); lockedPulses[ pulse ] = true; var doLiveUpdate:Boolean = (liveManagers > 0); var updated:Array; if (doLiveUpdate) updated = []; // syncs the live manager list to items actually updated for (var item:* in items) { if (items[ item ]==pulse && !addQueue[ item ]) { (item as IUpdatable).update(currentTime); if (doLiveUpdate) updated.push(item); } } lockedPulses[ pulse ] = false; if (delayedPulses[ pulse ]) { for (item in addQueue) delete addQueue[ item ]; delete delayedPulses[ pulse ]; } // updateAfterEvent() should not be needed as long as items follow tight-syncing instructions in GoItem.update() documentation. // if (pulse!=ENTER_FRAME) (event as TimerEvent).updateAfterEvent(); if (doLiveUpdate) for each (var manager:Object in managers) if (manager is ILiveManager) (manager as ILiveManager).onUpdate(pulse, updated, currentTime); // * see note } // * note: In one rare case that has not been reported yet but is theoretically possible, the 'updated' list // passed could contain already-released items. This could only happen if the item is removed & released // just after the main update cycle but before the the doLiveUpdate() routine runs. If you encounter this issue // please report it to the GoASAP mailing list, it's too involved to bother with before it's a problem. /** * Creates new timers when a previously unused interval is specified, * and tracks the number of items associated with that interval. * * @param pulse The pulseInterval requested * @return Whether a pulse was added */ private static function addPulse(pulse : int) : Boolean { if (pulse==ENTER_FRAME) { if (!pulseSprite) { timers[ENTER_FRAME] = pulseSprite = new Sprite(); pulseSprite.addEventListener(Event.ENTER_FRAME, update); } return true; } var t:Timer = timers[ pulse ] as Timer; if (!t) { t = timers[ pulse ] = new Timer(pulse); (timers[ pulse ] as Timer).addEventListener(TimerEvent.TIMER, update); t.start(); return true; } return false; } /** * Tracks whether a removed item was the last one using a timer * and if so, removes that timer. * * @param pulse The pulseInterval corresponding to an item being removed. * @return Whether a pulse was removed */ private static function removePulse(pulse : int) : Boolean { if (pulse==ENTER_FRAME) { if (pulseSprite) { pulseSprite.removeEventListener(Event.ENTER_FRAME, update); delete timers[ ENTER_FRAME ]; pulseSprite = null; return true; } } var t:Timer = timers[ pulse ] as Timer; if (t) { t.stop(); t.removeEventListener(TimerEvent.TIMER, update); delete timers[ pulse ]; return true; } return false; } } } \ No newline at end of file Modified: branches/goasap0.5.2/src_go/org/goasap/items/LinearGo.as ============================================================================== --- branches/goasap0.5.2/src_go/org/goasap/items/LinearGo.as (original) +++ branches/goasap0.5.2/src_go/org/goasap/items/LinearGo.as Sun Mar 15 17:46:33 2009 @@ -1 +1 @@ -?/** * Copyright (c) 2007 Moses Gunesch * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.goasap.items { import flash.utils.getTimer; import org.goasap.GoEngine; import org.goasap.PlayStates; import org.goasap.errors.EasingFormatError; import org.goasap.events.GoEvent; import org.goasap.interfaces.IPlayable; import org.goasap.managers.LinearGoRepeater; /** * Dispatched during an animation's first update after the delay * has completed, if one was set. Any number of callbacks may also be * associated with this event usingaddCallback.
* @eventType org.goasap.events.START
*/
[Event(name="START", type="org.goasap.events.GoEvent")]
/**
* Dispatched on the animation's update pulse. Any number of callbacks
* may also be associated with this event using addCallback.
* @eventType org.goasap.events.UPDATE
*/
[Event(name="UPDATE", type="org.goasap.events.GoEvent")]
/**
* Dispatched when pause() is called successfully. Any number of callbacks
* may also be associated with this event using addCallback.
* @eventType org.goasap.events.PAUSE
*/
[Event(name="PAUSE", type="org.goasap.events.GoEvent")]
/**
* Dispatched when resume() is called successfully. Any number of callbacks
* may also be associated with this event using addCallback.
* @eventType org.goasap.events.RESUME
*/
[Event(name="RESUME", type="org.goasap.events.GoEvent")]
/**
* Dispatched at the end of each cycle if the tween has more than one.
* Any number of callbacks may also be associated with this event using
* addCallback.
* @eventType org.goasap.events.CYCLE
*/
[Event(name="CYCLE", type="org.goasap.events.GoEvent")]
/**
* Dispatched if an animation is manually stopped. Any number of callbacks
* may also be associated with this event using addCallback.
* @eventType org.goasap.events.STOP
*/
[Event(name="STOP", type="org.goasap.events.GoEvent")]
/**
* Dispatched on an animation's final update, just after the last update
event.
* Any number of callbacks may also be associated with this event using
* addCallback.
* @eventType org.goasap.events.COMPLETE
*/
[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]
/**
* LinearGo extends the base class GoItem to define a playable A-to-B
animation.
*
* LinearGo: A very simple tween
* *A LinearGo instance is a playable object that animates a single
number. It dispatches events
* and callbacks associated with the animation's start, update and
completion. Instances can be used
* directly, or easily subclassed to build custom tweening APIs. LinearGo
extends GoItem, which
* provides basic settings shared by physics and tween items. These
include a state property,
* pulseInterval, and the two common animation options
useRounding and
* useRelative.
The tween can be customized using the instance properties
duration, easing
* and delay. The number crunched by a LinearGo is readable
in its position
* property. This number always starts at 0 and completes at 1, regardless
of the tween's duration
* or easing (those parameters are factored in to produce accurate
fractional in-between values).
* As the tween runs, you can use position as a multiplier to
animate virtually anything:
* motion, alpha, a sound level, the values in a ColorTransform,
BitmapFilter, a 3D scene, and so on.
* Note that at times position may be less than 0 or greater than 1
depending on the easing function.
The START event occurs just before the first update (after the
delay). UPDATE is fired once on
* every update pulse, and COMPLETE just after the final update.
The STOP event is fired by LinearGo
* only if a tween is stopped before it completes. Additional events are
fired on PAUSE, RESUME and at
* the end of each CYCLE if the tween plays more than one cycle. Besides
standard events, you can store
* callback functions (method-closures) using addCallback.
Any number of callbacks can be
* associated with each GoEvent type. This alternative to the standard
event model was included in
* LinearGo since it's a common feature of many modern tweening APIs, and
very slightly more efficient
* than standard events.
LinearGo can play multiple back-and-forth tween cycles or repeat
forward-play any number of times.
* This functionality is handled by the LinearGo's repeater
instance, which has settings for
* alternate easing on reverse-cycles, infinite cycling, plus
currentCycle and done
* state properties.
Subclassing to create custom tweens
* *Important: Store your custom tween classes in a package bearing your own classpath, not in the core * package! This will help avoid confusion with other authors' work.
* *It's possible to build virtually any tweening API over LinearGo because all of the specifics are left * up to you: target objects, tweenable properties, tween values ? and importantly, the datatypes of all of these.
* *A basic subclass can be created in three steps: Gathering target &
property information, subclassing the
* start method to set up the tween, and finally subclassing
the onUpdate method
* to affect the tween. The first step, gathering tween target and
property information, can be done by writing
* getter/setter properties, customizing the constructor, or both.
Consider various options such as allowing for
* single vs. multiple target objects, open vs. specific tween properties,
and so on. The next step, subclassing
* start, involves figuring the tween's amount of change and
implementing a standard Go convention,
* useRelative. This option should enable the user to declare
tween values as relative to existing
* values instead of as fixed absolutes. In the final step, you subclass
onUpdate to apply the tween,
* using the _position calculated by this base class:
target[ propName ] = super.correctValue(start + change * _position);* *
The helper method correctValue is provided in the
superclass GoItem, to clean up NaN values
* and apply rounding when useRounding is activated. That's
it ??events and callbacks are
* dispatched by LinearGo, so subclasses can remain simple.
An optional fourth step will make your custom tween compatible with Go managers. To do this, implement * the IManageable interface. (OverlapMonitor prevents different tween instances from handling the same * property at once; you can build other managers as well.)
* * {In the game of Go a black or white stone is called a go-ishi.} * * @author Moses Gunesch */ public class LinearGo extends GoItem implements IPlayable { // -== Settable Class Defaults ==- /** * Class default for the instance property delay. * @default 0 * @see #delay */ public static var defaultDelay : Number = 0; /** * Class default for the instance property duration. * @default 1 * @see #duration */ public static var defaultDuration : Number = 1; /** * Class default for the instance property easing. * Note that this property is left null until the first LinearGo * is instantiated, at which time it is set to Quintic.easeOut. * @default fl.motion.easing.Quintic.easeOut * @see #easing */ public static var defaultEasing:Function; /** * Normal default easing, this is Quintic.easeOut. * (The two default easings in this class are included because there's * currently no single easing classpath shared between Flash & Flex.) */ public static function easeOut(t:Number, b:Number, c:Number, d:Number) : Number { return c * ((t = t / d - 1) * t * t * t * t + 1) + b; }; // -== Class Methods ==- /** * An alternative default easing with no acceleration. * (The two default easings in this class are included because there's * currently no single easing classpath shared between Flash & Flex.) */ public static function easeNone(t:Number, b:Number, c:Number, d:Number) : Number { return c * t / d + b; }; /** * A quick one-time setup command that lets you turn on useFrames mode * as a default for all new tweens and adjust some related settings. * (Note that useFrames mode is normally only used for specialty situations.) * * @param defaultToFramesMode Sets an internal default so all new LinearGo instances * will be set to use framecounts for their delay and duration. * Also sets GoItem.defaultPulseInterval to enterframe which is * most normal for frame-based updates. * @param useZeroBasedFrameIndex Normally currentFrame reads 1 on first update, like the Flash * timeline starts at Frame 1. Set this option to use a zero-based * index on all tweens instead. * @see #useFrames * @see #currentFrame */ public static function setupUseFramesMode( defaultToFramesMode: Boolean = true, useZeroBasedFrameIndex: Boolean=false):void { GoItem.defaultPulseInterval = GoEngine.ENTER_FRAME; _useFramesMode = defaultToFramesMode; if (useZeroBasedFrameIndex) { _framesBase = 0; } } // -== Pulic Properties ==- /** * Number of seconds after start() call that the LinearGo begins processing. *If not set manually, the class default defaultDelay is adopted.
* @see #defaultDelay */ public function get delay():Number { return _delay; } public function set delay(seconds:Number):void { if (_state==PlayStates.STOPPED && seconds >= 0) { _delay = seconds; } } /** * Number of seconds the LinearGo takes to process. *If not set manually, the class default defaultDuration is adopted.
* @see #defaultDuration */ public function get duration():Number { return _duration; } public function set duration(seconds:Number):void { if (_state==PlayStates.STOPPED && seconds >= 0) { _duration = seconds; } } /** * Any standard easing-equation function such as the ones found in * the Flash package fl.motion.easing or the flex package mx.effects.easing. * *If not set manually, the class default defaultEasing is adopted. An
error
* is thrown if the function does not follow the typical format. For
easings
* that accept more than four parameters use
extraEasingParams.
*
You may pass a LinearGoRepeater instance to the constructor's
* repeater parameter to set all options at instantiation. The
* repeater's cycles property can be set to an integer, or
* to Repeater.INFINITE or 0 to repeat indefinitely, and checked using
* linearGo.repeater.currentCycle. LinearGoRepeater's
* reverseOnCycle flag is true by default, which
* causes animation to cycle back and forth. In that mode you can
* also specify a separate easing function (plus extraEasingParams)
* to use for the reverse animation cycle. For example, an easeOut
* easing with an easeIn easingOnCycle will produce a more
* natural-looking result. If reverseOnCycle is disabled,
* the animation will repeat its play forward each time.
(The repeater property replaces the cycles, easeOnCycle and * currentCycle parameters in earlier releases of LinearGo).
* * @see org.goasap.managers.LinearGoRepeater LinearGoRepeater */ public function get repeater(): LinearGoRepeater { return _repeater; } /** * When useFrames mode is activated, duration and delay are treated * as update-counts instead of time values. * *(This mode is normally only used for specialty situations.)
* *Using this feature with a pulseInterval of GoEngine.ENTER_FRAME * will result in a frame-based update that mimics the behavior of the * flash timeline. As with the timeline, frame-based tween durations can * vary based on the host computer's processor load and other factors.
* *The setupUseFramesMode() class method is a much easier
* way to use frames in your project, instead of setting this property
* on every tween individually.
Use this number as a multiplier to apply values to targets * across time.
* *
Here's an example of what an overridden update method might contain:
** super.update(currentTime); * target[ propName ] = super.correctValue(startValue + change*_position); ** @see #timePosition */ public function get position():Number { return _position; } /** * For time-based tweens, returns a time value which is negative during delay * then spans the tween duration in positive values, ignoring repeat cycles. * *
In useFrames mode, this getter differs from
currentFrame
* significantly. Instead of constantly increasing through all cycles as
if
* tweens were back-to-back in a timeline layer, this method acts more
like
* a single tween placed at frame 1, with a timeline playhead that scans
back
* and forth or loops during cycles. So for a 10-frame tween with a
5-frame
* delay and 2 repeater cycles with reverseOnCycle set to true, this
method
* will return values starting at -5, start the animation at 1, play to 10
* then step backward to 1 again.
This update-count property does not necessarily correspond * to the actual player framerate, just the instance's pulseInterval.
* *This property is set up to mirror the flash timeline. Imagine a
timeline
* layer with a delay being a set of blank frames followed by the tween,
* followed by subsequent cycles as additional tweens: this is the way
* the currentFrame property works. Its first value is 1 by
* default, which can be changed to 0 in
setupUseFramesMode().
* This differs significantly from timePosition, which places
* the start of a single instance of the tween at frame 1 and steps its
* values from negative during delay then cycling through the single
tween.
CONVENTION ALERT: If useRelative is true, calculate
tween values
* relative to the target object's existing value as in the example
below.
Most typically you should also store the tween's start and change
values
* for later use in onUpdate.
* protected var _target : DisplayObject;
* protected var _width : Number;
* protected var _changeWidth : Number;
*
* public function start():Boolean
* {
* if (!_target || !_width || isNaN(_width))
* return false;
*
* _startWidth = _target.width;
*
* if (useRelative) {
* _changeWidth = _width;
* } else {
* _changeWidth = (_width - _startWidth);
* }
*
* return (super.start());
* }
*
*
* @return Successful addition of the item to GoEngine
*
* @see GoItem#useRelative
* @see #onUpdate()
*/
public function start() : Boolean {
stop(); // does nothing if already stopped.
if (GoEngine.addItem(this)==false)
return false;
reset();
_state = (_delay > 0 ? PlayStates.PLAYING_DELAY : PlayStates.PLAYING);
// has to be set here since delay is not included in PlayableBase.
// note: start event is dispatched on the first update cycle for tighter
cross-item syncing.
return true;
}
/**
* Ends play for this LinearGo instance and dispatches a GoEvent.STOP
* event if the tween is incomplete. This method does not typically
* require subclassing.
*
* @return Successful removal of the item from GoEngine
*/
public function stop() : Boolean {
if (_state==PlayStates.STOPPED || GoEngine.removeItem(this)==false)
return false;
_state = PlayStates.STOPPED;
var completed:Boolean = (_easeParams!=null &&
_position==_easeParams[1]+_change);
reset();
if (!completed) // otherwise a COMPLETE event was dispatched.
dispatch( GoEvent.STOP );
return true;
}
/**
* Pauses play (including delay) for this LinearGo instance.
* This method does not typically require subclassing.
*
* @return Success
* @see #resume()
* @see org.goasap.GoEngine#setPaused GoEngine.setPaused()
*/
public function pause() : Boolean {
if (_state==PlayStates.STOPPED || _state==PlayStates.PAUSED)
return false;
_state = PlayStates.PAUSED;
_pauseTime = (_useFrames ? _currentFrame : getTimer()); // This causes
update() to skip processing.
dispatch(GoEvent.PAUSE);
return true;
}
/**
* Resumes previously paused play, including delay.
* This method does not typically require subclassing.
*
* @return Success
* @see #pause()
* @see org.goasap.GoEngine#setPaused GoEngine.setPaused()
*/
public function resume() : Boolean {
if (_state != PlayStates.PAUSED)
return false;
var currentTime:Number = (_useFrames ? _currentFrame : getTimer());
setup(currentTime - (_pauseTime - _startTime));
_pauseTime = NaN;
_state = (_startTime > currentTime ? PlayStates.PLAYING_DELAY :
PlayStates.PLAYING);
dispatch(GoEvent.RESUME);
return true;
}
/**
* Skips to a point in the tween's duration and plays, from any state.
* This method does not typically require subclassing.
*
* If GoItem.timeMultiplier is set to a custom value, you should still pass a * seconds value based on the tween's real duration setting.
* * @param time Seconds or frames to jump to across all cycles, where 0 (or 1 in useFramesMode) * represents tween start, numbers greater than duration represent higher repeat cycles, * and negative numbers represent a new delay to play before tween start. * @return Success * @see #timePosition */ public function skipTo(time : Number) : Boolean { if (_state==PlayStates.STOPPED) { if (start()==false) return false; } if (isNaN(time)) { time = 0; } var mult:Number = Math.max(0, timeMultiplier) * (_useFrames ? 1 : 1000); var startTime:Number; var currentTime:Number; if (time < _framesBase) { // Negative value: rewind and add a new delay. _repeater.reset(); if (_position>0) { skipTo(_framesBase); } // skips to start so new pause occurs in starting position } else { time = _repeater.skipTo(_duration, time-_framesBase); // sets cycles and returns new position } if (_useFrames) { startTime = _framesBase; currentTime = _currentFrame = Math.round(time*mult); } else { currentTime = getTimer(); startTime = (currentTime - (time * mult)); // skipTo operation is performed by altering the tween's start & end times. } setup(startTime); _state = (_startTime > currentTime ? PlayStates.PLAYING_DELAY : PlayStates.PLAYING); update(currentTime); // sets _position return true; } /** * An alternative to subscribing to events is to store callbacks. You can * associate any number of callbacks with the primary GoEvent types START, * UPDATE, COMPLETE, and STOP (only fired if the tween is stopped before it * completes). * ** Note that there is little difference between using callbacks and events. * Both are common techniques used in many various modern tweening APIs. Callbacks * are slightly faster, but this won't normally be noticeable unless thousands of * tweens are being run at once. *
* * @param closure A reference to a callback function * @param type Any GoEvent type constant, the default is COMPLETE. * @see #removeCallback * @see org.goasap.events.GoEvent GoEvent */ public function addCallback(closure : Function, type : String=GoEvent.COMPLETE):void { if (!_callbacks[ type ]) _callbacks[ type ] = new Array(); var a:Array = (_callbacks[ type ] as Array); if (a.indexOf(closure)==-1) a.push(closure); } /** * Removes a method closure previously stored using addCallback. * * @param closure A reference to a function * @param type A GoEvent constant, default is COMPLETE. * @see #addCallback * @see org.goasap.events.GoEvent GoEvent */ public function removeCallback(closure : Function, type : String=GoEvent.COMPLETE):void { var a:Array = (_callbacks[ type ] as Array); if (a) while (a.indexOf(closure)>-1) a.splice(a.indexOf(closure), 1); } /** * Performs tween calculations on GoEngine pulse. * *Subclass onUpdate instead of this method.
*
* @param currentTime Clock time for the current block of updates.
* @see #onUpdate()
*/
override public function update(currentTime:Number) : void
{
if (_state==PlayStates.PAUSED)
return;
_currentFrame ++;
if (_useFrames)
currentTime = _currentFrame;
if (isNaN(_startTime)) // setup() must be called once prior to tween's
1st update.
setup(currentTime); // This is done here, not in start, for tighter
syncing of items.
if (_startTime > currentTime)
return; // still PlayStates.PLAYING_DELAY
// (1.) Set _position and determine primary update type.
var type:String = GoEvent.UPDATE;
if (currentTime < _endTime) { // start, update...
if (!_started)
type = GoEvent.START;
var time:Number = _easeParams[0] = (currentTime - _startTime);
_position = _currentEasing.apply(null, _easeParams); // update position
using easing function.
if (_position==2.220446049250313e-16) { _position = 0; }// Corrects for
a computer rounding error in Back.easeOut() at position 0.
}
else { // complete, cycle...
_position = _easeParams[1] + _change; // set absolute 1 or 0 position
at end of cycle
type = (_repeater.hasNext() ? GoEvent.CYCLE : GoEvent.COMPLETE);
}
// (2.) Run onUpdate() passing the primary update type, then
// (3.) dispatch up to three events in correct order.
onUpdate(type);
if (!_started) {
_state = PlayStates.PLAYING;
_started = true;
dispatch(GoEvent.START);
}
dispatch(GoEvent.UPDATE);
if (type==GoEvent.COMPLETE) {
stop();
dispatch(GoEvent.COMPLETE);
}
else if (type==GoEvent.CYCLE) {
_repeater.next();
dispatch(GoEvent.CYCLE);
_startTime = NaN; // causes setup() to be called again on next update
to prep next cycle.
}
}
// -== Protected Methods ==-
/**
* Subclass this method (instead of the update method) for simplicity.
*
*
Use this method to manipulate targets based on the current _position * setting, which is a 0-1 multiplier precalculated to the tween's position * based on its easing style and the current time in the tween.
* *CONVENTION ALERT: To implement the Go convention
useRounding,
* always call GoItem's correctValue() method on each
calculated
* tween value before you apply it to a target. This corrects NaN to 0 and
* rounds the value if useRounding is true.
* override protected function onUpdate(type:String):void
* {
* target[ propName ] = super.correctValue(startValue +
change*_position);
* }
*
*
* @param type A constant from the class GoEvent: START, UPDATE, CYCLE,
or COMPLETE.
* @see GoItem#correctValue()
* @see GoItem#useRounding
* @see #update()
*/
protected function onUpdate(type : String) : void
{
// Subclass this method and start to implement your tween class.
}
/**
* @private
* Internal setup routine used by start() and other methods.
*
* @param time Tween start time based on getTimer
*/
protected function setup(startTime : Number) : void
{
_startTime = startTime;
var mult:Number = Math.max(0, timeMultiplier) * (_useFrames ? 1 : 1000);
_tweenDuration = (_useFrames ? Math.round(_duration * mult)-1 :
(_duration * mult));
_endTime = _startTime + _tweenDuration;
if (!_started) {
var d:Number = (_useFrames ? Math.round(_delay * mult) : (_delay *
mult));
_startTime += d;
_endTime += d;
}
// Set up a tween cycle: _currentEasing, _change, _position, and
_easeParams.
// Be sure _repeater is updated before this call so the next cycle gets
set up.
var useCycleEase:Boolean = _repeater.currentCycleHasEasing;
_currentEasing = (useCycleEase ? _repeater.easingOnCycle : _easing);
var extras:Array = (useCycleEase ? _repeater.extraEasingParams :
_extraEaseParams);
_change = _repeater.direction;
_position = (_repeater.direction==-1 ? 1 : 0);
_easeParams = new Array(0, _position, _change, _tweenDuration); //
stored to reduce runtime object-creation
if (extras) _easeParams = _easeParams.concat(extras);
}
/**
* @private
* Internal, dispatches events and executes callbacks of any pre-verified
type.
*
* @param type Verified in addCallback, not in this method.
* @see #org.goasap.events.GoEvent GoEvent
*/
protected function dispatch(type:String):void
{
var a:Array = (_callbacks[ type ] as Array);
if (a)
for each (var callback:Function in a)
callback();
if (hasEventListener(type))
dispatchEvent(new GoEvent( type ));
}
/**
* @private
*/
protected function reset() : void {
_position = 0;
_change = 1;
_repeater.reset();
_currentFrame = _framesBase-1;
_currentEasing = _easing;
_easeParams = null;
_started = false;
_pauseTime = NaN;
_startTime = NaN;
}
}
}
\ No newline at end of file
+?/**
* Copyright (c) 2007 Moses Gunesch
*
* Permission is hereby granted, free of charge, to any person obtaining a
copy
* of this software and associated documentation files (the "Software"), to
deal
* in the Software without restriction, including without limitation the
rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.goasap.items {
import flash.utils.getTimer;
import org.goasap.GoEngine;
import org.goasap.PlayStates;
import org.goasap.errors.EasingFormatError;
import org.goasap.events.GoEvent;
import org.goasap.interfaces.IPlayable;
import org.goasap.managers.LinearGoRepeater;
/**
* Dispatched during an animation's first update after the delay
* has completed, if one was set. Any number of callbacks may also be
* associated with this event using addCallback.
* @eventType org.goasap.events.START
*/
[Event(name="START", type="org.goasap.events.GoEvent")]
/**
* Dispatched on the animation's update pulse. Any number of callbacks
* may also be associated with this event using addCallback.
* @eventType org.goasap.events.UPDATE
*/
[Event(name="UPDATE", type="org.goasap.events.GoEvent")]
/**
* Dispatched when pause() is called successfully. Any number of callbacks
* may also be associated with this event using addCallback.
* @eventType org.goasap.events.PAUSE
*/
[Event(name="PAUSE", type="org.goasap.events.GoEvent")]
/**
* Dispatched when resume() is called successfully. Any number of callbacks
* may also be associated with this event using addCallback.
* @eventType org.goasap.events.RESUME
*/
[Event(name="RESUME", type="org.goasap.events.GoEvent")]
/**
* Dispatched at the end of each cycle if the tween has more than one.
* Any number of callbacks may also be associated with this event using
* addCallback.
* @eventType org.goasap.events.CYCLE
*/
[Event(name="CYCLE", type="org.goasap.events.GoEvent")]
/**
* Dispatched if an animation is manually stopped. Any number of callbacks
* may also be associated with this event using addCallback.
* @eventType org.goasap.events.STOP
*/
[Event(name="STOP", type="org.goasap.events.GoEvent")]
/**
* Dispatched on an animation's final update, just after the last update
event.
* Any number of callbacks may also be associated with this event using
* addCallback.
* @eventType org.goasap.events.COMPLETE
*/
[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]
/**
* LinearGo extends the base class GoItem to define a playable A-to-B
animation.
*
* LinearGo: A very simple tween
* *A LinearGo instance is a playable object that animates a single
number. It dispatches events
* and callbacks associated with the animation's start, update and
completion. Instances can be used
* directly, or easily subclassed to build custom tweening APIs. LinearGo
extends GoItem, which
* provides basic settings shared by physics and tween items. These
include a state property,
* pulseInterval, and the two common animation options
useRounding and
* useRelative.
The tween can be customized using the instance properties
duration, easing
* and delay. The number crunched by a LinearGo is readable
in its position
* property. This number always starts at 0 and completes at 1, regardless
of the tween's duration
* or easing (those parameters are factored in to produce accurate
fractional in-between values).
* As the tween runs, you can use position as a multiplier to
animate virtually anything:
* motion, alpha, a sound level, the values in a ColorTransform,
BitmapFilter, a 3D scene, and so on.
* Note that at times position may be less than 0 or greater than 1
depending on the easing function.
The START event occurs just before the first update (after the
delay). UPDATE is fired once on
* every update pulse, and COMPLETE just after the final update.
The STOP event is fired by LinearGo
* only if a tween is stopped before it completes. Additional events are
fired on PAUSE, RESUME and at
* the end of each CYCLE if the tween plays more than one cycle. Besides
standard events, you can store
* callback functions (method-closures) using addCallback.
Any number of callbacks can be
* associated with each GoEvent type. This alternative to the standard
event model was included in
* LinearGo since it's a common feature of many modern tweening APIs, and
very slightly more efficient
* than standard events.
LinearGo can play multiple back-and-forth tween cycles or repeat
forward-play any number of times.
* This functionality is handled by the LinearGo's repeater
instance, which has settings for
* alternate easing on reverse-cycles, infinite cycling, plus
currentCycle and done
* state properties.
Subclassing to create custom tweens
* *Important: Store your custom tween classes in a package bearing your own classpath, not in the core * package! This will help avoid confusion with other authors' work.
* *It's possible to build virtually any tweening API over LinearGo because all of the specifics are left * up to you: target objects, tweenable properties, tween values ? and importantly, the datatypes of all of these.
* *A basic subclass can be created in three steps: Gathering target &
property information, subclassing the
* start method to set up the tween, and finally subclassing
the onUpdate method
* to affect the tween. The first step, gathering tween target and
property information, can be done by writing
* getter/setter properties, customizing the constructor, or both.
Consider various options such as allowing for
* single vs. multiple target objects, open vs. specific tween properties,
and so on. The next step, subclassing
* start, involves figuring the tween's amount of change and
implementing a standard Go convention,
* useRelative. This option should enable the user to declare
tween values as relative to existing
* values instead of as fixed absolutes. In the final step, you subclass
onUpdate to apply the tween,
* using the _position calculated by this base class:
target[ propName ] = super.correctValue(start + change * _position);* *
The helper method correctValue is provided in the
superclass GoItem, to clean up NaN values
* and apply rounding when useRounding is activated. That's
it ??events and callbacks are
* dispatched by LinearGo, so subclasses can remain simple.
An optional fourth step will make your custom tween compatible with Go managers. To do this, implement * the IManageable interface. (OverlapMonitor prevents different tween instances from handling the same * property at once; you can build other managers as well.)
* * {In the game of Go a black or white stone is called a go-ishi.} * * @author Moses Gunesch */ public class LinearGo extends GoItem implements IPlayable { // -== Settable Class Defaults ==- /** * Class default for the instance property delay. * @default 0 * @see #delay */ public static var defaultDelay : Number = 0; /** * Class default for the instance property duration. * @default 1 * @see #duration */ public static var defaultDuration : Number = 1; /** * Class default for the instance property easing. * Note that this property is left null until the first LinearGo * is instantiated, at which time it is set to Quintic.easeOut. * @default fl.motion.easing.Quintic.easeOut * @see #easing */ public static var defaultEasing:Function; /** * Normal default easing, this is Quintic.easeOut. * (The two default easings in this class are included because there's * currently no single easing classpath shared between Flash & Flex.) */ public static function easeOut(t:Number, b:Number, c:Number, d:Number) : Number { return c * ((t = t / d - 1) * t * t * t * t + 1) + b; }; // -== Class Methods ==- /** * An alternative default easing with no acceleration. * (The two default easings in this class are included because there's * currently no single easing classpath shared between Flash & Flex.) */ public static function easeNone(t:Number, b:Number, c:Number, d:Number) : Number { return c * t / d + b; }; /** * A quick one-time setup command that lets you turn on useFrames mode * as a default for all new tweens and adjust some related settings. * (Note that useFrames mode is normally only used for specialty situations.) * * @param defaultToFramesMode Sets an internal default so all new LinearGo instances * will be set to use framecounts for their delay and duration. * Also sets GoItem.defaultPulseInterval to enterframe which is * most normal for frame-based updates. * @param useZeroBasedFrameIndex Normally currentFrame reads 1 on first update, like the Flash * timeline starts at Frame 1. Set this option to use a zero-based * index on all tweens instead. * @see #useFrames * @see #currentFrame */ public static function setupUseFramesMode( defaultToFramesMode: Boolean = true, useZeroBasedFrameIndex: Boolean=false):void { GoItem.defaultPulseInterval = GoEngine.ENTER_FRAME; _useFramesMode = defaultToFramesMode; if (useZeroBasedFrameIndex) { _framesBase = 0; } } // -== Pulic Properties ==- /** * Number of seconds after start() call that the LinearGo begins processing. *If not set manually, the class default defaultDelay is adopted.
* @see #defaultDelay */ public function get delay():Number { return _delay; } public function set delay(seconds:Number):void { if (_state==PlayStates.STOPPED && seconds >= 0) { _delay = seconds; } } /** * Number of seconds the LinearGo takes to process. *If not set manually, the class default defaultDuration is adopted.
* @see #defaultDuration */ public function get duration():Number { return _duration; } public function set duration(seconds:Number):void { if (_state==PlayStates.STOPPED && seconds >= 0) { _duration = seconds; } } /** * Any standard easing-equation function such as the ones found in * the Flash package fl.motion.easing or the flex package mx.effects.easing. * *If not set manually, the class default defaultEasing is adopted. An
error
* is thrown if the function does not follow the typical format. For
easings
* that accept more than four parameters use
extraEasingParams.
*
You may pass a LinearGoRepeater instance to the constructor's
* repeater parameter to set all options at instantiation. The
* repeater's cycles property can be set to an integer, or
* to Repeater.INFINITE or 0 to repeat indefinitely, and checked using
* linearGo.repeater.currentCycle. LinearGoRepeater's
* reverseOnCycle flag is true by default, which
* causes animation to cycle back and forth. In that mode you can
* also specify a separate easing function (plus extraEasingParams)
* to use for the reverse animation cycle. For example, an easeOut
* easing with an easeIn easingOnCycle will produce a more
* natural-looking result. If reverseOnCycle is disabled,
* the animation will repeat its play forward each time.
(The repeater property replaces the cycles, easeOnCycle and * currentCycle parameters in earlier releases of LinearGo).
* * @see org.goasap.managers.LinearGoRepeater LinearGoRepeater */ public function get repeater(): LinearGoRepeater { return _repeater; } /** * When useFrames mode is activated, duration and delay are treated * as update-counts instead of time values. * *(This mode is normally only used for specialty situations.)
* *Using this feature with a pulseInterval of GoEngine.ENTER_FRAME * will result in a frame-based update that mimics the behavior of the * flash timeline. As with the timeline, frame-based tween durations can * vary based on the host computer's processor load and other factors.
* *The setupUseFramesMode() class method is a much easier
* way to use frames in your project, instead of setting this property
* on every tween individually.
Use this number as a multiplier to apply values to targets * across time.
* *
Here's an example of what an overridden update method might contain:
** super.update(currentTime); * target[ propName ] = super.correctValue(startValue + change*_position); ** @see #timePosition */ public function get position():Number { return _position; } /** * For time-based tweens, returns a time value which is negative during delay * then spans the tween duration in positive values, ignoring repeat cycles. * *
In useFrames mode, this getter differs from
currentFrame
* significantly. Instead of constantly increasing through all cycles as
if
* tweens were back-to-back in a timeline layer, this method acts more
like
* a single tween placed at frame 1, with a timeline playhead that scans
back
* and forth or loops during cycles. So for a 10-frame tween with a
5-frame
* delay and 2 repeater cycles with reverseOnCycle set to true, this
method
* will return values starting at -5, start the animation at 1, play to 10
* then step backward to 1 again.
This update-count property does not necessarily correspond * to the actual player framerate, just the instance's pulseInterval.
* *This property is set up to mirror the flash timeline. Imagine a
timeline
* layer with a delay being a set of blank frames followed by the tween,
* followed by subsequent cycles as additional tweens: this is the way
* the currentFrame property works. Its first value is 1 by
* default, which can be changed to 0 in
setupUseFramesMode().
* This differs significantly from timePosition, which places
* the start of a single instance of the tween at frame 1 and steps its
* values from negative during delay then cycling through the single
tween.
CONVENTION ALERT: If useRelative is true, calculate
tween values
* relative to the target object's existing value as in the example
below.
Most typically you should also store the tween's start and change
values
* for later use in onUpdate.
* protected var _target : DisplayObject;
* protected var _width : Number;
* protected var _changeWidth : Number;
*
* public function start():Boolean
* {
* if (!_target || !_width || isNaN(_width))
* return false;
*
* _startWidth = _target.width;
*
* if (useRelative) {
* _changeWidth = _width;
* } else {
* _changeWidth = (_width - _startWidth);
* }
*
* return (super.start());
* }
*
*
* @return Successful addition of the item to GoEngine
*
* @see GoItem#useRelative
* @see #onUpdate()
*/
public function start() : Boolean {
stop(); // does nothing if already stopped.
if (GoEngine.addItem(this)==false)
return false;
reset();
_state = (_delay > 0 ? PlayStates.PLAYING_DELAY : PlayStates.PLAYING);
// has to be set here since delay is not included in PlayableBase.
// note: start event is dispatched on the first update cycle for tighter
cross-item syncing.
return true;
}
/**
* Ends play for this LinearGo instance and dispatches a GoEvent.STOP
* event if the tween is incomplete. This method does not typically
* require subclassing.
*
* @return Successful removal of the item from GoEngine
*/
public function stop() : Boolean {
if (_state==PlayStates.STOPPED || GoEngine.removeItem(this)==false)
return false;
_state = PlayStates.STOPPED;
var completed:Boolean = (_easeParams!=null &&
_position==_easeParams[1]+_change);
reset();
if (!completed) // otherwise a COMPLETE event was dispatched.
dispatch( GoEvent.STOP );
return true;
}
/**
* Pauses play (including delay) for this LinearGo instance.
* This method does not typically require subclassing.
*
* @return Success
* @see #resume()
* @see org.goasap.GoEngine#setPaused GoEngine.setPaused()
*/
public function pause() : Boolean {
if (_state==PlayStates.STOPPED || _state==PlayStates.PAUSED)
return false;
_state = PlayStates.PAUSED;
_pauseTime = (_useFrames ? _currentFrame : getTimer()); // This causes
update() to skip processing.
dispatch(GoEvent.PAUSE);
return true;
}
/**
* Resumes previously paused play, including delay.
* This method does not typically require subclassing.
*
* @return Success
* @see #pause()
* @see org.goasap.GoEngine#setPaused GoEngine.setPaused()
*/
public function resume() : Boolean {
if (_state != PlayStates.PAUSED)
return false;
var currentTime:Number = (_useFrames ? _currentFrame : getTimer());
setup(currentTime - (_pauseTime - _startTime));
_pauseTime = NaN;
_state = (_startTime > currentTime ? PlayStates.PLAYING_DELAY :
PlayStates.PLAYING);
dispatch(GoEvent.RESUME);
return true;
}
/**
* Skips to a point in the tween's duration and plays, from any state.
* This method does not typically require subclassing.
*
* If GoItem.timeMultiplier is set to a custom value, you should still pass a * seconds value based on the tween's real duration setting.
* * @param time Seconds or frames to jump to across all cycles, where 0 (or 1 in useFramesMode) * represents tween start, numbers greater than duration represent higher repeat cycles, * and negative numbers represent a new delay to play before tween start. * @return Success * @see #timePosition */ public function skipTo(time : Number) : Boolean { if (_state==PlayStates.STOPPED) { if (start()==false) return false; } if (isNaN(time)) { time = 0; } var mult:Number = Math.max(0, timeMultiplier) * (_useFrames ? 1 : 1000); var startTime:Number; var currentTime:Number; if (time < _framesBase) { // Negative value: rewind and add a new delay. _repeater.reset(); if (_position>0) { skipTo(_framesBase); } // skips to start so new pause occurs in starting position } else { time = _repeater.skipTo(_duration, time-_framesBase); // sets cycles and returns new position } if (_useFrames) { startTime = _framesBase; currentTime = _currentFrame = Math.round(time*mult); } else { currentTime = getTimer(); startTime = (currentTime - (time * mult)); // skipTo operation is performed by altering the tween's start & end times. } setup(startTime); _state = (_startTime > currentTime ? PlayStates.PLAYING_DELAY : PlayStates.PLAYING); update(currentTime); // sets _position return true; } /** * An alternative to subscribing to events is to store callbacks. You can * associate any number of callbacks with the primary GoEvent types START, * UPDATE, COMPLETE, and STOP (only fired if the tween is stopped before it * completes). * ** Note that there is little difference between using callbacks and events. * Both are common techniques used in many various modern tweening APIs. Callbacks * are slightly faster, but this won't normally be noticeable unless thousands of * tweens are being run at once. *
* * @param closure A reference to a callback function * @param type Any GoEvent type constant, the default is COMPLETE. * @see #removeCallback * @see org.goasap.events.GoEvent GoEvent */ public function addCallback(closure : Function, type : String=GoEvent.COMPLETE):void { if (!_callbacks[ type ]) _callbacks[ type ] = new Array(); var a:Array = (_callbacks[ type ] as Array); if (a.indexOf(closure)==-1) a.push(closure); } /** * Removes a method closure previously stored using addCallback. * * @param closure A reference to a function * @param type A GoEvent constant, default is COMPLETE. * @see #addCallback * @see org.goasap.events.GoEvent GoEvent */ public function removeCallback(closure : Function, type : String=GoEvent.COMPLETE):void { var a:Array = (_callbacks[ type ] as Array); if (a) while (a.indexOf(closure)>-1) a.splice(a.indexOf(closure), 1); } /** * Performs tween calculations on GoEngine pulse. * *Subclass onUpdate instead of this method.
*
* @param currentTime Clock time for the current block of updates.
* @see #onUpdate()
*/
override public function update(currentTime:Number) : void
{
if (_state==PlayStates.PAUSED)
return;
_currentFrame ++;
if (_useFrames)
currentTime = _currentFrame;
if (isNaN(_startTime)) // setup() must be called once prior to tween's
1st update.
setup(currentTime); // This is done here, not in start, for tighter
syncing of items.
if (_startTime > currentTime)
return; // still PlayStates.PLAYING_DELAY
// (1.) Set _position and determine primary update type.
var type:String = GoEvent.UPDATE;
if (currentTime < _endTime) { // start, update...
if (!_started)
type = GoEvent.START;
var time:Number = _easeParams[0] = (currentTime - _startTime);
_position = (time==0 ? 0 : _currentEasing.apply(null, _easeParams)); //
update position using easing function.
} // [note: checking for zero may reduce efficiency a
little. Back easing gives a faulty return value @ 0.]
else { // complete, cycle...
_position = _easeParams[1] + _change; // set absolute 1 or 0 position
at end of cycle
type = (_repeater.hasNext() ? GoEvent.CYCLE : GoEvent.COMPLETE);
}
// (2.) Run onUpdate() passing the primary update type, then
// (3.) dispatch up to three events in correct order.
onUpdate(type);
if (!_started) {
_state = PlayStates.PLAYING;
_started = true;
dispatch(GoEvent.START);
}
dispatch(GoEvent.UPDATE);
if (type==GoEvent.COMPLETE) {
stop();
dispatch(GoEvent.COMPLETE);
}
else if (type==GoEvent.CYCLE) {
_repeater.next();
dispatch(GoEvent.CYCLE);
_startTime = NaN; // causes setup() to be called again on next update
to prep next cycle.
}
}
// -== Protected Methods ==-
/**
* Subclass this method (instead of the update method) for simplicity.
*
*
Use this method to manipulate targets based on the current _position * setting, which is a 0-1 multiplier precalculated to the tween's position * based on its easing style and the current time in the tween.
* *CONVENTION ALERT: To implement the Go convention
useRounding,
* always call GoItem's correctValue() method on each
calculated
* tween value before you apply it to a target. This corrects NaN to 0 and
* rounds the value if useRounding is true.
* override protected function onUpdate(type:String):void
* {
* target[ propName ] = super.correctValue(startValue +
change*_position);
* }
*
*
* @param type A constant from the class GoEvent: START, UPDATE, CYCLE,
or COMPLETE.
* @see GoItem#correctValue()
* @see GoItem#useRounding
* @see #update()
*/
protected function onUpdate(type : String) : void
{
// Subclass this method and start to implement your tween class.
}
/**
* @private
* Internal setup routine used by start() and other methods.
*
* @param time Tween start time based on getTimer
*/
protected function setup(startTime : Number) : void
{
_startTime = startTime;
var mult:Number = Math.max(0, timeMultiplier) * (_useFrames ? 1 : 1000);
_tweenDuration = (_useFrames ? Math.round(_duration * mult)-1 :
(_duration * mult));
_endTime = _startTime + _tweenDuration;
if (!_started) {
var d:Number = (_useFrames ? Math.round(_delay * mult) : (_delay *
mult));
_startTime += d;
_endTime += d;
}
// Set up a tween cycle: _currentEasing, _change, _position, and
_easeParams.
// Be sure _repeater is updated before this call so the next cycle gets
set up.
var useCycleEase:Boolean = _repeater.currentCycleHasEasing;
_currentEasing = (useCycleEase ? _repeater.easingOnCycle : _easing);
var extras:Array = (useCycleEase ? _repeater.extraEasingParams :
_extraEaseParams);
_change = _repeater.direction;
_position = (_repeater.direction==-1 ? 1 : 0);
_easeParams = new Array(0, _position, _change, _tweenDuration); //
stored to reduce runtime object-creation
if (extras) _easeParams = _easeParams.concat(extras);
}
/**
* @private
* Internal, dispatches events and executes callbacks of any pre-verified
type.
*
* @param type Verified in addCallback, not in this method.
* @see #org.goasap.events.GoEvent GoEvent
*/
protected function dispatch(type:String):void
{
var a:Array = (_callbacks[ type ] as Array);
if (a)
for each (var callback:Function in a)
callback();
if (hasEventListener(type))
dispatchEvent(new GoEvent( type ));
}
/**
* @private
*/
protected function reset() : void {
_position = 0;
_change = 1;
_repeater.reset();
_currentFrame = _framesBase-1;
_currentEasing = _easing;
_easeParams = null;
_started = false;
_pauseTime = NaN;
_startTime = NaN;
}
}
}
\ No newline at end of file
Modified: branches/goasap0.5.2/src_go/org/goasap/utils/PlayableGroup.as
==============================================================================
--- branches/goasap0.5.2/src_go/org/goasap/utils/PlayableGroup.as (original)
+++ branches/goasap0.5.2/src_go/org/goasap/utils/PlayableGroup.as Sun Mar
15 17:46:33 2009
@@ -1 +1 @@
-/**
* Copyright (c) 2007 Moses Gunesch
*
* Permission is hereby granted, free of charge, to any person obtaining a
copy
* of this software and associated documentation files (the "Software"), to
deal
* in the Software without restriction, including without limitation the
rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.goasap.utils {
import flash.utils.Dictionary;
import org.goasap.PlayStates;
import org.goasap.PlayableBase;
import org.goasap.events.GoEvent;
import org.goasap.interfaces.IPlayable;
import org.goasap.managers.Repeater;
/**
* Dispatched when the group starts.
* @eventType org.goasap.events.START
*/
[Event(name="START", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the group is paused successfully.
* @eventType org.goasap.events.PAUSE
*/
[Event(name="PAUSE", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the group is resumed successfully.
* @eventType org.goasap.events.RESUME
*/
[Event(name="RESUME", type="org.goasap.events.GoEvent")]
/**
* Dispatched at the end the group if repeater.cycles is set
to
* a value other than one, just before the group starts its next play
cycle.
* @eventType org.goasap.events.CYCLE
*/
[Event(name="CYCLE", type="org.goasap.events.GoEvent")]
/**
* Dispatched if the group is manually stopped.
* @eventType org.goasap.events.STOP
*/
[Event(name="STOP", type="org.goasap.events.GoEvent")]
/**
* Dispatched after all children have dispatched a STOP or COMPLETE event.
* @eventType org.goasap.events.COMPLETE
*/
[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]
/**
* Batch-play a set of items and receive an event when all of them have
finished.
*
* PlayableGroup accepts any IPlayable for its children, which can include * tweens, other groups, sequences and so forth. The group listens for both * GoEvent.STOP and GoEvent.COMPLETE events from its children, either of which * are counted toward group completion.
* *The repeater property of PlayableGroup allows you to
loop play
* any number of times, or indefinitely by setting its cycles to
Repeater.INFINITE.
* GoEvent.CYCLE is dispatched on each loop and GoEvent.COMPLETE when
finished.
* Other events dispatched include the GoEvent types START, STOP, PAUSE,
and RESUME.
addChild and removeChild,
* setting this property will stop any group play currently in progress.
*/
public function get children():Array {
var a:Array = [];
for (var item:Object in _children)
a.push(item);
return a;
}
public function set children(a:Array):void {
if (_listeners > 0)
stop();
for each (var item:Object in a)
if (item is IPlayable)
addChild(item as IPlayable);
}
/**
* The groups's Repeater instance, which may be used to make
* it loop and play more than one time.
*
* The Repeater's cycles property can be set to an integer, or * to Repeater.INFINITE or 0 to repeat indefinitely.
* ** var group:PlayableGroup = new PlayableGroup(tween1, tween2, tween3); * group.repeater.cycles = 2; * group.start(); * trace(group.repeater.currentCycle); // output: 0 **/ public function get repeater(): Repeater { return _repeater; } /** * Determines the number of children currently being monitored * for completion by the group. */ public function get listenerCount() : uint { return _listeners; } // I think I'm going to strip the adoptChildState option and add this simpler flag. //public var restartActiveChildren: Boolean = true; // -== Protected Properties ==- /** @private */ protected var _children: Dictionary = new Dictionary(); /** @private */ protected var _listeners: uint = 0; /** @private */ protected var _repeater: Repeater; // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable items as separate arguments, * or a single array of them. */ public function PlayableGroup(...items) { super(); if (items.length > 0) this.children = ((items[ 0 ] is Array) ? items[ 0 ] : items); _repeater = new Repeater(); _repeater.setParent(this); } /** * Searches for a child with the specified playableID. * * @param playableID The item playableID to search for. (The item must have a * property called
playableID which is a general
* GoASAP convention established in PlayableBase.)
* @param deepSearch If child is not found in the group, this option runs
a
* recursive search on any children that are PlayableGroup.
* @return The SequenceStep with the matching playableID.
*/
public function getChildByID(playableID:*,
deepSearch:Boolean=true):IPlayable {
for (var item:Object in _children)
if (item.hasOwnProperty("playableID") &&
item["playableID"]===playableID)
return (item as IPlayable);
if (deepSearch) {
for (item in _children) {
if (item is PlayableGroup) {
var match:IPlayable = ((item as
PlayableGroup).getChildByID(playableID, true));
if (match) { return (match as IPlayable); }
}
}
}
return null;
}
/**
* Adds a single IPlayable to the children array (duplicates are
rejected) and
* syncs up the group and child play-states based on various conditions.
*
* If both the group and the item being added are STOPPED, the item is simply * added to the children list.
* *If both the group and the item being added are PAUSED or PLAYING/PLAYING_DELAY, * the child is actively added to the group during play and will be monitored for * completion along with others.
* *In other cases where the child's state mismatches the group's
state, there
* are several behaviors available. Normally if the second parameter
adoptChildState
* is left false, the child's mismatched state will be updated to match
the group's
* state. This can result in it being stopped, paused, or started/resumed
and monitored
* for completion along with other children. Passing true for
adoptChildState
* results in updating the group's state to match the child's. This
option could be used, for
* example, if you wanted to build a group of already-playing items
without disrupting their
* play cycle with a start() call to the group.
Note that if play is in progress when a child is added it does not * interrupt play and the child is monitored for completion along with * others.
* * @param item Any instance that implements IPlayable and uses PlayState constants. * @return Success. */ public function removeChild(item:IPlayable): Boolean { var v:* = _children[ item ]; if (v===null) return false; if (v===true) unListenTo( item ); delete _children[ item ]; return true; } /** * Test whether any child has a particular PlayState. * *
* // Example: resume a paused group
* if ( myGroup.anyChildHasState(PlayStates.PlayStates.PAUSED) ) {
* myGroup.resume();
* }
*
* @see org.goasap.PlayStates PlayStates
*/
public function anyChildHasState(state:String): Boolean {
for (var item:Object in _children)
if ((item as IPlayable).state==state)
return true;
return false;
}
// -== IPlayable implementation ==-
/**
* Calls start on all children.
*
* If the group is active when this method is called, a
stop call
* is automated which will result in a GoEVent.STOP event being
dispatched.
pause on all children.
*
* @return Returns true only if all playing children in the group paused
successfully
* and at least one child was paused.
*/
public function pause() : Boolean {
if (_state!= PlayStates.PLAYING)
return false;
var r:Boolean = true;
var n:uint = 0;
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
var success:Boolean = item.pause();
if (success) n++;
r = (r && success);
}
if (n>0) {
_state = PlayStates.PAUSED; // state should reflect that at least one
item was paused,
// while return value may indicate that not all pause calls
succeeded.
dispatchEvent(new GoEvent( GoEvent.PAUSE ));
}
return (n>0 && r);
}
/**
* Calls resume on all children.
*
* @return Returns true only if all paused children in the group resumed
successfully
* and at least one child was resumed.
*/
public function resume() : Boolean {
if (_state!= PlayStates.PAUSED)
return false;
var r:Boolean = true;
var n:uint = 0;
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
var success:Boolean = item.resume();
if (success) n++;
r = (r && success);
}
if (n>0) {
_state = PlayStates.PLAYING; // state should reflect that at least one
item was resumed,
// while return value may indicate that not all resume calls
succeeded.
dispatchEvent(new GoEvent( GoEvent.RESUME ));
}
return (n>0 && r);
}
/**
* Calls skipTo on all children.
*
* @return Returns true only if all children in the group skipTo the
position successfully
* and at least one child was affected.
*/
public function skipTo(position : Number) : Boolean {
var r:Boolean = true;
var n:uint = 0;
position = _repeater.skipTo(_repeater.cycles, position); // TODO: TEST
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
listenTo(item); // first in case of immediate STOP/COMPLETE.
var started:Boolean = item.skipTo(position);
if (!started || item.state==PlayStates.STOPPED) {
unListenTo(item);
started = false;
}
else { n++; }
r = (started && r);
}
_state = (r ? PlayStates.PLAYING : PlayStates.STOPPED);
return (n>0 && r);
}
// -== Protected Methods ==-
/**
* @private
* Internal handler for item completion.
* @param event GoEvent dispatched by child item.
*/
protected function onItemEnd(event:GoEvent) : void {
unListenTo(event.target as IPlayable);
if (_listeners==0) {
complete();
}
}
/**
* @private
* Internal handler for group completion.
*/
protected function complete() : void {
if (_repeater.next()) {
dispatchEvent(new GoEvent( GoEvent.CYCLE ));
var c:Array = this.children; // array is used because cycling
Dictionary and setting entries can result in duplicates (Dictionary bug,
apparently.)
for each (var item:IPlayable in c) {
listenTo(item); // first in case of immediate STOP/COMPLETE.
var started:Boolean = (item).start();
if (!started || item.state==PlayStates.STOPPED)
unListenTo(item);
}
}
else {
stop();
}
}
/**
* @private
* Internal. Listen for item completion, keeping tight track of listeners.
* @param item Any instance that extends IPlayable (IPlayable itself
should not be used directly).
*/
protected function listenTo(item:IPlayable) : void {
if (_children[ item ] === false) {
item.addEventListener(GoEvent.STOP, onItemEnd, false, 0, true);
item.addEventListener(GoEvent.COMPLETE, onItemEnd, false, 0, true);
_children[ item ] = true;
_listeners++;
}
}
/**
* @private
* Internal. Stop listening for item completion.
* @param item Any instance that extends IPlayable (IPlayable itself
should not be used directly).
* @return Number of completion listeners remaining.
*/
protected function unListenTo(item:IPlayable) : void {
if (_children[ item ] === true) {
item.removeEventListener(GoEvent.STOP, onItemEnd);
item.removeEventListener(GoEvent.COMPLETE, onItemEnd);
_children[ item ] = false;
_listeners--;
}
}
/**
* @private
* @param item Item whose state to adopt
* @see #addChild
*/
protected function adoptState(item:IPlayable): Boolean {
if (item.state==PlayStates.STOPPED) {
this.stop();
return true;
}
listenTo(item);
var startOthers:Boolean = false;
if (item.state==PlayStates.PAUSED) { // pause() is called after the
group is started
if (_state==PlayStates.STOPPED)
startOthers = true;
}
else if (item.state==PlayStates.PLAYING ||
item.state==PlayStates.PLAYING_DELAY) {
if (_state==PlayStates.PAUSED)
this.resume();
else
startOthers = true;
}
if (startOthers) { // differs from start() in that only non-playing
items are started.
var c:Array = this.children; // array is used because cycling
Dictionary and setting entries can result in duplicates (Dictionary bug,
apparently.)
for each (var child:IPlayable in c) {
if (child!=item) {
if (child.state==PlayStates.PAUSED) {
child.resume();
}
else if (child.state==PlayStates.STOPPED) {
listenTo(child); // first in case of immediate STOP/COMPLETE.
if (child.start()==false || child.state==PlayStates.STOPPED)
unListenTo(child);
}
}
}
_state = PlayStates.PLAYING; // Group adopts child playing state
dispatchEvent(new GoEvent( GoEvent.START));
}
if (item.state==PlayStates.PAUSED) // see note above
this.pause();
return true;
}
}
}
\ No newline at end of file
+/**
* Copyright (c) 2007 Moses Gunesch
*
* Permission is hereby granted, free of charge, to any person obtaining a
copy
* of this software and associated documentation files (the "Software"), to
deal
* in the Software without restriction, including without limitation the
rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.goasap.utils {
import org.goasap.PlayStates;
import org.goasap.PlayableBase;
import org.goasap.events.GoEvent;
import org.goasap.interfaces.IPlayable;
import org.goasap.managers.Repeater;
import flash.utils.Dictionary;
/**
* Dispatched when the group starts.
* @eventType org.goasap.events.START
*/
[Event(name="START", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the group is paused successfully.
* @eventType org.goasap.events.PAUSE
*/
[Event(name="PAUSE", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the group is resumed successfully.
* @eventType org.goasap.events.RESUME
*/
[Event(name="RESUME", type="org.goasap.events.GoEvent")]
/**
* Dispatched at the end the group if repeater.cycles is set
to
* a value other than one, just before the group starts its next play
cycle.
* @eventType org.goasap.events.CYCLE
*/
[Event(name="CYCLE", type="org.goasap.events.GoEvent")]
/**
* Dispatched if the group is manually stopped.
* @eventType org.goasap.events.STOP
*/
[Event(name="STOP", type="org.goasap.events.GoEvent")]
/**
* Dispatched after all children have dispatched a STOP or COMPLETE event.
* @eventType org.goasap.events.COMPLETE
*/
[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]
/**
* Batch-play a set of items and receive an event when all of them have
finished.
*
* PlayableGroup accepts any IPlayable for its children, which can include * tweens, other groups, sequences and so forth. The group listens for both * GoEvent.STOP and GoEvent.COMPLETE events from its children, either of which * are counted toward group completion.
* *The repeater property of PlayableGroup allows you to
loop play
* any number of times, or indefinitely by setting its cycles to
Repeater.INFINITE.
* GoEvent.CYCLE is dispatched on each loop and GoEvent.COMPLETE when
finished.
* Other events dispatched include the GoEvent types START, STOP, PAUSE,
and RESUME.
addChild and removeChild,
* setting this property will stop any group play currently in progress.
*/
public function get children():Array {
var a:Array = [];
for (var item:Object in _children)
a.push(item);
return a;
}
public function set children(a:Array):void {
if (_listeners > 0)
stop();
for each (var item:Object in a)
if (item is IPlayable)
addChild(item as IPlayable);
}
/**
* The groups's Repeater instance, which may be used to make
* it loop and play more than one time.
*
* The Repeater's cycles property can be set to an integer, or * to Repeater.INFINITE or 0 to repeat indefinitely.
* ** var group:PlayableGroup = new PlayableGroup(tween1, tween2, tween3); * group.repeater.cycles = 2; * group.start(); * trace(group.repeater.currentCycle); // output: 0 **/ public function get repeater(): Repeater { return _repeater; } /** * Determines the number of children currently being monitored * for completion by the group. */ public function get listenerCount() : uint { return _listeners; } // -== Protected Properties ==- /** @private */ protected var _children: Dictionary = new Dictionary(); /** @private */ protected var _listeners: uint = 0; /** @private */ protected var _repeater: Repeater; // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable items as separate arguments, * or a single array of them. */ public function PlayableGroup(...items) { super(); if (items.length > 0) this.children = ((items[ 0 ] is Array) ? items[ 0 ] : items); _repeater = new Repeater(); _repeater.setParent(this); } /** * Searches for a child with the specified playableID. * * @param playableID The item playableID to search for. (The item must have a * property called
playableID which is a general
* GoASAP convention established in PlayableBase.)
* @param deepSearch If child is not found in the group, this option runs
a
* recursive search on any children that are PlayableGroup.
* @return The SequenceStep with the matching playableID.
*/
public function getChildByID(playableID:*,
deepSearch:Boolean=true):IPlayable {
for (var item:Object in _children)
if (item.hasOwnProperty("playableID") &&
item["playableID"]===playableID)
return (item as IPlayable);
if (deepSearch) {
for (item in _children) {
if (item is PlayableGroup) {
var match:IPlayable = ((item as
PlayableGroup).getChildByID(playableID, true));
if (match) { return (match as IPlayable); }
}
}
}
return null;
}
/**
* Adds a single IPlayable to the children array (duplicates are
rejected) and
* syncs up the group and child play-states based on various conditions.
*
* Normally this method is called when both the group and the child item are * not playing. However in some cases either the group or the child might be * playing or paused, in which case calling this method will attempt to sync * the child state with the group's state. If syncing fails the child is not added * and false is returned.
* * @param item Any instance that implements IPlayable and uses PlayState constants. * @return The item added, or null if the item was already in the group or * a mismatched child state was not syncable to the group's state. */ public function addChild(item:IPlayable): IPlayable { var itemState:String = item.state; if (_children[ item ]!=null) { return null; // child already added! } _children[ item ] = false; if (_state==PlayStates.STOPPED && itemState!=PlayStates.STOPPED) { item.stop(); } if (itemState==_state) { return item; // states match: no further action is needed. this is most common. } // Group is active: attempt syncing of child state to group state. var success:Boolean = true; listenTo(item); if (_state==PlayStates.PLAYING || _state==PlayStates.PLAYING_DELAY) { if (itemState==PlayStates.PAUSED) { success = item.resume(); } else if (itemState==PlayStates.STOPPED) { success = item.start(); } else { return item; // Item is already playing and only delay states mismatch: Success. } } else if (_state==PlayStates.PAUSED) { if (itemState==PlayStates.STOPPED) { item.start(); } success = item.pause(); } if (!success) { unListenTo(item); delete _children[ item ]; return null; } return item; } /** * Removes a single IPlayable from the children array. * *Note that if play is in progress when a child is added it does not * interrupt play and the child is monitored for completion along with * others.
* * @param item Any instance that implements IPlayable and uses PlayState constants. * @return The item removed, or null if the call failed. */ public function removeChild(item:IPlayable): IPlayable { var v:* = _children[ item ]; if (v===null) return null; if (v===true) unListenTo( item ); delete _children[ item ]; return item; } /** * Enables already-playing items to be added to a group seamlessly by presetting * the group's state to PLAYING instead of interrupting the child item. * * @param item Any instance that implements IPlayable and uses PlayState constants. * @return The item added, or null if the call failed. */ public function addPlayingChildAndStart(item:IPlayable): IPlayable { _state = PlayStates.PLAYING; item = addChild(item); // Item will start if stopped and resume if paused. if (_listeners > 0) { dispatchEvent(new GoEvent( GoEvent.START)); _playRetainer[ this ] = 1; // Developers - Important! Look up _playRetainer. } else { _state = PlayStates.STOPPED; } return item; } /** * Test whether any child has a particular PlayState. * *
* // Example: resume a paused group
* if ( myGroup.anyChildHasState(PlayStates.PlayStates.PAUSED) ) {
* myGroup.resume();
* }
*
* @see org.goasap.PlayStates PlayStates
*/
public function anyChildHasState(state:String): Boolean {
for (var item:Object in _children)
if ((item as IPlayable).state==state)
return true;
return false;
}
// -== IPlayable implementation ==-
/**
* Calls start on all children.
*
* If the group is active when this method is called, a
stop call
* is automated which will result in a GoEVent.STOP event being
dispatched.
pause on all children.
*
* @return Returns true only if all playing children in the group paused
successfully
* and at least one child was paused.
*/
public function pause() : Boolean {
if (_state!= PlayStates.PLAYING)
return false;
var r:Boolean = true;
var n:uint = 0;
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
var success:Boolean = item.pause();
if (success) n++;
r = (r && success);
}
if (n>0) {
_state = PlayStates.PAUSED; // state should reflect that at least one
item was paused,
// while return value may indicate that not all pause calls
succeeded.
dispatchEvent(new GoEvent( GoEvent.PAUSE ));
}
return (n>0 && r);
}
/**
* Calls resume on all children.
*
* @return Returns true only if all paused children in the group resumed
successfully
* and at least one child was resumed.
*/
public function resume() : Boolean {
if (_state!= PlayStates.PAUSED)
return false;
var r:Boolean = true;
var n:uint = 0;
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
var success:Boolean = item.resume();
if (success) n++;
r = (r && success);
}
if (n>0) {
_state = PlayStates.PLAYING; // state should reflect that at least one
item was resumed,
// while return value may indicate that not all resume calls
succeeded.
dispatchEvent(new GoEvent( GoEvent.RESUME ));
}
return (n>0 && r);
}
/**
* Calls skipTo on all children.
*
* @return Returns true only if all children in the group skipTo the
position successfully
* and at least one child was affected.
*/
public function skipTo(position : Number) : Boolean {
var r:Boolean = true;
var n:uint = 0;
position = _repeater.skipTo(_repeater.cycles, position); // TODO: TEST
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
listenTo(item); // first in case of immediate STOP/COMPLETE.
var started:Boolean = item.skipTo(position);
if (!started || item.state==PlayStates.STOPPED) {
unListenTo(item);
started = false;
}
else { n++; }
r = (started && r);
}
_state = (r ? PlayStates.PLAYING : PlayStates.STOPPED);
return (n>0 && r);
}
// -== Protected Methods ==-
/**
* @private
* Internal handler for item completion.
* @param event GoEvent dispatched by child item.
*/
protected function onItemEnd(event:GoEvent) : void {
unListenTo(event.target as IPlayable);
if (_listeners==0) {
complete();
}
}
/**
* @private
* Internal handler for group completion.
*/
protected function complete() : void {
if (_repeater.next()) {
dispatchEvent(new GoEvent( GoEvent.CYCLE ));
var c:Array = this.children; // array is used because cycling
Dictionary and setting entries can result in duplicates (Dictionary bug,
apparently.)
for each (var item:IPlayable in c) {
listenTo(item); // first in case of immediate STOP/COMPLETE.
var started:Boolean = (item).start();
if (!started || item.state==PlayStates.STOPPED)
unListenTo(item);
}
}
else {
stop();
}
}
/**
* @private
* Internal. Listen for item completion, keeping tight track of listeners.
* @param item Any instance that extends IPlayable (IPlayable itself
should not be used directly).
*/
protected function listenTo(item:IPlayable) : void {
if (_children[ item ] === false) {
item.addEventListener(GoEvent.STOP, onItemEnd, false, 0, true);
item.addEventListener(GoEvent.COMPLETE, onItemEnd, false, 0, true);
_children[ item ] = true;
_listeners++;
}
}
/**
* @private
* Internal. Stop listening for item completion.
* @param item Any instance that extends IPlayable (IPlayable itself
should not be used directly).
* @return Number of completion listeners remaining.
*/
protected function unListenTo(item:IPlayable) : void {
if (_children[ item ] === true) {
item.removeEventListener(GoEvent.STOP, onItemEnd);
item.removeEventListener(GoEvent.COMPLETE, onItemEnd);
_children[ item ] = false;
_listeners--;
}
}
}
}
\ No newline at end of file
Modified: branches/goasap0.5.2/src_go/org/goasap/utils/Sequence.as
==============================================================================
--- branches/goasap0.5.2/src_go/org/goasap/utils/Sequence.as (original)
+++ branches/goasap0.5.2/src_go/org/goasap/utils/Sequence.as Sun Mar 15
17:46:33 2009
@@ -1 +1 @@
-/**
* Copyright (c) 2007 Moses Gunesch
*
* Permission is hereby granted, free of charge, to any person obtaining a
copy
* of this software and associated documentation files (the "Software"), to
deal
* in the Software without restriction, including without limitation the
rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.goasap.utils {
import org.goasap.interfaces.IPlayable;
/**
* Simple playable sequence, composed of groups of playable items.
*
* A sequence can be built by passing any item that implements IPlayable * and uses the standard set of PlayState constants. Sequences are composed * of SequenceStep instances, which can contain any number of child items * such as LinearGo or PlayableGroup instances. Sequences dispatch SequenceEvent.ADVANCE * each time a step completes and the play index advances to the next one, * then GoEvent.COMPLETE when done.
* *Other events dispatched include the GoEvent types START, STOP, PAUSE, RESUME, * and CYCLE if the repeater.cycles property is set to a value other than one.
* *All items in each step must dispatch COMPLETE or STOP before a Sequence * will advance. This simple behavior can be limiting, especially with steps that * are composed of groups of items. The Go utility package includes another * sequencer called SequenceCA, which allows you to define different ways a sequence * can advance: after a particular item in a step, a particular duration, after * an event fires, etc.
* * @see SequenceBase * @see SequenceCA * @author Moses Gunesch */ public class Sequence extends SequenceBase { // -== Public Properties ==- // Also in super: // length : uint [Read-only.] // playIndex : int [Read-only.] // steps : Array // start() : Boolean // stop() : Boolean // pause() : Boolean // resume() : Boolean // skipTo(index:Number) : Boolean /** * Returns the currently-playing SequenceStep. * @return The currently-playing SequenceStep. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #lastStep */ public function get currentStep() : SequenceStep { return (super._getCurrentStep()); } /** * Returns the final SequenceStep in the current sequence. * @return The final SequenceStep in the current sequence. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #currentStep */ public function get lastStep() : SequenceStep { return (super._getLastStep()); } // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable instances (e.g. LinearGo, PlayableGroup, * SequenceStep) as separate arguments, or a single array of them. */ public function Sequence(...items) { super((items[ 0 ] is Array) ? items[ 0 ] : items); } /** * Retrieves any SequenceStep from the steps array. * @param index An array index starting at 0. * @return The SequenceStep instance at this index. * @see #getStepByID() * @see #currentStep * @see #lastStep */ public function getStepAt(index:int) : SequenceStep { return (super._getStepAt(index) as SequenceStep); } /** * Locates a step with the specified playableID. To search within a step for a * child by playableID, use the step instance'sgetChildByID
method.
*
* @param playableID The step instance's playableID to search for.
* @return The SequenceStep with the matching playableID.
* @see #getStepAt()
*/
public function getStepByID(playableID:*) : SequenceStep {
return (super._getStepByID(playableID) as SequenceStep);
}
/**
* Adds a single IPlayable instance (e.g. LinearGo, PlayableGroup,
SequenceStep)
* to the end of the steps array, or optionally adds the instance into
the last
* SequenceStep instead of adding it as a new step.
*
* To remove a step use the removeStepAt method.
A sequence can be built by passing any item that implements IPlayable * and uses the standard set of PlayState constants. Sequences are composed * of SequenceStep instances, which can contain any number of child items * such as LinearGo or PlayableGroup instances. Sequences dispatch SequenceEvent.ADVANCE * each time a step completes and the play index advances to the next one, * then GoEvent.COMPLETE when done.
* *Other events dispatched include the GoEvent types START, STOP, PAUSE, RESUME, * and CYCLE if the repeater.cycles property is set to a value other than one.
* *All items in each step must dispatch COMPLETE or STOP before a Sequence * will advance. This simple behavior can be limiting, especially with steps that * are composed of groups of items. The Go utility package includes another * sequencer called SequenceCA, which allows you to define different ways a sequence * can advance: after a particular item in a step, a particular duration, after * an event fires, etc.
* * @see SequenceBase * @see SequenceCA * @author Moses Gunesch */ public class Sequence extends SequenceBase { // -== Public Properties ==- // Also in super: // length : uint [Read-only.] // playIndex : int [Read-only.] // steps : Array // start() : Boolean // stop() : Boolean // pause() : Boolean // resume() : Boolean // skipTo(index:Number) : Boolean /** * Returns the currently-playing SequenceStep. * @return The currently-playing SequenceStep. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #lastStep */ public function get currentStep() : SequenceStep { return (super._getCurrentStep()); } /** * Returns the final SequenceStep in the current sequence. * @return The final SequenceStep in the current sequence. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #currentStep */ public function get lastStep() : SequenceStep { return (super._getLastStep()); } // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable instances (e.g. LinearGo, PlayableGroup, * SequenceStep) as separate arguments, or a single array of them. */ public function Sequence(...items) { super((items[ 0 ] is Array) ? items[ 0 ] : items); } /** * Retrieves any SequenceStep from the steps array. * @param index An array index starting at 0. * @return The SequenceStep instance at this index. * @see #getStepByID() * @see #currentStep * @see #lastStep */ public function getStepAt(index:int) : SequenceStep { return (super._getStepAt(index) as SequenceStep); } /** * Locates a step with the specified playableID. To search within a step for a * child by playableID, use the step instance'sgetChildByID
method.
*
* @param playableID The step instance's playableID to search for.
* @return The SequenceStep with the matching playableID.
* @see #getStepAt()
*/
public function getStepByID(playableID:*) : SequenceStep {
return (super._getStepByID(playableID) as SequenceStep);
}
/**
* Adds a single SequenceStep or IPlayable instance (LinearGo,
PlayableGroup,
* etc.) to the end of the steps array, or optionally adds the instance
into
* the last SequenceStep instead of adding it as a new step. Calling this
* method stops any sequence play currently in progress.
*
* To remove a step use the removeStepAt method.
repeater.cycles is set
to
* a value other than one, just before the sequence starts its next play
cycle.
* @eventType org.goasap.events.CYCLE
*/
[Event(name="CYCLE", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the sequence is manually stopped, which may also occur
* if one of its step instances is manually stopped outside the sequence.
* @eventType org.goasap.events.STOP
*/
[Event(name="STOP", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the sequence successfully finishes. (In SequenceCA this
event
* is not fired until all custom-advanced steps have dispatched STOP or
COMPLETE.)
* @eventType org.goasap.events.COMPLETE
*/
[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]
/**
* This base class should not be used directly, use it to build sequencing
classes.
*
* When subclassing, follow the instructions in the comments of the protected * methods to add a standard set of public getters and methods that work with the * specific datatype of your SequenceStep subclass, if you create one. (This system * is designed to work around the restrictiveness of overrides in AS3 which don't * allow you to redefine datatypes.) See Sequence and SequenceCA for examples.
* * @see Sequence * @see SequenceCA * * @author Moses Gunesch */ public class SequenceBase extends PlayableBase implements IPlayable { // -== Public Properties ==- /** * The number of steps in the sequence. */ public function get length(): int { return (_steps ? _steps.length : 0); } /** * The current play index of the sequence, starting a 0. */ public function get playIndex(): int { return _index; } /** * Get or set the list of SequenceStep instances that defines the sequence. * ** When setting this property, each item must implement IPlayable that uses * PlayState constants and dispatches STOP or COMPLETE when finished. * Each item is automatically wrapped in a SequenceStep if it is of any other IPlayable * type, such as a GoItem or PlayableGroup. Setting this property stops any sequence * play currently in progress. *
* @see #_getStepAt() * @see #_getStepByID() * @see #_getCurrentStep() * @see #_getLastStep() */ public function get steps():Array { return _steps; } public function set steps(a:Array):void { if (_state!=PlayStates.STOPPED) stop(); while (_steps.length > 0) _removeStepAt(_steps.length-1); for each (var item:Object in a) if (item is IPlayable) _addStep(item as IPlayable); } /** * The sequence's Repeater instance, which may be used to make * the sequence loop and play more than one time. * *The Repeater's cycles property can be set to an integer, or * to Repeater.INFINITE or 0 to repeat indefinitely.
* *var seq:Sequence = new Sequence(tween1, tween2, tween3); * seq.repeater.cycles = 2; * seq.start(); * trace(seq.repeater.currentCycle); // output: 0 * * seq.skipTo(4); // moves to 2nd action in 2nd cycle * trace(seq.repeater.currentCycle); // output: 1* *
(The repeater property replaces the repeatCount and currentCount * parameters in earlier releases of SequenceBase).
*/ public function get repeater(): Repeater { return _repeater; } // -== Protected Properties ==- /** @private */ protected var _index: int = 0; /** @private */ protected var _steps: Array; /** @private */ protected var _repeater: Repeater; // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable instances (e.g. LinearGo, PlayableGroup, * SequenceStep) as separate arguments, or a single array of them. */ public function SequenceBase(...items) { super(); var className:String = getQualifiedClassName(this); if (className.slice(className.lastIndexOf("::")+2) == "SequenceBase") { throw new InstanceNotAllowedError("SequenceBase"); } _steps = new Array(); if (items.length > 0) { steps = ((items[ 0 ] is Array) ? items[ 0 ] : items); } _repeater = new Repeater(); _repeater.setParent(this); } // -== IPlayable implementation ==- /** * Begins a sequence. * *If the group is active when this method is called, a
stop call
* is automated which will result in a GoEvent.STOP event being
dispatched.
currentStep to your
subclass as in Sequence.
*
* @return Developers: return the correct SequenceStep type for your
subclass in your corresponding public method.
*/
protected function _getCurrentStep() : * {
return (_steps.length==0 ? null : _steps[_index]);
}
/**
* Developers: Add a getter called lastStep to your subclass
as in Sequence.
*
* @return Developers: return the correct SequenceStep type for your
subclass in your corresponding public method.
*/
protected function _getLastStep() : * {
return (_steps.length==0 ? null : _steps[ _steps.length-1 ]);
}
/**
* Developers: Add a method called getStepAt to your
subclass as in Sequence.
*
* @param index An array index starting at 0.
* @return Developers: return the correct SequenceStep type for your
subclass in your corresponding public method.
*/
protected function _getStepAt(index:int) : * {
if (index >= _steps.length)
return null;
return (_steps[index] as SequenceStep);
}
/**
* Developers: Add a method called getStepByID to your
subclass as in Sequence.
*
* @param playableID The step instance's playableID to search for.
* @return Developers: return the correct SequenceStep type for your
subclass in your corresponding public method.
*/
protected function _getStepByID(playableID:*) : * {
for each (var step:SequenceStep in _steps)
if (step.playableID===playableID)
return step;
return null;
}
/**
* Developers: Add a method called addStep to your subclass
as in Sequence.
*
* Drop the third parameter in your subclass' addStep method. Use it to be sure * the correct type of wrapper is created, as in SequenceCA.
* * @param item The playable item to add to the sequence. * * @param addToLastStep If true is passed the item is added to the last * existing SequenceStep in the steps array. This * option should be used with individual items that * you want added as children to the SequenceStep. * If there are no steps yet this option ignored and * a new step is created. * * @param stepTypeAsClass Type for SequenceSteps. (Do not include this parameter in subclass addStep method.) * * @return New length of the steps array. */ protected function _addStep(item:IPlayable, addToLastStep:Boolean=false, stepTypeAsClass:*=null): int { if (item is SequenceStep && !addToLastStep) { return _addStepAt(_steps.length, item); } if (!stepTypeAsClass) stepTypeAsClass = SequenceStep; var step:SequenceStep = (addToLastStep && _steps.length > 0 ? (_steps.pop() as SequenceStep) : new stepTypeAsClass() as SequenceStep); step.addChild(item); return _addStepAt(_steps.length, step, stepTypeAsClass); // adds listeners } /** * Developers: Add a method calledaddStep to your subclass
as in Sequence.
*
* Drop the third parameter in your subclass' addStep method. Use it to be sure * the correct type of wrapper is created, as in SequenceCA.
* * @param index Position in the array starting at 0, or a negative * index like Array.splice. * * @param item The playable item to splice into the sequence. * * @param stepTypeAsClass Type for SequenceSteps. (Do not include this parameter in subclass addStep method.) * * @return New length of the steps array. */ protected function _addStepAt(index:int, item:IPlayable, stepTypeAsClass:*=null): int { if (_state!=PlayStates.STOPPED) stop(); if (!stepTypeAsClass) stepTypeAsClass = SequenceStep; var step:SequenceStep = (item is SequenceStep ? item as SequenceStep : new stepTypeAsClass(item) as SequenceStep); step.addEventListener(SequenceEvent.ADVANCE, onStepEvent, false, 0, true); step.addEventListener(GoEvent.STOP, onStepEvent, false, 0, true); _steps.splice(index, 0, step); return _steps.length; } /** * Developers: Add a method calledaddStep to your subclass
as in Sequence.
*
* @param index Position in the array starting at 0, or a negative
* index like Array.splice.
*
* @return Developers: return the correct SequenceStep type for your
subclass in your corresponding public method.
*/
protected function _removeStepAt(index:int) : * {
if (_state!=PlayStates.STOPPED)
stop();
var step:SequenceStep = _steps.splice(index, 1) as SequenceStep;
step.removeEventListener(SequenceEvent.ADVANCE, onStepEvent);
step.removeEventListener(GoEvent.STOP, onStepEvent);
return step;
}
// -== Protected Methods ==-
/**
* @private
* Internal handler for step advance.
*
* @param event SequenceEvent dispatched by child item.
*/
protected function onStepEvent(event : Event) : void {
// A stop() call to the sequence results in step dispatching STOP, which
would recurse here.
if (_state==PlayStates.STOPPED || event.target!=_steps[_index])
return;
// Only occurs if the SequenceItem is manually stopped outside of this
manager.
if (event.type==GoEvent.STOP) {
stop();
return;
}
// Normal step advance
if (event.type==SequenceEvent.ADVANCE) {
if (_steps.length-_index == 1) {
complete();
}
else {
advance();
}
}
}
/**
* @private
* Internal handler for group completion.
*/
protected function advance() : void {
if (_steps.length-_index > 1) {
_index ++; // this changes currentStep value in following code
_getCurrentStep().start();
}
dispatchEvent(new SequenceEvent( SequenceEvent.ADVANCE ));
}
/**
* @private
* Internal handler for group completion.
*/
protected function complete() : void {
// order-sensitive
if (_repeater.next()) {
dispatchEvent(new GoEvent( GoEvent.CYCLE ));
_index = 0;
_getCurrentStep().start();
}
else {
_index = _steps.length - 1;
stop();
}
}
}
}
\ No newline at end of file
+/**
* Copyright (c) 2007 Moses Gunesch
*
* Permission is hereby granted, free of charge, to any person obtaining a
copy
* of this software and associated documentation files (the "Software"), to
deal
* in the Software without restriction, including without limitation the
rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.goasap.utils {
import flash.events.Event;
import flash.utils.getQualifiedClassName;
import org.goasap.PlayStates;
import org.goasap.PlayableBase;
import org.goasap.errors.InstanceNotAllowedError;
import org.goasap.events.GoEvent;
import org.goasap.events.SequenceEvent;
import org.goasap.interfaces.IPlayable;
import org.goasap.managers.Repeater;
/**
* Dispatched when the sequence starts.
* @eventType org.goasap.events.START
*/
[Event(name="START", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the sequence advances to its next step.
* @eventType org.goasap.events.SequenceEvent.ADVANCE
*/
[Event(name="ADVANCE", type="org.goasap.events.SequenceEvent")]
/**
* Dispatched when the sequence is paused successfully.
* @eventType org.goasap.events.PAUSE
*/
[Event(name="PAUSE", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the sequence is resumed successfully.
* @eventType org.goasap.events.RESUME
*/
[Event(name="RESUME", type="org.goasap.events.GoEvent")]
/**
* Dispatched at the end the group if repeater.cycles is set
to
* a value other than one, just before the sequence starts its next play
cycle.
* @eventType org.goasap.events.CYCLE
*/
[Event(name="CYCLE", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the sequence is manually stopped, which may also occur
* if one of its step instances is manually stopped outside the sequence.
* @eventType org.goasap.events.STOP
*/
[Event(name="STOP", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the sequence successfully finishes. (In SequenceCA this
event
* is not fired until all custom-advanced steps have dispatched STOP or
COMPLETE.)
* @eventType org.goasap.events.COMPLETE
*/
[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]
/**
* This base class should not be used directly, use it to build sequencing
classes.
*
* When subclassing, follow the instructions in the comments of the protected * methods to add a standard set of public getters and methods that work with the * specific datatype of your SequenceStep subclass, if you create one. (This system * is designed to work around the restrictiveness of overrides in AS3 which don't * allow you to redefine datatypes.) See Sequence and SequenceCA for examples.
* * @see Sequence * @see SequenceCA * * @author Moses Gunesch */ public class SequenceBase extends PlayableBase implements IPlayable { // -== Public Properties ==- /** * The number of steps in the sequence. */ public function get length(): int { return (_steps ? _steps.length : 0); } /** * The current play index of the sequence, starting a 0. */ public function get playIndex(): int { return _index; } /** * Get or set the list of SequenceStep instances that defines the sequence. * ** When setting this property, each item must implement IPlayable that uses * PlayState constants and dispatches STOP or COMPLETE when finished. * Each item is automatically wrapped in a SequenceStep if it is of any other IPlayable * type, such as a GoItem or PlayableGroup. Setting this property stops any sequence * play currently in progress. *
* @see #_getStepAt() * @see #_getStepByID() * @see #_getCurrentStep() * @see #_getLastStep() */ public function get steps():Array { return _steps; } public function set steps(a:Array):void { if (_state!=PlayStates.STOPPED) stop(); while (_steps.length > 0) _removeStepAt(_steps.length-1); for each (var item:Object in a) if (item is IPlayable) _addStep(item as IPlayable); } /** * The sequence's Repeater instance, which may be used to make * the sequence loop and play more than one time. * *The Repeater's cycles property can be set to an integer, or * to Repeater.INFINITE or 0 to repeat indefinitely.
* *var seq:Sequence = new Sequence(tween1, tween2, tween3); * seq.repeater.cycles = 2; * seq.start(); * trace(seq.repeater.currentCycle); // output: 0 * * seq.skipTo(4); // moves to 2nd action in 2nd cycle * trace(seq.repeater.currentCycle); // output: 1* *
(The repeater property replaces the repeatCount and currentCount * parameters in earlier releases of SequenceBase).
*/ public function get repeater(): Repeater { return _repeater; } // -== Protected Properties ==- /** @private */ protected var _index: int = 0; /** @private */ protected var _steps: Array; /** @private */ protected var _repeater: Repeater; // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable instances (e.g. LinearGo, PlayableGroup, * SequenceStep) as separate arguments, or a single array of them. */ public function SequenceBase(...items) { super(); var className:String = getQualifiedClassName(this); if (className.slice(className.lastIndexOf("::")+2) == "SequenceBase") { throw new InstanceNotAllowedError("SequenceBase"); } _steps = new Array(); if (items.length > 0) { steps = ((items[ 0 ] is Array) ? items[ 0 ] : items); } _repeater = new Repeater(); _repeater.setParent(this); } // -== IPlayable implementation ==- /** * Begins a sequence. * *If the group is active when this method is called, a
stop call
* is automated which will result in a GoEvent.STOP event being
dispatched.
currentStep to your
subclass as in Sequence.
*
* @return Developers: return the correct SequenceStep type for your
subclass in your corresponding public method.
*/
protected function _getCurrentStep() : SequenceStep {
return (_steps.length==0 ? null : _steps[_index]);
}
/**
* Developers: Add a getter called lastStep to your subclass
as in Sequence.
*
* @return Developers: return the correct SequenceStep type for your
subclass in your corresponding public method.
*/
protected function _getLastStep() : SequenceStep {
return (_steps.length==0 ? null : _steps[ _steps.length-1 ]);
}
/**
* Developers: Add a method called getStepAt to your
subclass as in Sequence.
*
* @param index An array index starting at 0.
* @return Developers: return the correct SequenceStep type for your
subclass in your corresponding public method.
*/
protected function _getStepAt(index:int) : SequenceStep {
if (index >= _steps.length)
return null;
return (_steps[index] as SequenceStep);
}
/**
* Developers: Add a method called getStepByID to your
subclass as in Sequence.
*
* @param playableID The step instance's playableID to search for.
* @return Developers: return the correct SequenceStep type for your
subclass in your corresponding public method.
*/
protected function _getStepByID(playableID:*) : SequenceStep {
for each (var step:SequenceStep in _steps)
if (step.playableID===playableID)
return step;
return null;
}
/**
* Developers: Add a method called addStep to your subclass
as in Sequence.
*
* Drop the third parameter in your subclass' addStep method. Use it to be sure * the correct type of wrapper is created, as in SequenceCA.
* * @param item The playable item to add to the sequence. * * @param addToLastStep If true is passed the item is added to the last * existing SequenceStep in the steps array. This * option should be used with individual items that * you want added as children to the SequenceStep. * If there are no steps yet this option ignored and * a new step is created. * * @param stepTypeAsClass Type for SequenceSteps. (Do not include this parameter in subclass addStep method.) * * @return Item added. */ protected function _addStep(item:IPlayable, addToLastStep:Boolean=false, stepTypeAsClass:*=null): IPlayable { if (item is SequenceStep && !addToLastStep) { _addStepAt(_steps.length, item); return item; } if (!stepTypeAsClass) stepTypeAsClass = SequenceStep; var step:SequenceStep = (addToLastStep && _steps.length > 0 ? (_steps.pop() as SequenceStep) : new stepTypeAsClass() as SequenceStep); step.addChild(item); _addStepAt(_steps.length, step, stepTypeAsClass); // adds listeners return item; } /** * Developers: Add a method calledaddStep to your subclass
as in Sequence.
*
* Drop the third parameter in your subclass' addStep method. Use it to be sure * the correct type of wrapper is created, as in SequenceCA.
* * @param index Position in the array starting at 0, or a negative * index like Array.splice. * * @param item The playable item to splice into the sequence. * * @param stepTypeAsClass Type for SequenceSteps. (Do not include this parameter in subclass addStep method.) * * @return Item added. */ protected function _addStepAt(index:int, item:IPlayable, stepTypeAsClass:*=null): IPlayable { if (_state!=PlayStates.STOPPED) stop(); if (!stepTypeAsClass) stepTypeAsClass = SequenceStep; var step:SequenceStep = (item is SequenceStep ? item as SequenceStep : new stepTypeAsClass(item) as SequenceStep); if (step==null) return null; step.addEventListener(SequenceEvent.ADVANCE, onStepEvent, false, 0, true); step.addEventListener(GoEvent.STOP, onStepEvent, false, 0, true); _steps.splice(Math.min(index, _steps.length), 0, step); return item; } /** * Developers: Add a method calledaddStep to your subclass
as in Sequence.
*
* @param index Position in the array starting at 0, or a negative
* index like Array.splice.
*
* @return Developers: return the correct SequenceStep type for your
subclass in your corresponding public method.
*/
protected function _removeStepAt(index:int) : SequenceStep {
if (_state!=PlayStates.STOPPED)
stop();
var step:SequenceStep = _steps.splice(index, 1) as SequenceStep;
step.removeEventListener(SequenceEvent.ADVANCE, onStepEvent);
step.removeEventListener(GoEvent.STOP, onStepEvent);
return step;
}
// -== Protected Methods ==-
/**
* @private
* Internal handler for step advance.
*
* @param event SequenceEvent dispatched by child item.
*/
protected function onStepEvent(event : Event) : void {
// A stop() call to the sequence results in step dispatching STOP, which
would recurse here.
if (_state==PlayStates.STOPPED || event.target!=_steps[_index])
return;
// Only occurs if the SequenceItem is manually stopped outside of this
manager.
if (event.type==GoEvent.STOP) {
stop();
return;
}
// Normal step advance
if (event.type==SequenceEvent.ADVANCE) {
if (_steps.length-_index == 1) {
complete();
}
else {
advance();
}
}
}
/**
* @private
* Internal handler for group completion.
*/
protected function advance() : void {
if (_steps.length-_index > 1) {
_index ++; // this changes currentStep value in following code
_getCurrentStep().start();
}
dispatchEvent(new SequenceEvent( SequenceEvent.ADVANCE ));
}
/**
* @private
* Internal handler for group completion.
*/
protected function complete() : void {
// order-sensitive
if (_repeater.next()) {
dispatchEvent(new GoEvent( GoEvent.CYCLE ));
_index = 0;
_getCurrentStep().start();
}
else {
_index = _steps.length - 1;
stop();
}
}
}
}
\ No newline at end of file
Modified: branches/goasap0.5.2/src_go/org/goasap/utils/SequenceCA.as
==============================================================================
--- branches/goasap0.5.2/src_go/org/goasap/utils/SequenceCA.as (original)
+++ branches/goasap0.5.2/src_go/org/goasap/utils/SequenceCA.as Sun Mar 15
17:46:33 2009
@@ -1 +1 @@
-/**
* Copyright (c) 2007 Moses Gunesch
*
* Permission is hereby granted, free of charge, to any person obtaining a
copy
* of this software and associated documentation files (the "Software"), to
deal
* in the Software without restriction, including without limitation the
rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.goasap.utils {
import flash.events.Event;
import org.goasap.PlayStates;
import org.goasap.events.GoEvent;
import org.goasap.events.SequenceEvent;
import org.goasap.interfaces.IPlayable;
/**
* Sequence with "Custom Advance" options, in which steps can specify when
they should advance.
*
* This class works like Sequence but uses the special class
SequenceStepCA for its steps.
* SequenceStepCA has a property called advance. When steps
advance before animation
* finishes, the trailing steps are tracked so that the SequenceCA doesn't
dispatch its COMPLETE
* event until all activity has completed.
Any step's advance property can be set to an instance of OnDurationComplete, OnPlayableComplete, * OnEventComplete or OnConditionTrue. Each of those classes defines its own parameters and rules for * when the advance occurs. For example, using OnPlayableComplete a sequence can advance after one * particular item in the step finishes, without needing to wait for all the other ones in that group * to complete.
* *Additionally, you can create your own custom advance types by subclassing the SequenceAdvance * base class.
* * @see SequenceStepCA * @see org.goasap.utils.customadvance.OnConditionTrue OnConditionTrue * @see org.goasap.utils.customadvance.OnDurationComplete OnDurationComplete * @see org.goasap.utils.customadvance.OnEventComplete OnEventComplete * @see org.goasap.utils.customadvance.OnPlayableComplete OnPlayableComplete * @see org.goasap.utils.customadvance.SequenceAdvance SequenceAdvance * @see Sequence * @see SequenceBase * * @author Moses Gunesch */ public class SequenceCA extends SequenceBase { // -== Public Properties ==- // Also in super: // length : uint [Read-only.] // playIndex : int [Read-only.] // steps : Array // start() : Boolean // stop() : Boolean // pause() : Boolean // resume() : Boolean // skipTo(index:Number) : Boolean /** * Returns the currently-playing SequenceStepCA. * @return The currently-playing SequenceStepCA. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #lastStep */ public function get currentStep() : SequenceStepCA { return (super._getCurrentStep()); } /** * Returns the final SequenceStepCA in the current sequence. * @return The final SequenceStepCA in the current sequence. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #currentStep */ public function get lastStep() : SequenceStepCA { return (super._getLastStep()); } // -== Protected Properties ==- /** * @private */ protected var _trailingSteps : SequenceStep; // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable instances (e.g. LinearGo, PlayableGroup, * SequenceStepCA) as separate arguments, or a single array of them. */ public function SequenceCA(...items) { super((items[ 0 ] is Array) ? items[ 0 ] : items); } /** * Retrieves any SequenceStepCA from the steps array. * @param index An array index starting at 0. * @return The SequenceStepCA instance at this index. * @see #getStepByID() */ public function getStepAt(index:int) : SequenceStepCA { return (super._getStepAt(index) as SequenceStepCA); } /** * Locates a step with the specified playableID. To search within a step for a * child by playableID, use the step instance'sgetChildByID
method.
*
* @param playableID The step instance's playableID to search for.
* @return The SequenceStepCA with the matching playableID.
*/
public function getStepByID(playableID:*) : SequenceStepCA {
return (super._getStepByID(playableID) as SequenceStepCA);
}
/**
* Adds a single IPlayable instance (e.g. LinearGo, PlayableGroup,
SequenceStepCA)
* to the end of the steps array, or optionally adds the instance into
the last
* SequenceStepCA instead of adding it as a new step.
*
* To remove a step use the removeStepAt method.
If the group is active when this method is called, a
stop call
* is automated which will result in a GoEvent.STOP event being
dispatched.
This class works like Sequence but uses the special class
SequenceStepCA for its steps.
* SequenceStepCA has a property called advance. When steps
advance before animation
* finishes, the trailing steps are tracked so that the SequenceCA doesn't
dispatch its COMPLETE
* event until all activity has completed.
Any step's advance property can be set to an instance of OnDurationComplete, OnPlayableComplete, * OnEventComplete or OnConditionTrue. Each of those classes defines its own parameters and rules for * when the advance occurs. For example, using OnPlayableComplete a sequence can advance after one * particular item in the step finishes, without needing to wait for all the other ones in that group * to complete.
* *Additionally, you can create your own custom advance types by subclassing the SequenceAdvance * base class.
* * @see SequenceStepCA * @see org.goasap.utils.customadvance.OnConditionTrue OnConditionTrue * @see org.goasap.utils.customadvance.OnDurationComplete OnDurationComplete * @see org.goasap.utils.customadvance.OnEventComplete OnEventComplete * @see org.goasap.utils.customadvance.OnPlayableComplete OnPlayableComplete * @see org.goasap.utils.customadvance.SequenceAdvance SequenceAdvance * @see Sequence * @see SequenceBase * * @author Moses Gunesch */ public class SequenceCA extends SequenceBase { // -== Public Properties ==- // Also in super: // length : uint [Read-only.] // playIndex : int [Read-only.] // steps : Array // start() : Boolean // stop() : Boolean // pause() : Boolean // resume() : Boolean // skipTo(index:Number) : Boolean /** * Returns the currently-playing SequenceStepCA. * @return The currently-playing SequenceStepCA. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #lastStep */ public function get currentStep() : SequenceStepCA { return (super._getCurrentStep() as SequenceStepCA); } /** * Returns the final SequenceStepCA in the current sequence. * @return The final SequenceStepCA in the current sequence. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #currentStep */ public function get lastStep() : SequenceStepCA { return (super._getLastStep() as SequenceStepCA); } // -== Protected Properties ==- /** * @private */ protected var _trailingSteps : SequenceStep; // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable instances (e.g. LinearGo, PlayableGroup, * SequenceStepCA) as separate arguments, or a single array of them. */ public function SequenceCA(...items) { ============================================================================== Diff truncated at 200k characters From gil at allflashwebsite.com Tue Mar 17 13:15:56 2009 From: gil at allflashwebsite.com (Gil Birman) Date: Tue, 17 Mar 2009 15:15:56 -0500 Subject: [Golist] Go 0.5.2 branch notes In-Reply-To:repeater.cycles is set
to
* a value other than one, just before the group starts its next play
cycle.
* @eventType org.goasap.events.CYCLE
*/
[Event(name="CYCLE", type="org.goasap.events.GoEvent")]
/**
* Dispatched if the group is manually stopped.
* @eventType org.goasap.events.STOP
*/
[Event(name="STOP", type="org.goasap.events.GoEvent")]
/**
* Dispatched after all children have dispatched a STOP or COMPLETE event.
* @eventType org.goasap.events.COMPLETE
*/
[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]
/**
* Batch-play a set of items and receive an event when all of them have
finished.
*
* PlayableGroup accepts any IPlayable for its children, which can include * tweens, other groups, sequences and so forth. The group listens for both * GoEvent.STOP and GoEvent.COMPLETE events from its children, either of which * are counted toward group completion.
* *The repeater property of PlayableGroup allows you to
loop play
* any number of times, or indefinitely by setting its cycles to
Repeater.INFINITE.
* GoEvent.CYCLE is dispatched on each loop and GoEvent.COMPLETE when
finished.
* Other events dispatched include the GoEvent types START, STOP, PAUSE,
and RESUME.
addChild and removeChild,
* setting this property will stop any group play currently in progress.
*/
public function get children():Array {
var a:Array = [];
for (var item:Object in _children)
a.push(item);
return a;
}
public function set children(a:Array):void {
if (_listeners > 0)
stop();
for each (var item:Object in a)
if (item is IPlayable)
addChild(item as IPlayable);
}
/**
* The groups's Repeater instance, which may be used to make
* it loop and play more than one time.
*
* The Repeater's cycles property can be set to an integer, or * to Repeater.INFINITE or 0 to repeat indefinitely.
* ** var group:PlayableGroup = new PlayableGroup(tween1, tween2, tween3); * group.repeater.cycles = 2; * group.start(); * trace(group.repeater.currentCycle); // output: 0 **/ public function get repeater(): Repeater { return _repeater; } /** * Determines the number of children currently being monitored * for completion by the group. */ public function get listenerCount() : uint { return _listeners; } // -== Protected Properties ==- /** @private */ protected var _children: Dictionary = new Dictionary(); /** @private */ protected var _listeners: uint = 0; /** @private */ protected var _repeater: Repeater; // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable items as separate arguments, * or a single array of them. */ public function PlayableGroup(...items) { super(); if (items.length > 0) this.children = ((items[ 0 ] is Array) ? items[ 0 ] : items); _repeater = new Repeater(); _repeater.setParent(this); } /** * Searches for a child with the specified playableID. * * @param playableID The item playableID to search for. (The item must have a * property called
playableID which is a general
* GoASAP convention established in PlayableBase.)
* @param deepSearch If child is not found in the group, this option runs
a
* recursive search on any children that are PlayableGroup.
* @return The SequenceStep with the matching playableID.
*/
public function getChildByID(playableID:*,
deepSearch:Boolean=true):IPlayable {
for (var item:Object in _children)
if (item.hasOwnProperty("playableID") &&
item["playableID"]===playableID)
return (item as IPlayable);
if (deepSearch) {
for (item in _children) {
if (item is PlayableGroup) {
var match:IPlayable = ((item as
PlayableGroup).getChildByID(playableID, true));
if (match) { return (match as IPlayable); }
}
}
}
return null;
}
/**
* Adds a single IPlayable to the children array (duplicates are
rejected) and
* syncs up the group and child play-states based on various conditions.
*
* Normally this method is called when both the group and the child item are * not playing. However in some cases either the group or the child might be * playing or paused, in which case calling this method will attempt to sync * the child state with the group's state. If syncing fails the child is not added * and false is returned.
* * @param item Any instance that implements IPlayable and uses PlayState constants. * @return The item added, or null if the item was already in the group or * a mismatched child state was not syncable to the group's state. */ public function addChild(item:IPlayable): IPlayable { var itemState:String = item.state; if (_children[ item ]!=null) { return null; // child already added! } _children[ item ] = false; if (_state==PlayStates.STOPPED && itemState!=PlayStates.STOPPED) { item.stop(); } if (itemState==_state) { return item; // states match: no further action is needed. this is most common. } // Group is active: attempt syncing of child state to group state. var success:Boolean = true; listenTo(item); if (_state==PlayStates.PLAYING || _state==PlayStates.PLAYING_DELAY) { if (itemState==PlayStates.PAUSED) { success = item.resume(); } else if (itemState==PlayStates.STOPPED) { success = item.start(); } else { return item; // Item is already playing and only delay states mismatch: Success. } } else if (_state==PlayStates.PAUSED) { if (itemState==PlayStates.STOPPED) { item.start(); } success = item.pause(); } if (!success) { unListenTo(item); delete _children[ item ]; return null; } return item; } /** * Removes a single IPlayable from the children array. * *Note that if play is in progress when a child is added it does not * interrupt play and the child is monitored for completion along with * others.
* * @param item Any instance that implements IPlayable and uses PlayState constants. * @return The item removed, or null if the call failed. */ public function removeChild(item:IPlayable): IPlayable { var v:* = _children[ item ]; if (v===null) return null; if (v===true) unListenTo( item ); delete _children[ item ]; return item; } /** * Enables already-playing items to be added to a group seamlessly by presetting * the group's state to PLAYING instead of interrupting the child item. * * @param item Any instance that implements IPlayable and uses PlayState constants. * @return The item added, or null if the call failed. */ public function addPlayingChildAndStart(item:IPlayable): IPlayable { _state = PlayStates.PLAYING; item = addChild(item); // Item will start if stopped and resume if paused. if (_listeners > 0) { dispatchEvent(new GoEvent( GoEvent.START)); _playRetainer[ this ] = 1; // Developers - Important! Look up _playRetainer. } else { _state = PlayStates.STOPPED; } return item; } /** * Test whether any child has a particular PlayState. * *
* // Example: resume a paused group
* if ( myGroup.anyChildHasState(PlayStates.PlayStates.PAUSED) ) {
* myGroup.resume();
* }
*
* @see org.goasap.PlayStates PlayStates
*/
public function anyChildHasState(state:String): Boolean {
for (var item:Object in _children)
if ((item as IPlayable).state==state)
return true;
return false;
}
// -== IPlayable implementation ==-
/**
* Calls start on all children.
*
* If the group is active when this method is called, a
stop call
* is automated which will result in a GoEVent.STOP event being
dispatched.
pause on all children.
*
* @return Returns true only if all playing children in the group paused
successfully
* and at least one child was paused.
*/
public function pause() : Boolean {
if (_state!= PlayStates.PLAYING)
return false;
var r:Boolean = true;
var n:uint = 0;
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
var success:Boolean = item.pause();
if (success) n++;
r = (r && success);
}
if (n>0) {
_state = PlayStates.PAUSED; // state should reflect that at least one
item was paused,
// while return value may indicate that not all pause calls
succeeded.
dispatchEvent(new GoEvent( GoEvent.PAUSE ));
}
return (n>0 && r);
}
/**
* Calls resume on all children.
*
* @return Returns true only if all paused children in the group resumed
successfully
* and at least one child was resumed.
*/
public function resume() : Boolean {
if (_state!= PlayStates.PAUSED)
return false;
var r:Boolean = true;
var n:uint = 0;
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
var success:Boolean = item.resume();
if (success) n++;
r = (r && success);
}
if (n>0) {
_state = PlayStates.PLAYING; // state should reflect that at least one
item was resumed,
// while return value may indicate that not all resume calls
succeeded.
dispatchEvent(new GoEvent( GoEvent.RESUME ));
}
return (n>0 && r);
}
/**
* Calls skipTo on all children.
*
* @return Returns true only if all children in the group skipTo the
position successfully
* and at least one child was affected.
*/
public function skipTo(position : Number) : Boolean {
var r:Boolean = true;
var n:uint = 0;
position = _repeater.skipTo(_repeater.cycles, position); // TODO: TEST
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
listenTo(item); // first in case of immediate STOP/COMPLETE.
var started:Boolean = item.skipTo(position);
if (!started || item.state==PlayStates.STOPPED) {
unListenTo(item);
started = false;
}
else { n++; }
r = (started && r);
}
_state = (r ? PlayStates.PLAYING : PlayStates.STOPPED);
return (n>0 && r);
}
// -== Protected Methods ==-
/**
* @private
* Internal handler for item completion.
* @param event GoEvent dispatched by child item.
*/
protected function onItemEnd(event:GoEvent) : void {
unListenTo(event.target as IPlayable);
if (_listeners==0) {
complete();
}
}
/**
* @private
* Internal handler for group completion.
*/
protected function complete() : void {
if (_repeater.next()) {
dispatchEvent(new GoEvent( GoEvent.CYCLE ));
var c:Array = this.children; // array is used because cycling
Dictionary and setting entries can result in duplicates (Dictionary bug,
apparently.)
for each (var item:IPlayable in c) {
listenTo(item); // first in case of immediate STOP/COMPLETE.
var started:Boolean = (item).start();
if (!started || item.state==PlayStates.STOPPED)
unListenTo(item);
}
}
else {
stop();
}
}
/**
* @private
* Internal. Listen for item completion, keeping tight track of listeners.
* @param item Any instance that extends IPlayable (IPlayable itself
should not be used directly).
*/
protected function listenTo(item:IPlayable) : void {
if (_children[ item ] === false) {
item.addEventListener(GoEvent.STOP, onItemEnd, false, 0, true);
item.addEventListener(GoEvent.COMPLETE, onItemEnd, false, 0, true);
_children[ item ] = true;
_listeners++;
}
}
/**
* @private
* Internal. Stop listening for item completion.
* @param item Any instance that extends IPlayable (IPlayable itself
should not be used directly).
* @return Number of completion listeners remaining.
*/
protected function unListenTo(item:IPlayable) : void {
if (_children[ item ] === true) {
item.removeEventListener(GoEvent.STOP, onItemEnd);
item.removeEventListener(GoEvent.COMPLETE, onItemEnd);
_children[ item ] = false;
_listeners--;
}
}
}
}
\ No newline at end of file
+/**
* Copyright (c) 2007 Moses Gunesch
*
* Permission is hereby granted, free of charge, to any person obtaining a
copy
* of this software and associated documentation files (the "Software"), to
deal
* in the Software without restriction, including without limitation the
rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.goasap.utils {
import org.goasap.PlayStates;
import org.goasap.PlayableBase;
import org.goasap.events.GoEvent;
import org.goasap.interfaces.IPlayable;
import org.goasap.managers.Repeater;
import flash.utils.Dictionary;
/**
* Dispatched when the group starts.
* @eventType org.goasap.events.START
*/
[Event(name="START", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the group is paused successfully.
* @eventType org.goasap.events.PAUSE
*/
[Event(name="PAUSE", type="org.goasap.events.GoEvent")]
/**
* Dispatched when the group is resumed successfully.
* @eventType org.goasap.events.RESUME
*/
[Event(name="RESUME", type="org.goasap.events.GoEvent")]
/**
* Dispatched at the end the group if repeater.cycles is set
to
* a value other than one, just before the group starts its next play
cycle.
* @eventType org.goasap.events.CYCLE
*/
[Event(name="CYCLE", type="org.goasap.events.GoEvent")]
/**
* Dispatched if the group is manually stopped.
* @eventType org.goasap.events.STOP
*/
[Event(name="STOP", type="org.goasap.events.GoEvent")]
/**
* Dispatched after all children have dispatched a STOP or COMPLETE event.
* @eventType org.goasap.events.COMPLETE
*/
[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]
/**
* Batch-play a set of items and receive an event when all of them have
finished.
*
* PlayableGroup accepts any IPlayable for its children, which can include * tweens, other groups, sequences and so forth. The group listens for both * GoEvent.STOP and GoEvent.COMPLETE events from its children, either of which * are counted toward group completion.
* *The repeater property of PlayableGroup allows you to
loop play
* any number of times, or indefinitely by setting its cycles to
Repeater.INFINITE.
* GoEvent.CYCLE is dispatched on each loop and GoEvent.COMPLETE when
finished.
* Other events dispatched include the GoEvent types START, STOP, PAUSE,
and RESUME.
addChild and removeChild,
* setting this property will stop any group play currently in progress.
*/
public function get children():Array {
var a:Array = [];
for (var item:Object in _children)
a.push(item);
return a;
}
public function set children(a:Array):void {
if (_listeners > 0)
stop();
for each (var item:Object in a)
if (item is IPlayable)
addChild(item as IPlayable);
}
/**
* The groups's Repeater instance, which may be used to make
* it loop and play more than one time.
*
* The Repeater's cycles property can be set to an integer, or * to Repeater.INFINITE or 0 to repeat indefinitely.
* ** var group:PlayableGroup = new PlayableGroup(tween1, tween2, tween3); * group.repeater.cycles = 2; * group.start(); * trace(group.repeater.currentCycle); // output: 0 **/ public function get repeater(): Repeater { return _repeater; } /** * Determines the number of children currently being monitored * for completion by the group. */ public function get listenerCount() : uint { return _listeners; } // -== Protected Properties ==- /** @private */ protected var _children: Dictionary = new Dictionary(); /** @private */ protected var _listeners: uint = 0; /** @private */ protected var _repeater: Repeater; // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable items as separate arguments, * or a single array of them. */ public function PlayableGroup(...items) { super(); if (items.length > 0) this.children = ((items[ 0 ] is Array) ? items[ 0 ] : items); _repeater = new Repeater(); _repeater.setParent(this); } /** * Searches for a child with the specified playableID. * * @param playableID The item playableID to search for. (The item must have a * property called
playableID which is a general
* GoASAP convention established in PlayableBase.)
* @param deepSearch If child is not found in the group, this option runs
a
* recursive search on any children that are PlayableGroup.
* @return The SequenceStep with the matching playableID.
*/
public function getChildByID(playableID:*,
deepSearch:Boolean=true):IPlayable {
for (var item:Object in _children)
if (item.hasOwnProperty("playableID") &&
item["playableID"]===playableID)
return (item as IPlayable);
if (deepSearch) {
for (item in _children) {
if (item is PlayableGroup) {
var match:IPlayable = ((item as
PlayableGroup).getChildByID(playableID, true));
if (match) { return (match as IPlayable); }
}
}
}
return null;
}
/**
* Adds a single IPlayable to the children array (duplicates are
rejected) and
* syncs up the group and child play-states based on various conditions.
*
* Normally this method is called when both the group and the child item are * not playing. However in some cases either the group or the child might be * playing or paused, in which case calling this method will attempt to sync * the child state with the group's state. If syncing fails the child is not added * and false is returned.
* * @param item Any instance that implements IPlayable and uses PlayState constants. * @return The item added, or null if the item was already in the group or * a mismatched child state was not syncable to the group's state. */ public function addChild(item:IPlayable): IPlayable { var itemState:String = item.state; if (_children[ item ]!=null) { return null; // child already added! } _children[ item ] = false; if (_state==PlayStates.STOPPED) { // group is inactive, sync item by stopping if necessary. if (itemState!=PlayStates.STOPPED) { item.stop(); } return item; } // Group is active: attempt syncing of child state to group state. var success:Boolean = true; listenTo(item); if (_state==PlayStates.PLAYING || _state==PlayStates.PLAYING_DELAY) { if (itemState==PlayStates.PAUSED) { success = item.resume(); } else if (itemState==PlayStates.STOPPED) { success = item.start(); } else { return item; // Item is already playing and only delay states mismatch: Success. } } else if (_state==PlayStates.PAUSED) { if (itemState==PlayStates.STOPPED) { item.start(); } success = item.pause(); } if (!success) { unListenTo(item); delete _children[ item ]; return null; } return item; } /** * Removes a single IPlayable from the children array. * *Note that if play is in progress when a child is added it does not * interrupt play and the child is monitored for completion along with * others.
* * @param item Any instance that implements IPlayable and uses PlayState constants. * @return The item removed, or null if the call failed. */ public function removeChild(item:IPlayable): IPlayable { var v:* = _children[ item ]; if (v===null) return null; if (v===true) unListenTo( item ); delete _children[ item ]; return item; } /** * Enables already-playing items to be added to a group seamlessly by presetting * the group's state to PLAYING instead of interrupting the child item. * * @param item Any instance that implements IPlayable and uses PlayState constants. * @return The item added, or null if the call failed. */ public function addPlayingChildAndStart(item:IPlayable): IPlayable { _state = PlayStates.PLAYING; item = addChild(item); // Item will start if stopped and resume if paused. if (_listeners > 0) { dispatchEvent(new GoEvent( GoEvent.START)); _playRetainer[ this ] = 1; // Developers - Important! Look up _playRetainer. } else { _state = PlayStates.STOPPED; } return item; } /** * Test whether any child has a particular PlayState. * *
* // Example: resume a paused group
* if ( myGroup.anyChildHasState(PlayStates.PlayStates.PAUSED) ) {
* myGroup.resume();
* }
*
* @see org.goasap.PlayStates PlayStates
*/
public function anyChildHasState(state:String): Boolean {
for (var item:Object in _children)
if ((item as IPlayable).state==state)
return true;
return false;
}
// -== IPlayable implementation ==-
/**
* Calls start on all children.
*
* If the group is active when this method is called, a
stop call
* is automated which will result in a GoEVent.STOP event being
dispatched.
pause on all children.
*
* @return Returns true only if all playing children in the group paused
successfully
* and at least one child was paused.
*/
public function pause() : Boolean {
if (_state!= PlayStates.PLAYING)
return false;
var r:Boolean = true;
var n:uint = 0;
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
var success:Boolean = item.pause();
if (success) n++;
r = (r && success);
}
if (n>0) {
_state = PlayStates.PAUSED; // state should reflect that at least one
item was paused,
// while return value may indicate that not all pause calls
succeeded.
dispatchEvent(new GoEvent( GoEvent.PAUSE ));
}
return (n>0 && r);
}
/**
* Calls resume on all children.
*
* @return Returns true only if all paused children in the group resumed
successfully
* and at least one child was resumed.
*/
public function resume() : Boolean {
if (_state!= PlayStates.PAUSED)
return false;
var r:Boolean = true;
var n:uint = 0;
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
var success:Boolean = item.resume();
if (success) n++;
r = (r && success);
}
if (n>0) {
_state = PlayStates.PLAYING; // state should reflect that at least one
item was resumed,
// while return value may indicate that not all resume calls
succeeded.
dispatchEvent(new GoEvent( GoEvent.RESUME ));
}
return (n>0 && r);
}
/**
* Calls skipTo on all children.
*
* @return Returns true only if all children in the group skipTo the
position successfully
* and at least one child was affected.
*/
public function skipTo(position : Number) : Boolean {
var r:Boolean = true;
var n:uint = 0;
position = _repeater.skipTo(_repeater.cycles, position); // TODO: TEST
var c:Array = this.children; // array is used because cycling Dictionary
and setting entries can result in duplicates (Dictionary bug, apparently.)
for each (var item:IPlayable in c) {
listenTo(item); // first in case of immediate STOP/COMPLETE.
var started:Boolean = item.skipTo(position);
if (!started || item.state==PlayStates.STOPPED) {
unListenTo(item);
started = false;
}
else { n++; }
r = (started && r);
}
_state = (r ? PlayStates.PLAYING : PlayStates.STOPPED);
return (n>0 && r);
}
// -== Protected Methods ==-
/**
* @private
* Internal handler for item completion.
* @param event GoEvent dispatched by child item.
*/
protected function onItemEnd(event:GoEvent) : void {
unListenTo(event.target as IPlayable);
if (_listeners==0) {
complete();
}
}
/**
* @private
* Internal handler for group completion.
*/
protected function complete() : void {
if (_repeater.next()) {
dispatchEvent(new GoEvent( GoEvent.CYCLE ));
var c:Array = this.children; // array is used because cycling
Dictionary and setting entries can result in duplicates (Dictionary bug,
apparently.)
for each (var item:IPlayable in c) {
listenTo(item); // first in case of immediate STOP/COMPLETE.
var started:Boolean = (item).start();
if (!started || item.state==PlayStates.STOPPED)
unListenTo(item);
}
}
else {
stop();
}
}
/**
* @private
* Internal. Listen for item completion, keeping tight track of listeners.
* @param item Any instance that extends IPlayable (IPlayable itself
should not be used directly).
*/
protected function listenTo(item:IPlayable) : void {
if (_children[ item ] === false) {
item.addEventListener(GoEvent.STOP, onItemEnd, false, 0, true);
item.addEventListener(GoEvent.COMPLETE, onItemEnd, false, 0, true);
_children[ item ] = true;
_listeners++;
}
}
/**
* @private
* Internal. Stop listening for item completion.
* @param item Any instance that extends IPlayable (IPlayable itself
should not be used directly).
* @return Number of completion listeners remaining.
*/
protected function unListenTo(item:IPlayable) : void {
if (_children[ item ] === true) {
item.removeEventListener(GoEvent.STOP, onItemEnd);
item.removeEventListener(GoEvent.COMPLETE, onItemEnd);
_children[ item ] = false;
_listeners--;
}
}
}
}
\ No newline at end of file
Modified: branches/goasap0.5.2/src_go/org/goasap/utils/SequenceCA.as
==============================================================================
--- branches/goasap0.5.2/src_go/org/goasap/utils/SequenceCA.as (original)
+++ branches/goasap0.5.2/src_go/org/goasap/utils/SequenceCA.as Tue Mar 17
16:22:37 2009
@@ -1 +1 @@
-/**
* Copyright (c) 2007 Moses Gunesch
*
* Permission is hereby granted, free of charge, to any person obtaining a
copy
* of this software and associated documentation files (the "Software"), to
deal
* in the Software without restriction, including without limitation the
rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.goasap.utils {
import org.goasap.PlayStates;
import org.goasap.events.GoEvent;
import org.goasap.events.SequenceEvent;
import org.goasap.interfaces.IPlayable;
import flash.events.Event;
/**
* Sequence with "Custom Advance" options, in which steps can specify when
they should advance.
*
* This class works like Sequence but uses the special class
SequenceStepCA for its steps.
* SequenceStepCA has a property called advance. When steps
advance before animation
* finishes, the trailing steps are tracked so that the SequenceCA doesn't
dispatch its COMPLETE
* event until all activity has completed.
Any step's advance property can be set to an instance of OnDurationComplete, OnPlayableComplete, * OnEventComplete or OnConditionTrue. Each of those classes defines its own parameters and rules for * when the advance occurs. For example, using OnPlayableComplete a sequence can advance after one * particular item in the step finishes, without needing to wait for all the other ones in that group * to complete.
* *Additionally, you can create your own custom advance types by subclassing the SequenceAdvance * base class.
* * @see SequenceStepCA * @see org.goasap.utils.customadvance.OnConditionTrue OnConditionTrue * @see org.goasap.utils.customadvance.OnDurationComplete OnDurationComplete * @see org.goasap.utils.customadvance.OnEventComplete OnEventComplete * @see org.goasap.utils.customadvance.OnPlayableComplete OnPlayableComplete * @see org.goasap.utils.customadvance.SequenceAdvance SequenceAdvance * @see Sequence * @see SequenceBase * * @author Moses Gunesch */ public class SequenceCA extends SequenceBase { // -== Public Properties ==- // Also in super: // length : uint [Read-only.] // playIndex : int [Read-only.] // steps : Array // start() : Boolean // stop() : Boolean // pause() : Boolean // resume() : Boolean // skipTo(index:Number) : Boolean /** * Returns the currently-playing SequenceStepCA. * @return The currently-playing SequenceStepCA. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #lastStep */ public function get currentStep() : SequenceStepCA { return (super._getCurrentStep() as SequenceStepCA); } /** * Returns the final SequenceStepCA in the current sequence. * @return The final SequenceStepCA in the current sequence. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #currentStep */ public function get lastStep() : SequenceStepCA { return (super._getLastStep() as SequenceStepCA); } // -== Protected Properties ==- /** * @private */ protected var _trailingSteps : SequenceStep; // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable instances (e.g. LinearGo, PlayableGroup, * SequenceStepCA) as separate arguments, or a single array of them. */ public function SequenceCA(...items) { super((items[ 0 ] is Array) ? items[ 0 ] : items); } /** * Retrieves any SequenceStepCA from the steps array. * @param index An array index starting at 0. * @return The SequenceStepCA instance at this index. * @see #getStepByID() */ public function getStepAt(index:int) : SequenceStepCA { return (super._getStepAt(index) as SequenceStepCA); } /** * Locates a step with the specified playableID. To search within a step for a * child by playableID, use the step instance'sgetChildByID
method.
*
* @param playableID The step instance's playableID to search for.
* @return The SequenceStepCA with the matching playableID.
*/
public function getStepByID(playableID:*) : SequenceStepCA {
return (super._getStepByID(playableID) as SequenceStepCA);
}
/**
* Adds a single SequenceStepCA or IPlayable instance (LinearGo,
PlayableGroup,
* etc.) to the end of the steps array, or optionally adds the instance
into the
* last SequenceStepCA instead of adding it as a new step. Calling this
* method stops any sequence play currently in progress.
*
* To remove a step use the removeStepAt method.
If the group is active when this method is called, a
stop call
* is automated which will result in a GoEvent.STOP event being
dispatched.
This class works like Sequence but uses the special class
SequenceStepCA for its steps.
* SequenceStepCA has a property called advance. When steps
advance before animation
* finishes, the trailing steps are tracked so that the SequenceCA doesn't
dispatch its COMPLETE
* event until all activity has completed.
Any step's advance property can be set to an instance of OnDurationComplete, OnPlayableComplete, * OnEventComplete or OnConditionTrue. Each of those classes defines its own parameters and rules for * when the advance occurs. For example, using OnPlayableComplete a sequence can advance after one * particular item in the step finishes, without needing to wait for all the other ones in that group * to complete.
* *Additionally, you can create your own custom advance types by subclassing the SequenceAdvance * base class.
* * @see SequenceStepCA * @see org.goasap.utils.customadvance.OnConditionTrue OnConditionTrue * @see org.goasap.utils.customadvance.OnDurationComplete OnDurationComplete * @see org.goasap.utils.customadvance.OnEventComplete OnEventComplete * @see org.goasap.utils.customadvance.OnPlayableComplete OnPlayableComplete * @see org.goasap.utils.customadvance.SequenceAdvance SequenceAdvance * @see Sequence * @see SequenceBase * * @author Moses Gunesch */ public class SequenceCA extends SequenceBase { // -== Public Properties ==- // Also in super: // length : uint [Read-only.] // playIndex : int [Read-only.] // steps : Array // start() : Boolean // stop() : Boolean // pause() : Boolean // resume() : Boolean // skipTo(index:Number) : Boolean /** * Returns the currently-playing SequenceStepCA. * @return The currently-playing SequenceStepCA. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #lastStep */ public function get currentStep() : SequenceStepCA { return (super._getCurrentStep() as SequenceStepCA); } /** * Returns the final SequenceStepCA in the current sequence. * @return The final SequenceStepCA in the current sequence. * @see #getStepAt() * @see #getStepByID() * @see #steps * @see #currentStep */ public function get lastStep() : SequenceStepCA { return (super._getLastStep() as SequenceStepCA); } // -== Protected Properties ==- /** * @private */ protected var _trailingSteps : SequenceStep; // -== Public Methods ==- /** * Constructor. * * @param items Any number of IPlayable instances (e.g. LinearGo, PlayableGroup, * SequenceStepCA) as separate arguments, or a single array of them. */ public function SequenceCA(...items) { super((items[ 0 ] is Array) ? items[ 0 ] : items); } /** * Retrieves any SequenceStepCA from the steps array. * @param index An array index starting at 0. * @return The SequenceStepCA instance at this index. * @see #getStepByID() */ public function getStepAt(index:int) : SequenceStepCA { return (super._getStepAt(index) as SequenceStepCA); } /** * Locates a step with the specified playableID. To search within a step for a * child by playableID, use the step instance'sgetChildByID
method.
*
* @param playableID The step instance's playableID to search for.
* @return The SequenceStepCA with the matching playableID.
*/
public function getStepByID(playableID:*) : SequenceStepCA {
return (super._getStepByID(playableID) as SequenceStepCA);
}
/**
* Adds a single SequenceStepCA or IPlayable instance (LinearGo,
PlayableGroup,
* etc.) to the end of the steps array, or optionally adds the instance
into the
* last SequenceStepCA instead of adding it as a new step. Calling this
* method stops any sequence play currently in progress.
*
* To remove a step use the removeStepAt method.
If the group is active when this method is called, a
stop call
* is automated which will result in a GoEvent.STOP event being
dispatched.