From 00c0004ac6d923dbc0ea6252db7f5654c7658fc0 Mon Sep 17 00:00:00 2001 From: hoonsbory Date: Mon, 7 Oct 2024 14:53:23 +0900 Subject: [PATCH 1/4] feature: implement group-level onComplete callback, triggering both group and individual tween completion callbacks --- dist/tween.amd.js | 15 +++++++++++++++ dist/tween.cjs | 15 +++++++++++++++ dist/tween.d.ts | 2 ++ dist/tween.esm.js | 15 +++++++++++++++ dist/tween.umd.js | 15 +++++++++++++++ src/Group.ts | 11 +++++++++++ src/Tween.ts | 4 ++++ src/tests.ts | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 124 insertions(+) diff --git a/dist/tween.amd.js b/dist/tween.amd.js index 8d82ed6e..82c899e8 100644 --- a/dist/tween.amd.js +++ b/dist/tween.amd.js @@ -296,6 +296,18 @@ define(['exports'], (function (exports) { 'use strict'; tweenIds = Object.keys(this._tweensAddedDuringUpdate); } }; + Group.prototype.onComplete = function (callback) { + var group = this.getAll(); + group.forEach(function (tween) { + var prevCallback = tween.getCompleteCallback(); + tween.onComplete(function () { + prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); + // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed + if (group.slice(0, group.length - 1).every(function (t) { return !t.isPlaying(); })) + callback(group); + }); + }); + }; return Group; }()); @@ -442,6 +454,9 @@ define(['exports'], (function (exports) { 'use strict'; Tween.prototype.getId = function () { return this._id; }; + Tween.prototype.getCompleteCallback = function () { + return this._onCompleteCallback; + }; Tween.prototype.isPlaying = function () { return this._isPlaying; }; diff --git a/dist/tween.cjs b/dist/tween.cjs index 5c25edb8..fdad8129 100644 --- a/dist/tween.cjs +++ b/dist/tween.cjs @@ -298,6 +298,18 @@ var Group = /** @class */ (function () { tweenIds = Object.keys(this._tweensAddedDuringUpdate); } }; + Group.prototype.onComplete = function (callback) { + var group = this.getAll(); + group.forEach(function (tween) { + var prevCallback = tween.getCompleteCallback(); + tween.onComplete(function () { + prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); + // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed + if (group.slice(0, group.length - 1).every(function (t) { return !t.isPlaying(); })) + callback(group); + }); + }); + }; return Group; }()); @@ -444,6 +456,9 @@ var Tween = /** @class */ (function () { Tween.prototype.getId = function () { return this._id; }; + Tween.prototype.getCompleteCallback = function () { + return this._onCompleteCallback; + }; Tween.prototype.isPlaying = function () { return this._isPlaying; }; diff --git a/dist/tween.d.ts b/dist/tween.d.ts index cb192a9b..f768e831 100644 --- a/dist/tween.d.ts +++ b/dist/tween.d.ts @@ -98,6 +98,7 @@ declare class Tween { */ constructor(object: T, group: true); getId(): number; + getCompleteCallback(): ((object: T) => void) | undefined; isPlaying(): boolean; isPaused(): boolean; getDuration(): number; @@ -181,6 +182,7 @@ declare class Group { * tweens, and do not rely on tweens being automatically added or removed. */ update(time?: number, preserve?: boolean): void; + onComplete(callback: (object: Tween[]) => void): void; } declare const now: () => number; diff --git a/dist/tween.esm.js b/dist/tween.esm.js index e87520da..dabc7090 100644 --- a/dist/tween.esm.js +++ b/dist/tween.esm.js @@ -294,6 +294,18 @@ var Group = /** @class */ (function () { tweenIds = Object.keys(this._tweensAddedDuringUpdate); } }; + Group.prototype.onComplete = function (callback) { + var group = this.getAll(); + group.forEach(function (tween) { + var prevCallback = tween.getCompleteCallback(); + tween.onComplete(function () { + prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); + // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed + if (group.slice(0, group.length - 1).every(function (t) { return !t.isPlaying(); })) + callback(group); + }); + }); + }; return Group; }()); @@ -440,6 +452,9 @@ var Tween = /** @class */ (function () { Tween.prototype.getId = function () { return this._id; }; + Tween.prototype.getCompleteCallback = function () { + return this._onCompleteCallback; + }; Tween.prototype.isPlaying = function () { return this._isPlaying; }; diff --git a/dist/tween.umd.js b/dist/tween.umd.js index b3bd4cdb..3cb8ab30 100644 --- a/dist/tween.umd.js +++ b/dist/tween.umd.js @@ -300,6 +300,18 @@ tweenIds = Object.keys(this._tweensAddedDuringUpdate); } }; + Group.prototype.onComplete = function (callback) { + var group = this.getAll(); + group.forEach(function (tween) { + var prevCallback = tween.getCompleteCallback(); + tween.onComplete(function () { + prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); + // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed + if (group.slice(0, group.length - 1).every(function (t) { return !t.isPlaying(); })) + callback(group); + }); + }); + }; return Group; }()); @@ -446,6 +458,9 @@ Tween.prototype.getId = function () { return this._id; }; + Tween.prototype.getCompleteCallback = function () { + return this._onCompleteCallback; + }; Tween.prototype.isPlaying = function () { return this._isPlaying; }; diff --git a/src/Group.ts b/src/Group.ts index 1fbae2a7..0878d075 100644 --- a/src/Group.ts +++ b/src/Group.ts @@ -84,4 +84,15 @@ export default class Group { tweenIds = Object.keys(this._tweensAddedDuringUpdate) } } + onComplete(callback: (object: Tween[]) => void) { + const group = this.getAll() + group.forEach(tween => { + const prevCallback = tween.getCompleteCallback() + tween.onComplete(() => { + prevCallback?.(tween) + // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed + if (group.slice(0, group.length - 1).every(t => !t.isPlaying())) callback(group) + }) + }) + } } diff --git a/src/Tween.ts b/src/Tween.ts index fed7745e..4b536ac0 100644 --- a/src/Tween.ts +++ b/src/Tween.ts @@ -83,6 +83,10 @@ export class Tween { return this._id } + getCompleteCallback(): ((object: T) => void) | undefined { + return this._onCompleteCallback + } + isPlaying(): boolean { return this._isPlaying } diff --git a/src/tests.ts b/src/tests.ts index 43d0202e..7f8ec7b4 100644 --- a/src/tests.ts +++ b/src/tests.ts @@ -2060,6 +2060,53 @@ export const tests = { test.ok(group.getAll() instanceof Array) test.done() }, + 'Custom group.onComplete() should be triggered when all Tweens in the group have reached their completion, and the child Tween.onComplete() should also be fired'( + test: Test, + ): void { + TWEEN.removeAll() + + const t = new TWEEN.Tween({x: 1}), + t2 = new TWEEN.Tween({x: 1}, true), + group = new TWEEN.Group() + let groupCounter = 0, + childCounter = 0, + childCounter2 = 0 + + group.add(t) + group.add(t2) + + t.to({x: 2}, 1000) + t2.to({x: 2}, 2000) + + t.onComplete(function (): void { + childCounter++ + }) + t2.onComplete(function (): void { + childCounter2++ + }) + group.onComplete(function (): void { + groupCounter++ + }) + + t.start(0) + t2.start(0) + + group.update(0) + test.deepEqual(groupCounter, 0) + test.deepEqual(childCounter, 0) + test.deepEqual(childCounter2, 0) + + group.update(1000) + test.deepEqual(groupCounter, 0) + test.deepEqual(childCounter, 1) + test.deepEqual(childCounter2, 0) + + group.update(2000) + test.deepEqual(childCounter, 1) + test.deepEqual(groupCounter, 1) + test.deepEqual(childCounter2, 1) + test.done() + }, 'Custom group stores tweens instead of global TWEEN group'(test: Test): void { const group = new TWEEN.Group() From 1aee99eb2e61075757d743463cb1d77b4966b2a3 Mon Sep 17 00:00:00 2001 From: hoonsbory Date: Mon, 7 Oct 2024 16:09:23 +0900 Subject: [PATCH 2/4] test: add test tween unit --- src/tests.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/tests.ts b/src/tests.ts index 7f8ec7b4..6cf1324f 100644 --- a/src/tests.ts +++ b/src/tests.ts @@ -2066,17 +2066,21 @@ export const tests = { TWEEN.removeAll() const t = new TWEEN.Tween({x: 1}), - t2 = new TWEEN.Tween({x: 1}, true), + t2 = new TWEEN.Tween({x: 1}), + t3 = new TWEEN.Tween({x: 1}), group = new TWEEN.Group() let groupCounter = 0, childCounter = 0, - childCounter2 = 0 + childCounter2 = 0, + childCounter3 = 0 group.add(t) group.add(t2) + group.add(t3) t.to({x: 2}, 1000) t2.to({x: 2}, 2000) + t3.to({x: 2}, 3000) t.onComplete(function (): void { childCounter++ @@ -2084,27 +2088,40 @@ export const tests = { t2.onComplete(function (): void { childCounter2++ }) + t3.onComplete(function (): void { + childCounter3++ + }) group.onComplete(function (): void { groupCounter++ }) t.start(0) t2.start(0) + t3.start(0) group.update(0) test.deepEqual(groupCounter, 0) test.deepEqual(childCounter, 0) test.deepEqual(childCounter2, 0) + test.deepEqual(childCounter3, 0) group.update(1000) test.deepEqual(groupCounter, 0) test.deepEqual(childCounter, 1) test.deepEqual(childCounter2, 0) + test.deepEqual(childCounter3, 0) group.update(2000) test.deepEqual(childCounter, 1) + test.deepEqual(groupCounter, 0) + test.deepEqual(childCounter2, 1) + test.deepEqual(childCounter3, 0) + + group.update(3000) test.deepEqual(groupCounter, 1) + test.deepEqual(childCounter, 1) test.deepEqual(childCounter2, 1) + test.deepEqual(childCounter3, 1) test.done() }, From 2b788cd0f96ede804c37537c71708c2311af0391 Mon Sep 17 00:00:00 2001 From: hoonsbory Date: Fri, 25 Oct 2024 07:40:10 +0900 Subject: [PATCH 3/4] fix: Fixed error when registered tweens are not completed in order --- dist/tween.amd.js | 3 ++- dist/tween.cjs | 3 ++- dist/tween.esm.js | 3 ++- dist/tween.umd.js | 3 ++- src/Group.ts | 5 +++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dist/tween.amd.js b/dist/tween.amd.js index 82c899e8..4f22f4f5 100644 --- a/dist/tween.amd.js +++ b/dist/tween.amd.js @@ -303,7 +303,8 @@ define(['exports'], (function (exports) { 'use strict'; tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed - if (group.slice(0, group.length - 1).every(function (t) { return !t.isPlaying(); })) + var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); + if (completedGroup.length === group.length - 1) callback(group); }); }); diff --git a/dist/tween.cjs b/dist/tween.cjs index fdad8129..796a5da9 100644 --- a/dist/tween.cjs +++ b/dist/tween.cjs @@ -305,7 +305,8 @@ var Group = /** @class */ (function () { tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed - if (group.slice(0, group.length - 1).every(function (t) { return !t.isPlaying(); })) + var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); + if (completedGroup.length === group.length - 1) callback(group); }); }); diff --git a/dist/tween.esm.js b/dist/tween.esm.js index dabc7090..3540a3ab 100644 --- a/dist/tween.esm.js +++ b/dist/tween.esm.js @@ -301,7 +301,8 @@ var Group = /** @class */ (function () { tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed - if (group.slice(0, group.length - 1).every(function (t) { return !t.isPlaying(); })) + var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); + if (completedGroup.length === group.length - 1) callback(group); }); }); diff --git a/dist/tween.umd.js b/dist/tween.umd.js index 3cb8ab30..11a7ce42 100644 --- a/dist/tween.umd.js +++ b/dist/tween.umd.js @@ -307,7 +307,8 @@ tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed - if (group.slice(0, group.length - 1).every(function (t) { return !t.isPlaying(); })) + var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); + if (completedGroup.length === group.length - 1) callback(group); }); }); diff --git a/src/Group.ts b/src/Group.ts index 0878d075..3b503f65 100644 --- a/src/Group.ts +++ b/src/Group.ts @@ -90,8 +90,9 @@ export default class Group { const prevCallback = tween.getCompleteCallback() tween.onComplete(() => { prevCallback?.(tween) - // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed - if (group.slice(0, group.length - 1).every(t => !t.isPlaying())) callback(group) + // After the onComplete callback completes, _isPlaying is updated to false, so if the total number of completed tweens is -1, then they are all complete. + const completedGroup = group.filter(tween => !tween.isPlaying()) + if (completedGroup.length === group.length - 1) callback(group) }) }) } From d30c35fd9c5dd4ce5ec887ed8b8797a6023f8d5a Mon Sep 17 00:00:00 2001 From: hoonsbory Date: Fri, 25 Oct 2024 07:44:06 +0900 Subject: [PATCH 4/4] fix: Fixed error when registered tweens are not completed in order --- dist/tween.amd.js | 2 +- dist/tween.cjs | 2 +- dist/tween.esm.js | 2 +- dist/tween.umd.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/tween.amd.js b/dist/tween.amd.js index 4f22f4f5..ebc58402 100644 --- a/dist/tween.amd.js +++ b/dist/tween.amd.js @@ -302,7 +302,7 @@ define(['exports'], (function (exports) { 'use strict'; var prevCallback = tween.getCompleteCallback(); tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); - // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed + // After the onComplete callback completes, _isPlaying is updated to false, so if the total number of completed tweens is -1, then they are all complete. var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); if (completedGroup.length === group.length - 1) callback(group); diff --git a/dist/tween.cjs b/dist/tween.cjs index 796a5da9..fbffbe3f 100644 --- a/dist/tween.cjs +++ b/dist/tween.cjs @@ -304,7 +304,7 @@ var Group = /** @class */ (function () { var prevCallback = tween.getCompleteCallback(); tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); - // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed + // After the onComplete callback completes, _isPlaying is updated to false, so if the total number of completed tweens is -1, then they are all complete. var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); if (completedGroup.length === group.length - 1) callback(group); diff --git a/dist/tween.esm.js b/dist/tween.esm.js index 3540a3ab..8d6d4358 100644 --- a/dist/tween.esm.js +++ b/dist/tween.esm.js @@ -300,7 +300,7 @@ var Group = /** @class */ (function () { var prevCallback = tween.getCompleteCallback(); tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); - // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed + // After the onComplete callback completes, _isPlaying is updated to false, so if the total number of completed tweens is -1, then they are all complete. var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); if (completedGroup.length === group.length - 1) callback(group); diff --git a/dist/tween.umd.js b/dist/tween.umd.js index 11a7ce42..788adaa0 100644 --- a/dist/tween.umd.js +++ b/dist/tween.umd.js @@ -306,7 +306,7 @@ var prevCallback = tween.getCompleteCallback(); tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); - // Since _isPlaying is updated to false after the onComplete callback finishes, the final tween is omitted from the check to determine if all animations have completed + // After the onComplete callback completes, _isPlaying is updated to false, so if the total number of completed tweens is -1, then they are all complete. var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); if (completedGroup.length === group.length - 1) callback(group);