
This patch fixes animations so that if multiple sub @triggers are used and are blocked by a parent animation then the engine will not lead itself into an infinite loop. PR Close #21119
150 lines
3.7 KiB
TypeScript
150 lines
3.7 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {scheduleMicroTask} from '../util';
|
|
import {AnimationPlayer} from './animation_player';
|
|
|
|
export class AnimationGroupPlayer implements AnimationPlayer {
|
|
private _onDoneFns: Function[] = [];
|
|
private _onStartFns: Function[] = [];
|
|
private _finished = false;
|
|
private _started = false;
|
|
private _destroyed = false;
|
|
private _onDestroyFns: Function[] = [];
|
|
|
|
public parentPlayer: AnimationPlayer|null = null;
|
|
public totalTime: number = 0;
|
|
public readonly players: AnimationPlayer[];
|
|
|
|
constructor(_players: AnimationPlayer[]) {
|
|
this.players = _players;
|
|
let doneCount = 0;
|
|
let destroyCount = 0;
|
|
let startCount = 0;
|
|
const total = this.players.length;
|
|
|
|
if (total == 0) {
|
|
scheduleMicroTask(() => this._onFinish());
|
|
} else {
|
|
this.players.forEach(player => {
|
|
player.onDone(() => {
|
|
if (++doneCount == total) {
|
|
this._onFinish();
|
|
}
|
|
});
|
|
player.onDestroy(() => {
|
|
if (++destroyCount == total) {
|
|
this._onDestroy();
|
|
}
|
|
});
|
|
player.onStart(() => {
|
|
if (++startCount == total) {
|
|
this._onStart();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
this.totalTime = this.players.reduce((time, player) => Math.max(time, player.totalTime), 0);
|
|
}
|
|
|
|
private _onFinish() {
|
|
if (!this._finished) {
|
|
this._finished = true;
|
|
this._onDoneFns.forEach(fn => fn());
|
|
this._onDoneFns = [];
|
|
}
|
|
}
|
|
|
|
init(): void { this.players.forEach(player => player.init()); }
|
|
|
|
onStart(fn: () => void): void { this._onStartFns.push(fn); }
|
|
|
|
private _onStart() {
|
|
if (!this.hasStarted()) {
|
|
this._started = true;
|
|
this._onStartFns.forEach(fn => fn());
|
|
this._onStartFns = [];
|
|
}
|
|
}
|
|
|
|
onDone(fn: () => void): void { this._onDoneFns.push(fn); }
|
|
|
|
onDestroy(fn: () => void): void { this._onDestroyFns.push(fn); }
|
|
|
|
hasStarted() { return this._started; }
|
|
|
|
play() {
|
|
if (!this.parentPlayer) {
|
|
this.init();
|
|
}
|
|
this._onStart();
|
|
this.players.forEach(player => player.play());
|
|
}
|
|
|
|
pause(): void { this.players.forEach(player => player.pause()); }
|
|
|
|
restart(): void { this.players.forEach(player => player.restart()); }
|
|
|
|
finish(): void {
|
|
this._onFinish();
|
|
this.players.forEach(player => player.finish());
|
|
}
|
|
|
|
destroy(): void { this._onDestroy(); }
|
|
|
|
private _onDestroy() {
|
|
if (!this._destroyed) {
|
|
this._destroyed = true;
|
|
this._onFinish();
|
|
this.players.forEach(player => player.destroy());
|
|
this._onDestroyFns.forEach(fn => fn());
|
|
this._onDestroyFns = [];
|
|
}
|
|
}
|
|
|
|
reset(): void {
|
|
this.players.forEach(player => player.reset());
|
|
this._destroyed = false;
|
|
this._finished = false;
|
|
this._started = false;
|
|
}
|
|
|
|
setPosition(p: number): void {
|
|
const timeAtPosition = p * this.totalTime;
|
|
this.players.forEach(player => {
|
|
const position = player.totalTime ? Math.min(1, timeAtPosition / player.totalTime) : 1;
|
|
player.setPosition(position);
|
|
});
|
|
}
|
|
|
|
getPosition(): number {
|
|
let min = 0;
|
|
this.players.forEach(player => {
|
|
const p = player.getPosition();
|
|
min = Math.min(p, min);
|
|
});
|
|
return min;
|
|
}
|
|
|
|
beforeDestroy(): void {
|
|
this.players.forEach(player => {
|
|
if (player.beforeDestroy) {
|
|
player.beforeDestroy();
|
|
}
|
|
});
|
|
}
|
|
|
|
/* @internal */
|
|
triggerCallback(phaseName: string): void {
|
|
const methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns;
|
|
methods.forEach(fn => fn());
|
|
methods.length = 0;
|
|
}
|
|
}
|