diff --git a/ui/animation/animation.ios.ts b/ui/animation/animation.ios.ts index 0010c0aad..f030a3377 100644 --- a/ui/animation/animation.ios.ts +++ b/ui/animation/animation.ios.ts @@ -15,8 +15,7 @@ let FLT_MAX = 340282346638528859811704183484516925440.000000; declare var CASpringAnimation:any; -class AnimationInfo -{ +class AnimationInfo { public propertyNameToAnimate: string; public fromValue: any; public toValue: any; @@ -25,6 +24,21 @@ class AnimationInfo public delay: number; } +interface PropertyAnimationInfo extends common.PropertyAnimation { + _propertyResetCallback?: any; + _originalValue?: any; +} + +interface AnimationDefinitionInternal extends definition.AnimationDefinition { + valueSource: number; +} + +interface IOSView extends viewModule.View { + _suspendPresentationLayerUpdates(); + _resumePresentationLayerUpdates(); + _isPresentationLayerUpdateSuspeneded(); +} + class AnimationDelegateImpl extends NSObject { public nextAnimation: Function; @@ -42,76 +56,44 @@ class AnimationDelegateImpl extends NSObject { } animationDidStart(anim: CAAnimation): void { - let value = this._propertyAnimation.value; + let value = this._propertyAnimation.value; + let valueSource = this._valueSource || dependencyObservable.ValueSource.Local; + let targetStyle = this._propertyAnimation.target.style; - (this._propertyAnimation.target)._suspendPresentationLayerUpdates(); + (this._propertyAnimation.target)._suspendPresentationLayerUpdates(); - if (this._valueSource !== undefined) { - let targetStyle = this._propertyAnimation.target.style; - switch (this._propertyAnimation.property) { + switch (this._propertyAnimation.property) { case common.Properties.backgroundColor: - targetStyle._setValue(style.backgroundColorProperty, value, this._valueSource); + targetStyle._setValue(style.backgroundColorProperty, value, valueSource); break; case common.Properties.opacity: - targetStyle._setValue(style.opacityProperty, value, this._valueSource); + targetStyle._setValue(style.opacityProperty, value, valueSource); break; case common.Properties.rotate: - targetStyle._setValue(style.rotateProperty, value, this._valueSource); + targetStyle._setValue(style.rotateProperty, value, valueSource); break; case common.Properties.translate: - targetStyle._setValue(style.translateXProperty, value.x, this._valueSource); - targetStyle._setValue(style.translateYProperty, value.y, this._valueSource); + targetStyle._setValue(style.translateXProperty, value.x, valueSource); + targetStyle._setValue(style.translateYProperty, value.y, valueSource); break; case common.Properties.scale: - targetStyle._setValue(style.scaleXProperty, value.x, this._valueSource); - targetStyle._setValue(style.scaleYProperty, value.y, this._valueSource); + targetStyle._setValue(style.scaleXProperty, value.x, valueSource); + targetStyle._setValue(style.scaleYProperty, value.y, valueSource); break; case _transform: if (value[common.Properties.translate] !== undefined) { - targetStyle._setValue(style.translateXProperty, value[common.Properties.translate].x, this._valueSource); - targetStyle._setValue(style.translateYProperty, value[common.Properties.translate].y, this._valueSource); + targetStyle._setValue(style.translateXProperty, value[common.Properties.translate].x, valueSource); + targetStyle._setValue(style.translateYProperty, value[common.Properties.translate].y, valueSource); } if (value[common.Properties.scale] !== undefined) { - targetStyle._setValue(style.scaleXProperty, value[common.Properties.scale].x, this._valueSource); - targetStyle._setValue(style.scaleYProperty, value[common.Properties.scale].y, this._valueSource); + targetStyle._setValue(style.scaleXProperty, value[common.Properties.scale].x, valueSource); + targetStyle._setValue(style.scaleYProperty, value[common.Properties.scale].y, valueSource); } break; - } - } - else { - switch (this._propertyAnimation.property) { - case common.Properties.backgroundColor: - this._propertyAnimation.target.backgroundColor = value; - break; - case common.Properties.opacity: - this._propertyAnimation.target.opacity = value; - break; - case common.Properties.rotate: - this._propertyAnimation.target.rotate = value; - break; - case common.Properties.translate: - this._propertyAnimation.target.translateX = value.x; - this._propertyAnimation.target.translateY = value.y; - break; - case common.Properties.scale: - this._propertyAnimation.target.scaleX = value.x; - this._propertyAnimation.target.scaleY = value.y; - break; - case _transform: - if (value[common.Properties.translate] !== undefined) { - this._propertyAnimation.target.translateX = value[common.Properties.translate].x; - this._propertyAnimation.target.translateY = value[common.Properties.translate].y; - } - if (value[common.Properties.scale] !== undefined) { - this._propertyAnimation.target.scaleX = value[common.Properties.scale].x; - this._propertyAnimation.target.scaleY = value[common.Properties.scale].y; - } - break; - } - } + } - (this._propertyAnimation.target)._resumePresentationLayerUpdates(); - } + (this._propertyAnimation.target)._resumePresentationLayerUpdates(); + } public animationDidStopFinished(anim: CAAnimation, finished: boolean): void { if (this._finishedCallback) { @@ -127,7 +109,7 @@ export class Animation extends common.Animation implements definition.Animation private _iOSAnimationFunction: Function; private _finishedAnimations: number; private _cancelledAnimations: number; - private _mergedPropertyAnimations: Array; + private _mergedPropertyAnimations: Array; private _valueSource: number; public play(): definition.AnimationPromise { @@ -144,18 +126,19 @@ export class Animation extends common.Animation implements definition.Animation let i = 0; let length = this._mergedPropertyAnimations.length; for (; i < length; i++) { - (this._mergedPropertyAnimations[i].target._nativeView).layer.removeAllAnimations(); - if ((this._mergedPropertyAnimations[i])._propertyResetCallback) { - (this._mergedPropertyAnimations[i])._propertyResetCallback((this._mergedPropertyAnimations[i])._originalValue); + let propertyAnimation = this._mergedPropertyAnimations[i]; + propertyAnimation.target._nativeView.layer.removeAllAnimations(); + if (propertyAnimation._propertyResetCallback) { + propertyAnimation._propertyResetCallback(propertyAnimation._originalValue, this._valueSource); } } } - constructor(animationDefinitions: Array, playSequentially?: boolean) { + constructor(animationDefinitions: Array, playSequentially?: boolean) { super(animationDefinitions, playSequentially); - if (animationDefinitions.length > 0 && (animationDefinitions[0]).valueSource !== undefined) { - this._valueSource = (animationDefinitions[0]).valueSource; + if (animationDefinitions.length > 0 && animationDefinitions[0].valueSource !== undefined) { + this._valueSource = animationDefinitions[0].valueSource; } if (!playSequentially) { @@ -222,7 +205,7 @@ export class Animation extends common.Animation implements definition.Animation } } - private static _getNativeAnimationArguments(animation: common.PropertyAnimation, valueSource: number): AnimationInfo { + private static _getNativeAnimationArguments(animation: PropertyAnimationInfo, valueSource: number): AnimationInfo { let nativeView = animation.target._nativeView; let presentationLayer = nativeView.layer.presentationLayer(); @@ -235,8 +218,10 @@ export class Animation extends common.Animation implements definition.Animation switch (animation.property) { case common.Properties.backgroundColor: - (animation)._originalValue = animation.target.backgroundColor; - (animation)._propertyResetCallback = (value) => { animation.target.backgroundColor = value; }; + animation._originalValue = animation.target.backgroundColor; + animation._propertyResetCallback = (value, valueSource) => { + animation.target._setValue(style.backgroundColorProperty, value, valueSource); + }; if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = presentationLayer.backgroundColor; } @@ -250,8 +235,10 @@ export class Animation extends common.Animation implements definition.Animation value = value.CGColor; break; case common.Properties.opacity: - (animation)._originalValue = animation.target.opacity; - (animation)._propertyResetCallback = (value) => { animation.target.opacity = value; }; + animation._originalValue = animation.target.opacity; + animation._propertyResetCallback = (value, valueSource) => { + animation.target._setValue(style.opacityProperty, value, valueSource); + }; if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = presentationLayer.opacity; } @@ -260,8 +247,10 @@ export class Animation extends common.Animation implements definition.Animation } break; case common.Properties.rotate: - (animation)._originalValue = animation.target.rotate; - (animation)._propertyResetCallback = (value) => { animation.target.rotate = value; }; + animation._originalValue = animation.target.rotate; + animation._propertyResetCallback = (value, valueSource) => { + animation.target._setValue(style.rotateProperty, value, valueSource); + }; propertyNameToAnimate = "transform.rotation"; if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = presentationLayer.valueForKeyPath("transform.rotation"); @@ -276,8 +265,11 @@ export class Animation extends common.Animation implements definition.Animation } break; case common.Properties.translate: - (animation)._originalValue = { x:animation.target.translateX, y:animation.target.translateY }; - (animation)._propertyResetCallback = (value) => { animation.target.translateX = value.x; animation.target.translateY = value.y; }; + animation._originalValue = { x: animation.target.translateX, y: animation.target.translateY }; + animation._propertyResetCallback = (value, valueSource) => { + animation.target._setValue(style.translateXProperty, value.x, valueSource); + animation.target._setValue(style.translateYProperty, value.y, valueSource); + }; propertyNameToAnimate = "transform"; if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform); @@ -288,8 +280,11 @@ export class Animation extends common.Animation implements definition.Animation value = NSValue.valueWithCATransform3D(CATransform3DTranslate(nativeView.layer.transform, value.x, value.y, 0)); break; case common.Properties.scale: - (animation)._originalValue = { x:animation.target.scaleX, y:animation.target.scaleY }; - (animation)._propertyResetCallback = (value) => { animation.target.scaleX = value.x; animation.target.scaleY = value.y; }; + animation._originalValue = { x: animation.target.scaleX, y: animation.target.scaleY }; + animation._propertyResetCallback = (value, valueSource) => { + animation.target._setValue(style.scaleXProperty, value.x, valueSource); + animation.target._setValue(style.scaleYProperty, value.y, valueSource); + }; propertyNameToAnimate = "transform"; if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform); @@ -306,13 +301,13 @@ export class Animation extends common.Animation implements definition.Animation else { originalValue = NSValue.valueWithCATransform3D(nativeView.layer.transform); } - (animation)._originalValue = { xs:animation.target.scaleX, ys:animation.target.scaleY, - xt:animation.target.translateX, yt:animation.target.translateY }; - (animation)._propertyResetCallback = (value) => { - animation.target.translateX = value.xt; - animation.target.translateY = value.yt; - animation.target.scaleX = value.xs; - animation.target.scaleY = value.ys; + animation._originalValue = { xs: animation.target.scaleX, ys: animation.target.scaleY, + xt: animation.target.translateX, yt: animation.target.translateY }; + animation._propertyResetCallback = (value, valueSource) => { + animation.target._setValue(style.translateXProperty, value.xt, valueSource); + animation.target._setValue(style.translateYProperty, value.yt, valueSource); + animation.target._setValue(style.scaleXProperty, value.xs, valueSource); + animation.target._setValue(style.scaleYProperty, value.ys, valueSource); }; propertyNameToAnimate = "transform"; value = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation)); @@ -385,7 +380,7 @@ export class Animation extends common.Animation implements definition.Animation } } - private static _createNativeSpringAnimation(propertyAnimations: Array, index: number, playSequentially: boolean, args: AnimationInfo, animation: common.PropertyAnimation, valueSource: number, finishedCallback: (cancelled?: boolean) => void) { + private static _createNativeSpringAnimation(propertyAnimations: Array, index: number, playSequentially: boolean, args: AnimationInfo, animation: PropertyAnimationInfo, valueSource: number, finishedCallback: (cancelled?: boolean) => void) { let nativeView = animation.target._nativeView; @@ -424,9 +419,9 @@ export class Animation extends common.Animation implements definition.Animation nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate); break; case _transform: - (animation)._originalValue = nativeView.layer.transform; + animation._originalValue = nativeView.layer.transform; nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate); - (animation)._propertyResetCallback = function (value) { + animation._propertyResetCallback = function (value) { nativeView.layer.transform = value; } break; @@ -445,8 +440,8 @@ export class Animation extends common.Animation implements definition.Animation } } else { - if ((animation)._propertyResetCallback) { - (animation)._propertyResetCallback((animation)._originalValue); + if (animation._propertyResetCallback) { + animation._propertyResetCallback(animation._originalValue); } } if (finishedCallback) { @@ -461,7 +456,7 @@ export class Animation extends common.Animation implements definition.Animation private static _createNativeAffineTransform(animation: common.PropertyAnimation): CATransform3D { let value = animation.value; - let result:CATransform3D = CATransform3DIdentity; + let result: CATransform3D = CATransform3DIdentity; if (value[common.Properties.translate] !== undefined) { let x = value[common.Properties.translate].x; diff --git a/ui/animation/keyframe-animation.d.ts b/ui/animation/keyframe-animation.d.ts index bbb11551e..15e385c3b 100644 --- a/ui/animation/keyframe-animation.d.ts +++ b/ui/animation/keyframe-animation.d.ts @@ -86,6 +86,11 @@ import view = require("ui/core/view"); */ public play: (view: view.View) => Promise; + /** + * Cancels a playing animation. + */ + public cancel: () => void; + /** * Creates a keyframe animation from animation definition. */ diff --git a/ui/animation/keyframe-animation.ts b/ui/animation/keyframe-animation.ts index 589ccfd4e..3beb36af6 100644 --- a/ui/animation/keyframe-animation.ts +++ b/ui/animation/keyframe-animation.ts @@ -1,4 +1,5 @@ import definition = require("ui/animation/keyframe-animation"); +import animationModule = require("ui/animation"); import view = require("ui/core/view"); import enums = require("ui/enums"); import style = require("ui/styling/style"); @@ -34,6 +35,7 @@ export class KeyframeAnimation { private _reject; private _isPlaying: boolean; private _isForwards: boolean; + private _currentAnimation: animationModule.Animation; public static keyframeAnimationFromInfo(info: KeyframeAnimationInfo, valueSourceModifier: number) { let animations = new Array(); @@ -98,6 +100,16 @@ export class KeyframeAnimation { return this._isPlaying; } + public cancel() { + if (this._isPlaying) { + if (this._currentAnimation && this._currentAnimation.isPlaying) { + this._currentAnimation.cancel(); + } + this._isPlaying = false; + this._rejectAnimationFinishedPromise(); + } + } + public play(view: view.View): Promise { if (this._isPlaying) { throw new Error("Animation is already playing."); @@ -178,19 +190,25 @@ export class KeyframeAnimation { } } else { - view.animate(this.animations[index]).then(() => { + let animationDef = this.animations[index]; + (animationDef).target = view; + let animation = new animationModule.Animation([animationDef]); + animation.play().then(() => { this.animate(view, index + 1, iterations); }); + this._currentAnimation = animation; } } public _resolveAnimationFinishedPromise() { this._isPlaying = false; + this._currentAnimation = undefined; this._resolve(); } public _rejectAnimationFinishedPromise() { this._isPlaying = false; + this._currentAnimation = undefined; this._reject(new Error("Animation cancelled.")); } } diff --git a/ui/core/view-common.ts b/ui/core/view-common.ts index 996451a86..97861046d 100644 --- a/ui/core/view-common.ts +++ b/ui/core/view-common.ts @@ -9,6 +9,7 @@ import enums = require("ui/enums"); import utils = require("utils/utils"); import color = require("color"); import observable = require("data/observable"); +import keyframeAnimationModule = require("ui/animation/keyframe-animation"); import {PropertyMetadata, ProxyObject} from "ui/core/proxy"; import {PropertyMetadataSettings, PropertyChangeData, Property, ValueSource, PropertyMetadata as doPropertyMetadata} from "ui/core/dependency-observable"; import {registerSpecialProperty} from "ui/builder/special-properties"; @@ -174,6 +175,8 @@ export class View extends ProxyObject implements definition.View { private _isLoaded: boolean; private _isLayoutValid: boolean = false; + private _registeredAnimations: Array; + public _domId: number; public _isAddedToNativeVisualTree = false; @@ -1170,12 +1173,36 @@ export class View extends ProxyObject implements definition.View { } public createAnimation(animation: any): any { - var animationModule: typeof animModule = require("ui/animation"); - var that = this; + let animationModule: typeof animModule = require("ui/animation"); + let that = this; animation.target = that; return new animationModule.Animation([animation]); } + public _registerAnimation(animation: keyframeAnimationModule.KeyframeAnimation) { + if (this._registeredAnimations === undefined) { + this._registeredAnimations = new Array(); + } + this._registeredAnimations.push(animation); + } + + public _unregisterAnimation(animation: keyframeAnimationModule.KeyframeAnimation) { + if (this._registeredAnimations) { + let index = this._registeredAnimations.indexOf(animation); + if (index >= 0) { + this._registeredAnimations.splice(index, 1); + } + } + } + + public _unregisterAllAnimations() { + if (this._registeredAnimations) { + for (let animation of this._registeredAnimations) { + animation.cancel(); + } + } + } + public toString(): string { var str = this.typeName; if (this.id) { diff --git a/ui/core/view.d.ts b/ui/core/view.d.ts index ff55e6fe0..cd6594e20 100644 --- a/ui/core/view.d.ts +++ b/ui/core/view.d.ts @@ -6,6 +6,7 @@ declare module "ui/core/view" { import color = require("color"); import observable = require("data/observable"); import animation = require("ui/animation"); + import keyframeAnimationModule = require("ui/animation/keyframe-animation"); /** * Gets a child view by id. @@ -574,6 +575,10 @@ declare module "ui/core/view" { _domId: number; _cssClasses: Array; + _registerAnimation(animation: keyframeAnimationModule.KeyframeAnimation); + _unregisterAnimation(animation: keyframeAnimationModule.KeyframeAnimation); + _unregisterAllAnimations(); + _isAddedToNativeVisualTree: boolean; /** diff --git a/ui/styling/css-selector.ts b/ui/styling/css-selector.ts index f5f20511f..fce7c8fb0 100644 --- a/ui/styling/css-selector.ts +++ b/ui/styling/css-selector.ts @@ -90,9 +90,12 @@ export class CssSelector { }); if (this.animations && view.isLoaded) { for (let animationInfo of this.animations) { - let realAnimation = keyframeAnimation.KeyframeAnimation.keyframeAnimationFromInfo(animationInfo, modifier); - if (realAnimation) { - realAnimation.play(view); + let animation = keyframeAnimation.KeyframeAnimation.keyframeAnimationFromInfo(animationInfo, modifier); + if (animation) { + view._registerAnimation(animation); + animation.play(view) + .then(() => { view._unregisterAnimation(animation); }) + .catch((e) => { view._unregisterAnimation(animation); }); } } } diff --git a/ui/styling/visual-state.ts b/ui/styling/visual-state.ts index 44bf1d716..e0e77fcdf 100644 --- a/ui/styling/visual-state.ts +++ b/ui/styling/visual-state.ts @@ -83,6 +83,8 @@ function resetProperties(view: viewModule.View, oldState: VisualState, newState: } } + view._unregisterAllAnimations(); + for (let selector of oldState.animatedSelectors) { for (let animationInfo of selector.animations) { for (let keyframe of animationInfo.keyframes) {