import { KeyframeDeclaration as KeyframeDeclarationDefinition, KeyframeInfo as KeyframeInfoDefinition, KeyframeAnimationInfo as KeyframeAnimationInfoDefinition, KeyframeAnimation as KeyframeAnimationDefinition } from "ui/animation/keyframe-animation"; import { View, unsetValue } from "ui/core/view"; import { Animation } from "ui/animation"; export class KeyframeDeclaration implements KeyframeDeclarationDefinition { public property: string; public value: any; } export class KeyframeInfo implements KeyframeInfoDefinition { public duration: number; public curve: any; public declarations: Array; } export class KeyframeAnimationInfo implements KeyframeAnimationInfoDefinition { public name: string = ""; public duration: number = 0.3; public delay: number = 0; public iterations: number = 1; public curve: any = "ease"; public isForwards: boolean = false; public isReverse: boolean = false; public keyframes: Array; } export class KeyframeAnimation implements KeyframeAnimationDefinition { public animations: Array; public delay: number = 0; public iterations: number = 1; private _resolve; private _reject; private _isPlaying: boolean; private _isForwards: boolean; private _nativeAnimations: Array; private _target: View; public static keyframeAnimationFromInfo(info: KeyframeAnimationInfo) { let animations = new Array(); let length = info.keyframes.length; let startDuration = 0; if (info.isReverse) { for (let index = length - 1; index >= 0; index--) { let keyframe = info.keyframes[index]; startDuration = KeyframeAnimation.parseKeyframe(info, keyframe, animations, startDuration); } } else { for (let index = 0; index < length; index++) { let keyframe = info.keyframes[index]; startDuration = KeyframeAnimation.parseKeyframe(info, keyframe, animations, startDuration); } for (let index = length - 1; index > 0; index--) { let a1 = animations[index]; let a2 = animations[index - 1]; if (a2["curve"] !== undefined) { a1["curve"] = a2["curve"]; a2["curve"] = undefined; } } } for (let index = 1; index < length; index++) { let a = animations[index]; if (a["curve"] === undefined) { a["curve"] = info.curve; } } let animation: KeyframeAnimation = new KeyframeAnimation(); animation.delay = info.delay; animation.iterations = info.iterations; animation.animations = animations; animation._isForwards = info.isForwards; return animation; } private static parseKeyframe(info: KeyframeAnimationInfo, keyframe: KeyframeInfo, animations: Array, startDuration: number): number { let animation = {}; for (let declaration of keyframe.declarations) { animation[declaration.property] = declaration.value; } let duration = keyframe.duration; if (duration === 0) { duration = 0.01; } else { duration = (info.duration * duration) - startDuration; startDuration += duration; } animation["duration"] = info.isReverse ? info.duration - duration : duration; animation["curve"] = keyframe.curve; animation["forceLayer"] = true; animations.push(animation); return startDuration; } public get isPlaying(): boolean { return this._isPlaying; } public cancel() { if (this._isPlaying) { this._isPlaying = false; for (let i = this._nativeAnimations.length - 1; i >= 0; i--) { let animation = this._nativeAnimations[i]; if (animation.isPlaying) { animation.cancel(); } } if (this._nativeAnimations.length > 0) { let animation = this._nativeAnimations[0]; this._resetAnimationValues(this._target, animation); } this._rejectAnimationFinishedPromise(); } } public play(view: View): Promise { if (this._isPlaying) { throw new Error("Animation is already playing."); } let animationFinishedPromise = new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; }); this._isPlaying = true; this._nativeAnimations = new Array(); this._target = view; if (this.delay !== 0) { setTimeout(() => this.animate(view, 0, this.iterations), this.delay); } else { this.animate(view, 0, this.iterations); } return animationFinishedPromise; } private animate(view: View, index: number, iterations: number) { if (!this._isPlaying) { return; } if (index === 0) { let animation = this.animations[0]; if ("backgroundColor" in animation) { view.style[`css-background-color`] = animation["backgroundColor"]; } if ("scale" in animation) { view.style["css-scaleX"] = animation["scale"].x; view.style["css-scaleY"] = animation["scale"].y; } if ("translate" in animation) { view.style["css-translateX"] = animation["translate"].x; view.style["css-translateY"] = animation["translate"].y; } if ("rotate" in animation) { view.style["css-rotate"] = animation["rotate"]; } if ("opacity" in animation) { view.style["css-opacity"] = animation["opacity"]; } setTimeout(() => this.animate(view, 1, iterations), 1); } else if (index < 0 || index >= this.animations.length) { iterations -= 1; if (iterations > 0) { this.animate(view, 0, iterations); } else { if (this._isForwards === false) { let animation = this.animations[this.animations.length - 1]; this._resetAnimationValues(view, animation); } this._resolveAnimationFinishedPromise(); } } else { let animationDef = this.animations[index]; (animationDef).target = view; let animation = new Animation([animationDef]); animation.play().then(() => { this.animate(view, index + 1, iterations); }); this._nativeAnimations.push(animation); } } public _resolveAnimationFinishedPromise() { this._nativeAnimations = new Array(); this._isPlaying = false; this._target = null; this._resolve(); } public _rejectAnimationFinishedPromise() { this._nativeAnimations = new Array(); this._isPlaying = false; this._target = null; this._reject(new Error("Animation cancelled.")); } private _resetAnimationValues(view: View, animation: Object) { if ("backgroundColor" in animation) { view.style[`css-background-color`] = unsetValue; } if ("scale" in animation) { view.style["css-scaleX"] = animation["scale"].x; view.style["css-scaleY"] = animation["scale"].y; } if ("translate" in animation) { view.style["css-translateX"] = animation["translate"].x; view.style["css-translateY"] = animation["translate"].y; } if ("rotate" in animation) { view.style["css-rotate"] = animation["rotate"]; } if ("opacity" in animation) { view.style["css-opacity"] = animation["opacity"]; } } }