From c4e8e4fd04ef4583f9d2cee405697fac8ee0d678 Mon Sep 17 00:00:00 2001 From: Chris Speciale Date: Wed, 10 Jul 2024 09:06:37 -0400 Subject: [PATCH] Formatting and haxe 3.4.7 compat --- src/starling/animation/Juggler.hx | 776 +++--- src/starling/core/Starling.hx | 2312 +++++++++-------- .../display/DisplayObjectContainer.hx | 18 +- src/starling/display/Stage.hx | 674 +++-- src/starling/utils/ArrayUtil.hx | 73 +- 5 files changed, 1939 insertions(+), 1914 deletions(-) diff --git a/src/starling/animation/Juggler.hx b/src/starling/animation/Juggler.hx index 6fe9c7cf..2c04e722 100644 --- a/src/starling/animation/Juggler.hx +++ b/src/starling/animation/Juggler.hx @@ -7,13 +7,10 @@ // in accordance with the terms of the accompanying license agreement. // // ================================================================================================= - package starling.animation; import haxe.Constraints.Function; - import openfl.errors.ArgumentError; - import starling.events.Event; import starling.events.EventDispatcher; import starling.utils.ArrayUtil; @@ -48,396 +45,385 @@ import starling.utils.ArrayUtil; * @see Tween * @see DelayedCall */ -class Juggler implements IAnimatable -{ - @:noCompletion private var __objects:Array; - @:noCompletion private var __objectIDs:Map; - @:noCompletion private var __elapsedTime:Float; - @:noCompletion private var __timeScale:Float; - - private static var sCurrentObjectID:UInt = 0; - private static var sTweenInstanceFields:Array; - - #if commonjs - private static function __init__ () { - - untyped Object.defineProperties (Juggler.prototype, { - "elapsedTime": { get: untyped __js__ ("function () { return this.get_elapsedTime (); }") }, - "timeScale": { get: untyped __js__ ("function () { return this.get_timeScale (); }"), set: untyped __js__ ("function (v) { return this.set_timeScale (v); }") }, - "objects": { get: untyped __js__ ("function () { return this.get_objects (); }") }, - }); - - } - #end - - /** Create an empty juggler. */ - public function new() - { - __elapsedTime = 0; - __timeScale = 1.0; - __objects = new Array(); - __objectIDs = new Map(); - } - - /** Adds an object to the juggler. - * - * @return Unique numeric identifier for the animation. This identifier may be used - * to remove the object via removeByID(). - */ - public function add(object:IAnimatable):UInt - { - return addWithID(object, getNextID()); - } - - public function addWithID(object:IAnimatable, objectID:UInt):UInt - { - if (object != null && !__objectIDs.exists(object)) - { - var dispatcher:EventDispatcher = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(object, EventDispatcher) ? cast object : null; - if (dispatcher != null) dispatcher.addEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); - - __objects[__objects.length] = object; - __objectIDs[object] = objectID; - - return objectID; - } - else return 0; - } - - /** Determines if an object has been added to the juggler. */ - public function contains(object:IAnimatable):Bool - { - return __objectIDs.exists(object); - } - - /** Removes an object from the juggler. - * - * @return The (now meaningless) unique numeric identifier for the animation, or zero - * if the object was not found. - */ - public function remove(object:IAnimatable):UInt - { - var objectID:UInt = 0; - - if (object != null && __objectIDs.exists(object)) - { - var dispatcher:EventDispatcher = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(object, EventDispatcher) ? cast object : null; - if (dispatcher != null) dispatcher.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); - - var index:Int = __objects.indexOf(object); - __objects[index] = null; - - objectID = __objectIDs[object]; - __objectIDs.remove(object); - } - - return objectID; - } - - /** Removes an object from the juggler, identified by the unique numeric identifier you - * received when adding it. - * - *

It's not uncommon that an animatable object is added to a juggler repeatedly, - * e.g. when using an object-pool. Thus, when using the remove method, - * you might accidentally remove an object that has changed its context. By using - * removeByID instead, you can be sure to avoid that, since the objectID - * will always be unique.

- * - * @return if successful, the passed objectID; if the object was not found, zero. - */ - public function removeByID(objectID:UInt):UInt - { - var object:IAnimatable; - var i = __objects.length - 1; - while (i >= 0) - { - object = __objects[i]; - - if (object != null && __objectIDs[object] == objectID) - { - remove(object); - return objectID; - } - - --i; - } - - return 0; - } - - /** Removes all tweens with a certain target. */ - public function removeTweens(target:Dynamic):Void - { - if (target == null) return; - - var i:Int = __objects.length - 1; - while (i >= 0) - { - var tween:Tween = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(__objects[i], Tween) ? cast __objects[i] : null; - if (tween != null && tween.target == target) - { - tween.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); - __objects[i] = null; - __objectIDs.remove(tween); - } - --i; - } - } - - /** Removes all delayed and repeated calls with a certain callback. */ - public function removeDelayedCalls(callback:Function):Void - { - if (callback == null) return; - - var delayedCall:DelayedCall; - var i = __objects.length - 1; - while (i >= 0) - { - delayedCall = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(__objects[i], DelayedCall) ? cast __objects[i] : null; - if (delayedCall != null && delayedCall.__callback == callback) - { - delayedCall.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); - __objects[i] = null; - __objectIDs.remove(delayedCall); - } - --i; - } - } - - /** Figures out if the juggler contains one or more tweens with a certain target. */ - public function containsTweens(target:Dynamic):Bool - { - if (target != null) - { - var tween:Tween; - var i:Int = __objects.length - 1; - while (i >= 0) - { - tween = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(__objects[i], Tween) ? cast __objects[i] : null; - if (tween != null && tween.target == target) return true; - --i; - } - } - - return false; - } - - /** Figures out if the juggler contains one or more delayed calls with a certain callback. */ - public function containsDelayedCalls(callback:Function):Bool - { - if (callback != null) - { - var delayedCall:DelayedCall; - var i = __objects.length - 1; - while (i >= 0) - { - delayedCall = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(__objects[i], DelayedCall) ? cast __objects[i] : null; - if (delayedCall != null && delayedCall.__callback == callback) return true; - --i; - } - } - - return false; - } - - /** Removes all objects at once. */ - public function purge():Void - { - // the object Array is not purged right away, because if this method is called - // from an 'advanceTime' call, this would make the loop crash. Instead, the - // Array is filled with 'null' values. They will be cleaned up on the next call - // to 'advanceTime'. - - var object:IAnimatable, dispatcher:EventDispatcher; - var i:Int = __objects.length - 1; - while (i >= 0) - { - object = __objects[i]; - if (object != null) { - dispatcher = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(object, EventDispatcher) ? cast object : null; - if (dispatcher != null) dispatcher.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); - __objects[i] = null; - __objectIDs.remove(object); - } - --i; - } - } - - /** Delays the execution of a function until delay seconds have passed. - * This method provides a convenient alternative for creating and adding a DelayedCall - * manually. - * - * @return Unique numeric identifier for the delayed call. This identifier may be used - * to remove the object via removeByID(). - */ - public function delayCall(call:Function, delay:Float, args:Array = null):UInt - { - if (call == null) throw new ArgumentError("call must not be null"); - if (args == null) args = []; - - var delayedCall:DelayedCall = DelayedCall.fromPool(call, delay, args); - delayedCall.addEventListener(Event.REMOVE_FROM_JUGGLER, onPooledDelayedCallComplete); - return add(delayedCall); - } - - /** Runs a function at a specified interval (in seconds). A 'repeatCount' of zero - * means that it runs indefinitely. - * - * @return Unique numeric identifier for the delayed call. This identifier may be used - * to remove the object via removeByID(). - */ - public function repeatCall(call:Function, interval:Float, repeatCount:Int=0, args:Array = null):UInt - { - if (call == null) throw new ArgumentError("call must not be null"); - if (args == null) args = []; - - var delayedCall:DelayedCall = DelayedCall.fromPool(call, interval, args); - delayedCall.repeatCount = repeatCount; - delayedCall.addEventListener(Event.REMOVE_FROM_JUGGLER, onPooledDelayedCallComplete); - return add(delayedCall); - } - - private function onPooledDelayedCallComplete(event:Event):Void - { - DelayedCall.toPool(cast event.target); - } - - /** Utilizes a tween to animate the target object over time seconds. Internally, - * this method uses a tween instance (taken from an object pool) that is added to the - * juggler right away. This method provides a convenient alternative for creating - * and adding a tween manually. - * - *

Fill 'properties' with key-value pairs that describe both the - * tween and the animation target. Here is an example:

- * - *
-     *  juggler.tween(object, 2.0, {
-     *      transition: Transitions.EASE_IN_OUT,
-     *      delay: 20, // -> tween.delay = 20
-     *      x: 50      // -> tween.animate("x", 50)
-     *  });
-     *  
- * - *

To cancel the tween, call 'Juggler.removeTweens' with the same target, or pass - * the returned ID to 'Juggler.removeByID()'.

- * - *

Note that some property types may be animated in a special way:

- *
    - *
  • If the property contains the string color or Color, - * it will be treated as an unsigned integer with a color value - * (e.g. 0xff0000 for red). Each color channel will be animated - * individually.
  • - *
  • The same happens if you append the string #rgb to the name.
  • - *
  • If you append #rad, the property is treated as an angle in radians, - * making sure it always uses the shortest possible arc for the rotation.
  • - *
  • The string #deg does the same for angles in degrees.
  • - *
- */ - public function tween(target:Dynamic, time:Float, properties:Dynamic):UInt - { - if (target == null) throw new ArgumentError("target must not be null"); - - var tween:Tween = Tween.fromPool(target, time); - var value:Dynamic; - - if (sTweenInstanceFields == null) sTweenInstanceFields = Type.getInstanceFields (Tween); - - for (property in Reflect.fields(properties)) - { - value = Reflect.field(properties, property); - - if (sTweenInstanceFields.indexOf ("set_" + property) != -1) - Reflect.setProperty(tween, property, value); - else if (Reflect.hasField(target, property) || Reflect.getProperty(target, property) != null) - tween.animate(property, value); - else - throw new ArgumentError("Invalid property: " + property); - } - - tween.addEventListener(Event.REMOVE_FROM_JUGGLER, onPooledTweenComplete); - return add(tween); - } - - private function onPooledTweenComplete(event:Event):Void - { - Tween.toPool(cast event.target); - } - - /** Advances all objects by a certain time (in seconds). */ - public function advanceTime(time:Float):Void - { - var numObjects:Int = __objects.length; - var currentIndex:Int = 0; - var i:Int = 0; - - __elapsedTime += time; - time *= __timeScale; - - if (numObjects == 0 || time == 0) return; - - // there is a high probability that the "advanceTime" function modifies the list - // of animatables. we must not process new objects right now (they will be processed - // in the next frame), and we need to clean up any empty slots in the list. - - var object:IAnimatable; - while (i < numObjects) - { - object = __objects[i]; - if (object != null) - { - // shift objects into empty slots along the way - if (currentIndex != i) - { - __objects[currentIndex] = object; - __objects[i] = null; - } - - object.advanceTime(time); - ++currentIndex; - } - ++i; - } - - if (currentIndex != i) - { - numObjects = __objects.length; // count might have changed! - - while (i < numObjects) - __objects[currentIndex++] = __objects[i++]; - #if (haxe_ver >= 4.0) - __objects.resize(currentIndex); - #else - ArrayUtil.resize(__objects, currentIndex); - #end - } - } - - private function onRemove(event:Event):Void - { - var objectID:UInt = remove(cast event.target); - - if (objectID != 0) - { - var tween:Tween = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(event.target, Tween) ? cast event.target : null; - if (tween != null && tween.isComplete) - addWithID(tween.nextTween, objectID); - } - } - - private static function getNextID():UInt { return ++sCurrentObjectID; } - - /** The total life time of the juggler (in seconds). */ - public var elapsedTime(get, never):Float; - private function get_elapsedTime():Float { return __elapsedTime; } - - /** The scale at which the time is passing. This can be used for slow motion or time laps - * effects. Values below '1' will make all animations run slower, values above '1' faster. - * @default 1.0 */ - public var timeScale(get, set):Float; - private function get_timeScale():Float { return __timeScale; } - private function set_timeScale(value:Float):Float { return __timeScale = value; } - - /** The actual Array that contains all objects that are currently being animated. */ - private var objects(get, never):Array; - private function get_objects():Array { return __objects; } +class Juggler implements IAnimatable { + @:noCompletion private var __objects:Array; + @:noCompletion private var __objectIDs:Map; + @:noCompletion private var __elapsedTime:Float; + @:noCompletion private var __timeScale:Float; + + private static var sCurrentObjectID:UInt = 0; + private static var sTweenInstanceFields:Array; + + #if commonjs + private static function __init__() { + untyped Object.defineProperties(Juggler.prototype, { + "elapsedTime": {get: untyped __js__("function () { return this.get_elapsedTime (); }")}, + "timeScale": {get: untyped __js__("function () { return this.get_timeScale (); }"), + set: untyped __js__("function (v) { return this.set_timeScale (v); }")}, + "objects": {get: untyped __js__("function () { return this.get_objects (); }")}, + }); + } + #end + + /** Create an empty juggler. */ + public function new() { + __elapsedTime = 0; + __timeScale = 1.0; + __objects = new Array(); + __objectIDs = new Map(); + } + + /** Adds an object to the juggler. + * + * @return Unique numeric identifier for the animation. This identifier may be used + * to remove the object via removeByID(). + */ + public function add(object:IAnimatable):UInt { + return addWithID(object, getNextID()); + } + + public function addWithID(object:IAnimatable, objectID:UInt):UInt { + if (object != null && !__objectIDs.exists(object)) { + var dispatcher:EventDispatcher = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (object, EventDispatcher) ? cast object : null; + if (dispatcher != null) + dispatcher.addEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); + + __objects[__objects.length] = object; + __objectIDs[object] = objectID; + + return objectID; + } else + return 0; + } + + /** Determines if an object has been added to the juggler. */ + public function contains(object:IAnimatable):Bool { + return __objectIDs.exists(object); + } + + /** Removes an object from the juggler. + * + * @return The (now meaningless) unique numeric identifier for the animation, or zero + * if the object was not found. + */ + public function remove(object:IAnimatable):UInt { + var objectID:UInt = 0; + + if (object != null && __objectIDs.exists(object)) { + var dispatcher:EventDispatcher = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (object, EventDispatcher) ? cast object : null; + if (dispatcher != null) + dispatcher.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); + + var index:Int = __objects.indexOf(object); + __objects[index] = null; + + objectID = __objectIDs[object]; + __objectIDs.remove(object); + } + + return objectID; + } + + /** Removes an object from the juggler, identified by the unique numeric identifier you + * received when adding it. + * + *

It's not uncommon that an animatable object is added to a juggler repeatedly, + * e.g. when using an object-pool. Thus, when using the remove method, + * you might accidentally remove an object that has changed its context. By using + * removeByID instead, you can be sure to avoid that, since the objectID + * will always be unique.

+ * + * @return if successful, the passed objectID; if the object was not found, zero. + */ + public function removeByID(objectID:UInt):UInt { + var object:IAnimatable; + var i = __objects.length - 1; + while (i >= 0) { + object = __objects[i]; + + if (object != null && __objectIDs[object] == objectID) { + remove(object); + return objectID; + } + + --i; + } + + return 0; + } + + /** Removes all tweens with a certain target. */ + public function removeTweens(target:Dynamic):Void { + if (target == null) + return; + + var i:Int = __objects.length - 1; + while (i >= 0) { + var tween:Tween = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (__objects[i], Tween) ? cast __objects[i] : null; + if (tween != null && tween.target == target) { + tween.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); + __objects[i] = null; + __objectIDs.remove(tween); + } + --i; + } + } + + /** Removes all delayed and repeated calls with a certain callback. */ + public function removeDelayedCalls(callback:Function):Void { + if (callback == null) + return; + + var delayedCall:DelayedCall; + var i = __objects.length - 1; + while (i >= 0) { + delayedCall = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (__objects[i], DelayedCall) ? cast __objects[i] : null; + if (delayedCall != null && delayedCall.__callback == callback) { + delayedCall.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); + __objects[i] = null; + __objectIDs.remove(delayedCall); + } + --i; + } + } + + /** Figures out if the juggler contains one or more tweens with a certain target. */ + public function containsTweens(target:Dynamic):Bool { + if (target != null) { + var tween:Tween; + var i:Int = __objects.length - 1; + while (i >= 0) { + tween = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (__objects[i], Tween) ? cast __objects[i] : null; + if (tween != null && tween.target == target) + return true; + --i; + } + } + + return false; + } + + /** Figures out if the juggler contains one or more delayed calls with a certain callback. */ + public function containsDelayedCalls(callback:Function):Bool { + if (callback != null) { + var delayedCall:DelayedCall; + var i = __objects.length - 1; + while (i >= 0) { + delayedCall = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (__objects[i], DelayedCall) ? cast __objects[i] : null; + if (delayedCall != null && delayedCall.__callback == callback) + return true; + --i; + } + } + + return false; + } + + /** Removes all objects at once. */ + public function purge():Void { + // the object Array is not purged right away, because if this method is called + // from an 'advanceTime' call, this would make the loop crash. Instead, the + // Array is filled with 'null' values. They will be cleaned up on the next call + // to 'advanceTime'. + + var object:IAnimatable, dispatcher:EventDispatcher; + var i:Int = __objects.length - 1; + while (i >= 0) { + object = __objects[i]; + if (object != null) { + dispatcher = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (object, EventDispatcher) ? cast object : null; + if (dispatcher != null) + dispatcher.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); + __objects[i] = null; + __objectIDs.remove(object); + } + --i; + } + } + + /** Delays the execution of a function until delay seconds have passed. + * This method provides a convenient alternative for creating and adding a DelayedCall + * manually. + * + * @return Unique numeric identifier for the delayed call. This identifier may be used + * to remove the object via removeByID(). + */ + public function delayCall(call:Function, delay:Float, args:Array = null):UInt { + if (call == null) + throw new ArgumentError("call must not be null"); + if (args == null) + args = []; + + var delayedCall:DelayedCall = DelayedCall.fromPool(call, delay, args); + delayedCall.addEventListener(Event.REMOVE_FROM_JUGGLER, onPooledDelayedCallComplete); + return add(delayedCall); + } + + /** Runs a function at a specified interval (in seconds). A 'repeatCount' of zero + * means that it runs indefinitely. + * + * @return Unique numeric identifier for the delayed call. This identifier may be used + * to remove the object via removeByID(). + */ + public function repeatCall(call:Function, interval:Float, repeatCount:Int = 0, args:Array = null):UInt { + if (call == null) + throw new ArgumentError("call must not be null"); + if (args == null) + args = []; + + var delayedCall:DelayedCall = DelayedCall.fromPool(call, interval, args); + delayedCall.repeatCount = repeatCount; + delayedCall.addEventListener(Event.REMOVE_FROM_JUGGLER, onPooledDelayedCallComplete); + return add(delayedCall); + } + + private function onPooledDelayedCallComplete(event:Event):Void { + DelayedCall.toPool(cast event.target); + } + + /** Utilizes a tween to animate the target object over time seconds. Internally, + * this method uses a tween instance (taken from an object pool) that is added to the + * juggler right away. This method provides a convenient alternative for creating + * and adding a tween manually. + * + *

Fill 'properties' with key-value pairs that describe both the + * tween and the animation target. Here is an example:

+ * + *
+	 *  juggler.tween(object, 2.0, {
+	 *      transition: Transitions.EASE_IN_OUT,
+	 *      delay: 20, // -> tween.delay = 20
+	 *      x: 50      // -> tween.animate("x", 50)
+	 *  });
+	 *  
+ * + *

To cancel the tween, call 'Juggler.removeTweens' with the same target, or pass + * the returned ID to 'Juggler.removeByID()'.

+ * + *

Note that some property types may be animated in a special way:

+ *
    + *
  • If the property contains the string color or Color, + * it will be treated as an unsigned integer with a color value + * (e.g. 0xff0000 for red). Each color channel will be animated + * individually.
  • + *
  • The same happens if you append the string #rgb to the name.
  • + *
  • If you append #rad, the property is treated as an angle in radians, + * making sure it always uses the shortest possible arc for the rotation.
  • + *
  • The string #deg does the same for angles in degrees.
  • + *
+ */ + public function tween(target:Dynamic, time:Float, properties:Dynamic):UInt { + if (target == null) + throw new ArgumentError("target must not be null"); + + var tween:Tween = Tween.fromPool(target, time); + var value:Dynamic; + + if (sTweenInstanceFields == null) + sTweenInstanceFields = Type.getInstanceFields(Tween); + + for (property in Reflect.fields(properties)) { + value = Reflect.field(properties, property); + + if (sTweenInstanceFields.indexOf("set_" + property) != -1) + Reflect.setProperty(tween, property, value); + else if (Reflect.hasField(target, property) || Reflect.getProperty(target, property) != null) + tween.animate(property, value); + else + throw new ArgumentError("Invalid property: " + property); + } + + tween.addEventListener(Event.REMOVE_FROM_JUGGLER, onPooledTweenComplete); + return add(tween); + } + + private function onPooledTweenComplete(event:Event):Void { + Tween.toPool(cast event.target); + } + + /** Advances all objects by a certain time (in seconds). */ + public function advanceTime(time:Float):Void { + var numObjects:Int = __objects.length; + var currentIndex:Int = 0; + var i:Int = 0; + + __elapsedTime += time; + time *= __timeScale; + + if (numObjects == 0 || time == 0) + return; + + // there is a high probability that the "advanceTime" function modifies the list + // of animatables. we must not process new objects right now (they will be processed + // in the next frame), and we need to clean up any empty slots in the list. + + var object:IAnimatable; + while (i < numObjects) { + object = __objects[i]; + if (object != null) { + // shift objects into empty slots along the way + if (currentIndex != i) { + __objects[currentIndex] = object; + __objects[i] = null; + } + + object.advanceTime(time); + ++currentIndex; + } + ++i; + } + + if (currentIndex != i) { + numObjects = __objects.length; // count might have changed! + + while (i < numObjects) + __objects[currentIndex++] = __objects[i++]; + #if (haxe_ver >= 4.0) + __objects.resize(currentIndex); + #else + ArrayUtil.resize(__objects, currentIndex); + #end + } + } + + private function onRemove(event:Event):Void { + var objectID:UInt = remove(cast event.target); + + if (objectID != 0) { + var tween:Tween = #if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (event.target, Tween) ? cast event.target : null; + if (tween != null && tween.isComplete) + addWithID(tween.nextTween, objectID); + } + } + + private static function getNextID():UInt { + return ++sCurrentObjectID; + } + + /** The total life time of the juggler (in seconds). */ + public var elapsedTime(get, never):Float; + + private function get_elapsedTime():Float { + return __elapsedTime; + } + + /** The scale at which the time is passing. This can be used for slow motion or time laps + * effects. Values below '1' will make all animations run slower, values above '1' faster. + * @default 1.0 */ + public var timeScale(get, set):Float; + + private function get_timeScale():Float { + return __timeScale; + } + + private function set_timeScale(value:Float):Float { + return __timeScale = value; + } + + /** The actual Array that contains all objects that are currently being animated. */ + private var objects(get, never):Array; + + private function get_objects():Array { + return __objects; + } } diff --git a/src/starling/core/Starling.hx b/src/starling/core/Starling.hx index b35d216a..d03b1c74 100644 --- a/src/starling/core/Starling.hx +++ b/src/starling/core/Starling.hx @@ -7,12 +7,10 @@ // in accordance with the terms of the accompanying license agreement. // // ================================================================================================= - package starling.core; import haxe.macro.Compiler; import haxe.Timer; - import openfl.display.DisplayObjectContainer; import openfl.display.Shape; import openfl.display.Sprite; @@ -47,7 +45,6 @@ import openfl.ui.Multitouch; import openfl.ui.MultitouchInputMode; import openfl.utils.ByteArray; import openfl.Lib; - import starling.animation.Juggler; import starling.display.DisplayObject; import starling.display.Stage; @@ -64,18 +61,14 @@ import starling.utils.RectangleUtil; import starling.utils.SystemUtil; /** Dispatched when a new render context is created. The 'data' property references the context. */ -@:meta(Event(name="context3DCreate", type="starling.events.Event")) - +@:meta(Event(name = "context3DCreate", type = "starling.events.Event")) /** Dispatched when the root class has been created. The 'data' property references that object. */ -@:meta(Event(name="rootCreated", type="starling.events.Event")) - +@:meta(Event(name = "rootCreated", type = "starling.events.Event")) /** Dispatched when a fatal error is encountered. The 'data' property contains an error string. */ -@:meta(Event(name="fatalError", type="starling.events.Event")) - +@:meta(Event(name = "fatalError", type = "starling.events.Event")) /** Dispatched when the display list is about to be rendered. This event provides the last * opportunity to make changes before the display list is rendered. */ -@:meta(Event(name="render", type="starling.events.Event")) - +@:meta(Event(name = "render", type = "starling.events.Event")) /** The Starling class represents the core of the Starling framework. * *

The Starling framework makes it possible to create 2D applications and games that make @@ -208,1143 +201,1184 @@ import starling.utils.SystemUtil; * @see starling.textures.Texture * */ - @:access(starling.display.Stage) - -class Starling extends EventDispatcher -{ - /** The version of the Starling framework. */ - public static var VERSION:String = Compiler.getDefine("starling"); - - // members - - @:noCompletion private var __stage:Stage; // starling.display.stage! - @:noCompletion private var __rootClass:Class; - @:noCompletion private var __root:DisplayObject; - @:noCompletion private var __juggler:Juggler; - @:noCompletion private var __painter:Painter; - @:noCompletion private var __touchProcessor:TouchProcessor; - @:noCompletion private var __antiAliasing:Int; - @:noCompletion private var __frameTimestamp:Float; - @:noCompletion private var __frameID:UInt; - @:noCompletion private var __leftMouseDown:Bool; - @:noCompletion private var __statsDisplay:StatsDisplay; - @:noCompletion private var __statsDisplayAlign:openfl.utils.Object; - @:noCompletion private var __started:Bool; - @:noCompletion private var __rendering:Bool; - @:noCompletion private var __supportHighResolutions:Bool; - @:noCompletion private var __supportBrowserZoom:Bool; - @:noCompletion private var __skipUnchangedFrames:Bool; - @:noCompletion private var __showStats:Bool; - @:noCompletion private var __supportsCursor:Bool; - @:noCompletion private var __multitouchEnabled:Bool; - - @:noCompletion private var __viewPort:Rectangle; - @:noCompletion private var __previousViewPort:Rectangle; - @:noCompletion private var __clippedViewPort:Rectangle; - - @:noCompletion private var __nativeStage:OpenFLStage; - @:noCompletion private var __nativeStageEmpty:Bool; - @:noCompletion private var __nativeOverlay:Sprite; - @:noCompletion private var __nativeOverlayBlocksTouches:Bool; - - private static var sCurrent:Starling; - private static var sAll:Array = new Array(); - - #if commonjs - private static function __init__ () { - - untyped Object.defineProperties (Starling.prototype, { - "touchEventTypes": { get: untyped __js__ ("function () { return this.get_touchEventTypes (); }") }, - "mustAlwaysRender": { get: untyped __js__ ("function () { return this.get_mustAlwaysRender (); }") }, - "isStarted": { get: untyped __js__ ("function () { return this.get_isStarted (); }") }, - "juggler": { get: untyped __js__ ("function () { return this.get_juggler (); }") }, - "context": { get: untyped __js__ ("function () { return this.get_context (); }"), set: untyped __js__ ("function (v) { return this.set_context (v); }") }, - "simulateMultitouch": { get: untyped __js__ ("function () { return this.get_simulateMultitouch (); }"), set: untyped __js__ ("function (v) { return this.set_simulateMultitouch (v); }") }, - "enableErrorChecking": { get: untyped __js__ ("function () { return this.get_enableErrorChecking (); }"), set: untyped __js__ ("function (v) { return this.set_enableErrorChecking (v); }") }, - "antiAliasing": { get: untyped __js__ ("function () { return this.get_antiAliasing (); }"), set: untyped __js__ ("function (v) { return this.set_antiAliasing (v); }") }, - "viewPort": { get: untyped __js__ ("function () { return this.get_viewPort (); }"), set: untyped __js__ ("function (v) { return this.set_viewPort (v); }") }, - "contentScaleFactor": { get: untyped __js__ ("function () { return this.get_contentScaleFactor (); }") }, - "nativeOverlay": { get: untyped __js__ ("function () { return this.get_nativeOverlay (); }") }, - "nativeOverlayBlocksTouches": { get: untyped __js__ ("function () { return this.get_nativeOverlayBlocksTouches (); }"), set: untyped __js__ ("function (v) { return this.set_nativeOverlayBlocksTouches (v); }") }, - "showStats": { get: untyped __js__ ("function () { return this.get_showStats (); }"), set: untyped __js__ ("function (v) { return this.set_showStats (v); }") }, - "stage": { get: untyped __js__ ("function () { return this.get_stage (); }") }, - "stage3D": { get: untyped __js__ ("function () { return this.get_stage3D (); }") }, - "nativeStage": { get: untyped __js__ ("function () { return this.get_nativeStage (); }") }, - "root": { get: untyped __js__ ("function () { return this.get_root (); }") }, - "rootClass": { get: untyped __js__ ("function () { return this.get_rootClass (); }"), set: untyped __js__ ("function (v) { return this.set_rootClass (v); }") }, - "shareContext": { get: untyped __js__ ("function () { return this.get_shareContext (); }"), set: untyped __js__ ("function (v) { return this.set_shareContext (v); }") }, - "profile": { get: untyped __js__ ("function () { return this.get_profile (); }") }, - "supportHighResolutions": { get: untyped __js__ ("function () { return this.get_supportHighResolutions (); }"), set: untyped __js__ ("function (v) { return this.set_supportHighResolutions (v); }") }, - "skipUnchangedFrames": { get: untyped __js__ ("function () { return this.get_skipUnchangedFrames (); }"), set: untyped __js__ ("function (v) { return this.set_skipUnchangedFrames (v); }") }, - "touchProcessor": { get: untyped __js__ ("function () { return this.get_touchProcessor (); }"), set: untyped __js__ ("function (v) { return this.set_touchProcessor (v); }") }, - "discardSystemGestures": { get: untyped __js__ ("function () { return this.get_discardSystemGestures (); }"), set: untyped __js__ ("function (v) { return this.set_discardSystemGestures (v); }") }, - "frameID": { get: untyped __js__ ("function () { return this.get_frameID (); }") }, - "contextValid": { get: untyped __js__ ("function () { return this.get_contextValid (); }") }, - }); - - untyped Object.defineProperties (Starling, { - "current": { get: untyped __js__ ("function () { return Starling.get_current (); }") }, - "all": { get: untyped __js__ ("function () { return Starling.get_all (); }") }, - "contentScaleFactor": { get: untyped __js__ ("function () { return Starling.get_contentScaleFactor (); }") }, - "multitouchEnabled": { get: untyped __js__ ("function () { return Starling.get_multitouchEnabled (); }"), set: untyped __js__ ("function (v) { return Starling.set_multitouchEnabled (v); }") }, - }); - - } - #end - - // construction - - /** Creates a new Starling instance. - * @param rootClass A subclass of 'starling.display.DisplayObject'. It will be created - * as soon as initialization is finished and will become the first child - * of the Starling stage. Pass null if you don't want to - * create a root object right away. (You can use the - * rootClass property later to make that happen.) - * @param stage The Flash (2D) stage. - * @param viewPort A rectangle describing the area into which the content will be - * rendered. Default: stage size - * @param stage3D The Stage3D object into which the content will be rendered. If it - * already contains a context, sharedContext will be set - * to true. Default: the first available Stage3D. - * @param renderMode The Context3D render mode that should be requested. - * Use this parameter if you want to force "software" rendering. - * @param profile The Context3D profile that should be requested. - * - *

    - *
  • If you pass a profile String, this profile is enforced.
  • - *
  • Pass an Array of profiles to make Starling pick the first - * one that works (starting with the first array element).
  • - *
  • Pass the String "auto" to make Starling pick the best available - * profile automatically.
  • - *
- */ - public function new(rootClass:Class, stage:openfl.display.Stage, - viewPort:Rectangle=null, stage3D:Stage3D=null, - renderMode:Context3DRenderMode=AUTO, profile:Dynamic="auto", sharedContext:Null=null) - { - super(); - - if (stage == null) throw new ArgumentError("Stage must not be null"); - if (viewPort == null) viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); - if (stage3D == null) stage3D = stage.stage3Ds[0]; - - // TODO it might make sense to exchange the 'renderMode' and 'profile' parameters. - - SystemUtil.initialize(); - sAll.push(this); - makeCurrent(); - - __rootClass = rootClass; - __viewPort = viewPort; - __previousViewPort = new Rectangle(); - __stage = new Stage(Std.int(viewPort.width), Std.int(viewPort.height), stage.color); - __nativeOverlay = new Sprite(); - __nativeStage = stage; - __nativeStage.addChild(__nativeOverlay); - __touchProcessor = new TouchProcessor(__stage); - __touchProcessor.discardSystemGestures = !SystemUtil.isDesktop; - __juggler = new Juggler(); - __antiAliasing = 0; - __supportHighResolutions = false; - __painter = new Painter(stage3D, sharedContext); - __frameTimestamp = Lib.getTimer() / 1000.0; - __frameID = 1; - __supportsCursor = Mouse.supportsCursor || Capabilities.os.indexOf("Windows") == 0; - __statsDisplayAlign = {}; - - // register appropriate touch/mouse event handlers - setMultitouchEnabled(Multitouch.inputMode == MultitouchInputMode.TOUCH_POINT, true); - - // make the native overlay behave just like one would expect intuitively - nativeOverlayBlocksTouches = true; - - // all other modes are problematic in Starling, so we force those here - stage.scaleMode = StageScaleMode.NO_SCALE; - stage.align = StageAlign.TOP_LEFT; - - // register other event handlers - stage.addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true); - stage.addEventListener(KeyboardEvent.KEY_DOWN, onKey, false, 0, true); - stage.addEventListener(KeyboardEvent.KEY_UP, onKey, false, 0, true); - stage.addEventListener(Event.RESIZE, onResize, false, 0, true); - stage.addEventListener(Event.MOUSE_LEAVE, onMouseLeave, false, 0, true); - stage.addEventListener(Event.ACTIVATE, onActivate, false, 0, true); - - stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated, false, 10, true); - stage3D.addEventListener(ErrorEvent.ERROR, onStage3DError, false, 10, true); - - var runtimeVersion:Int = #if flash Std.parseInt(SystemUtil.version.split(",").shift()) #else 26 #end; - if (runtimeVersion < 19) - { - var runtime:String = SystemUtil.isAIR ? "Adobe AIR" : "Flash Player"; - stopWithFatalError( - "Your " + runtime + " installation is outdated. " + - "This software requires at least version 19."); - } - else if (__painter.shareContext) - { - Timer.delay(initialize, 1); // we don't call it right away, because Starling should - // behave the same way with or without a shared context - } - else - { - __painter.requestContext3D(renderMode, profile); - } - } - - /** Disposes all children of the stage and the render context; removes all registered - * event listeners. */ - public function dispose():Void - { - stop(true); - - __nativeStage.removeEventListener(Event.ENTER_FRAME, onEnterFrame, false); - __nativeStage.removeEventListener(KeyboardEvent.KEY_DOWN, onKey, false); - __nativeStage.removeEventListener(KeyboardEvent.KEY_UP, onKey, false); - __nativeStage.removeEventListener(Event.RESIZE, onResize, false); - __nativeStage.removeEventListener(Event.MOUSE_LEAVE, onMouseLeave, false); - #if air - __nativeStage.removeEventListener(Event.BROWSER_ZOOM_CHANGE, onBrowserZoomChange, false); - #end - __nativeStage.removeChild(__nativeOverlay); - - stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated, false); - stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextRestored, false); - stage3D.removeEventListener(ErrorEvent.ERROR, onStage3DError, false); - - for (touchEventType in getTouchEventTypes(__multitouchEnabled)) - __nativeStage.removeEventListener(touchEventType, onTouch, false); - - if (__touchProcessor != null) __touchProcessor.dispose(); - if (__painter != null) __painter.dispose(); - if (__stage != null) __stage.dispose(); - - var index:Int = sAll.indexOf(this); - if (index != -1) sAll.splice(index, 1); - if (sCurrent == this) sCurrent = null; - } - - // functions - - private function initialize():Void - { - makeCurrent(); - updateViewPort(true); - - // ideal time: after viewPort setup, before root creation - dispatchEventWith(Event.CONTEXT3D_CREATE, false, context); - - initializeRoot(); - __frameTimestamp = Lib.getTimer() / 1000.0; - } - - private function initializeRoot():Void - { - if (__root == null && __rootClass != null) - { - __root = Type.createInstance(__rootClass, []); - if (__root == null || !#if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(__root, DisplayObject)) throw new Error("Invalid root class: " + __rootClass); - __stage.addChildAt(__root, 0); - - dispatchEventWith(starling.events.Event.ROOT_CREATED, false, __root); - } - } - - /** Calls advanceTime() (with the time that has passed since the last frame) - * and render(). */ - public function nextFrame():Void - { - var now:Float = Lib.getTimer() / 1000.0; - var passedTime:Float = now - __frameTimestamp; - __frameTimestamp = now; - - // to avoid overloading time-based animations, the maximum delta is truncated. - if (passedTime > 1.0) passedTime = 1.0; - - // after about 25 days, 'getTimer()' will roll over. A rare event, but still ... - if (passedTime < 0.0) passedTime = 1.0 / __nativeStage.frameRate; - - advanceTime(passedTime); - render(); - } - - /** Dispatches ENTER_FRAME events on the display list, advances the Juggler - * and processes touches. */ - public function advanceTime(passedTime:Float):Void - { - if (!contextValid) - return; - - makeCurrent(); - - __touchProcessor.advanceTime(passedTime); - __stage.advanceTime(passedTime); - __juggler.advanceTime(passedTime); - } - - /** Renders the complete display list. Before rendering, the context is cleared; afterwards, - * it is presented (to avoid this, enable shareContext). - * - *

This method also dispatches an Event.RENDER-event on the Starling - * instance. That's the last opportunity to make changes before the display list is - * rendered.

*/ - public function render():Void - { - if (!contextValid) - return; - - makeCurrent(); - updateViewPort(); - - var doRedraw:Bool = __stage.requiresRedraw || mustAlwaysRender; - if (doRedraw) - { - dispatchEventWith(starling.events.Event.RENDER); - - var shareContext:Bool = __painter.shareContext; - var scaleX:Float = __viewPort.width / __stage.stageWidth; - var scaleY:Float = __viewPort.height / __stage.stageHeight; - var stageColor:UInt = __stage.color; - - __painter.nextFrame(); - __painter.pixelSize = 1.0 / contentScaleFactor; - __painter.state.setProjectionMatrix( - __viewPort.x < 0 ? -__viewPort.x / scaleX : 0.0, - __viewPort.y < 0 ? -__viewPort.y / scaleY : 0.0, - __clippedViewPort.width / scaleX, - __clippedViewPort.height / scaleY, - __stage.stageWidth, __stage.stageHeight, __stage.cameraPosition); - - if (!shareContext) - __painter.clear(stageColor, Color.getAlpha(stageColor)); - - __stage.render(__painter); - __painter.finishFrame(); - __painter.frameID = ++__frameID; - - if (!shareContext) - __painter.present(); - } - else - { +class Starling extends EventDispatcher { + /** The version of the Starling framework. */ + public static var VERSION:String = Compiler.getDefine("starling"); + + // members + @:noCompletion private var __stage:Stage; // starling.display.stage! + @:noCompletion private var __rootClass:Class; + @:noCompletion private var __root:DisplayObject; + @:noCompletion private var __juggler:Juggler; + @:noCompletion private var __painter:Painter; + @:noCompletion private var __touchProcessor:TouchProcessor; + @:noCompletion private var __antiAliasing:Int; + @:noCompletion private var __frameTimestamp:Float; + @:noCompletion private var __frameID:UInt; + @:noCompletion private var __leftMouseDown:Bool; + @:noCompletion private var __statsDisplay:StatsDisplay; + @:noCompletion private var __statsDisplayAlign:openfl.utils.Object; + @:noCompletion private var __started:Bool; + @:noCompletion private var __rendering:Bool; + @:noCompletion private var __supportHighResolutions:Bool; + @:noCompletion private var __supportBrowserZoom:Bool; + @:noCompletion private var __skipUnchangedFrames:Bool; + @:noCompletion private var __showStats:Bool; + @:noCompletion private var __supportsCursor:Bool; + @:noCompletion private var __multitouchEnabled:Bool; + + @:noCompletion private var __viewPort:Rectangle; + @:noCompletion private var __previousViewPort:Rectangle; + @:noCompletion private var __clippedViewPort:Rectangle; + + @:noCompletion private var __nativeStage:OpenFLStage; + @:noCompletion private var __nativeStageEmpty:Bool; + @:noCompletion private var __nativeOverlay:Sprite; + @:noCompletion private var __nativeOverlayBlocksTouches:Bool; + + private static var sCurrent:Starling; + private static var sAll:Array = new Array(); + + #if commonjs + private static function __init__() { + untyped Object.defineProperties(Starling.prototype, { + "touchEventTypes": {get: untyped __js__("function () { return this.get_touchEventTypes (); }")}, + "mustAlwaysRender": {get: untyped __js__("function () { return this.get_mustAlwaysRender (); }")}, + "isStarted": {get: untyped __js__("function () { return this.get_isStarted (); }")}, + "juggler": {get: untyped __js__("function () { return this.get_juggler (); }")}, + "context": {get: untyped __js__("function () { return this.get_context (); }"), + set: untyped __js__("function (v) { return this.set_context (v); }")}, + "simulateMultitouch": {get: untyped __js__("function () { return this.get_simulateMultitouch (); }"), + set: untyped __js__("function (v) { return this.set_simulateMultitouch (v); }")}, + "enableErrorChecking": {get: untyped __js__("function () { return this.get_enableErrorChecking (); }"), + set: untyped __js__("function (v) { return this.set_enableErrorChecking (v); }")}, + "antiAliasing": {get: untyped __js__("function () { return this.get_antiAliasing (); }"), + set: untyped __js__("function (v) { return this.set_antiAliasing (v); }")}, + "viewPort": {get: untyped __js__("function () { return this.get_viewPort (); }"), + set: untyped __js__("function (v) { return this.set_viewPort (v); }")}, + "contentScaleFactor": {get: untyped __js__("function () { return this.get_contentScaleFactor (); }")}, + "nativeOverlay": {get: untyped __js__("function () { return this.get_nativeOverlay (); }")}, + "nativeOverlayBlocksTouches": {get: untyped __js__("function () { return this.get_nativeOverlayBlocksTouches (); }"), + set: untyped __js__("function (v) { return this.set_nativeOverlayBlocksTouches (v); }")}, + "showStats": {get: untyped __js__("function () { return this.get_showStats (); }"), + set: untyped __js__("function (v) { return this.set_showStats (v); }")}, + "stage": {get: untyped __js__("function () { return this.get_stage (); }")}, + "stage3D": {get: untyped __js__("function () { return this.get_stage3D (); }")}, + "nativeStage": {get: untyped __js__("function () { return this.get_nativeStage (); }")}, + "root": {get: untyped __js__("function () { return this.get_root (); }")}, + "rootClass": {get: untyped __js__("function () { return this.get_rootClass (); }"), + set: untyped __js__("function (v) { return this.set_rootClass (v); }")}, + "shareContext": {get: untyped __js__("function () { return this.get_shareContext (); }"), + set: untyped __js__("function (v) { return this.set_shareContext (v); }")}, + "profile": {get: untyped __js__("function () { return this.get_profile (); }")}, + "supportHighResolutions": {get: untyped __js__("function () { return this.get_supportHighResolutions (); }"), + set: untyped __js__("function (v) { return this.set_supportHighResolutions (v); }")}, + "skipUnchangedFrames": {get: untyped __js__("function () { return this.get_skipUnchangedFrames (); }"), + set: untyped __js__("function (v) { return this.set_skipUnchangedFrames (v); }")}, + "touchProcessor": {get: untyped __js__("function () { return this.get_touchProcessor (); }"), + set: untyped __js__("function (v) { return this.set_touchProcessor (v); }")}, + "discardSystemGestures": {get: untyped __js__("function () { return this.get_discardSystemGestures (); }"), + set: untyped __js__("function (v) { return this.set_discardSystemGestures (v); }")}, + "frameID": {get: untyped __js__("function () { return this.get_frameID (); }")}, + "contextValid": {get: untyped __js__("function () { return this.get_contextValid (); }")}, + }); + + untyped Object.defineProperties(Starling, { + "current": {get: untyped __js__("function () { return Starling.get_current (); }")}, + "all": {get: untyped __js__("function () { return Starling.get_all (); }")}, + "contentScaleFactor": {get: untyped __js__("function () { return Starling.get_contentScaleFactor (); }")}, + "multitouchEnabled": {get: untyped __js__("function () { return Starling.get_multitouchEnabled (); }"), + set: untyped __js__("function (v) { return Starling.set_multitouchEnabled (v); }")}, + }); + } + #end + + // construction + + /** Creates a new Starling instance. + * @param rootClass A subclass of 'starling.display.DisplayObject'. It will be created + * as soon as initialization is finished and will become the first child + * of the Starling stage. Pass null if you don't want to + * create a root object right away. (You can use the + * rootClass property later to make that happen.) + * @param stage The Flash (2D) stage. + * @param viewPort A rectangle describing the area into which the content will be + * rendered. Default: stage size + * @param stage3D The Stage3D object into which the content will be rendered. If it + * already contains a context, sharedContext will be set + * to true. Default: the first available Stage3D. + * @param renderMode The Context3D render mode that should be requested. + * Use this parameter if you want to force "software" rendering. + * @param profile The Context3D profile that should be requested. + * + *
    + *
  • If you pass a profile String, this profile is enforced.
  • + *
  • Pass an Array of profiles to make Starling pick the first + * one that works (starting with the first array element).
  • + *
  • Pass the String "auto" to make Starling pick the best available + * profile automatically.
  • + *
+ */ + public function new(rootClass:Class, stage:openfl.display.Stage, viewPort:Rectangle = null, stage3D:Stage3D = null, + renderMode:Context3DRenderMode = AUTO, profile:Dynamic = "auto", sharedContext:Null = null) { + super(); + + if (stage == null) + throw new ArgumentError("Stage must not be null"); + if (viewPort == null) + viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); + if (stage3D == null) + stage3D = stage.stage3Ds[0]; + + // TODO it might make sense to exchange the 'renderMode' and 'profile' parameters. + + SystemUtil.initialize(); + sAll.push(this); + makeCurrent(); + + __rootClass = rootClass; + __viewPort = viewPort; + __previousViewPort = new Rectangle(); + __stage = new Stage(Std.int(viewPort.width), Std.int(viewPort.height), stage.color); + __nativeOverlay = new Sprite(); + __nativeStage = stage; + __nativeStage.addChild(__nativeOverlay); + __touchProcessor = new TouchProcessor(__stage); + __touchProcessor.discardSystemGestures = !SystemUtil.isDesktop; + __juggler = new Juggler(); + __antiAliasing = 0; + __supportHighResolutions = false; + __painter = new Painter(stage3D, sharedContext); + __frameTimestamp = Lib.getTimer() / 1000.0; + __frameID = 1; + __supportsCursor = Mouse.supportsCursor || Capabilities.os.indexOf("Windows") == 0; + __statsDisplayAlign = {}; + + // register appropriate touch/mouse event handlers + setMultitouchEnabled(Multitouch.inputMode == MultitouchInputMode.TOUCH_POINT, true); + + // make the native overlay behave just like one would expect intuitively + nativeOverlayBlocksTouches = true; + + // all other modes are problematic in Starling, so we force those here + stage.scaleMode = StageScaleMode.NO_SCALE; + stage.align = StageAlign.TOP_LEFT; + + // register other event handlers + stage.addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true); + stage.addEventListener(KeyboardEvent.KEY_DOWN, onKey, false, 0, true); + stage.addEventListener(KeyboardEvent.KEY_UP, onKey, false, 0, true); + stage.addEventListener(Event.RESIZE, onResize, false, 0, true); + stage.addEventListener(Event.MOUSE_LEAVE, onMouseLeave, false, 0, true); + stage.addEventListener(Event.ACTIVATE, onActivate, false, 0, true); + + stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated, false, 10, true); + stage3D.addEventListener(ErrorEvent.ERROR, onStage3DError, false, 10, true); + + var runtimeVersion:Int = #if flash Std.parseInt(SystemUtil.version.split(",").shift()) #else 26 #end; + if (runtimeVersion < 19) { + var runtime:String = SystemUtil.isAIR ? "Adobe AIR" : "Flash Player"; + stopWithFatalError("Your " + runtime + " installation is outdated. " + "This software requires at least version 19."); + } else if (__painter.shareContext) { + Timer.delay(initialize, 1); // we don't call it right away, because Starling should + // behave the same way with or without a shared context + } else { + __painter.requestContext3D(renderMode, profile); + } + } + + /** Disposes all children of the stage and the render context; removes all registered + * event listeners. */ + public function dispose():Void { + stop(true); + + __nativeStage.removeEventListener(Event.ENTER_FRAME, onEnterFrame, false); + __nativeStage.removeEventListener(KeyboardEvent.KEY_DOWN, onKey, false); + __nativeStage.removeEventListener(KeyboardEvent.KEY_UP, onKey, false); + __nativeStage.removeEventListener(Event.RESIZE, onResize, false); + __nativeStage.removeEventListener(Event.MOUSE_LEAVE, onMouseLeave, false); + #if air + __nativeStage.removeEventListener(Event.BROWSER_ZOOM_CHANGE, onBrowserZoomChange, false); + #end + __nativeStage.removeChild(__nativeOverlay); + + stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated, false); + stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextRestored, false); + stage3D.removeEventListener(ErrorEvent.ERROR, onStage3DError, false); + + for (touchEventType in getTouchEventTypes(__multitouchEnabled)) + __nativeStage.removeEventListener(touchEventType, onTouch, false); + + if (__touchProcessor != null) + __touchProcessor.dispose(); + if (__painter != null) + __painter.dispose(); + if (__stage != null) + __stage.dispose(); + + var index:Int = sAll.indexOf(this); + if (index != -1) + sAll.splice(index, 1); + if (sCurrent == this) + sCurrent = null; + } + + // functions + + private function initialize():Void { + makeCurrent(); + updateViewPort(true); + + // ideal time: after viewPort setup, before root creation + dispatchEventWith(Event.CONTEXT3D_CREATE, false, context); + + initializeRoot(); + __frameTimestamp = Lib.getTimer() / 1000.0; + } + + private function initializeRoot():Void { + if (__root == null && __rootClass != null) { + __root = Type.createInstance(__rootClass, []); + if (__root == null || !#if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (__root, DisplayObject)) + throw new Error("Invalid root class: " + __rootClass); + __stage.addChildAt(__root, 0); + + dispatchEventWith(starling.events.Event.ROOT_CREATED, false, __root); + } + } + + /** Calls advanceTime() (with the time that has passed since the last frame) + * and render(). */ + public function nextFrame():Void { + var now:Float = Lib.getTimer() / 1000.0; + var passedTime:Float = now - __frameTimestamp; + __frameTimestamp = now; + + // to avoid overloading time-based animations, the maximum delta is truncated. + if (passedTime > 1.0) + passedTime = 1.0; + + // after about 25 days, 'getTimer()' will roll over. A rare event, but still ... + if (passedTime < 0.0) + passedTime = 1.0 / __nativeStage.frameRate; + + advanceTime(passedTime); + render(); + } + + /** Dispatches ENTER_FRAME events on the display list, advances the Juggler + * and processes touches. */ + public function advanceTime(passedTime:Float):Void { + if (!contextValid) + return; + + makeCurrent(); + + __touchProcessor.advanceTime(passedTime); + __stage.advanceTime(passedTime); + __juggler.advanceTime(passedTime); + } + + /** Renders the complete display list. Before rendering, the context is cleared; afterwards, + * it is presented (to avoid this, enable shareContext). + * + *

This method also dispatches an Event.RENDER-event on the Starling + * instance. That's the last opportunity to make changes before the display list is + * rendered.

*/ + public function render():Void { + if (!contextValid) + return; + + makeCurrent(); + updateViewPort(); + + var doRedraw:Bool = __stage.requiresRedraw || mustAlwaysRender; + if (doRedraw) { + dispatchEventWith(starling.events.Event.RENDER); + + var shareContext:Bool = __painter.shareContext; + var scaleX:Float = __viewPort.width / __stage.stageWidth; + var scaleY:Float = __viewPort.height / __stage.stageHeight; + var stageColor:UInt = __stage.color; + + __painter.nextFrame(); + __painter.pixelSize = 1.0 / contentScaleFactor; + __painter.state.setProjectionMatrix(__viewPort.x < 0 ? -__viewPort.x / scaleX : 0.0, __viewPort.y < 0 ? -__viewPort.y / scaleY : 0.0, + __clippedViewPort.width / scaleX, __clippedViewPort.height / scaleY, __stage.stageWidth, __stage.stageHeight, __stage.cameraPosition); + + if (!shareContext) + __painter.clear(stageColor, Color.getAlpha(stageColor)); + + __stage.render(__painter); + __painter.finishFrame(); + __painter.frameID = ++__frameID; + + if (!shareContext) + __painter.present(); + } else { dispatchEventWith(starling.events.Event.SKIP_FRAME); } - if (__statsDisplay != null) - { - __statsDisplay.drawCount = __painter.drawCount; - } - } - - private function updateViewPort(forceUpdate:Bool=false):Void - { - // the last set viewport is stored in a variable; that way, people can modify the - // viewPort directly (without a copy) and we still know if it has changed. - - if (forceUpdate || !RectangleUtil.compare(__viewPort, __previousViewPort)) - { - __previousViewPort.setTo(__viewPort.x, __viewPort.y, __viewPort.width, __viewPort.height); - - // Constrained mode requires that the viewport is within the native stage bounds; - // thus, we use a clipped viewport when configuring the back buffer. (In baseline - // mode, that's not necessary, but it does not hurt either.) - - updateClippedViewPort(); - updateStatsDisplayPosition(); - - var contentScaleFactor:Float = - __supportHighResolutions ? __nativeStage.contentsScaleFactor : 1.0; - - #if air - if (__supportBrowserZoom) contentScaleFactor *= __nativeStage.browserZoomFactor; - #end - - __painter.configureBackBuffer(__clippedViewPort, contentScaleFactor, - __antiAliasing, true, __supportBrowserZoom); - - setRequiresRedraw(); - } - } - - private function updateClippedViewPort():Void - { - var stageBounds:Rectangle = Pool.getRectangle(0, 0, - __nativeStage.stageWidth, __nativeStage.stageHeight); - - __clippedViewPort = RectangleUtil.intersect(__viewPort, stageBounds, __clippedViewPort); - - if (__clippedViewPort.width < 32) __clippedViewPort.width = 32; - if (__clippedViewPort.height < 32) __clippedViewPort.height = 32; - - Pool.putRectangle(stageBounds); - } - - private function updateNativeOverlay():Void - { - __nativeOverlay.x = __viewPort.x; - __nativeOverlay.y = __viewPort.y; - __nativeOverlay.scaleX = __viewPort.width / __stage.stageWidth; - __nativeOverlay.scaleY = __viewPort.height / __stage.stageHeight; - } - - /** Stops Starling right away and displays an error message on the native overlay. - * This method will also cause Starling to dispatch a FATAL_ERROR event. */ - public function stopWithFatalError(message:String):Void - { - var background:Shape = new Shape(); - background.graphics.beginFill(0x0, 0.8); - background.graphics.drawRect(0, 0, __stage.stageWidth, __stage.stageHeight); - background.graphics.endFill(); - - var textField:TextField = new TextField(); - var textFormat:TextFormat = new TextFormat("_sans", 14, 0xFFFFFF); - textFormat.align = TextFormatAlign.CENTER; - textField.defaultTextFormat = textFormat; - textField.wordWrap = true; - textField.width = __stage.stageWidth * 0.75; - textField.autoSize = TextFieldAutoSize.CENTER; - textField.text = message; - textField.x = (__stage.stageWidth - textField.width) / 2; - textField.y = (__stage.stageHeight - textField.height) / 2; - textField.background = true; - textField.backgroundColor = 0x550000; - - updateNativeOverlay(); - nativeOverlay.addChild(background); - nativeOverlay.addChild(textField); - stop(true); - - trace("[Starling] " + message); - dispatchEventWith(starling.events.Event.FATAL_ERROR, false, message); - } - - /** Make this Starling instance the current one. */ - public function makeCurrent():Void - { - sCurrent = this; - } - - /** As soon as Starling is started, it will queue input events (keyboard/mouse/touch); - * furthermore, the method nextFrame will be called once per Flash Player - * frame. (Except when shareContext is enabled: in that case, you have to - * call that method manually.) */ - public function start():Void - { - __started = __rendering = true; - __frameTimestamp = Lib.getTimer() / 1000.0; - } - - /** Stops all logic and input processing, effectively freezing the app in its current state. - * Per default, rendering will continue: that's because the classic display list - * is only updated when stage3D is. (If Starling stopped rendering, conventional Flash - * contents would freeze, as well.) - * - *

However, if you don't need classic Flash contents, you can stop rendering, too. - * On some mobile systems (e.g. iOS), you are even required to do so if you have - * activated background code execution.

- */ - public function stop(suspendRendering:Bool=false):Void - { - __started = false; - __rendering = !suspendRendering; - } - - /** Makes sure that the next frame is actually rendered. - * - *

When skipUnchangedFrames is enabled, some situations require that you - * manually force a redraw, e.g. when a RenderTexture is changed. This method is the - * easiest way to do so; it's just a shortcut to stage.setRequiresRedraw(). - *

- */ - public function setRequiresRedraw():Void - { - __stage.setRequiresRedraw(); - } - - // event handlers - - private function onStage3DError(event:ErrorEvent):Void - { - if (event.errorID == 3702) - { - var mode:String = Capabilities.playerType == "Desktop" ? "renderMode" : "wmode"; - stopWithFatalError("Context3D not available! Possible reasons: wrong " + mode + - " or missing device support."); - } - else - stopWithFatalError("Stage3D error: " + event.text); - } - - private function onContextCreated(event:Event):Void - { - stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated); - stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextRestored, false, 10, true); - - trace("[Starling] Context ready. Display Driver: " + context.driverInfo); - initialize(); - } - - private function onContextRestored(event:Event):Void - { - trace("[Starling] Context restored."); - updateViewPort(true); + if (__statsDisplay != null) { + __statsDisplay.drawCount = __painter.drawCount; + } + } + + private function updateViewPort(forceUpdate:Bool = false):Void { + // the last set viewport is stored in a variable; that way, people can modify the + // viewPort directly (without a copy) and we still know if it has changed. + + if (forceUpdate || !RectangleUtil.compare(__viewPort, __previousViewPort)) { + __previousViewPort.setTo(__viewPort.x, __viewPort.y, __viewPort.width, __viewPort.height); + + // Constrained mode requires that the viewport is within the native stage bounds; + // thus, we use a clipped viewport when configuring the back buffer. (In baseline + // mode, that's not necessary, but it does not hurt either.) + + updateClippedViewPort(); + updateStatsDisplayPosition(); + + var contentScaleFactor:Float = __supportHighResolutions ? __nativeStage.contentsScaleFactor : 1.0; + + #if air + if (__supportBrowserZoom) + contentScaleFactor *= __nativeStage.browserZoomFactor; + #end + + __painter.configureBackBuffer(__clippedViewPort, contentScaleFactor, __antiAliasing, true, __supportBrowserZoom); + + setRequiresRedraw(); + } + } + + private function updateClippedViewPort():Void { + var stageBounds:Rectangle = Pool.getRectangle(0, 0, __nativeStage.stageWidth, __nativeStage.stageHeight); + + __clippedViewPort = RectangleUtil.intersect(__viewPort, stageBounds, __clippedViewPort); + + if (__clippedViewPort.width < 32) + __clippedViewPort.width = 32; + if (__clippedViewPort.height < 32) + __clippedViewPort.height = 32; + + Pool.putRectangle(stageBounds); + } + + private function updateNativeOverlay():Void { + __nativeOverlay.x = __viewPort.x; + __nativeOverlay.y = __viewPort.y; + __nativeOverlay.scaleX = __viewPort.width / __stage.stageWidth; + __nativeOverlay.scaleY = __viewPort.height / __stage.stageHeight; + } + + /** Stops Starling right away and displays an error message on the native overlay. + * This method will also cause Starling to dispatch a FATAL_ERROR event. */ + public function stopWithFatalError(message:String):Void { + var background:Shape = new Shape(); + background.graphics.beginFill(0x0, 0.8); + background.graphics.drawRect(0, 0, __stage.stageWidth, __stage.stageHeight); + background.graphics.endFill(); + + var textField:TextField = new TextField(); + var textFormat:TextFormat = new TextFormat("_sans", 14, 0xFFFFFF); + textFormat.align = TextFormatAlign.CENTER; + textField.defaultTextFormat = textFormat; + textField.wordWrap = true; + textField.width = __stage.stageWidth * 0.75; + textField.autoSize = TextFieldAutoSize.CENTER; + textField.text = message; + textField.x = (__stage.stageWidth - textField.width) / 2; + textField.y = (__stage.stageHeight - textField.height) / 2; + textField.background = true; + textField.backgroundColor = 0x550000; + + updateNativeOverlay(); + nativeOverlay.addChild(background); + nativeOverlay.addChild(textField); + stop(true); + + trace("[Starling] " + message); + dispatchEventWith(starling.events.Event.FATAL_ERROR, false, message); + } + + /** Make this Starling instance the current one. */ + public function makeCurrent():Void { + sCurrent = this; + } + + /** As soon as Starling is started, it will queue input events (keyboard/mouse/touch); + * furthermore, the method nextFrame will be called once per Flash Player + * frame. (Except when shareContext is enabled: in that case, you have to + * call that method manually.) */ + public function start():Void { + __started = __rendering = true; + __frameTimestamp = Lib.getTimer() / 1000.0; + } + + /** Stops all logic and input processing, effectively freezing the app in its current state. + * Per default, rendering will continue: that's because the classic display list + * is only updated when stage3D is. (If Starling stopped rendering, conventional Flash + * contents would freeze, as well.) + * + *

However, if you don't need classic Flash contents, you can stop rendering, too. + * On some mobile systems (e.g. iOS), you are even required to do so if you have + * activated background code execution.

+ */ + public function stop(suspendRendering:Bool = false):Void { + __started = false; + __rendering = !suspendRendering; + } + + /** Makes sure that the next frame is actually rendered. + * + *

When skipUnchangedFrames is enabled, some situations require that you + * manually force a redraw, e.g. when a RenderTexture is changed. This method is the + * easiest way to do so; it's just a shortcut to stage.setRequiresRedraw(). + *

+ */ + public function setRequiresRedraw():Void { + __stage.setRequiresRedraw(); + } + + // event handlers + + private function onStage3DError(event:ErrorEvent):Void { + if (event.errorID == 3702) { + var mode:String = Capabilities.playerType == "Desktop" ? "renderMode" : "wmode"; + stopWithFatalError("Context3D not available! Possible reasons: wrong " + mode + " or missing device support."); + } else + stopWithFatalError("Stage3D error: " + event.text); + } + + private function onContextCreated(event:Event):Void { + stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated); + stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextRestored, false, 10, true); + + trace("[Starling] Context ready. Display Driver: " + context.driverInfo); + initialize(); + } + + private function onContextRestored(event:Event):Void { + trace("[Starling] Context restored."); + updateViewPort(true); __painter.setupContextDefaults(); - dispatchEventWith(Event.CONTEXT3D_CREATE, false, context); - } - - private function onEnterFrame(event:Event):Void - { - // On mobile, the native display list is only updated on stage3D draw calls. - // Thus, we render even when Starling is paused. - - if (!__painter.shareContext) - { - if (__started) nextFrame(); - else if (__rendering) render(); - } - - updateNativeOverlay(); - } - - private function onActivate(event:Event):Void - { - // with 'skipUnchangedFrames' enabled, a forced redraw is required when the app - // is restored on some platforms (namely Windows with BASELINE_CONSTRAINED profile - // and some Android versions). - - Timer.delay(setRequiresRedraw, 100); - } - - private function onKey(event:KeyboardEvent):Void - { - if (!__started) return; - - var keyEvent:starling.events.KeyboardEvent = new starling.events.KeyboardEvent( - event.type, event.charCode, event.keyCode, event.keyLocation, - event.ctrlKey, event.altKey, event.shiftKey); - - makeCurrent(); - __stage.dispatchEvent(keyEvent); - - if (keyEvent.isDefaultPrevented()) - event.preventDefault(); - } - - private function onResize(event:Event):Void - { - var stageWidth:Int = cast(event.target, OpenFLStage).stageWidth; - var stageHeight:Int = cast(event.target, OpenFLStage).stageHeight; - - function dispatchResizeEvent():Void - { - // on Android, the context is not valid while we're resizing. To avoid problems - // with user code, we delay the event dispatching until it becomes valid again. - - makeCurrent(); - removeEventListener(Event.CONTEXT3D_CREATE, dispatchResizeEvent); - __stage.dispatchEvent(new ResizeEvent(Event.RESIZE, stageWidth, stageHeight)); - } - - if (contextValid) - dispatchResizeEvent(); - else - addEventListener(Event.CONTEXT3D_CREATE, dispatchResizeEvent); - } - - private function onBrowserZoomChange(event:Event):Void - { - #if air - __painter.refreshBackBufferSize( - __nativeStage.contentsScaleFactor * __nativeStage.browserZoomFactor); - #end - } - - private function onMouseLeave(event:Event):Void - { - __touchProcessor.enqueueMouseLeftStage(); - } - - private function onTouch(event:Event):Void - { - if (!__started) return; - - var globalX:Float; - var globalY:Float; - var touchID:Int; - var phase:String = null; - var pressure:Float = 1.0; - var width:Float = 1.0; - var height:Float = 1.0; - - // figure out general touch properties - if (#if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(event, MouseEvent)) - { - var mouseEvent:MouseEvent = cast event; - globalX = mouseEvent.stageX; - globalY = mouseEvent.stageY; - touchID = 0; - - // MouseEvent.buttonDown returns true for both left and right button (AIR supports - // the right mouse button). We only want to react on the left button for now, - // so we have to save the state for the left button manually. - if (event.type == MouseEvent.MOUSE_DOWN) __leftMouseDown = true; - else if (event.type == MouseEvent.MOUSE_UP) __leftMouseDown = false; - } - else - { - var touchEvent:TouchEvent = cast(event, TouchEvent); - - // On a system that supports both mouse and touch input, the primary touch point - // is dispatched as mouse event as well. Since we don't want to listen to that - // event twice, we ignore the primary touch in that case. - - if (__supportsCursor && touchEvent.isPrimaryTouchPoint) return; - else - { - globalX = touchEvent.stageX; - globalY = touchEvent.stageY; - touchID = touchEvent.touchPointID; - pressure = touchEvent.pressure; - width = touchEvent.sizeX; - height = touchEvent.sizeY; - } - } - - // figure out touch phase - switch (event.type) - { - case TouchEvent.TOUCH_BEGIN: phase = TouchPhase.BEGAN; - case TouchEvent.TOUCH_MOVE: phase = TouchPhase.MOVED; - case TouchEvent.TOUCH_END: phase = TouchPhase.ENDED; - case MouseEvent.MOUSE_DOWN: phase = TouchPhase.BEGAN; - case MouseEvent.MOUSE_UP: phase = TouchPhase.ENDED; - case MouseEvent.MOUSE_MOVE: - phase = (__leftMouseDown ? TouchPhase.MOVED : TouchPhase.HOVER); - } - - // move position into viewport bounds - globalX = __stage.stageWidth * (globalX - __viewPort.x) / __viewPort.width; - globalY = __stage.stageHeight * (globalY - __viewPort.y) / __viewPort.height; - - // enqueue touch in touch processor - __touchProcessor.enqueue(touchID, phase, globalX, globalY, pressure, width, height); - - // allow objects that depend on mouse-over state to be updated immediately - if (event.type == MouseEvent.MOUSE_UP && __supportsCursor) - __touchProcessor.enqueue(touchID, TouchPhase.HOVER, globalX, globalY); - } - - private function hitTestNativeOverlay(localX:Float, localY:Float):Bool - { - if (__nativeOverlay.numChildren > 0) - { - var globalPos:Point = Pool.getPoint(); - var matrix:Matrix = Pool.getMatrix( - __nativeOverlay.scaleX, 0, 0, __nativeOverlay.scaleY, - __nativeOverlay.x, __nativeOverlay.y); - MatrixUtil.transformCoords(matrix, localX, localY, globalPos); - var result:Bool = __nativeOverlay.hitTestPoint(globalPos.x, globalPos.y, true); - Pool.putPoint(globalPos); - Pool.putMatrix(matrix); - return result; - } - return false; - } - - private function setMultitouchEnabled(value:Bool, forceUpdate:Bool=false):Void - { - if (forceUpdate || value != __multitouchEnabled) - { - var oldEventTypes:Array = getTouchEventTypes(__multitouchEnabled); - var newEventTypes:Array = getTouchEventTypes(value); - - for (oldEventType in oldEventTypes) - __nativeStage.removeEventListener(oldEventType, onTouch); - - for (newEventType in newEventTypes) - __nativeStage.addEventListener(newEventType, onTouch, false, 0, true); - - __touchProcessor.cancelTouches(); - __multitouchEnabled = value; - } - } - - private function getTouchEventTypes(multitouchEnabled:Bool):Array - { - var types = new Array(); - - if (multitouchEnabled) - { - types.push(TouchEvent.TOUCH_BEGIN); - types.push(TouchEvent.TOUCH_MOVE); - types.push(TouchEvent.TOUCH_END); - } - - if (!multitouchEnabled || __supportsCursor) - { - types.push(MouseEvent.MOUSE_DOWN); - types.push(MouseEvent.MOUSE_MOVE); - types.push(MouseEvent.MOUSE_UP); - } - - return types; - } - - private var mustAlwaysRender(get, never):Bool; - private function get_mustAlwaysRender():Bool - { - // On mobile, and in some browsers with the "baselineConstrained" profile, the - // standard display list is only rendered after calling "context.present()". - // In such a case, we cannot omit frames if there is any content on the stage. - - #if !flash - if (!Reflect.hasField (__nativeStage, "context3D") || Reflect.field (__nativeStage, "context3D") == context) - return true; - #end - - if (!__skipUnchangedFrames || __painter.shareContext) - return true; - else if (SystemUtil.isDesktop && profile != Context3DProfile.BASELINE_CONSTRAINED) - return false; - else - { - // Rendering can be skipped when both this and previous frame are empty. - var nativeStageEmpty:Bool = isNativeDisplayObjectEmpty(__nativeStage); - var mustAlwaysRender:Bool = !nativeStageEmpty || !__nativeStageEmpty; - __nativeStageEmpty = nativeStageEmpty; - - return mustAlwaysRender; - } - } - - // properties - - /** Indicates if this Starling instance is started. */ - public var isStarted(get, never):Bool; - private function get_isStarted():Bool { return __started; } - - /** The default juggler of this instance. Will be advanced once per frame. */ - public var juggler(get, never):Juggler; - private function get_juggler():Juggler { return __juggler; } - - /** The painter, which is used for all rendering. The same instance is passed to all - * rendermethods each frame. */ - public var painter(get, never):Painter; - private function get_painter():Painter { return __painter; } - - /** The render context of this instance. */ - public var context(get, never):Context3D; - private function get_context():Context3D { return __painter.context; } - - /** Indicates if multitouch simulation with "Shift" and "Ctrl"/"Cmd"-keys is enabled. - * @default false */ - public var simulateMultitouch(get, set):Bool; - private function get_simulateMultitouch():Bool { return __touchProcessor.simulateMultitouch; } - private function set_simulateMultitouch(value:Bool):Bool - { - return __touchProcessor.simulateMultitouch = value; - } - - /** Indicates if Stage3D render methods will report errors. It's recommended to activate - * this when writing custom rendering code (shaders, etc.), since you'll get more detailed - * error messages. However, it has a very negative impact on performance, and it prevents - * ATF textures from being restored on a context loss. Never activate for release builds! - * - * @default false */ - public var enableErrorChecking(get, set):Bool; - private function get_enableErrorChecking():Bool { return __painter.enableErrorChecking; } - private function set_enableErrorChecking(value:Bool):Bool - { - return __painter.enableErrorChecking = value; - } - - /** The antialiasing level. 0 - no antialasing, 16 - maximum antialiasing. @default 0 */ - public var antiAliasing(get, set):Int; - private function get_antiAliasing():Int { return __antiAliasing; } - private function set_antiAliasing(value:Int):Int - { - if (__antiAliasing != value) - { - __antiAliasing = value; - if (contextValid) updateViewPort(true); - } - return value; - } - - /** The viewport into which Starling contents will be rendered. */ - public var viewPort(get, set):Rectangle; - private function get_viewPort():Rectangle { return __viewPort; } - private function set_viewPort(value:Rectangle):Rectangle { __viewPort.copyFrom(value); return value; } - - /** The ratio between viewPort width and stage width. Useful for choosing a different - * set of textures depending on the display resolution. */ - public var contentScaleFactor(get, never):Float; - private function get_contentScaleFactor():Float - { - return (__viewPort.width * __painter.backBufferScaleFactor) / __stage.stageWidth; - } - - /** A Flash Sprite placed directly on top of the Starling content. Use it to display native - * Flash components. */ - public var nativeOverlay(get, never):Sprite; - private function get_nativeOverlay():Sprite { return __nativeOverlay; } - - /** If enabled, touches or mouse events on the native overlay won't be propagated to - * Starling. @default false */ - public var nativeOverlayBlocksTouches(get, set):Bool; - private function get_nativeOverlayBlocksTouches():Bool - { - return __touchProcessor.occlusionTest != null; - } - - private function set_nativeOverlayBlocksTouches(value:Bool):Bool - { - if (value != __nativeOverlayBlocksTouches) - __touchProcessor.occlusionTest = value ? hitTestNativeOverlay : null; - return __nativeOverlayBlocksTouches = value; - } - - /** Indicates if a small statistics box (with FPS, memory usage and draw count) is - * displayed. - * - *

Beware that the memory usage should be taken with a grain of salt. The value is - * determined via System.totalMemory and does not take texture memory - * into account. It is recommended to use Adobe Scout for reliable and comprehensive - * memory analysis.

- */ - public var showStats(get, set):Bool; - private function get_showStats():Bool { return __showStats; } - private function set_showStats(value:Bool):Bool - { - __showStats = value; - - if (value) - { - var h = Reflect.hasField(__statsDisplayAlign, "horizontal") ? Reflect.field(__statsDisplayAlign, "horizontal") : null; - var v = Reflect.hasField(__statsDisplayAlign, "vertical") ? Reflect.field(__statsDisplayAlign, "vertical") : null; - showStatsAt(h != null ? h : "left", v != null ? v : "top"); - } - else if (__statsDisplay != null) - { - __statsDisplay.removeFromParent(); - } - - return value; - } - - /** Displays the statistics box at a certain position. */ - public function showStatsAt(horizontalAlign:String="left", - verticalAlign:String="top", scale:Float=1):Void - { - function onRootCreated():Void - { - if (__showStats) showStatsAt(horizontalAlign, verticalAlign, scale); - removeEventListener(starling.events.Event.ROOT_CREATED, onRootCreated); - } - - __showStats = true; - Reflect.setField(__statsDisplayAlign, "horizontal", horizontalAlign); - Reflect.setField(__statsDisplayAlign, "vertical", verticalAlign); - - if (context == null) - { - // Starling is not yet ready - we postpone this until it's initialized. - addEventListener(starling.events.Event.ROOT_CREATED, onRootCreated); - } - else - { - if (__statsDisplay == null) - { - __statsDisplay = new StatsDisplay(); - __statsDisplay.touchable = false; - } - - __stage.addChild(__statsDisplay); - __statsDisplay.scaleX = __statsDisplay.scaleY = scale; + dispatchEventWith(Event.CONTEXT3D_CREATE, false, context); + } + + private function onEnterFrame(event:Event):Void { + // On mobile, the native display list is only updated on stage3D draw calls. + // Thus, we render even when Starling is paused. + + if (!__painter.shareContext) { + if (__started) + nextFrame(); + else if (__rendering) + render(); + } + + updateNativeOverlay(); + } + + private function onActivate(event:Event):Void { + // with 'skipUnchangedFrames' enabled, a forced redraw is required when the app + // is restored on some platforms (namely Windows with BASELINE_CONSTRAINED profile + // and some Android versions). + + Timer.delay(setRequiresRedraw, 100); + } + + private function onKey(event:KeyboardEvent):Void { + if (!__started) + return; + + var keyEvent:starling.events.KeyboardEvent = new starling.events.KeyboardEvent(event.type, event.charCode, event.keyCode, event.keyLocation, + event.ctrlKey, event.altKey, event.shiftKey); + + makeCurrent(); + __stage.dispatchEvent(keyEvent); + + if (keyEvent.isDefaultPrevented()) + event.preventDefault(); + } + + private function onResize(event:Event):Void { + var stageWidth:Int = cast(event.target, OpenFLStage).stageWidth; + var stageHeight:Int = cast(event.target, OpenFLStage).stageHeight; + + function dispatchResizeEvent():Void { + // on Android, the context is not valid while we're resizing. To avoid problems + // with user code, we delay the event dispatching until it becomes valid again. + + makeCurrent(); + removeEventListener(Event.CONTEXT3D_CREATE, dispatchResizeEvent); + __stage.dispatchEvent(new ResizeEvent(Event.RESIZE, stageWidth, stageHeight)); + } + + if (contextValid) + dispatchResizeEvent(); + else + addEventListener(Event.CONTEXT3D_CREATE, dispatchResizeEvent); + } + + private function onBrowserZoomChange(event:Event):Void { + #if air + __painter.refreshBackBufferSize(__nativeStage.contentsScaleFactor * __nativeStage.browserZoomFactor); + #end + } + + private function onMouseLeave(event:Event):Void { + __touchProcessor.enqueueMouseLeftStage(); + } + + private function onTouch(event:Event):Void { + if (!__started) + return; + + var globalX:Float; + var globalY:Float; + var touchID:Int; + var phase:String = null; + var pressure:Float = 1.0; + var width:Float = 1.0; + var height:Float = 1.0; + + // figure out general touch properties + if (#if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (event, MouseEvent)) { + var mouseEvent:MouseEvent = cast event; + globalX = mouseEvent.stageX; + globalY = mouseEvent.stageY; + touchID = 0; + + // MouseEvent.buttonDown returns true for both left and right button (AIR supports + // the right mouse button). We only want to react on the left button for now, + // so we have to save the state for the left button manually. + if (event.type == MouseEvent.MOUSE_DOWN) + __leftMouseDown = true; + else if (event.type == MouseEvent.MOUSE_UP) + __leftMouseDown = false; + } else { + var touchEvent:TouchEvent = cast(event, TouchEvent); + + // On a system that supports both mouse and touch input, the primary touch point + // is dispatched as mouse event as well. Since we don't want to listen to that + // event twice, we ignore the primary touch in that case. + + if (__supportsCursor && touchEvent.isPrimaryTouchPoint) + return; + else { + globalX = touchEvent.stageX; + globalY = touchEvent.stageY; + touchID = touchEvent.touchPointID; + pressure = touchEvent.pressure; + width = touchEvent.sizeX; + height = touchEvent.sizeY; + } + } + + // figure out touch phase + switch (event.type) { + case TouchEvent.TOUCH_BEGIN: + phase = TouchPhase.BEGAN; + case TouchEvent.TOUCH_MOVE: + phase = TouchPhase.MOVED; + case TouchEvent.TOUCH_END: + phase = TouchPhase.ENDED; + case MouseEvent.MOUSE_DOWN: + phase = TouchPhase.BEGAN; + case MouseEvent.MOUSE_UP: + phase = TouchPhase.ENDED; + case MouseEvent.MOUSE_MOVE: + phase = (__leftMouseDown ? TouchPhase.MOVED : TouchPhase.HOVER); + } + + // move position into viewport bounds + globalX = __stage.stageWidth * (globalX - __viewPort.x) / __viewPort.width; + globalY = __stage.stageHeight * (globalY - __viewPort.y) / __viewPort.height; + + // enqueue touch in touch processor + __touchProcessor.enqueue(touchID, phase, globalX, globalY, pressure, width, height); + + // allow objects that depend on mouse-over state to be updated immediately + if (event.type == MouseEvent.MOUSE_UP && __supportsCursor) + __touchProcessor.enqueue(touchID, TouchPhase.HOVER, globalX, globalY); + } + + private function hitTestNativeOverlay(localX:Float, localY:Float):Bool { + if (__nativeOverlay.numChildren > 0) { + var globalPos:Point = Pool.getPoint(); + var matrix:Matrix = Pool.getMatrix(__nativeOverlay.scaleX, 0, 0, __nativeOverlay.scaleY, __nativeOverlay.x, __nativeOverlay.y); + MatrixUtil.transformCoords(matrix, localX, localY, globalPos); + var result:Bool = __nativeOverlay.hitTestPoint(globalPos.x, globalPos.y, true); + Pool.putPoint(globalPos); + Pool.putMatrix(matrix); + return result; + } + return false; + } + + private function setMultitouchEnabled(value:Bool, forceUpdate:Bool = false):Void { + if (forceUpdate || value != __multitouchEnabled) { + var oldEventTypes:Array = getTouchEventTypes(__multitouchEnabled); + var newEventTypes:Array = getTouchEventTypes(value); + + for (oldEventType in oldEventTypes) + __nativeStage.removeEventListener(oldEventType, onTouch); + + for (newEventType in newEventTypes) + __nativeStage.addEventListener(newEventType, onTouch, false, 0, true); + + __touchProcessor.cancelTouches(); + __multitouchEnabled = value; + } + } + + private function getTouchEventTypes(multitouchEnabled:Bool):Array { + var types = new Array(); + + if (multitouchEnabled) { + types.push(TouchEvent.TOUCH_BEGIN); + types.push(TouchEvent.TOUCH_MOVE); + types.push(TouchEvent.TOUCH_END); + } + + if (!multitouchEnabled || __supportsCursor) { + types.push(MouseEvent.MOUSE_DOWN); + types.push(MouseEvent.MOUSE_MOVE); + types.push(MouseEvent.MOUSE_UP); + } + + return types; + } + + private var mustAlwaysRender(get, never):Bool; + + private function get_mustAlwaysRender():Bool { + // On mobile, and in some browsers with the "baselineConstrained" profile, the + // standard display list is only rendered after calling "context.present()". + // In such a case, we cannot omit frames if there is any content on the stage. + + #if !flash + if (!Reflect.hasField(__nativeStage, "context3D") || Reflect.field(__nativeStage, "context3D") == context) + return true; + #end + + if (!__skipUnchangedFrames || __painter.shareContext) + return true; + else if (SystemUtil.isDesktop && profile != Context3DProfile.BASELINE_CONSTRAINED) + return false; + else { + // Rendering can be skipped when both this and previous frame are empty. + var nativeStageEmpty:Bool = isNativeDisplayObjectEmpty(__nativeStage); + var mustAlwaysRender:Bool = !nativeStageEmpty || !__nativeStageEmpty; + __nativeStageEmpty = nativeStageEmpty; + + return mustAlwaysRender; + } + } + + // properties + + /** Indicates if this Starling instance is started. */ + public var isStarted(get, never):Bool; + + private function get_isStarted():Bool { + return __started; + } + + /** The default juggler of this instance. Will be advanced once per frame. */ + public var juggler(get, never):Juggler; + + private function get_juggler():Juggler { + return __juggler; + } + + /** The painter, which is used for all rendering. The same instance is passed to all + * rendermethods each frame. */ + public var painter(get, never):Painter; + + private function get_painter():Painter { + return __painter; + } + + /** The render context of this instance. */ + public var context(get, never):Context3D; + + private function get_context():Context3D { + return __painter.context; + } + + /** Indicates if multitouch simulation with "Shift" and "Ctrl"/"Cmd"-keys is enabled. + * @default false */ + public var simulateMultitouch(get, set):Bool; + + private function get_simulateMultitouch():Bool { + return __touchProcessor.simulateMultitouch; + } + + private function set_simulateMultitouch(value:Bool):Bool { + return __touchProcessor.simulateMultitouch = value; + } + + /** Indicates if Stage3D render methods will report errors. It's recommended to activate + * this when writing custom rendering code (shaders, etc.), since you'll get more detailed + * error messages. However, it has a very negative impact on performance, and it prevents + * ATF textures from being restored on a context loss. Never activate for release builds! + * + * @default false */ + public var enableErrorChecking(get, set):Bool; + + private function get_enableErrorChecking():Bool { + return __painter.enableErrorChecking; + } + + private function set_enableErrorChecking(value:Bool):Bool { + return __painter.enableErrorChecking = value; + } + + /** The antialiasing level. 0 - no antialasing, 16 - maximum antialiasing. @default 0 */ + public var antiAliasing(get, set):Int; + + private function get_antiAliasing():Int { + return __antiAliasing; + } + + private function set_antiAliasing(value:Int):Int { + if (__antiAliasing != value) { + __antiAliasing = value; + if (contextValid) + updateViewPort(true); + } + return value; + } + + /** The viewport into which Starling contents will be rendered. */ + public var viewPort(get, set):Rectangle; + + private function get_viewPort():Rectangle { + return __viewPort; + } + + private function set_viewPort(value:Rectangle):Rectangle { + __viewPort.copyFrom(value); + return value; + } + + /** The ratio between viewPort width and stage width. Useful for choosing a different + * set of textures depending on the display resolution. */ + public var contentScaleFactor(get, never):Float; + + private function get_contentScaleFactor():Float { + return (__viewPort.width * __painter.backBufferScaleFactor) / __stage.stageWidth; + } + + /** A Flash Sprite placed directly on top of the Starling content. Use it to display native + * Flash components. */ + public var nativeOverlay(get, never):Sprite; + + private function get_nativeOverlay():Sprite { + return __nativeOverlay; + } + + /** If enabled, touches or mouse events on the native overlay won't be propagated to + * Starling. @default false */ + public var nativeOverlayBlocksTouches(get, set):Bool; + + private function get_nativeOverlayBlocksTouches():Bool { + return __touchProcessor.occlusionTest != null; + } + + private function set_nativeOverlayBlocksTouches(value:Bool):Bool { + if (value != __nativeOverlayBlocksTouches) + __touchProcessor.occlusionTest = value ? hitTestNativeOverlay : null; + return __nativeOverlayBlocksTouches = value; + } + + /** Indicates if a small statistics box (with FPS, memory usage and draw count) is + * displayed. + * + *

Beware that the memory usage should be taken with a grain of salt. The value is + * determined via System.totalMemory and does not take texture memory + * into account. It is recommended to use Adobe Scout for reliable and comprehensive + * memory analysis.

+ */ + public var showStats(get, set):Bool; + + private function get_showStats():Bool { + return __showStats; + } + + private function set_showStats(value:Bool):Bool { + __showStats = value; + + if (value) { + var h = Reflect.hasField(__statsDisplayAlign, "horizontal") ? Reflect.field(__statsDisplayAlign, "horizontal") : null; + var v = Reflect.hasField(__statsDisplayAlign, "vertical") ? Reflect.field(__statsDisplayAlign, "vertical") : null; + showStatsAt(h != null ? h : "left", v != null ? v : "top"); + } else if (__statsDisplay != null) { + __statsDisplay.removeFromParent(); + } + + return value; + } + + /** Displays the statistics box at a certain position. */ + public function showStatsAt(horizontalAlign:String = "left", verticalAlign:String = "top", scale:Float = 1):Void { + function onRootCreated():Void { + if (__showStats) + showStatsAt(horizontalAlign, verticalAlign, scale); + removeEventListener(starling.events.Event.ROOT_CREATED, onRootCreated); + } + + __showStats = true; + Reflect.setField(__statsDisplayAlign, "horizontal", horizontalAlign); + Reflect.setField(__statsDisplayAlign, "vertical", verticalAlign); + + if (context == null) { + // Starling is not yet ready - we postpone this until it's initialized. + addEventListener(starling.events.Event.ROOT_CREATED, onRootCreated); + } else { + if (__statsDisplay == null) { + __statsDisplay = new StatsDisplay(); + __statsDisplay.touchable = false; + } + + __stage.addChild(__statsDisplay); + __statsDisplay.scaleX = __statsDisplay.scaleY = scale; __statsDisplay.showSkipped = __skipUnchangedFrames; - - updateClippedViewPort(); - updateStatsDisplayPosition(); - } - } - - private function updateStatsDisplayPosition():Void - { - if (!__showStats || __statsDisplay == null) return; - - // The stats display must always be visible, i.e. inside the clipped viewPort. - // So we take viewPort clipping into account when calculating its position. - - var horizontalAlign:String = Reflect.hasField(__statsDisplayAlign, "horizontal") ? Reflect.field(__statsDisplayAlign, "horizontal") : null; - var verticalAlign:String = Reflect.hasField(__statsDisplayAlign, "vertical") ? Reflect.field(__statsDisplayAlign, "vertical") : null; - var scaleX:Float = __viewPort.width / __stage.stageWidth; - var scaleY:Float = __viewPort.height / __stage.stageHeight; - var clipping:Rectangle = Pool.getRectangle( - __viewPort.x < 0 ? -__viewPort.x / scaleX : 0.0, - __viewPort.y < 0 ? -__viewPort.y / scaleY : 0.0, - __clippedViewPort.width / scaleX, - __clippedViewPort.height / scaleY); - - if (horizontalAlign == Align.LEFT) __statsDisplay.x = clipping.x; - else if (horizontalAlign == Align.RIGHT) __statsDisplay.x = clipping.right - __statsDisplay.width; - else if (horizontalAlign == Align.CENTER) __statsDisplay.x = (clipping.right - __statsDisplay.width) / 2; - else throw new ArgumentError("Invalid horizontal alignment: " + horizontalAlign); - - if (verticalAlign == Align.TOP) __statsDisplay.y = clipping.y; - else if (verticalAlign == Align.BOTTOM) __statsDisplay.y = clipping.bottom - __statsDisplay.height; - else if (verticalAlign == Align.CENTER) __statsDisplay.y = (clipping.bottom - __statsDisplay.height) / 2; - else throw new ArgumentError("Invalid vertical alignment: " + verticalAlign); - - Pool.putRectangle(clipping); - } - - /** The Starling stage object, which is the root of the display tree that is rendered. */ - public var stage(get, never):Stage; - private function get_stage():Stage { return __stage; } - - /** The Flash Stage3D object Starling renders into. */ - public var stage3D(get, never):Stage3D; - private function get_stage3D():Stage3D { return __painter.stage3D; } - - /** The Flash (2D) stage object Starling renders beneath. */ - public var nativeStage(get, never):OpenFLStage; - private function get_nativeStage():OpenFLStage { return __nativeStage; } - - /** The instance of the root class provided in the constructor. Available as soon as - * the event 'ROOT_CREATED' has been dispatched. */ - public var root(get, never):DisplayObject; - private function get_root():DisplayObject { return __root; } - - /** The class that will be instantiated by Starling as the 'root' display object. - * Must be a subclass of 'starling.display.DisplayObject'. - * - *

If you passed null as first parameter to the Starling constructor, - * you can use this property to set the root class at a later time. As soon as the class - * is instantiated, Starling will dispatch a ROOT_CREATED event.

- * - *

Beware: you cannot change the root class once the root object has been - * instantiated.

- */ - public var rootClass(get, set):Class; - private function get_rootClass():Class { return __rootClass; } - private function set_rootClass(value:Class):Class - { - if (__rootClass != null && __root != null) - throw new Error("Root class may not change after root has been instantiated"); - else if (__rootClass == null) - { - __rootClass = value; - if (context != null) initializeRoot(); - } - return value; - } - - /** Indicates if another Starling instance (or another Stage3D framework altogether) - * uses the same render context. If enabled, Starling will not execute any destructive - * context operations (e.g. not call 'configureBackBuffer', 'clear', 'present', etc. - * This has to be done manually, then. @default false */ - public var shareContext(get, set):Bool; - private function get_shareContext():Bool { return __painter.shareContext; } - private function set_shareContext(value:Bool):Bool - { - if (!value) __previousViewPort.setEmpty(); // forces back buffer update - return __painter.shareContext = value; - } - - /** The Context3D profile of the current render context, or null - * if the context has not been created yet. */ - public var profile(get, never):Context3DProfile; - private function get_profile():Context3DProfile { return __painter.profile; } - - /** Indicates that if the device supports HiDPI screens Starling will attempt to allocate - * a larger back buffer than indicated via the viewPort size. Note that this is used - * on Desktop only; mobile AIR apps still use the "requestedDisplayResolution" parameter - * the application descriptor XML. @default false */ - public var supportHighResolutions(get, set):Bool; - private function get_supportHighResolutions():Bool { return __supportHighResolutions; } - private function set_supportHighResolutions(value:Bool):Bool - { - if (__supportHighResolutions != value) - { - __supportHighResolutions = value; - if (contextValid) updateViewPort(true); - } - return value; - } - - /** If enabled, the Stage3D back buffer will change its size according to the browser zoom - * value - similar to what's done when "supportHighResolutions" is enabled. The resolution - * is updated on the fly when the zoom factor changes. Only relevant for the browser plugin. - * @default false */ - public var supportBrowserZoom(get, set):Bool; - private function get_supportBrowserZoom():Bool { return __supportBrowserZoom; } - private function set_supportBrowserZoom(value:Bool):Bool - { - if (__supportBrowserZoom != value) - { - __supportBrowserZoom = value; - #if air - if (contextValid) updateViewPort(true); - - if (value) __nativeStage.addEventListener( - Event.BROWSER_ZOOM_CHANGE, onBrowserZoomChange, false, 0, true); - else __nativeStage.removeEventListener( - Event.BROWSER_ZOOM_CHANGE, onBrowserZoomChange, false); - #end - } - return value; - } - - /** When enabled, Starling will skip rendering the stage if it hasn't changed since the - * last frame. This is great for apps that remain static from time to time, since it will - * greatly reduce power consumption. You should activate this whenever possible! - * - *

The reason why it's disabled by default is just that it causes problems with Render- - * and VideoTextures. When you use those, you either have to disable this property - * temporarily, or call setRequiresRedraw() (ideally on the stage) whenever - * those textures are changing. Otherwise, the changes won't show up.

- * - * @default false - */ - public var skipUnchangedFrames(get, set):Bool; - private function get_skipUnchangedFrames():Bool { return __skipUnchangedFrames; } - private function set_skipUnchangedFrames(value:Bool):Bool - { - __skipUnchangedFrames = value; - __nativeStageEmpty = false; // required by 'mustAlwaysRender' - if (__statsDisplay != null) __statsDisplay.showSkipped = value; - return value; - } - - /** The TouchProcessor is passed all mouse and touch input and is responsible for - * dispatching TouchEvents to the Starling display tree. If you want to handle these - * types of input manually, pass your own custom subclass to this property. */ - public var touchProcessor(get, set):TouchProcessor; - private function get_touchProcessor():TouchProcessor { return __touchProcessor; } - private function set_touchProcessor(value:TouchProcessor):TouchProcessor - { - if (value == null) throw new ArgumentError("TouchProcessor must not be null"); - else if (value != __touchProcessor) - { - __touchProcessor.dispose(); - __touchProcessor = value; - } - return value; - } - - /** When enabled, all touches that start very close to the screen edges are discarded. + + updateClippedViewPort(); + updateStatsDisplayPosition(); + } + } + + private function updateStatsDisplayPosition():Void { + if (!__showStats || __statsDisplay == null) + return; + + // The stats display must always be visible, i.e. inside the clipped viewPort. + // So we take viewPort clipping into account when calculating its position. + + var horizontalAlign:String = Reflect.hasField(__statsDisplayAlign, "horizontal") ? Reflect.field(__statsDisplayAlign, "horizontal") : null; + var verticalAlign:String = Reflect.hasField(__statsDisplayAlign, "vertical") ? Reflect.field(__statsDisplayAlign, "vertical") : null; + var scaleX:Float = __viewPort.width / __stage.stageWidth; + var scaleY:Float = __viewPort.height / __stage.stageHeight; + var clipping:Rectangle = Pool.getRectangle(__viewPort.x < 0 ? -__viewPort.x / scaleX : 0.0, __viewPort.y < 0 ? -__viewPort.y / scaleY : 0.0, + __clippedViewPort.width / scaleX, __clippedViewPort.height / scaleY); + + if (horizontalAlign == Align.LEFT) + __statsDisplay.x = clipping.x; + else if (horizontalAlign == Align.RIGHT) + __statsDisplay.x = clipping.right - __statsDisplay.width; + else if (horizontalAlign == Align.CENTER) + __statsDisplay.x = (clipping.right - __statsDisplay.width) / 2; + else + throw new ArgumentError("Invalid horizontal alignment: " + horizontalAlign); + + if (verticalAlign == Align.TOP) + __statsDisplay.y = clipping.y; + else if (verticalAlign == Align.BOTTOM) + __statsDisplay.y = clipping.bottom - __statsDisplay.height; + else if (verticalAlign == Align.CENTER) + __statsDisplay.y = (clipping.bottom - __statsDisplay.height) / 2; + else + throw new ArgumentError("Invalid vertical alignment: " + verticalAlign); + + Pool.putRectangle(clipping); + } + + /** The Starling stage object, which is the root of the display tree that is rendered. */ + public var stage(get, never):Stage; + + private function get_stage():Stage { + return __stage; + } + + /** The Flash Stage3D object Starling renders into. */ + public var stage3D(get, never):Stage3D; + + private function get_stage3D():Stage3D { + return __painter.stage3D; + } + + /** The Flash (2D) stage object Starling renders beneath. */ + public var nativeStage(get, never):OpenFLStage; + + private function get_nativeStage():OpenFLStage { + return __nativeStage; + } + + /** The instance of the root class provided in the constructor. Available as soon as + * the event 'ROOT_CREATED' has been dispatched. */ + public var root(get, never):DisplayObject; + + private function get_root():DisplayObject { + return __root; + } + + /** The class that will be instantiated by Starling as the 'root' display object. + * Must be a subclass of 'starling.display.DisplayObject'. + * + *

If you passed null as first parameter to the Starling constructor, + * you can use this property to set the root class at a later time. As soon as the class + * is instantiated, Starling will dispatch a ROOT_CREATED event.

+ * + *

Beware: you cannot change the root class once the root object has been + * instantiated.

+ */ + public var rootClass(get, set):Class; + + private function get_rootClass():Class { + return __rootClass; + } + + private function set_rootClass(value:Class):Class { + if (__rootClass != null && __root != null) + throw new Error("Root class may not change after root has been instantiated"); + else if (__rootClass == null) { + __rootClass = value; + if (context != null) + initializeRoot(); + } + return value; + } + + /** Indicates if another Starling instance (or another Stage3D framework altogether) + * uses the same render context. If enabled, Starling will not execute any destructive + * context operations (e.g. not call 'configureBackBuffer', 'clear', 'present', etc. + * This has to be done manually, then. @default false */ + public var shareContext(get, set):Bool; + + private function get_shareContext():Bool { + return __painter.shareContext; + } + + private function set_shareContext(value:Bool):Bool { + if (!value) + __previousViewPort.setEmpty(); // forces back buffer update + return __painter.shareContext = value; + } + + /** The Context3D profile of the current render context, or null + * if the context has not been created yet. */ + public var profile(get, never):Context3DProfile; + + private function get_profile():Context3DProfile { + return __painter.profile; + } + + /** Indicates that if the device supports HiDPI screens Starling will attempt to allocate + * a larger back buffer than indicated via the viewPort size. Note that this is used + * on Desktop only; mobile AIR apps still use the "requestedDisplayResolution" parameter + * the application descriptor XML. @default false */ + public var supportHighResolutions(get, set):Bool; + + private function get_supportHighResolutions():Bool { + return __supportHighResolutions; + } + + private function set_supportHighResolutions(value:Bool):Bool { + if (__supportHighResolutions != value) { + __supportHighResolutions = value; + if (contextValid) + updateViewPort(true); + } + return value; + } + + /** If enabled, the Stage3D back buffer will change its size according to the browser zoom + * value - similar to what's done when "supportHighResolutions" is enabled. The resolution + * is updated on the fly when the zoom factor changes. Only relevant for the browser plugin. + * @default false */ + public var supportBrowserZoom(get, set):Bool; + + private function get_supportBrowserZoom():Bool { + return __supportBrowserZoom; + } + + private function set_supportBrowserZoom(value:Bool):Bool { + if (__supportBrowserZoom != value) { + __supportBrowserZoom = value; + #if air + if (contextValid) + updateViewPort(true); + + if (value) + __nativeStage.addEventListener(Event.BROWSER_ZOOM_CHANGE, onBrowserZoomChange, false, 0, true); + else + __nativeStage.removeEventListener(Event.BROWSER_ZOOM_CHANGE, onBrowserZoomChange, false); + #end + } + return value; + } + + /** When enabled, Starling will skip rendering the stage if it hasn't changed since the + * last frame. This is great for apps that remain static from time to time, since it will + * greatly reduce power consumption. You should activate this whenever possible! + * + *

The reason why it's disabled by default is just that it causes problems with Render- + * and VideoTextures. When you use those, you either have to disable this property + * temporarily, or call setRequiresRedraw() (ideally on the stage) whenever + * those textures are changing. Otherwise, the changes won't show up.

+ * + * @default false + */ + public var skipUnchangedFrames(get, set):Bool; + + private function get_skipUnchangedFrames():Bool { + return __skipUnchangedFrames; + } + + private function set_skipUnchangedFrames(value:Bool):Bool { + __skipUnchangedFrames = value; + __nativeStageEmpty = false; // required by 'mustAlwaysRender' + if (__statsDisplay != null) + __statsDisplay.showSkipped = value; + return value; + } + + /** The TouchProcessor is passed all mouse and touch input and is responsible for + * dispatching TouchEvents to the Starling display tree. If you want to handle these + * types of input manually, pass your own custom subclass to this property. */ + public var touchProcessor(get, set):TouchProcessor; + + private function get_touchProcessor():TouchProcessor { + return __touchProcessor; + } + + private function set_touchProcessor(value:TouchProcessor):TouchProcessor { + if (value == null) + throw new ArgumentError("TouchProcessor must not be null"); + else if (value != __touchProcessor) { + __touchProcessor.dispose(); + __touchProcessor = value; + } + return value; + } + + /** When enabled, all touches that start very close to the screen edges are discarded. * On mobile, such touches often indicate swipes that are meant to use OS features. * Per default, margins of 15 points at the top, bottom, and left side of the screen are * checked. Call starling.touchProcessor.setSystemGestureMargins() to adapt * the margins in each direction. @default true on mobile, false on desktop */ - public var discardSystemGestures(get, set):Bool; - private function get_discardSystemGestures():Bool - { - return __touchProcessor.discardSystemGestures; - } - - private function set_discardSystemGestures(value:Bool):Bool - { - __touchProcessor.discardSystemGestures = value; - return value; - } - - /** The number of frames that have been rendered since this instance was created. */ - public var frameID(get, never):UInt; - private function get_frameID():UInt { return __frameID; } - - /** Indicates if the Context3D object is currently valid (i.e. it hasn't been lost or - * disposed). */ - public var contextValid(get, never):Bool; - private function get_contextValid():Bool { return __painter.contextValid; } - - // static properties - - /** The currently active Starling instance. */ - public static var current(get, never):Starling; - private static function get_current():Starling { return sCurrent; } - - /** All Starling instances.

CAUTION: not a copy, but the actual object! Do not modify!

*/ - public static var all(get, never):Array; - private static function get_all():Array { return sAll; } - - /** The render context of the currently active Starling instance. */ - public static var currentContext(get, never):Context3D; - private static function get_currentContext():Context3D { return sCurrent != null ? sCurrent.context : null; } - - /** The default juggler of the currently active Starling instance. */ - public static var currentJuggler(get, never):Juggler; - private static function get_currentJuggler():Juggler { return sCurrent != null ? sCurrent.__juggler : null; } - - /** The contentScaleFactor of the currently active Starling instance. */ - public static var currentContentScaleFactor(get, never):Float; - private static function get_currentContentScaleFactor():Float - { - return sCurrent != null ? sCurrent.contentScaleFactor : 1.0; - } - - /** Indicates if multitouch input should be supported. You can enable or disable - * multitouch at any time; just beware that any current touches will be cancelled. */ - public static var multitouchEnabled(get, set):Bool; - private static function get_multitouchEnabled():Bool - { - var enabled:Bool = Multitouch.inputMode == MultitouchInputMode.TOUCH_POINT; - var outOfSync:Bool = false; - - for (star in sAll) - if (star.__multitouchEnabled != enabled) - outOfSync = true; - - if (outOfSync) - trace("[Starling] Warning: multitouch settings are out of sync. Always set " + - "'Starling.multitouchEnabled' instead of 'Multitouch.inputMode'."); - - return enabled; - } - - private static function set_multitouchEnabled(value:Bool):Bool - { - var wasEnabled:Bool = Multitouch.inputMode == MultitouchInputMode.TOUCH_POINT; - - Multitouch.inputMode = value ? MultitouchInputMode.TOUCH_POINT : - MultitouchInputMode.NONE; - - var isEnabled:Bool = Multitouch.inputMode == MultitouchInputMode.TOUCH_POINT; - - if (wasEnabled != isEnabled) - { - for (star in sAll) - star.setMultitouchEnabled(isEnabled); - } - - return value; - } - - /** The number of frames that have been rendered since the current instance was created. */ - // public static var frameID(get, never):UInt; - // public static function get_frameID():UInt - // { - // return sCurrent != null ? sCurrent.__frameID : 0; - // } - - private function isNativeDisplayObjectEmpty(object:openfl.display.DisplayObject):Bool - { - if (object == null) return true; - else if (#if (haxe_ver < 4.2) Std.is #else Std.isOfType #end(object, DisplayObjectContainer)) - { - var container:DisplayObjectContainer = cast object; - var numChildren:Int = container.numChildren; - - for (i in 0...numChildren) - { - if (!isNativeDisplayObjectEmpty(container.getChildAt(i))) - return false; - } - - return true; - } - else return !object.visible; - } + public var discardSystemGestures(get, set):Bool; + + private function get_discardSystemGestures():Bool { + return __touchProcessor.discardSystemGestures; + } + + private function set_discardSystemGestures(value:Bool):Bool { + __touchProcessor.discardSystemGestures = value; + return value; + } + + /** The number of frames that have been rendered since this instance was created. */ + public var frameID(get, never):UInt; + + private function get_frameID():UInt { + return __frameID; + } + + /** Indicates if the Context3D object is currently valid (i.e. it hasn't been lost or + * disposed). */ + public var contextValid(get, never):Bool; + + private function get_contextValid():Bool { + return __painter.contextValid; + } + + // static properties + + /** The currently active Starling instance. */ + public static var current(get, never):Starling; + + private static function get_current():Starling { + return sCurrent; + } + + /** All Starling instances.

CAUTION: not a copy, but the actual object! Do not modify!

*/ + public static var all(get, never):Array; + + private static function get_all():Array { + return sAll; + } + + /** The render context of the currently active Starling instance. */ + public static var currentContext(get, never):Context3D; + + private static function get_currentContext():Context3D { + return sCurrent != null ? sCurrent.context : null; + } + + /** The default juggler of the currently active Starling instance. */ + public static var currentJuggler(get, never):Juggler; + + private static function get_currentJuggler():Juggler { + return sCurrent != null ? sCurrent.__juggler : null; + } + + /** The contentScaleFactor of the currently active Starling instance. */ + public static var currentContentScaleFactor(get, never):Float; + + private static function get_currentContentScaleFactor():Float { + return sCurrent != null ? sCurrent.contentScaleFactor : 1.0; + } + + /** Indicates if multitouch input should be supported. You can enable or disable + * multitouch at any time; just beware that any current touches will be cancelled. */ + public static var multitouchEnabled(get, set):Bool; + + private static function get_multitouchEnabled():Bool { + var enabled:Bool = Multitouch.inputMode == MultitouchInputMode.TOUCH_POINT; + var outOfSync:Bool = false; + + for (star in sAll) + if (star.__multitouchEnabled != enabled) + outOfSync = true; + + if (outOfSync) + trace("[Starling] Warning: multitouch settings are out of sync. Always set " + "'Starling.multitouchEnabled' instead of 'Multitouch.inputMode'."); + + return enabled; + } + + private static function set_multitouchEnabled(value:Bool):Bool { + var wasEnabled:Bool = Multitouch.inputMode == MultitouchInputMode.TOUCH_POINT; + + Multitouch.inputMode = value ? MultitouchInputMode.TOUCH_POINT : MultitouchInputMode.NONE; + + var isEnabled:Bool = Multitouch.inputMode == MultitouchInputMode.TOUCH_POINT; + + if (wasEnabled != isEnabled) { + for (star in sAll) + star.setMultitouchEnabled(isEnabled); + } + + return value; + } + + /** The number of frames that have been rendered since the current instance was created. */ + // public static var frameID(get, never):UInt; + // public static function get_frameID():UInt + // { + // return sCurrent != null ? sCurrent.__frameID : 0; + // } + + private function isNativeDisplayObjectEmpty(object:openfl.display.DisplayObject):Bool { + if (object == null) + return true; + else if (#if (haxe_ver < 4.2) Std.is #else Std.isOfType #end (object, DisplayObjectContainer)) { + var container:DisplayObjectContainer = cast object; + var numChildren:Int = container.numChildren; + + for (i in 0...numChildren) { + if (!isNativeDisplayObjectEmpty(container.getChildAt(i))) + return false; + } + + return true; + } else + return !object.visible; + } } diff --git a/src/starling/display/DisplayObjectContainer.hx b/src/starling/display/DisplayObjectContainer.hx index 7e44c2d5..4a9d409c 100644 --- a/src/starling/display/DisplayObjectContainer.hx +++ b/src/starling/display/DisplayObjectContainer.hx @@ -21,7 +21,7 @@ import starling.rendering.BatchToken; import starling.rendering.Painter; import starling.utils.MatrixUtil; import starling.utils.Max; - +import starling.utils.ArrayUtil; /** * A DisplayObjectContainer represents a collection of display objects. * It is the base class of all display objects that act as a container for other objects. By @@ -358,9 +358,17 @@ class DisplayObjectContainer extends DisplayObject { * first object is greater than the second. */ public function sortChildren(compareFunction:DisplayObject->DisplayObject->Int):Void { + #if (haxe_ver >= 4.0) sSortBuffer.resize(__children.length); + #else + ArrayUtil.resize(sSortBuffer, __children.length); + #end mergeSort(__children, compareFunction, 0, __children.length, sSortBuffer); + #if (haxe_ver >= 4.0) sSortBuffer.resize(0); + #else + ArrayUtil.resize(sSortBuffer, 0); + #end setRequiresRedraw(); } @@ -551,8 +559,12 @@ class DisplayObjectContainer extends DisplayObject { for (i in fromIndex...toIndex) sBroadcastListeners[i].dispatchEvent(event); - + + #if (haxe_ver >= 4.0) sBroadcastListeners.resize(fromIndex); + #else + ArrayUtil.resize(sBroadcastListeners, fromIndex); + #end } /** @@ -649,7 +661,7 @@ class DisplayObjectContainer extends DisplayObject { if (object.hasEventListener(eventType)) { listeners[listeners.length] = object; // avoiding 'push' } - + var container:DisplayObjectContainer = Std.isOfType(object, DisplayObjectContainer) ? cast object : null; if (container != null) { diff --git a/src/starling/display/Stage.hx b/src/starling/display/Stage.hx index ea211ea1..0faa8a81 100644 --- a/src/starling/display/Stage.hx +++ b/src/starling/display/Stage.hx @@ -7,7 +7,6 @@ // in accordance with the terms of the accompanying license agreement. // // ================================================================================================= - package starling.display; import openfl.errors.IllegalOperationError; @@ -16,7 +15,6 @@ import openfl.geom.Matrix3D; import openfl.geom.Point; import openfl.geom.Rectangle; import openfl.geom.Vector3D; - import starling.core.Starling; import starling.events.EnterFrameEvent; import starling.events.Event; @@ -25,8 +23,7 @@ import starling.utils.MatrixUtil; import starling.utils.RectangleUtil; /** Dispatched when the Flash container is resized. */ -@:meta(Event(name="resize", type="starling.events.ResizeEvent")) - +@:meta(Event(name = "resize", type = "starling.events.ResizeEvent")) /** A Stage represents the root of the display tree. * Only objects that are direct or indirect children of the stage will be rendered. * @@ -53,339 +50,336 @@ import starling.utils.RectangleUtil; * @see starling.events.ResizeEvent * */ -class Stage extends DisplayObjectContainer -{ - @:noCompletion private var __width:Int; - @:noCompletion private var __height:Int; - @:noCompletion private var __color:UInt; - @:noCompletion private var __fieldOfView:Float; - @:noCompletion private var __projectionOffset:Point; - @:noCompletion private var __cameraPosition:Vector3D; - @:noCompletion private var __enterFrameEvent:EnterFrameEvent; - @:noCompletion private var __enterFrameListeners:Array; - - /** Helper objects. */ - private static var sMatrix:Matrix = new Matrix(); - private static var sMatrix3D:Matrix3D = new Matrix3D(); - - #if commonjs - private static function __init__ () { - - untyped Object.defineProperties (Stage.prototype, { - "color": { get: untyped __js__ ("function () { return this.get_color (); }"), set: untyped __js__ ("function (v) { return this.set_color (v); }") }, - "stageWidth": { get: untyped __js__ ("function () { return this.get_stageWidth (); }"), set: untyped __js__ ("function (v) { return this.set_stageWidth (v); }") }, - "stageHeight": { get: untyped __js__ ("function () { return this.get_stageHeight (); }"), set: untyped __js__ ("function (v) { return this.set_stageHeight (v); }") }, - "starling": { get: untyped __js__ ("function () { return this.get_starling (); }"), set: untyped __js__ ("function (v) { return this.set_starling (v); }") }, - "focalLength": { get: untyped __js__ ("function () { return this.get_focalLength (); }"), set: untyped __js__ ("function (v) { return this.set_focalLength (v); }") }, - "fieldOfView": { get: untyped __js__ ("function () { return this.get_fieldOfView (); }"), set: untyped __js__ ("function (v) { return this.set_fieldOfView (v); }") }, - "projectionOffset": { get: untyped __js__ ("function () { return this.get_projectionOffset (); }"), set: untyped __js__ ("function (v) { return this.set_projectionOffset (v); }") }, - "cameraPosition": { get: untyped __js__ ("function () { return this.get_cameraPosition (); }") }, - }); - - } - #end - - /** @private */ - private function new(width:Int, height:Int, color:UInt=0) - { - super(); - - __width = width; - __height = height; - __color = color; - __fieldOfView = 1.0; - __projectionOffset = new Point(); - __cameraPosition = new Vector3D(); - __enterFrameEvent = new EnterFrameEvent(Event.ENTER_FRAME, 0.0); - __enterFrameListeners = new Array(); - } - - /** @inheritDoc */ - public function advanceTime(passedTime:Float):Void - { - __enterFrameEvent.reset(Event.ENTER_FRAME, false, passedTime); - broadcastEvent(__enterFrameEvent); - } - - /** Returns the object that is found topmost beneath a point in stage coordinates, or - * the stage itself if nothing else is found. */ - public override function hitTest(localPoint:Point):DisplayObject - { - if (!visible || !touchable) return null; - - // locations outside of the stage area shouldn't be accepted - if (localPoint.x < 0 || localPoint.x > __width || - localPoint.y < 0 || localPoint.y > __height) - return null; - - // if nothing else is hit, the stage returns itself as target - var target:DisplayObject = super.hitTest(localPoint); - return target != null ? target : this; - } - - /** Returns the stage bounds (i.e. not the bounds of its contents, but the rectangle - * spawned up by 'stageWidth' and 'stageHeight') in another coordinate system. */ - public function getStageBounds(targetSpace:DisplayObject, out:Rectangle=null):Rectangle - { - if (out == null) out = new Rectangle(); - - out.setTo(0, 0, __width, __height); - getTransformationMatrix(targetSpace, sMatrix); - - return RectangleUtil.getBounds(out, sMatrix, out); - } - - /** Returns the bounds of the screen (or application window, on Desktop) relative to - * a certain coordinate system. In most cases, that's identical to the stage bounds; - * however, this changes if the viewPort is customized. */ - public function getScreenBounds(targetSpace:DisplayObject, out:Rectangle=null):Rectangle - { - var target:Starling = this.starling; - if (target == null) return getStageBounds(targetSpace, out); - if (out == null) out = new Rectangle(); - - var nativeStage:Dynamic = target.nativeStage; - var viewPort:Rectangle = target.viewPort; - var scaleX:Float = __width / viewPort.width; - var scaleY:Float = __height / viewPort.height; - var x:Float = -viewPort.x * scaleX; - var y:Float = -viewPort.y * scaleY; - - out.setTo(x, y, nativeStage.stageWidth * scaleX, nativeStage.stageHeight * scaleY); - getTransformationMatrix(targetSpace, sMatrix); - - return RectangleUtil.getBounds(out, sMatrix, out); - } - - // camera positioning - - /** Returns the position of the camera within the local coordinate system of a certain - * display object. If you do not pass a space, the method returns the global position. - * To change the position of the camera, you can modify the properties 'fieldOfView', - * 'focalDistance' and 'projectionOffset'. - */ - public function getCameraPosition(space:DisplayObject=null, out:Vector3D=null):Vector3D - { - getTransformationMatrix3D(space, sMatrix3D); - - return MatrixUtil.transformCoords3D(sMatrix3D, - __width / 2 + __projectionOffset.x, __height / 2 + __projectionOffset.y, - -focalLength, out); - } - - // enter frame event optimization - - /** @private */ - @:allow(starling) private function addEnterFrameListener(listener:DisplayObject):Void - { - var index:Int = __enterFrameListeners.indexOf(listener); - if (index < 0) __enterFrameListeners[__enterFrameListeners.length] = listener; - } - - /** @private */ - @:allow(starling) private function removeEnterFrameListener(listener:DisplayObject):Void - { - var index:Int = __enterFrameListeners.indexOf(listener); - if (index >= 0) __enterFrameListeners.splice(index, 1); - } - - /** @private */ - @:allow(starling) private override function __getChildEventListeners(object:DisplayObject, eventType:String, - listeners:Array):Void - { - if (eventType == Event.ENTER_FRAME && object == this) - { - var length:Int = __enterFrameListeners.length; - for (i in 0...length) - listeners[listeners.length] = __enterFrameListeners[i]; // avoiding 'push' - } - else - super.__getChildEventListeners(object, eventType, listeners); - } - - // properties - - /** @private */ - private override function set_width(value:Float):Float - { - throw new IllegalOperationError("Cannot set width of stage"); - return 0; - } - - /** @private */ - private override function set_height(value:Float):Float - { - throw new IllegalOperationError("Cannot set height of stage"); - return 0; - } - - /** @private */ - private override function set_x(value:Float):Float - { - throw new IllegalOperationError("Cannot set x-coordinate of stage"); - return 0; - } - - /** @private */ - private override function set_y(value:Float):Float - { - throw new IllegalOperationError("Cannot set y-coordinate of stage"); - return 0; - } - - /** @private */ - private override function set_scaleX(value:Float):Float - { - throw new IllegalOperationError("Cannot scale stage"); - return 0; - } - - /** @private */ - private override function set_scaleY(value:Float):Float - { - throw new IllegalOperationError("Cannot scale stage"); - return 0; - } - - /** @private */ - private override function set_rotation(value:Float):Float - { - throw new IllegalOperationError("Cannot rotate stage"); - return 0; - } - - /** @private */ - private override function set_skewX(value:Float):Float - { - throw new IllegalOperationError("Cannot skew stage"); - return 0; - } - - /** @private */ - private override function set_skewY(value:Float):Float - { - throw new IllegalOperationError("Cannot skew stage"); - return 0; - } - - /** @private */ - private override function set_filter(value:FragmentFilter):FragmentFilter - { - throw new IllegalOperationError("Cannot add filter to stage. Add it to 'root' instead!"); - return null; - } - - /** The background color of the stage. */ - public var color(get, set):UInt; - private function get_color():UInt { return __color; } - private function set_color(value:UInt):UInt - { - if (__color != value) - { - __color = value; - setRequiresRedraw(); - } - return value; - } - - /** The width of the stage coordinate system. Change it to scale its contents relative - * to the viewPort property of the Starling object. */ - public var stageWidth(get, set):Int; - private function get_stageWidth():Int { return __width; } - private function set_stageWidth(value:Int):Int - { - if (__width != value) - { - __width = value; - setRequiresRedraw(); - } - return value; - } - - /** The height of the stage coordinate system. Change it to scale its contents relative - * to the viewPort property of the Starling object. */ - public var stageHeight(get, set):Int; - private function get_stageHeight():Int { return __height; } - private function set_stageHeight(value:Int):Int - { - if (__height != value) - { - __height = value; - setRequiresRedraw(); - } - return value; - } - - /** The Starling instance this stage belongs to. */ - public var starling(get, never):Starling; - private function get_starling():Starling - { - var instances:Array = Starling.all; - var numInstances:Int = instances.length; - - for (i in 0...numInstances) - if (instances[i].stage == this) return instances[i]; - - return null; - } - - /** The distance between the stage and the camera. Changing this value will update the - * field of view accordingly. */ - public var focalLength(get, set):Float; - private function get_focalLength():Float - { - return __width / (2 * Math.tan(__fieldOfView/2)); - } - - private function set_focalLength(value:Float):Float - { - __fieldOfView = 2 * Math.atan(stageWidth / (2*value)); - setRequiresRedraw(); - return value; - } - - /** Specifies an angle (radian, between zero and PI) for the field of view. This value - * determines how strong the perspective transformation and distortion apply to a Sprite3D - * object. - * - *

A value close to zero will look similar to an orthographic projection; a value - * close to PI results in a fisheye lens effect. If the field of view is set to 0 or PI, - * nothing is seen on the screen.

- * - * @default 1.0 - */ - public var fieldOfView(get, set):Float; - private function get_fieldOfView():Float { return __fieldOfView; } - private function set_fieldOfView(value:Float):Float - { - __fieldOfView = value; - setRequiresRedraw(); - return value; - } - - /** A Array that moves the camera away from its default position in the center of the - * stage. Use this property to change the center of projection, i.e. the vanishing - * point for 3D display objects.

CAUTION: not a copy, but the actual object!

- */ - public var projectionOffset(get, set):Point; - private function get_projectionOffset():Point { return __projectionOffset; } - private function set_projectionOffset(value:Point):Point - { - __projectionOffset.setTo(value.x, value.y); - setRequiresRedraw(); - return value; - } - - /** The global position of the camera. This property can only be used to find out the - * current position, but not to modify it. For that, use the 'projectionOffset', - * 'fieldOfView' and 'focalLength' properties. If you need the camera position in - * a certain coordinate space, use 'getCameraPosition' instead. - * - *

CAUTION: not a copy, but the actual object!

- */ - public var cameraPosition(get, never):Vector3D; - private function get_cameraPosition():Vector3D - { - return getCameraPosition(null, __cameraPosition); - } -} \ No newline at end of file +class Stage extends DisplayObjectContainer { + @:noCompletion private var __width:Int; + @:noCompletion private var __height:Int; + @:noCompletion private var __color:UInt; + @:noCompletion private var __fieldOfView:Float; + @:noCompletion private var __projectionOffset:Point; + @:noCompletion private var __cameraPosition:Vector3D; + @:noCompletion private var __enterFrameEvent:EnterFrameEvent; + @:noCompletion private var __enterFrameListeners:Array; + + /** Helper objects. */ + private static var sMatrix:Matrix = new Matrix(); + + private static var sMatrix3D:Matrix3D = new Matrix3D(); + + #if commonjs + private static function __init__() { + untyped Object.defineProperties(Stage.prototype, { + "color": {get: untyped __js__("function () { return this.get_color (); }"), set: untyped __js__("function (v) { return this.set_color (v); }")}, + "stageWidth": {get: untyped __js__("function () { return this.get_stageWidth (); }"), + set: untyped __js__("function (v) { return this.set_stageWidth (v); }")}, + "stageHeight": {get: untyped __js__("function () { return this.get_stageHeight (); }"), + set: untyped __js__("function (v) { return this.set_stageHeight (v); }")}, + "starling": {get: untyped __js__("function () { return this.get_starling (); }"), + set: untyped __js__("function (v) { return this.set_starling (v); }")}, + "focalLength": {get: untyped __js__("function () { return this.get_focalLength (); }"), + set: untyped __js__("function (v) { return this.set_focalLength (v); }")}, + "fieldOfView": {get: untyped __js__("function () { return this.get_fieldOfView (); }"), + set: untyped __js__("function (v) { return this.set_fieldOfView (v); }")}, + "projectionOffset": {get: untyped __js__("function () { return this.get_projectionOffset (); }"), + set: untyped __js__("function (v) { return this.set_projectionOffset (v); }")}, + "cameraPosition": {get: untyped __js__("function () { return this.get_cameraPosition (); }")}, + }); + } + #end + + /** @private */ + private function new(width:Int, height:Int, color:UInt = 0) { + super(); + + __width = width; + __height = height; + __color = color; + __fieldOfView = 1.0; + __projectionOffset = new Point(); + __cameraPosition = new Vector3D(); + __enterFrameEvent = new EnterFrameEvent(Event.ENTER_FRAME, 0.0); + __enterFrameListeners = new Array(); + } + + /** @inheritDoc */ + public function advanceTime(passedTime:Float):Void { + __enterFrameEvent.reset(Event.ENTER_FRAME, false, passedTime); + broadcastEvent(__enterFrameEvent); + } + + /** Returns the object that is found topmost beneath a point in stage coordinates, or + * the stage itself if nothing else is found. */ + public override function hitTest(localPoint:Point):DisplayObject { + if (!visible || !touchable) + return null; + + // locations outside of the stage area shouldn't be accepted + if (localPoint.x < 0 || localPoint.x > __width || localPoint.y < 0 || localPoint.y > __height) + return null; + + // if nothing else is hit, the stage returns itself as target + var target:DisplayObject = super.hitTest(localPoint); + return target != null ? target : this; + } + + /** Returns the stage bounds (i.e. not the bounds of its contents, but the rectangle + * spawned up by 'stageWidth' and 'stageHeight') in another coordinate system. */ + public function getStageBounds(targetSpace:DisplayObject, out:Rectangle = null):Rectangle { + if (out == null) + out = new Rectangle(); + + out.setTo(0, 0, __width, __height); + getTransformationMatrix(targetSpace, sMatrix); + + return RectangleUtil.getBounds(out, sMatrix, out); + } + + /** Returns the bounds of the screen (or application window, on Desktop) relative to + * a certain coordinate system. In most cases, that's identical to the stage bounds; + * however, this changes if the viewPort is customized. */ + public function getScreenBounds(targetSpace:DisplayObject, out:Rectangle = null):Rectangle { + var target:Starling = this.starling; + if (target == null) + return getStageBounds(targetSpace, out); + if (out == null) + out = new Rectangle(); + + var nativeStage:Dynamic = target.nativeStage; + var viewPort:Rectangle = target.viewPort; + var scaleX:Float = __width / viewPort.width; + var scaleY:Float = __height / viewPort.height; + var x:Float = -viewPort.x * scaleX; + var y:Float = -viewPort.y * scaleY; + + out.setTo(x, y, nativeStage.stageWidth * scaleX, nativeStage.stageHeight * scaleY); + getTransformationMatrix(targetSpace, sMatrix); + + return RectangleUtil.getBounds(out, sMatrix, out); + } + + // camera positioning + + /** Returns the position of the camera within the local coordinate system of a certain + * display object. If you do not pass a space, the method returns the global position. + * To change the position of the camera, you can modify the properties 'fieldOfView', + * 'focalDistance' and 'projectionOffset'. + */ + public function getCameraPosition(space:DisplayObject = null, out:Vector3D = null):Vector3D { + getTransformationMatrix3D(space, sMatrix3D); + + return MatrixUtil.transformCoords3D(sMatrix3D, __width / 2 + __projectionOffset.x, __height / 2 + __projectionOffset.y, -focalLength, out); + } + + // enter frame event optimization + + /** @private */ + @:allow(starling) private function addEnterFrameListener(listener:DisplayObject):Void { + var index:Int = __enterFrameListeners.indexOf(listener); + if (index < 0) + __enterFrameListeners[__enterFrameListeners.length] = listener; + } + + /** @private */ + @:allow(starling) private function removeEnterFrameListener(listener:DisplayObject):Void { + var index:Int = __enterFrameListeners.indexOf(listener); + if (index >= 0) + __enterFrameListeners.splice(index, 1); + } + + /** @private */ + @:allow(starling) private override function __getChildEventListeners(object:DisplayObject, eventType:String, listeners:Array):Void { + if (eventType == Event.ENTER_FRAME && object == this) { + var length:Int = __enterFrameListeners.length; + for (i in 0...length) + listeners[listeners.length] = __enterFrameListeners[i]; // avoiding 'push' + } else + super.__getChildEventListeners(object, eventType, listeners); + } + + // properties + + /** @private */ + private override function set_width(value:Float):Float { + throw new IllegalOperationError("Cannot set width of stage"); + return 0; + } + + /** @private */ + private override function set_height(value:Float):Float { + throw new IllegalOperationError("Cannot set height of stage"); + return 0; + } + + /** @private */ + private override function set_x(value:Float):Float { + throw new IllegalOperationError("Cannot set x-coordinate of stage"); + return 0; + } + + /** @private */ + private override function set_y(value:Float):Float { + throw new IllegalOperationError("Cannot set y-coordinate of stage"); + return 0; + } + + /** @private */ + private override function set_scaleX(value:Float):Float { + throw new IllegalOperationError("Cannot scale stage"); + return 0; + } + + /** @private */ + private override function set_scaleY(value:Float):Float { + throw new IllegalOperationError("Cannot scale stage"); + return 0; + } + + /** @private */ + private override function set_rotation(value:Float):Float { + throw new IllegalOperationError("Cannot rotate stage"); + return 0; + } + + /** @private */ + private override function set_skewX(value:Float):Float { + throw new IllegalOperationError("Cannot skew stage"); + return 0; + } + + /** @private */ + private override function set_skewY(value:Float):Float { + throw new IllegalOperationError("Cannot skew stage"); + return 0; + } + + /** @private */ + private override function set_filter(value:FragmentFilter):FragmentFilter { + throw new IllegalOperationError("Cannot add filter to stage. Add it to 'root' instead!"); + return null; + } + + /** The background color of the stage. */ + public var color(get, set):UInt; + + private function get_color():UInt { + return __color; + } + + private function set_color(value:UInt):UInt { + if (__color != value) { + __color = value; + setRequiresRedraw(); + } + return value; + } + + /** The width of the stage coordinate system. Change it to scale its contents relative + * to the viewPort property of the Starling object. */ + public var stageWidth(get, set):Int; + + private function get_stageWidth():Int { + return __width; + } + + private function set_stageWidth(value:Int):Int { + if (__width != value) { + __width = value; + setRequiresRedraw(); + } + return value; + } + + /** The height of the stage coordinate system. Change it to scale its contents relative + * to the viewPort property of the Starling object. */ + public var stageHeight(get, set):Int; + + private function get_stageHeight():Int { + return __height; + } + + private function set_stageHeight(value:Int):Int { + if (__height != value) { + __height = value; + setRequiresRedraw(); + } + return value; + } + + /** The Starling instance this stage belongs to. */ + public var starling(get, never):Starling; + + private function get_starling():Starling { + var instances:Array = Starling.all; + var numInstances:Int = instances.length; + + for (i in 0...numInstances) + if (instances[i].stage == this) + return instances[i]; + + return null; + } + + /** The distance between the stage and the camera. Changing this value will update the + * field of view accordingly. */ + public var focalLength(get, set):Float; + + private function get_focalLength():Float { + return __width / (2 * Math.tan(__fieldOfView / 2)); + } + + private function set_focalLength(value:Float):Float { + __fieldOfView = 2 * Math.atan(stageWidth / (2 * value)); + setRequiresRedraw(); + return value; + } + + /** Specifies an angle (radian, between zero and PI) for the field of view. This value + * determines how strong the perspective transformation and distortion apply to a Sprite3D + * object. + * + *

A value close to zero will look similar to an orthographic projection; a value + * close to PI results in a fisheye lens effect. If the field of view is set to 0 or PI, + * nothing is seen on the screen.

+ * + * @default 1.0 + */ + public var fieldOfView(get, set):Float; + + private function get_fieldOfView():Float { + return __fieldOfView; + } + + private function set_fieldOfView(value:Float):Float { + __fieldOfView = value; + setRequiresRedraw(); + return value; + } + + /** A Array that moves the camera away from its default position in the center of the + * stage. Use this property to change the center of projection, i.e. the vanishing + * point for 3D display objects.

CAUTION: not a copy, but the actual object!

+ */ + public var projectionOffset(get, set):Point; + + private function get_projectionOffset():Point { + return __projectionOffset; + } + + private function set_projectionOffset(value:Point):Point { + __projectionOffset.setTo(value.x, value.y); + setRequiresRedraw(); + return value; + } + + /** The global position of the camera. This property can only be used to find out the + * current position, but not to modify it. For that, use the 'projectionOffset', + * 'fieldOfView' and 'focalLength' properties. If you need the camera position in + * a certain coordinate space, use 'getCameraPosition' instead. + * + *

CAUTION: not a copy, but the actual object!

+ */ + public var cameraPosition(get, never):Vector3D; + + private function get_cameraPosition():Vector3D { + return getCameraPosition(null, __cameraPosition); + } +} diff --git a/src/starling/utils/ArrayUtil.hx b/src/starling/utils/ArrayUtil.hx index 6e0221c0..28fe867a 100644 --- a/src/starling/utils/ArrayUtil.hx +++ b/src/starling/utils/ArrayUtil.hx @@ -3,42 +3,41 @@ package starling.utils; /** * Array Utility. */ -class ArrayUtil -{ - /** - * Truncates an array to the specified length. - * @param arr The array to truncate. - * @param newSize The new size of the array. - */ - public static function truncate(arr:Array, newSize:Int):Void { - if (newSize < arr.length) { - arr.splice(newSize, arr.length - newSize); - } - } +class ArrayUtil { + /** + * Truncates an array to the specified length. + * @param arr The array to truncate. + * @param newSize The new size of the array. + */ + public static function truncate(arr:Array, newSize:Int):Void { + if (newSize < arr.length) { + arr.splice(newSize, arr.length - newSize); + } + } - /** - * Extends an array to the specified length, filling new elements with the default value. - * @param arr The array to extend. - * @param newSize The new size of the array. - * @param defaultValue The value to fill new elements with. - */ - public static function extend(arr:Array, newSize:Int, defaultValue:T):Void { - while (arr.length < newSize) { - arr.push(defaultValue); - } - } + /** + * Extends an array to the specified length, filling new elements with the default value. + * @param arr The array to extend. + * @param newSize The new size of the array. + * @param defaultValue The value to fill new elements with. + */ + public static function extend(arr:Array, newSize:Int, defaultValue:T):Void { + while (arr.length < newSize) { + arr.push(defaultValue); + } + } - /** - * Resizes an array to the specified length, truncating or extending as needed. - * @param arr The array to resize. - * @param newSize The new size of the array. - * @param defaultValue The value to fill new elements with when extending. - */ - public static function resize(arr:Array, newSize:Int, defaultValue:T):Void { - if (newSize < arr.length) { - truncate(arr, newSize); - } else { - extend(arr, newSize, defaultValue); - } - } -} \ No newline at end of file + /** + * Resizes an array to the specified length, truncating or extending as needed. + * @param arr The array to resize. + * @param newSize The new size of the array. + * @param defaultValue The value to fill new elements with when extending. + */ + public static function resize(arr:Array, newSize:Int, defaultValue:T):Void { + if (newSize < arr.length) { + truncate(arr, newSize); + } else { + extend(arr, newSize, defaultValue); + } + } +}