From ccdc394be4b27fbb82e388e7c20ca3e534174308 Mon Sep 17 00:00:00 2001 From: Nathanael Anderson Date: Mon, 21 Mar 2016 22:52:55 -0600 Subject: [PATCH] This adds the ability to Cancel the Animation via the Promise returned via a play command. --- apps/tests/ui/animation/animation-tests.ts | 58 ++++++++++++++++++++-- ui/animation/animation-common.ts | 36 +++++++++++++- ui/animation/animation.android.ts | 2 +- ui/animation/animation.d.ts | 15 +++++- ui/animation/animation.ios.ts | 2 +- ui/core/view-common.ts | 2 +- ui/core/view.d.ts | 2 +- 7 files changed, 104 insertions(+), 13 deletions(-) diff --git a/apps/tests/ui/animation/animation-tests.ts b/apps/tests/ui/animation/animation-tests.ts index cdde6ad96..dd4b831cb 100644 --- a/apps/tests/ui/animation/animation-tests.ts +++ b/apps/tests/ui/animation/animation-tests.ts @@ -83,21 +83,69 @@ export var test_CancellingAnimation = function (done) { // // # Cancelling animation // ``` JavaScript - var animation1 = label.createAnimation({ translate: { x: 100, y: 100 } }); + var animation1 = label.createAnimation({ translate: { x: 100, y: 100}, duration: 500 }); animation1.play() .then(() => { ////console.log("Animation finished"); // - assertIOSNativeTransformIsCorrect(label); - helper.goBack(); - done(); + throw new Error("Cancelling Animation - Should not be in the Promise Then()"); // }) .catch((e) => { ////console.log("Animation cancelled"); // helper.goBack(); - done(); + if (!e) { + done(new Error("Cancel path did not have proper error")); + } else if (e.toString() === "Error: Animation cancelled.") { + done() + } else { + done(e); + } + // + }); + animation1.cancel(); + // ``` + // +} + +export var test_CancellingAnimate = function (done) { + var mainPage: pageModule.Page; + var label: labelModule.Label; + var pageFactory = function (): pageModule.Page { + label = new labelModule.Label(); + label.text = "label"; + var stackLayout = new stackLayoutModule.StackLayout(); + stackLayout.addChild(label); + mainPage = new pageModule.Page(); + mainPage.content = stackLayout; + return mainPage; + }; + + helper.navigate(pageFactory); + TKUnit.waitUntilReady(() => { return label.isLoaded }); + + // + // # Cancelling animation + // ``` JavaScript + var animation1 = label.animate({ translate: { x: 100, y: 100 }, duration: 500 }) + .then(() => { + ////console.log("Animation finished"); + // + throw new Error("Cancelling Animate - Should not be in Promise Then()"); + // + }) + .catch((e) => { + ////console.log("Animation cancelled"); + // + helper.goBack(); + if (!e) { + done(new Error("Cancel path did not have proper error")); + } else if (e.toString() === "Error: Animation cancelled.") { + done() + } else { + done(e); + } // }); animation1.cancel(); diff --git a/ui/animation/animation-common.ts b/ui/animation/animation-common.ts index 5a461e57c..24d67aab3 100644 --- a/ui/animation/animation-common.ts +++ b/ui/animation/animation-common.ts @@ -42,6 +42,14 @@ export class CubicBezierAnimationCurve implements definition.CubicBezierAnimatio } } +// This is a BOGUS Class to make TypeScript happy - This is not needed other than to make TS happy. +// We didn't want to actually modify Promise; as the cancel() is ONLY valid for animations "Promise" +export class AnimationPromise implements definition.AnimationPromise { + public cancel(): void { /* Do Nothing */ } + public then(onFulfilled?: (value?: any) => void, onRejected?: (error?: any) => void): AnimationPromise { return new AnimationPromise();} + public catch(onRejected?: (error?: any) => void): AnimationPromise { return new AnimationPromise();} +} + export class Animation implements definition.Animation { public _propertyAnimations: Array; public _playSequentially: boolean; @@ -49,20 +57,44 @@ export class Animation implements definition.Animation { private _resolve; private _reject; - public play(): Promise { + public play(): AnimationPromise { if (this.isPlaying) { throw new Error("Animation is already playing."); } - var animationFinishedPromise = new Promise((resolve, reject) => { + // We have to actually create a "Promise" due to a bug in the v8 engine and decedent promises + // We just cast it to a animationPromise so that all the rest of the code works fine + var animationFinishedPromise = new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; }); + this.fixupAnimationPromise(animationFinishedPromise); + this._isPlaying = true; return animationFinishedPromise; } + private fixupAnimationPromise(promise: AnimationPromise): void { + // Since we are using function() below because of arguments, TS won't automatically do a _this for those functions. + var _this = this; + promise.cancel = () => { + _this.cancel(); + }; + var _then = promise.then; + promise.then = function() { + var r = _then.apply(promise, arguments); + _this.fixupAnimationPromise(r); + return r; + }; + var _catch = promise.catch; + promise.catch = function() { + var r = _catch.apply(promise, arguments); + _this.fixupAnimationPromise(r); + return r; + }; + } + public cancel(): void { if (!this.isPlaying) { throw new Error("Animation is not currently playing."); diff --git a/ui/animation/animation.android.ts b/ui/animation/animation.android.ts index 278faf29f..285cb09f1 100644 --- a/ui/animation/animation.android.ts +++ b/ui/animation/animation.android.ts @@ -31,7 +31,7 @@ export class Animation extends common.Animation implements definition.Animation private _propertyUpdateCallbacks: Array; private _propertyResetCallbacks: Array; - public play(): Promise { + public play(): definition.AnimationPromise { var animationFinishedPromise = super.play(); var i: number; diff --git a/ui/animation/animation.d.ts b/ui/animation/animation.d.ts index a8d4925ce..a4dda94bd 100644 --- a/ui/animation/animation.d.ts +++ b/ui/animation/animation.d.ts @@ -1,7 +1,7 @@ declare module "ui/animation" { import viewModule = require("ui/core/view"); import colorModule = require("color"); - + /** * Defines animation options for the View.animate method. */ @@ -82,12 +82,23 @@ y: number; } + /** + * Create Promise that can cancel the animation, we have to pretend our returns itself along with the cancel + */ + export class AnimationPromise extends Promise { + cancel(): void; + then(onFulfilled?: (value?: any) => Thenable, onRejected?: (error?: any) => Thenable): AnimationPromise; + then(onFulfilled?: (value?: any) => void, onRejected?: (error?: any) => void): AnimationPromise; + catch(onRejected?: (error?: any) => Thenable): AnimationPromise; + catch(onRejected?: (error?: any) => void): AnimationPromise; + } + /** * Defines a animation set. */ export class Animation { constructor(animationDefinitions: Array, playSequentially?: boolean); - public play: () => Promise; + public play: () => AnimationPromise; public cancel: () => void; public isPlaying: boolean; } diff --git a/ui/animation/animation.ios.ts b/ui/animation/animation.ios.ts index 0491182fc..f8201530b 100644 --- a/ui/animation/animation.ios.ts +++ b/ui/animation/animation.ios.ts @@ -88,7 +88,7 @@ export class Animation extends common.Animation implements definition.Animation private _cancelledAnimations: number; private _mergedPropertyAnimations: Array; - public play(): Promise { + public play(): definition.AnimationPromise { var animationFinishedPromise = super.play(); this._finishedAnimations = 0; this._cancelledAnimations = 0; diff --git a/ui/core/view-common.ts b/ui/core/view-common.ts index 3d9ed55af..78a583b50 100644 --- a/ui/core/view-common.ts +++ b/ui/core/view-common.ts @@ -1164,7 +1164,7 @@ export class View extends ProxyObject implements definition.View { } } - public animate(animation: any): Promise { + public animate(animation: any): animModule.AnimationPromise { return this.createAnimation(animation).play(); } diff --git a/ui/core/view.d.ts b/ui/core/view.d.ts index db4911afe..ff55e6fe0 100644 --- a/ui/core/view.d.ts +++ b/ui/core/view.d.ts @@ -518,7 +518,7 @@ declare module "ui/core/view" { /** * Animates one or more properties of the view based on the supplied options. */ - public animate(options: animation.AnimationDefinition): Promise; + public animate(options: animation.AnimationDefinition): animation.AnimationPromise; /** * Creates an Animation object based on the supplied options.