Skip to content

Commit

Permalink
Add cancelTweensOf and completeTweensOf (#2273)
Browse files Browse the repository at this point in the history
  • Loading branch information
Geokureli authored Nov 14, 2020
1 parent b38c74b commit a3ad1ed
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 15 deletions.
176 changes: 175 additions & 1 deletion flixel/tweens/FlxTween.hx
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,49 @@ class FlxTween implements IFlxDestroyable
return globalManager.quadPath(Object, Points, DurationOrSpeed, UseDuration, Options);
}

/**
* Cancels all related tweens on the specified object.
*
* Note: Any tweens with the specified fields are cancelled, if the tween has other properties they
* will also be cancelled.
*
* @param Object The object with tweens to cancel.
* @param FieldPaths Optional list of the tween field paths to search for. If null or empty, all tweens on the specified
* object are canceled. Allows dot paths to check child properties.
*
* @since 4.9.0
*/
public static function cancelTweensOf(Object:Dynamic, ?FieldPaths:Array<String>):Void
{
globalManager.cancelTweensOf(Object, FieldPaths);
}

/**
* Immediately updates all tweens on the specified object with the specified fields that
* are not looping (type `FlxTween.LOOPING` or `FlxTween.PINGPONG`) and `active` through
* their endings, triggering their `onComplete` callbacks.
*
* Note: if they haven't yet begun, this will first trigger their `onStart` callback.
*
* Note: their `onComplete` callbacks are triggered in the next frame.
* To trigger them immediately, call `FlxTween.globalManager.update(0);` after this function.
*
* In no case should it trigger an `onUpdate` callback.
*
* Note: Any tweens with the specified fields are completed, if the tween has other properties they
* will also be completed.
*
* @param Object The object with tweens to complete.
* @param FieldPaths Optional list of the tween field paths to search for. If null or empty, all tweens on
* the specified object are completed. Allows dot paths to check child properties.
*
* @since 4.9.0
*/
public static function completeTweensOf(Object:Dynamic, ?FieldPaths:Array<String>):Void
{
globalManager.completeTweensOf(Object, FieldPaths);
}

/**
* The manager to which this tween belongs
* @since 4.2.0
Expand Down Expand Up @@ -622,6 +665,19 @@ class FlxTween implements IFlxDestroyable
}
}

/**
* Returns true if this is tweening the specified field on the specified object.
*
* @param Object The object.
* @param Field Optional tween field. Ignored if null.
*
* @since 4.9.0
*/
function isTweenOf(Object:Dynamic, ?Field:String):Bool
{
return false;
}

/**
* Set both type of delays for this tween.
*
Expand Down Expand Up @@ -1120,14 +1176,126 @@ class FlxTweenManager extends FlxBasic
_tweens.splice(0, _tweens.length);
}

/**
* Cancels all related tweens on the specified object.
*
* Note: Any tweens with the specified fields are cancelled, if the tween has other properties they
* will also be cancelled.
*
* @param Object The object with tweens to cancel.
* @param FieldPaths Optional list of the tween field paths to search for. If null or empty, all tweens on the specified
* object are canceled. Allows dot paths to check child properties.
*
* @since 4.9.0
*/
public function cancelTweensOf(Object:Dynamic, ?FieldPaths:Array<String>):Void
{
forEachTweensOf(Object, FieldPaths, function (tween) tween.cancel());
}

/**
* Immediately updates all tweens on the specified object with the specified fields that
* are not looping (type `FlxTween.LOOPING` or `FlxTween.PINGPONG`) and `active` through
* their endings, triggering their `onComplete` callbacks.
*
* Note: if they haven't yet begun, this will first trigger their `onStart` callback.
*
* Note: their `onComplete` callbacks are triggered in the next frame.
* To trigger them immediately, call `FlxTween.globalManager.update(0);` after this function.
*
* In no case should it trigger an `onUpdate` callback.
*
* Note: Any tweens with the specified fields are completed, if the tween has other properties they
* will also be completed.
*
* @param Object The object with tweens to complete.
* @param FieldPaths Optional list of the tween field paths to search for. If null or empty, all tweens on
* the specified object are completed. Allows dot paths to check child properties.
*
* @since 4.9.0
*/
public function completeTweensOf(Object:Dynamic, ?FieldPaths:Array<String>):Void
{
forEachTweensOf(Object, FieldPaths,
function (tween)
{
if ((tween.type & FlxTweenType.LOOPING) == 0 && (tween.type & FlxTweenType.PINGPONG) == 0 && tween.active)
tween.update(FlxMath.MAX_VALUE_FLOAT);
}
);
}

/**
* Internal helper for iterating tweens with specific parameters.
*
* Note: loops backwards to allow removals.
*
* @param Object The object with tweens you are searching for.
* @param FieldPaths Optional list of the tween field paths to check. If null or empty, any tween of the specified
* object will match. Allows dot paths to check child properties.
* @param Function The function to call on each matching tween.
*
* @since 4.9.0
*/
function forEachTweensOf(Object:Dynamic, ?FieldPaths:Array<String>, Function:FlxTween->Void)
{
if (Object == null)
throw "Cannot cancel tween variables of an object that is null.";

if (FieldPaths == null || FieldPaths.length == 0)
{
var i = _tweens.length;
while (i-- > 0)
{
var tween = _tweens[i];
if (tween.isTweenOf(Object))
Function(tween);
}
}
else
{
// check for dot paths and convert to object/field pairs
var propertyInfos = new Array<TweenProperty>();
for (fieldPath in FieldPaths)
{
var target = Object;
var path = fieldPath.split(".");
var field = path.pop();
for (component in path)
{
target = Reflect.getProperty(target, component);
if (!Reflect.isObject(target))
break;
}

if (Reflect.isObject(target))
propertyInfos.push({ object:target, field:field });
}

var i = _tweens.length;
while (i-- > 0)
{
var tween = _tweens[i];
for (info in propertyInfos)
{
if (tween.isTweenOf(info.object, info.field))
{
Function(tween);
break;
}
}
}
}
}

/**
* Immediately updates all tweens that are not looping (type `FlxTween.LOOPING` or `FlxTween.PINGPONG`)
* and `active` through their endings, triggering their `onComplete` callbacks.
*
* Note: if they haven't yet begun, this will first trigger their `onStart` callback.
*
* Note: their `onComplete` callbacks are triggered in the next frame.
* To trigger them immediately, call `FlxTween.manager.update(0);` after this function.
* To trigger them immediately, call `FlxTween.globalManager.update(0);` after this function.
*
* In no case should it trigger an `onUpdate` callback.
*
Expand All @@ -1152,3 +1320,9 @@ class FlxTweenManager extends FlxBasic
Function(tween);
}
}

private typedef TweenProperty =
{
object:Dynamic,
field:String
}
5 changes: 5 additions & 0 deletions flixel/tweens/misc/AngleTween.hx
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ class AngleTween extends FlxTween
sprite.angle = spriteAngle;
}
}

override function isTweenOf(object:Dynamic, ?field:String):Bool
{
return sprite == object && (field == null || field == "angle");
}
}
5 changes: 5 additions & 0 deletions flixel/tweens/misc/ColorTween.hx
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ class ColorTween extends FlxTween
sprite.alpha = color.alphaFloat;
}
}

override function isTweenOf(object:Dynamic, ?field:String):Bool
{
return sprite == object && (field == null || field == "color");
}
}
47 changes: 33 additions & 14 deletions flixel/tweens/misc/VarTween.hx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class VarTween extends FlxTween
_propertyInfos = [];
this.duration = duration;
start();
initializeVars();
return this;
}

Expand All @@ -49,10 +50,9 @@ class VarTween extends FlxTween
super.update(elapsed);
else
{
// We don't initialize() in tween() because otherwise the start values
// will be inaccurate with delays
if (_propertyInfos.length == 0)
initializeVars();
// Wait until the delay is done to set the starting values of tweens
if (Math.isNaN(_propertyInfos[0].startValue))
setStartValues();

super.update(elapsed);

Expand Down Expand Up @@ -81,30 +81,49 @@ class VarTween extends FlxTween
throw 'The object does not have the property "$component" in "$fieldPath"';
}

if (Reflect.getProperty(target, field) == null)
throw 'The object does not have the property "$field"';

var value:Dynamic = Reflect.getProperty(target, field);
if (Math.isNaN(value))
throw 'The property "$field" is not numeric.';

var targetValue:Dynamic = Reflect.getProperty(_properties, fieldPath);
_propertyInfos.push({
object: target,
field: field,
startValue: value,
range: targetValue - value
startValue: Math.NaN, // gets set after delay
range: Reflect.getProperty(_properties, fieldPath)
});
}
}

function setStartValues()
{
for (info in _propertyInfos)
{
if (Reflect.getProperty(info.object, info.field) == null)
throw 'The object does not have the property "${info.field}"';

var value:Dynamic = Reflect.getProperty(info.object, info.field);
if (Math.isNaN(value))
throw 'The property "${info.field}" is not numeric.';

info.startValue = value;
info.range = info.range - value;
}
}

override public function destroy():Void
{
super.destroy();
_object = null;
_properties = null;
_propertyInfos = null;
}

override function isTweenOf(object:Dynamic, ?field:String):Bool
{
for (property in _propertyInfos)
{
if (object == property.object && (field == property.field || field == null))
return true;
}

return false;
}
}

private typedef VarTweenProperty =
Expand Down
5 changes: 5 additions & 0 deletions flixel/tweens/motion/Motion.hx
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ class Motion extends FlxTween
_object.setPosition(x, y);
}
}

override function isTweenOf(object:Dynamic, ?field:String):Bool {
return _object == object
&& (field == null || field == "x" || field == "y");
}
}
72 changes: 72 additions & 0 deletions tests/unit/src/flixel/tweens/FlxTweenTest.hx
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,78 @@ class FlxTweenTest extends FlxTest
Assert.isFalse(tween.finished);
}

@Test // #2273
function testCancelTweensOf()
{
var foo1 = {a: 0, b: 0};
var tween1a = FlxTween.tween(foo1, {a: 1}, 0.1);
step();
var tween1b = FlxTween.tween(foo1, {b: 1}, 0.1);
FlxTween.cancelTweensOf(foo1);
Assert.isTrue(tween1a.finished);
Assert.isTrue(tween1b.finished);

var foo2 = {a: 0, b: 0};
var tween2a = FlxTween.tween(foo2, {a: 1}, 0.1);
var tween2b = FlxTween.tween(foo2, {b: 1}, 0.1);
FlxTween.cancelTweensOf(foo2, ["a"]);
Assert.isTrue(tween2a.finished);
Assert.isFalse(tween2b.finished);

var foo3 = {a: {f:0}, b: {f:0}, c: {f:0}};
var tween3a = FlxTween.tween(foo3, {"a.f": 1}, 0.1);
var tween3b = FlxTween.tween(foo3, {"b.f": 1}, 0.1);
var tween3c = FlxTween.tween(foo3, {"c.f": 1}, 0.1);
FlxTween.cancelTweensOf(foo3, ["a.f"]);
FlxTween.cancelTweensOf(foo3.b, ["f"]);
Assert.isTrue(tween3a.finished);
Assert.isTrue(tween3b.finished);
Assert.isFalse(tween3c.finished);
}

@Test // #2273
function testCompleteTweensOf()
{
var foo1 = {a: 0, b: 0};
var complete1a = false;
var complete1b = false;
var tween1a = FlxTween.tween(foo1, {a: 1}, 0.1, {onComplete: function(_) complete1a = true});
step();
var tween1b = FlxTween.tween(foo1, {b: 1}, 0.1, {onComplete: function(_) complete1b = true});
FlxTween.completeTweensOf(foo1);
FlxTween.globalManager.update(0);
Assert.isTrue(tween1a.finished);
Assert.isTrue(complete1a, "tween1a.onComplete was expected to fire but did not");
Assert.isTrue(tween1b.finished);
Assert.isTrue(complete1b, "tween1b.onComplete was expected to fire but did not");

var foo2 = {a: 0, b: 0};
var complete2a = false;
var tween2a = FlxTween.tween(foo2, {a: 1}, 0.1, {onComplete: function(_) complete2a = true});
var tween2b = FlxTween.tween(foo2, {b: 1}, 0.1);
FlxTween.completeTweensOf(foo2, ["a"]);
FlxTween.globalManager.update(0);
Assert.isTrue(tween2a.finished);
Assert.isTrue(complete2a, "tween2a.onComplete was expected to fire but did not");
Assert.isFalse(tween2b.finished);

// dot paths
var foo3 = {a: {f:0}, b: {f:0}, c: {f:0}};
var complete3a = false;
var complete3b = false;
var tween3a = FlxTween.tween(foo3, {"a.f": 1}, 0.1, {onComplete: function(_) complete3a = true});
var tween3b = FlxTween.tween(foo3, {"b.f": 1}, 0.1, {onComplete: function(_) complete3b = true});
var tween3c = FlxTween.tween(foo3, {"c.f": 1}, 0.1);
FlxTween.completeTweensOf(foo3, ["a.f"]);
FlxTween.completeTweensOf(foo3.b, ["f"]);
FlxTween.globalManager.update(0);
Assert.isTrue(tween3a.finished);
Assert.isTrue(complete3a, "tween3a.onComplete was expected to fire but did not");
Assert.isTrue(tween3b.finished);
Assert.isTrue(complete3b, "tween3b.onComplete was expected to fire but did not");
Assert.isFalse(tween3c.finished);
}

@Test // #665
@:access(flixel.tweens.FlxTweenManager.remove)
function testManipulateListInCallback()
Expand Down

0 comments on commit a3ad1ed

Please sign in to comment.