From 073cc9492680aaa3c1f8d16f278fc82b38154851 Mon Sep 17 00:00:00 2001 From: Tsvetan Raikov Date: Thu, 24 Mar 2016 18:42:58 +0200 Subject: [PATCH 1/2] implemented CSS animations fixing animation tests --- apps/tests/testRunner.ts | 2 +- .../tests/ui/animation/css-animation-tests.ts | 302 ++++++++++++++++++ apps/tests/ui/animation/test.css | 5 + apps/tests/ui/view/view-tests-common.ts | 1 - tsconfig.json | 3 + ui/animation/animation.android.ts | 138 +++++--- ui/animation/animation.ios.ts | 254 +++++++++------ ui/animation/keyframe-animation.d.ts | 94 ++++++ ui/animation/keyframe-animation.ts | 196 ++++++++++++ ui/button/button.android.ts | 18 ++ ui/button/button.ios.ts | 31 +- ui/core/control-state-change.d.ts | 4 + ui/core/control-state-change.ios.ts | 28 +- ui/core/view.android.ts | 90 ++++++ ui/core/view.ios.ts | 90 ++++++ ui/enums/enums.d.ts | 7 +- ui/enums/enums.ts | 8 +- ui/list-view/list-view.android.ts | 15 + ui/list-view/list-view.ios.ts | 10 + ui/page/page-common.ts | 10 + ui/page/page.d.ts | 12 + ui/styling/converters.ts | 94 ++++++ ui/styling/css-animation-parser.ts | 202 ++++++++++++ ui/styling/css-selector.d.ts | 5 +- ui/styling/css-selector.ts | 72 +++-- ui/styling/style-scope.d.ts | 4 + ui/styling/style-scope.ts | 139 +++++--- ui/styling/style.d.ts | 11 + ui/styling/style.ts | 117 ++++++- ui/styling/visual-state.ts | 44 ++- 30 files changed, 1750 insertions(+), 256 deletions(-) create mode 100644 apps/tests/ui/animation/css-animation-tests.ts create mode 100644 apps/tests/ui/animation/test.css create mode 100644 ui/animation/keyframe-animation.d.ts create mode 100644 ui/animation/keyframe-animation.ts create mode 100644 ui/styling/css-animation-parser.ts diff --git a/apps/tests/testRunner.ts b/apps/tests/testRunner.ts index f8306e652..64712dc63 100644 --- a/apps/tests/testRunner.ts +++ b/apps/tests/testRunner.ts @@ -87,7 +87,7 @@ allTests["SEARCH-BAR"] = require('./ui/search-bar/search-bar-tests'); allTests["CONNECTIVITY"] = require("./connectivity-tests"); allTests["SEGMENTED-BAR"] = require("./ui/segmented-bar/segmented-bar-tests"); allTests["ANIMATION"] = require("./ui/animation/animation-tests"); - +allTests["CSS-ANIMATION"] = require("./ui/animation/css-animation-tests"); if (!isRunningOnEmulator()) { allTests["LOCATION"] = require("./location-tests"); } diff --git a/apps/tests/ui/animation/css-animation-tests.ts b/apps/tests/ui/animation/css-animation-tests.ts new file mode 100644 index 000000000..e6f6d3b63 --- /dev/null +++ b/apps/tests/ui/animation/css-animation-tests.ts @@ -0,0 +1,302 @@ +import TKUnit = require("../../TKUnit"); +import page = require("ui/page"); +import styleScope = require("ui/styling/style-scope"); +import keyframeAnimation = require("ui/animation/keyframe-animation"); +import enums = require("ui/enums"); +import helper = require("../../ui/helper"); +import stackModule = require("ui/layouts/stack-layout"); +import labelModule = require("ui/label"); +import color = require("color"); +import selectorModule = require("ui/styling/css-selector"); + +function createAnimationFromCSS(css: string, name: string): keyframeAnimation.KeyframeAnimationInfo { + let scope = new styleScope.StyleScope(); + scope.css = css; + scope.ensureSelectors(); + let selector = findSelectorInScope(scope, name); + if (selector !== undefined) { + let animation = selector.animations[0]; + return animation; + } + return undefined; +} + +function findSelectorInScope(scope: styleScope.StyleScope, name: string): selectorModule.CssSelector { + let selector = undefined; + for (let sel of (scope)._mergedCssSelectors) { + if (sel.expression === name) { + selector = sel; + break; + } + } + return selector; +} + +exports.test_ReadAnimationProperties = function () { + let css = ".test { " + + "animation-name: first; " + + "animation-duration: 4s; " + + "animation-timing-function: ease-in; " + + "animation-delay: 1.5; " + + "animation-iteration-count: 10; " + + "animation-direction: reverse; " + + "animation-fill-mode: forwards; " + + " }"; + let animation = createAnimationFromCSS(css, "test"); + TKUnit.assertEqual(animation.name, "first"); + TKUnit.assertEqual(animation.duration, 4000); + TKUnit.assertEqual(animation.curve, enums.AnimationCurve.easeIn); + TKUnit.assertEqual(animation.delay, 1500); + TKUnit.assertEqual(animation.iterations, 10); + TKUnit.assertTrue(animation.isForwards); + TKUnit.assertTrue(animation.isReverse); +}; +exports.test_ReadTheAnimationProperty = function () { + let animation = createAnimationFromCSS(".test { animation: second 0.2s ease-out 1 2 }", "test"); + TKUnit.assertEqual(animation.name, "second"); + TKUnit.assertEqual(animation.duration, 200); + TKUnit.assertEqual(animation.curve, enums.AnimationCurve.easeOut); + TKUnit.assertEqual(animation.delay, 1000); + TKUnit.assertEqual(animation.iterations, 2); +}; +exports.test_ReadAnimationCurve = function () { + let animation = createAnimationFromCSS(".test { animation-timing-function: ease-in; }", "test"); + TKUnit.assertEqual(animation.curve, enums.AnimationCurve.easeIn); + animation = createAnimationFromCSS(".test { animation-timing-function: ease-out; }", "test"); + TKUnit.assertEqual(animation.curve, enums.AnimationCurve.easeOut); + animation = createAnimationFromCSS(".test { animation-timing-function: linear; }", "test"); + TKUnit.assertEqual(animation.curve, enums.AnimationCurve.linear); + animation = createAnimationFromCSS(".test { animation-timing-function: ease-in-out; }", "test"); + TKUnit.assertEqual(animation.curve, enums.AnimationCurve.easeInOut); + animation = createAnimationFromCSS(".test { animation-timing-function: spring; }", "test"); + TKUnit.assertEqual(animation.curve, enums.AnimationCurve.spring); + animation = createAnimationFromCSS(".test { animation-timing-function: cubic-bezier(0.1, 1.0, 0.5, 0.5); }", "test"); + let curve = animation.curve; + TKUnit.assert(curve.x1 === 0.1 && curve.y1 === 1.0 && curve.x2 === 0.5 && curve.y2 === 0.5); +}; +exports.test_ReadIterations = function () { + let animation = createAnimationFromCSS(".test { animation-iteration-count: 5; }", "test"); + TKUnit.assertEqual(animation.iterations, 5); + animation = createAnimationFromCSS(".test { animation-iteration-count: infinite; }", "test"); + TKUnit.assertEqual(animation.iterations, Number.MAX_VALUE); +}; +exports.test_ReadFillMode = function () { + let animation = createAnimationFromCSS(".test { animation-iteration-count: 5; }", "test"); + TKUnit.assertFalse(animation.isForwards); + animation = createAnimationFromCSS(".test { animation-fill-mode: forwards; }", "test"); + TKUnit.assertTrue(animation.isForwards); + animation = createAnimationFromCSS(".test { animation-fill-mode: backwards; }", "test"); + TKUnit.assertFalse(animation.isForwards); +}; +exports.test_ReadDirection = function () { + let animation = createAnimationFromCSS(".test { animation-iteration-count: 5; }", "test"); + TKUnit.assertFalse(animation.isReverse); + animation = createAnimationFromCSS(".test { animation-direction: reverse; }", "test"); + TKUnit.assertTrue(animation.isReverse); + animation = createAnimationFromCSS(".test { animation-direction: normal; }", "test"); + TKUnit.assertFalse(animation.isReverse); +}; +exports.test_ReadKeyframe = function () { + let scope = new styleScope.StyleScope(); + scope.css = ".test { animation-name: test; } @keyframes test { from { background-color: red; } to { background-color: blue; } }"; + scope.ensureSelectors(); + let selector = findSelectorInScope(scope, "test"); + TKUnit.assert(selector !== undefined, "CSS selector was not created!"); + let animation = selector.animations[0]; + TKUnit.assertEqual(animation.name, "test", "Wrong animation name!"); + TKUnit.assertEqual(animation.keyframes.length, 2, "Keyframes not parsed correctly!"); + TKUnit.assertEqual(animation.keyframes[0].duration, 0, "First keyframe duration should be 0"); + TKUnit.assertEqual(animation.keyframes[1].duration, 1, "Second keyframe duration should be 1"); + TKUnit.assertEqual(animation.keyframes[0].declarations.length, 1, "Keyframe declarations are not correct"); + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "backgroundColor", "Keyframe declarations are not correct"); +}; +exports.test_ReadScale = function () { + let animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: scaleX(5),scaleY(10); } }", "test"); + let scale = animation.keyframes[0].declarations[0].value; + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "scale"); + TKUnit.assert(scale.x === 5 && scale.y === 10); + animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: scale(-5, 12.3pt); } }", "test"); + scale = animation.keyframes[0].declarations[0].value; + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "scale"); + TKUnit.assert(scale.x === -5 && scale.y === 12.3); + animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: scaleY(10); } }", "test"); + scale = animation.keyframes[0].declarations[0].value; + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "scale"); + TKUnit.assert(scale.x === 1 && scale.y === 10); + animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: scale3d(10, 20, 30); } }", "test"); + scale = animation.keyframes[0].declarations[0].value; + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "scale"); + TKUnit.assert(scale.x === 10 && scale.y === 20); +}; +exports.test_ReadTranslate = function () { + let animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: translateX(5),translateY(10); } }", "test"); + let translate = animation.keyframes[0].declarations[0].value; + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "translate"); + TKUnit.assert(translate.x === 5 && translate.y === 10); + animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: translate(-5, 12.3pt); } }", "test"); + translate = animation.keyframes[0].declarations[0].value; + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "translate"); + TKUnit.assert(translate.x === -5 && translate.y === 12.3); + animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: translateX(10); } }", "test"); + translate = animation.keyframes[0].declarations[0].value; + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "translate"); + TKUnit.assert(translate.x === 10 && translate.y === 0); + animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: translate3d(10, 20, 30); } }", "test"); + translate = animation.keyframes[0].declarations[0].value; + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "translate"); + TKUnit.assert(translate.x === 10 && translate.y === 20); +}; +exports.test_ReadRotate = function () { + let animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: rotate(5); } }", "test"); + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "rotate"); + TKUnit.assertEqual(animation.keyframes[0].declarations[0].value, 5); + animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: rotate(45deg); } }", "test"); + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "rotate"); + TKUnit.assertEqual(animation.keyframes[0].declarations[0].value, 45); + animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: rotate(0.7853981634rad); } }", "test"); + TKUnit.assertEqual(animation.keyframes[0].declarations[0].property, "rotate"); + TKUnit.assertTrue(animation.keyframes[0].declarations[0].value - 45 < 0.1); +}; +exports.test_ReadTransform = function () { + let css = ".test { animation-name: test; } @keyframes test { to { transform: rotate(10),scaleX(5),translate(2,4); } }"; + let animation = createAnimationFromCSS(css, "test"); + let rotate = animation.keyframes[0].declarations[0].value; + let scale = animation.keyframes[0].declarations[1].value; + let translate = animation.keyframes[0].declarations[2].value; + TKUnit.assertEqual(rotate, 10); + TKUnit.assert(scale.x === 5 && scale.y === 1); + TKUnit.assert(translate.x === 2 && translate.y === 4); + animation = createAnimationFromCSS(".test { animation-name: test; } @keyframes test { to { transform: none; } }", "test"); + rotate = animation.keyframes[0].declarations[0].value; + scale = animation.keyframes[0].declarations[1].value; + translate = animation.keyframes[0].declarations[2].value; + TKUnit.assertEqual(rotate, 0); + TKUnit.assert(scale.x === 1 && scale.y === 1); + TKUnit.assert(translate.x === 0 && translate.y === 0); +}; +exports.test_ReadAnimationWithUnsortedKeyframes = function () { + let css = ".test { animation-name: test; } " + + "@keyframes test { " + + "from { opacity: 0; } " + + "20%, 60% { opacity: 0.5; } " + + "40%, 80% { opacity: 0.3; } " + + "to { opacity: 1; } " + + "}"; + let animation = createAnimationFromCSS(css, "test"); + TKUnit.assertEqual(animation.keyframes.length, 6); + TKUnit.assertEqual(animation.keyframes[0].declarations[0].value, 0); + TKUnit.assertEqual(animation.keyframes[1].declarations[0].value, 0.5); + TKUnit.assertEqual(animation.keyframes[2].declarations[0].value, 0.3); + TKUnit.assertEqual(animation.keyframes[3].declarations[0].value, 0.5); + TKUnit.assertEqual(animation.keyframes[4].declarations[0].value, 0.3); + TKUnit.assertEqual(animation.keyframes[5].declarations[0].value, 1); + TKUnit.assertEqual(animation.keyframes[0].duration, 0); + TKUnit.assertEqual(animation.keyframes[1].duration, 0.2); + TKUnit.assertEqual(animation.keyframes[2].duration, 0.4); + TKUnit.assertEqual(animation.keyframes[3].duration, 0.6); + TKUnit.assertEqual(animation.keyframes[4].duration, 0.8); + TKUnit.assertEqual(animation.keyframes[5].duration, 1); +}; +exports.test_ReadAnimationsWithCSSImport = function () { + let css = "@import '~/ui/animation/test.css'; .test { animation-name: test; }"; + let animation = createAnimationFromCSS(css, "test"); + TKUnit.assertEqual(animation.keyframes.length, 3); + TKUnit.assertEqual(animation.keyframes[1].declarations[0].property, "backgroundColor"); +}; +exports.test_LoadTwoAnimationsWithTheSameName = function () { + let scope = new styleScope.StyleScope(); + scope.css = "@keyframes a1 { from { opacity: 0; } to { opacity: 1; } } @keyframes a1 { from { opacity: 0; } to { opacity: 0.5; } } .a { animation-name: a1; }"; + scope.ensureSelectors(); + let selector = findSelectorInScope(scope, "a"); + let animation = selector.animations[0]; + TKUnit.assertEqual(animation.keyframes.length, 2); + TKUnit.assertEqual(animation.keyframes[1].declarations[0].value, 0.5); + scope = new styleScope.StyleScope(); + scope.css = "@keyframes k { from { opacity: 0; } to { opacity: 1; } } .a { animation-name: k; animation-duration: 2; } .a { animation-name: k; animation-duration: 3; }"; + scope.ensureSelectors(); + selector = findSelectorInScope(scope, "a"); + TKUnit.assertEqual(selector.animations[0].keyframes.length, 2); + TKUnit.assertEqual(selector.animations[0].keyframes.length, 2); +}; +exports.test_LoadAnimationProgrammatically = function () { + let stack = new stackModule.StackLayout(); + helper.buildUIAndRunTest(stack, function (views) { + let page = views[1]; + page.css = "@keyframes a { from { opacity: 1; } to { opacity: 0; } }"; + let animation = page.getKeyframeAnimationWithName("a"); + TKUnit.assertEqual(animation.keyframes.length, 2); + TKUnit.assertEqual(animation.keyframes[1].declarations[0].property, "opacity"); + TKUnit.assertEqual(animation.keyframes[1].declarations[0].value, 0); + }); +}; +exports.test_ExecuteCSSAnimation = function () { + let mainPage; + let label; + let pageFactory = function () { + label = new labelModule.Label(); + label.text = "label"; + let stackLayout = new stackModule.StackLayout(); + stackLayout.addChild(label); + mainPage = new page.Page(); + mainPage.css = "@keyframes k { from { background-color: red; } to { background-color: green; } } .l { animation-name: k; animation-duration: 0.5s; animation-fill-mode: forwards; }"; + mainPage.content = stackLayout; + return mainPage; + }; + helper.navigate(pageFactory); + TKUnit.waitUntilReady(function () { return label.isLoaded; }); + label.className = "l"; + TKUnit.waitUntilReady(function () { return new color.Color("green").equals(label.backgroundColor); }, 1); + TKUnit.assert(new color.Color("green").equals(label.backgroundColor)); + helper.goBack(); +}; +exports.test_ExecuteFillMode = function () { + let mainPage; + let label; + let pageFactory = function () { + label = new labelModule.Label(); + label.text = "label"; + let stackLayout = new stackModule.StackLayout(); + stackLayout.addChild(label); + mainPage = new page.Page(); + mainPage.css = "@keyframes k { from { background-color: red; } to { background-color: green; } } " + + ".l { animation-name: k; animation-duration: 0.5s; animation-fill-mode: none; } " + + ".l2 { animation-name: k; animation-duration: 0.5s; animation-fill-mode: forwards; }"; + mainPage.content = stackLayout; + return mainPage; + }; + helper.navigate(pageFactory); + TKUnit.waitUntilReady(function () { return label.isLoaded; }); + TKUnit.assertEqual(label.backgroundColor, undefined); + label.className = "l"; + TKUnit.wait(2); + TKUnit.assertEqual(label.backgroundColor, undefined); + label.className = "l2"; + TKUnit.waitUntilReady(function() { return new color.Color("green").equals(label.backgroundColor); }, 1); + TKUnit.assert(new color.Color("green").equals(label.backgroundColor)); + helper.goBack(); + helper.goBack(); +}; +exports.test_ReadTwoAnimations = function () { + let scope = new styleScope.StyleScope(); + scope.css = ".test { animation: one 0.2s ease-out 1 2, two 2s ease-in; }"; + scope.ensureSelectors(); + let selector = findSelectorInScope(scope, "test"); + TKUnit.assertEqual(selector.animations.length, 2); + TKUnit.assertEqual(selector.animations[0].curve, enums.AnimationCurve.easeOut); + TKUnit.assertEqual(selector.animations[1].curve, enums.AnimationCurve.easeIn); + TKUnit.assertEqual(selector.animations[1].name, "two"); + TKUnit.assertEqual(selector.animations[1].duration, 2000); +}; +exports.test_AnimationCurveInKeyframes = function () { + let scope = new styleScope.StyleScope(); + scope.css = "@keyframes an { from { animation-timing-function: linear; background-color: red; } 50% { background-color: green; } to { background-color: black; } } .test { animation-name: an; animation-timing-function: ease-in; }"; + scope.ensureSelectors(); + let selector = findSelectorInScope(scope, "test"); + let animation = selector.animations[0]; + TKUnit.assertEqual(animation.keyframes[0].curve, enums.AnimationCurve.linear); + TKUnit.assertEqual(animation.keyframes[1].curve, undefined); + TKUnit.assertEqual(animation.keyframes[1].curve, undefined); + let realAnimation = keyframeAnimation.KeyframeAnimation.keyframeAnimationFromInfo(animation, 2); + TKUnit.assertEqual(realAnimation.animations[1].curve, enums.AnimationCurve.linear); + TKUnit.assertEqual(realAnimation.animations[2].curve, enums.AnimationCurve.easeIn); +}; diff --git a/apps/tests/ui/animation/test.css b/apps/tests/ui/animation/test.css new file mode 100644 index 000000000..be69ea5db --- /dev/null +++ b/apps/tests/ui/animation/test.css @@ -0,0 +1,5 @@ +@keyframes test { + from { background-color: red; } + 50% { background-color: yellow; } + to { background-color: green; } +} \ No newline at end of file diff --git a/apps/tests/ui/view/view-tests-common.ts b/apps/tests/ui/view/view-tests-common.ts index 0a3080656..395c97b4d 100644 --- a/apps/tests/ui/view/view-tests-common.ts +++ b/apps/tests/ui/view/view-tests-common.ts @@ -179,7 +179,6 @@ export var test_isAddedToNativeVisualTree_IsUpdated = function () { views[1]._addView(newButton); TKUnit.assert(newButton._isAddedToNativeVisualTree); - views[1]._removeView(newButton); TKUnit.assert(!newButton._isAddedToNativeVisualTree); } diff --git a/tsconfig.json b/tsconfig.json index c18e8e2b6..e4f2f3141 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -233,6 +233,7 @@ "apps/tests/ui/action-bar/action-bar-tests.ios.ts", "apps/tests/ui/activity-indicator/activity-indicator-tests.ts", "apps/tests/ui/animation/animation-tests.ts", + "apps/tests/ui/animation/css-animation-tests.ts", "apps/tests/ui/bindable-tests.ts", "apps/tests/ui/binding-expressions-tests.ts", "apps/tests/ui/bindingContext_testPage.ts", @@ -498,6 +499,8 @@ "ui/animation/animation.android.ts", "ui/animation/animation.d.ts", "ui/animation/animation.ios.ts", + "ui/animation/keyframe-animation.d.ts", + "ui/animation/keyframe-animation.ts", "ui/border/border.d.ts", "ui/border/border.ts", "ui/builder/binding-builder.d.ts", diff --git a/ui/animation/animation.android.ts b/ui/animation/animation.android.ts index 285cb09f1..af86e20eb 100644 --- a/ui/animation/animation.android.ts +++ b/ui/animation/animation.android.ts @@ -5,18 +5,19 @@ import color = require("color"); import trace = require("trace"); import types = require("utils/types"); import enums = require("ui/enums"); +import styleModule = require("ui/styling/style"); global.moduleMerge(common, exports); -var argbEvaluator: android.animation.ArgbEvaluator; +let argbEvaluator: android.animation.ArgbEvaluator; function ensureArgbEvaluator() { if (!argbEvaluator) { argbEvaluator = new android.animation.ArgbEvaluator(); } } -var keyPrefix = "ui.animation."; -var propertyKeys = {}; +let keyPrefix = "ui.animation."; +let propertyKeys = {}; propertyKeys[common.Properties.backgroundColor] = Symbol(keyPrefix + common.Properties.backgroundColor); propertyKeys[common.Properties.opacity] = Symbol(keyPrefix + common.Properties.opacity); propertyKeys[common.Properties.rotate] = Symbol(keyPrefix + common.Properties.rotate); @@ -30,12 +31,13 @@ export class Animation extends common.Animation implements definition.Animation private _animators: Array; private _propertyUpdateCallbacks: Array; private _propertyResetCallbacks: Array; - + private _valueSource: number; + public play(): definition.AnimationPromise { var animationFinishedPromise = super.play(); - var i: number; - var length: number; + let i: number; + let length: number; this._animators = new Array(); this._propertyUpdateCallbacks = new Array(); @@ -78,7 +80,11 @@ export class Animation extends common.Animation implements definition.Animation constructor(animationDefinitions: Array, playSequentially?: boolean) { super(animationDefinitions, playSequentially); - var that = this; + if (animationDefinitions.length > 0 && (animationDefinitions[0]).valueSource !== undefined) { + this._valueSource = (animationDefinitions[0]).valueSource; + } + + let that = this; this._animatorListener = new android.animation.Animator.AnimatorListener({ onAnimationStart: function (animator: android.animation.Animator): void { trace.write("MainAnimatorListener.onAndroidAnimationStart(" + animator +")", trace.categories.Animation); @@ -103,8 +109,8 @@ export class Animation extends common.Animation implements definition.Animation return; } - var i = 0; - var length = this._propertyUpdateCallbacks.length; + let i = 0; + let length = this._propertyUpdateCallbacks.length; for (; i < length; i++) { this._propertyUpdateCallbacks[i](); } @@ -112,8 +118,8 @@ export class Animation extends common.Animation implements definition.Animation } private _onAndroidAnimationCancel() { - var i = 0; - var length = this._propertyResetCallbacks.length; + let i = 0; + let length = this._propertyResetCallbacks.length; for (; i < length; i++) { this._propertyResetCallbacks[i](); } @@ -135,18 +141,18 @@ export class Animation extends common.Animation implements definition.Animation throw new Error("Animation value cannot be null or undefined!"); } - var nativeArray; - var nativeView: android.view.View = (propertyAnimation.target._nativeView); - var animators = new Array(); - var propertyUpdateCallbacks = new Array(); - var propertyResetCallbacks = new Array(); - var originalValue1; - var originalValue2; - var density = utils.layout.getDisplayDensity(); - var xyObjectAnimators: any; - var animatorSet: android.animation.AnimatorSet; + let nativeArray; + let nativeView: android.view.View = (propertyAnimation.target._nativeView); + let animators = new Array(); + let propertyUpdateCallbacks = new Array(); + let propertyResetCallbacks = new Array(); + let originalValue1; + let originalValue2; + let density = utils.layout.getDisplayDensity(); + let xyObjectAnimators: any; + let animatorSet: android.animation.AnimatorSet; - var key = propertyKeys[propertyAnimation.property]; + let key = propertyKeys[propertyAnimation.property]; if (key) { propertyAnimation.target[key] = propertyAnimation; } @@ -159,13 +165,22 @@ export class Animation extends common.Animation implements definition.Animation } } + let valueSource = this._valueSource; + switch (propertyAnimation.property) { case common.Properties.opacity: originalValue1 = nativeView.getAlpha(); nativeArray = (Array).create("float", 1); nativeArray[0] = propertyAnimation.value; - propertyUpdateCallbacks.push(checkAnimation(() => { propertyAnimation.target.opacity = propertyAnimation.value })); + if (this._valueSource !== undefined) { + propertyUpdateCallbacks.push(checkAnimation(() => { + propertyAnimation.target.style._setValue(styleModule.opacityProperty, propertyAnimation.value, valueSource); + })); + } + else { + propertyUpdateCallbacks.push(checkAnimation(() => { propertyAnimation.target.opacity = propertyAnimation.value; })); + } propertyResetCallbacks.push(checkAnimation(() => { nativeView.setAlpha(originalValue1); })); animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "alpha", nativeArray)); break; @@ -176,15 +191,23 @@ export class Animation extends common.Animation implements definition.Animation nativeArray = (Array).create(java.lang.Object, 2); nativeArray[0] = propertyAnimation.target.backgroundColor ? java.lang.Integer.valueOf((propertyAnimation.target.backgroundColor).argb) : java.lang.Integer.valueOf(-1); nativeArray[1] = java.lang.Integer.valueOf((propertyAnimation.value).argb); - var animator = android.animation.ValueAnimator.ofObject(argbEvaluator, nativeArray); + let animator = android.animation.ValueAnimator.ofObject(argbEvaluator, nativeArray); animator.addUpdateListener(new android.animation.ValueAnimator.AnimatorUpdateListener({ onAnimationUpdate(animator: android.animation.ValueAnimator) { - var argb = (animator.getAnimatedValue()).intValue(); - propertyAnimation.target.backgroundColor = new color.Color(argb); + let argb = (animator.getAnimatedValue()).intValue(); + propertyAnimation.target.style._setValue(styleModule.backgroundColorProperty, new color.Color(argb), valueSource); } })); - propertyUpdateCallbacks.push(checkAnimation(() => { propertyAnimation.target.backgroundColor = propertyAnimation.value; })); + if (this._valueSource !== undefined) { + let valueSource = this._valueSource; + propertyUpdateCallbacks.push(checkAnimation(() => { + propertyAnimation.target.style._setValue(styleModule.backgroundColorProperty, propertyAnimation.value, valueSource); + })); + } + else { + propertyUpdateCallbacks.push(checkAnimation(() => { propertyAnimation.target.backgroundColor = propertyAnimation.value; })); + } propertyResetCallbacks.push(checkAnimation(() => { nativeView.setBackground(originalValue1); })); animators.push(animator); break; @@ -205,10 +228,18 @@ export class Animation extends common.Animation implements definition.Animation originalValue1 = nativeView.getTranslationX(); originalValue2 = nativeView.getTranslationY(); - propertyUpdateCallbacks.push(checkAnimation(() => { - propertyAnimation.target.translateX = propertyAnimation.value.x; - propertyAnimation.target.translateY = propertyAnimation.value.y; - })); + if (this._valueSource !== undefined) { + propertyUpdateCallbacks.push(checkAnimation(() => { + propertyAnimation.target.style._setValue(styleModule.translateXProperty, propertyAnimation.value.x, valueSource); + propertyAnimation.target.style._setValue(styleModule.translateYProperty, propertyAnimation.value.y, valueSource); + })); + } + else { + propertyUpdateCallbacks.push(checkAnimation(() => { + propertyAnimation.target.translateX = propertyAnimation.value.x; + propertyAnimation.target.translateY = propertyAnimation.value.y; + })); + } propertyResetCallbacks.push(checkAnimation(() => { nativeView.setTranslationX(originalValue1); @@ -237,10 +268,18 @@ export class Animation extends common.Animation implements definition.Animation originalValue1 = nativeView.getScaleX(); originalValue2 = nativeView.getScaleY(); - propertyUpdateCallbacks.push(checkAnimation(() => { - propertyAnimation.target.scaleX = propertyAnimation.value.x; - propertyAnimation.target.scaleY = propertyAnimation.value.y; - })); + if (this._valueSource !== undefined) { + propertyUpdateCallbacks.push(checkAnimation(() => { + propertyAnimation.target.style._setValue(styleModule.scaleXProperty, propertyAnimation.value.x, valueSource); + propertyAnimation.target.style._setValue(styleModule.scaleYProperty, propertyAnimation.value.y, valueSource); + })); + } + else { + propertyUpdateCallbacks.push(checkAnimation(() => { + propertyAnimation.target.scaleX = propertyAnimation.value.x; + propertyAnimation.target.scaleY = propertyAnimation.value.y; + })); + } propertyResetCallbacks.push(checkAnimation(() => { nativeView.setScaleY(originalValue1); @@ -257,7 +296,14 @@ export class Animation extends common.Animation implements definition.Animation originalValue1 = nativeView.getRotation(); nativeArray = (Array).create("float", 1); nativeArray[0] = propertyAnimation.value; - propertyUpdateCallbacks.push(checkAnimation(() => { propertyAnimation.target.rotate = propertyAnimation.value; })); + if (this._valueSource !== undefined) { + propertyUpdateCallbacks.push(checkAnimation(() => { + propertyAnimation.target.style._setValue(styleModule.rotateProperty, propertyAnimation.value, valueSource); + })); + } + else { + propertyUpdateCallbacks.push(checkAnimation(() => { propertyAnimation.target.rotate = propertyAnimation.value; })); + } propertyResetCallbacks.push(checkAnimation(() => { nativeView.setRotation(originalValue1); })); animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "rotation", nativeArray)); break; @@ -266,8 +312,8 @@ export class Animation extends common.Animation implements definition.Animation throw new Error("Cannot animate " + propertyAnimation.property); } - var i = 0; - var length = animators.length; + let i = 0; + let length = animators.length; for (; i < length; i++) { // Duration @@ -302,11 +348,11 @@ export class Animation extends common.Animation implements definition.Animation } } -var easeIn = new android.view.animation.AccelerateInterpolator(1); -var easeOut = new android.view.animation.DecelerateInterpolator(1); -var easeInOut = new android.view.animation.AccelerateDecelerateInterpolator(); -var linear = new android.view.animation.LinearInterpolator(); -var bounce = new android.view.animation.BounceInterpolator(); +let easeIn = new android.view.animation.AccelerateInterpolator(1); +let easeOut = new android.view.animation.DecelerateInterpolator(1); +let easeInOut = new android.view.animation.AccelerateDecelerateInterpolator(); +let linear = new android.view.animation.LinearInterpolator(); +let bounce = new android.view.animation.BounceInterpolator(); export function _resolveAnimationCurve(curve: any): any { switch (curve) { case enums.AnimationCurve.easeIn: @@ -324,11 +370,13 @@ export function _resolveAnimationCurve(curve: any): any { case enums.AnimationCurve.spring: trace.write("Animation curve resolved to android.view.animation.BounceInterpolator().", trace.categories.Animation); return bounce; + case enums.AnimationCurve.ease: + return (android).support.v4.view.animation.PathInterpolatorCompat.create(0.25, 0.1, 0.25, 1.0); default: trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation); if (curve instanceof common.CubicBezierAnimationCurve) { - var animationCurve = curve; - var interpolator = (android).support.v4.view.animation.PathInterpolatorCompat.create(animationCurve.x1, animationCurve.y1, animationCurve.x2, animationCurve.y2); + let animationCurve = curve; + let interpolator = (android).support.v4.view.animation.PathInterpolatorCompat.create(animationCurve.x1, animationCurve.y1, animationCurve.x2, animationCurve.y2); return interpolator; } return curve; diff --git a/ui/animation/animation.ios.ts b/ui/animation/animation.ios.ts index f8201530b..0010c0aad 100644 --- a/ui/animation/animation.ios.ts +++ b/ui/animation/animation.ios.ts @@ -3,13 +3,15 @@ import common = require("./animation-common"); import viewModule = require("ui/core/view"); import trace = require("trace"); import enums = require("ui/enums"); +import style = require("ui/styling/style"); +import dependencyObservable = require("ui/core/dependency-observable"); global.moduleMerge(common, exports); -var _transform = "_transform"; -var _skip = "_skip"; +let _transform = "_transform"; +let _skip = "_skip"; -var FLT_MAX = 340282346638528859811704183484516925440.000000; +let FLT_MAX = 340282346638528859811704183484516925440.000000; declare var CASpringAnimation:any; @@ -29,53 +31,92 @@ class AnimationDelegateImpl extends NSObject { private _finishedCallback: Function; private _propertyAnimation: common.PropertyAnimation; + private _valueSource: number; - public static initWithFinishedCallback(finishedCallback: Function, propertyAnimation: common.PropertyAnimation): AnimationDelegateImpl { + public static initWithFinishedCallback(finishedCallback: Function, propertyAnimation: common.PropertyAnimation, valueSource: number): AnimationDelegateImpl { let delegate = AnimationDelegateImpl.new(); delegate._finishedCallback = finishedCallback; delegate._propertyAnimation = propertyAnimation; + delegate._valueSource = valueSource; return delegate; } animationDidStart(anim: CAAnimation): void { - var value = this._propertyAnimation.value; + let value = this._propertyAnimation.value; (this._propertyAnimation.target)._suspendPresentationLayerUpdates(); - 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 _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; - } + if (this._valueSource !== undefined) { + let targetStyle = this._propertyAnimation.target.style; + switch (this._propertyAnimation.property) { + case common.Properties.backgroundColor: + targetStyle._setValue(style.backgroundColorProperty, value, this._valueSource); + break; + case common.Properties.opacity: + targetStyle._setValue(style.opacityProperty, value, this._valueSource); + break; + case common.Properties.rotate: + targetStyle._setValue(style.rotateProperty, value, this._valueSource); + break; + case common.Properties.translate: + targetStyle._setValue(style.translateXProperty, value.x, this._valueSource); + targetStyle._setValue(style.translateYProperty, value.y, this._valueSource); + break; + case common.Properties.scale: + targetStyle._setValue(style.scaleXProperty, value.x, this._valueSource); + targetStyle._setValue(style.scaleYProperty, value.y, this._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); + } + 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); + } + 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) { this._finishedCallback(!finished); } - if (!finished) { - if ((this._propertyAnimation)._propertyResetCallback) { - (this._propertyAnimation)._propertyResetCallback((this._propertyAnimation)._originalValue); - } - } if (finished && this.nextAnimation) { this.nextAnimation(); } @@ -87,9 +128,10 @@ export class Animation extends common.Animation implements definition.Animation private _finishedAnimations: number; private _cancelledAnimations: number; private _mergedPropertyAnimations: Array; + private _valueSource: number; public play(): definition.AnimationPromise { - var animationFinishedPromise = super.play(); + let animationFinishedPromise = super.play(); this._finishedAnimations = 0; this._cancelledAnimations = 0; this._iOSAnimationFunction(); @@ -99,22 +141,34 @@ export class Animation extends common.Animation implements definition.Animation public cancel(): void { super.cancel(); - var i = 0; - var length = this._mergedPropertyAnimations.length; + 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); + } } } constructor(animationDefinitions: Array, playSequentially?: boolean) { super(animationDefinitions, playSequentially); - trace.write("Non-merged Property Animations: " + this._propertyAnimations.length, trace.categories.Animation); - this._mergedPropertyAnimations = Animation._mergeAffineTransformAnimations(this._propertyAnimations); - trace.write("Merged Property Animations: " + this._mergedPropertyAnimations.length, trace.categories.Animation); + if (animationDefinitions.length > 0 && (animationDefinitions[0]).valueSource !== undefined) { + this._valueSource = (animationDefinitions[0]).valueSource; + } - var that = this; - var animationFinishedCallback = (cancelled: boolean) => { + if (!playSequentially) { + trace.write("Non-merged Property Animations: " + this._propertyAnimations.length, trace.categories.Animation); + this._mergedPropertyAnimations = Animation._mergeAffineTransformAnimations(this._propertyAnimations); + trace.write("Merged Property Animations: " + this._mergedPropertyAnimations.length, trace.categories.Animation); + } + else { + this._mergedPropertyAnimations = this._propertyAnimations; + } + + let that = this; + let animationFinishedCallback = (cancelled: boolean) => { if (that._playSequentially) { // This function will be called by the last animation when done or by another animation if the user cancels them halfway through. if (cancelled) { @@ -144,10 +198,10 @@ export class Animation extends common.Animation implements definition.Animation } }; - this._iOSAnimationFunction = Animation._createiOSAnimationFunction(this._mergedPropertyAnimations, 0, this._playSequentially, animationFinishedCallback); + this._iOSAnimationFunction = Animation._createiOSAnimationFunction(this._mergedPropertyAnimations, 0, this._playSequentially, this._valueSource, animationFinishedCallback); } - private static _createiOSAnimationFunction(propertyAnimations: Array, index: number, playSequentially: boolean, finishedCallback: (cancelled?: boolean) => void): Function { + private static _createiOSAnimationFunction(propertyAnimations: Array, index: number, playSequentially: boolean, valueSource: number, finishedCallback: (cancelled?: boolean) => void): Function { return (cancelled?: boolean) => { if (cancelled && finishedCallback) { @@ -156,34 +210,34 @@ export class Animation extends common.Animation implements definition.Animation return; } - var animation = propertyAnimations[index]; - var args = Animation._getNativeAnimationArguments(animation); + let animation = propertyAnimations[index]; + let args = Animation._getNativeAnimationArguments(animation, valueSource); if (animation.curve === enums.AnimationCurve.spring) { - Animation._createNativeSpringAnimation(propertyAnimations, index, playSequentially, args, animation, finishedCallback); + Animation._createNativeSpringAnimation(propertyAnimations, index, playSequentially, args, animation, valueSource, finishedCallback); } else { - Animation._createNativeAnimation(propertyAnimations, index, playSequentially, args, animation, finishedCallback); + Animation._createNativeAnimation(propertyAnimations, index, playSequentially, args, animation, valueSource, finishedCallback); } } } - private static _getNativeAnimationArguments(animation: common.PropertyAnimation): AnimationInfo { + private static _getNativeAnimationArguments(animation: common.PropertyAnimation, valueSource: number): AnimationInfo { - var nativeView = animation.target._nativeView; - var presentationLayer = nativeView.layer.presentationLayer(); - var propertyNameToAnimate = animation.property; - var value = animation.value; - var originalValue; + let nativeView = animation.target._nativeView; + let presentationLayer = nativeView.layer.presentationLayer(); + let propertyNameToAnimate = animation.property; + let value = animation.value; + let originalValue; - var tempRotate = animation.target.rotate * Math.PI / 180; - var abs + let tempRotate = animation.target.rotate * Math.PI / 180; + let abs; switch (animation.property) { case common.Properties.backgroundColor: (animation)._originalValue = animation.target.backgroundColor; - (animation)._propertyResetCallback = (value) => { animation.target.backgroundColor = value }; - if (presentationLayer != null) { + (animation)._propertyResetCallback = (value) => { animation.target.backgroundColor = value; }; + if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = presentationLayer.backgroundColor; } else { @@ -197,8 +251,8 @@ export class Animation extends common.Animation implements definition.Animation break; case common.Properties.opacity: (animation)._originalValue = animation.target.opacity; - (animation)._propertyResetCallback = (value) => { animation.target.opacity = value }; - if (presentationLayer != null) { + (animation)._propertyResetCallback = (value) => { animation.target.opacity = value; }; + if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = presentationLayer.opacity; } else { @@ -207,9 +261,9 @@ 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)._propertyResetCallback = (value) => { animation.target.rotate = value; }; propertyNameToAnimate = "transform.rotation"; - if (presentationLayer != null) { + if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = presentationLayer.valueForKeyPath("transform.rotation"); } else { @@ -224,8 +278,8 @@ export class Animation extends common.Animation implements definition.Animation 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; }; - propertyNameToAnimate = "transform" - if (presentationLayer != null) { + propertyNameToAnimate = "transform"; + if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform); } else { @@ -236,8 +290,8 @@ export class Animation extends common.Animation implements definition.Animation 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; }; - propertyNameToAnimate = "transform" - if (presentationLayer != null) { + propertyNameToAnimate = "transform"; + if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform); } else { @@ -246,7 +300,7 @@ export class Animation extends common.Animation implements definition.Animation value = NSValue.valueWithCATransform3D(CATransform3DScale(nativeView.layer.transform, value.x, value.y, 1)); break; case _transform: - if (presentationLayer != null) { + if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) { originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform); } else { @@ -260,24 +314,24 @@ export class Animation extends common.Animation implements definition.Animation animation.target.scaleX = value.xs; animation.target.scaleY = value.ys; }; - propertyNameToAnimate = "transform" + propertyNameToAnimate = "transform"; value = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation)); break; default: throw new Error("Cannot animate " + animation.property); } - var duration = 0.3; + let duration = 0.3; if (animation.duration !== undefined) { duration = animation.duration / 1000.0; } - var delay = undefined; + let delay = undefined; if (animation.delay) { delay = animation.delay / 1000.0; } - var repeatCount = undefined; + let repeatCount = undefined; if (animation.iterations !== undefined) { if (animation.iterations === Number.POSITIVE_INFINITY) { repeatCount = FLT_MAX; @@ -297,10 +351,10 @@ export class Animation extends common.Animation implements definition.Animation }; } - private static _createNativeAnimation(propertyAnimations: Array, index: number, playSequentially: boolean, args: AnimationInfo, animation: common.PropertyAnimation, finishedCallback: (cancelled?: boolean) => void) { + private static _createNativeAnimation(propertyAnimations: Array, index: number, playSequentially: boolean, args: AnimationInfo, animation: common.PropertyAnimation, valueSource: number, finishedCallback: (cancelled?: boolean) => void) { - var nativeView = animation.target._nativeView; - var nativeAnimation = CABasicAnimation.animationWithKeyPath(args.propertyNameToAnimate); + let nativeView = animation.target._nativeView; + let nativeAnimation = CABasicAnimation.animationWithKeyPath(args.propertyNameToAnimate); nativeAnimation.fromValue = args.fromValue; nativeAnimation.toValue = args.toValue; nativeAnimation.duration = args.duration; @@ -314,14 +368,14 @@ export class Animation extends common.Animation implements definition.Animation nativeAnimation.timingFunction = animation.curve; } - var animationDelegate = AnimationDelegateImpl.initWithFinishedCallback(finishedCallback, animation); + let animationDelegate = AnimationDelegateImpl.initWithFinishedCallback(finishedCallback, animation, valueSource); nativeAnimation.setValueForKey(animationDelegate, "delegate"); nativeView.layer.addAnimationForKey(nativeAnimation, args.propertyNameToAnimate); - var callback = undefined; - if (index+1 < propertyAnimations.length) { - callback = Animation._createiOSAnimationFunction(propertyAnimations, index+1, playSequentially, finishedCallback); + let callback = undefined; + if (index + 1 < propertyAnimations.length) { + callback = Animation._createiOSAnimationFunction(propertyAnimations, index + 1, playSequentially, valueSource, finishedCallback); if (!playSequentially) { callback(); } @@ -331,14 +385,14 @@ export class Animation extends common.Animation implements definition.Animation } } - private static _createNativeSpringAnimation(propertyAnimations: Array, index: number, playSequentially: boolean, args: AnimationInfo, animation: common.PropertyAnimation, finishedCallback: (cancelled?: boolean) => void) { + private static _createNativeSpringAnimation(propertyAnimations: Array, index: number, playSequentially: boolean, args: AnimationInfo, animation: common.PropertyAnimation, valueSource: number, finishedCallback: (cancelled?: boolean) => void) { - var nativeView = animation.target._nativeView; + let nativeView = animation.target._nativeView; - var callback = undefined; - var nextAnimation; + let callback = undefined; + let nextAnimation; if (index + 1 < propertyAnimations.length) { - callback = Animation._createiOSAnimationFunction(propertyAnimations, index + 1, playSequentially, finishedCallback); + callback = Animation._createiOSAnimationFunction(propertyAnimations, index + 1, playSequentially, valueSource, finishedCallback); if (!playSequentially) { callback(); } @@ -347,7 +401,7 @@ export class Animation extends common.Animation implements definition.Animation } } - var delay = 0; + let delay = 0; if (args.delay) { delay = args.delay; } @@ -396,7 +450,7 @@ export class Animation extends common.Animation implements definition.Animation } } if (finishedCallback) { - var cancelled = !finished; + let cancelled = !finished; finishedCallback(cancelled); } if (finished && nextAnimation) { @@ -406,18 +460,18 @@ export class Animation extends common.Animation implements definition.Animation } private static _createNativeAffineTransform(animation: common.PropertyAnimation): CATransform3D { - var value = animation.value; - var result:CATransform3D = CATransform3DIdentity; + let value = animation.value; + let result:CATransform3D = CATransform3DIdentity; if (value[common.Properties.translate] !== undefined) { - var x = value[common.Properties.translate].x; - var y = value[common.Properties.translate].y; + let x = value[common.Properties.translate].x; + let y = value[common.Properties.translate].y; result = CATransform3DTranslate(result, x, y, 0); } if (value[common.Properties.scale] !== undefined) { - var x = value[common.Properties.scale].x; - var y = value[common.Properties.scale].y; + let x = value[common.Properties.scale].x; + let y = value[common.Properties.scale].y; result = CATransform3DScale(result, x, y, 1); } @@ -431,7 +485,7 @@ export class Animation extends common.Animation implements definition.Animation } private static _canBeMerged(animation1: common.PropertyAnimation, animation2: common.PropertyAnimation) { - var result = + let result = Animation._isAffineTransform(animation1.property) && Animation._isAffineTransform(animation2.property) && animation1.target === animation2.target && @@ -443,11 +497,11 @@ export class Animation extends common.Animation implements definition.Animation } private static _mergeAffineTransformAnimations(propertyAnimations: Array): Array { - var result = new Array(); + let result = new Array(); - var i = 0; - var j; - var length = propertyAnimations.length; + let i = 0; + let j; + let length = propertyAnimations.length; for (; i < length; i++) { if (propertyAnimations[i][_skip]) { continue; @@ -465,7 +519,7 @@ export class Animation extends common.Animation implements definition.Animation // rotate: 90, // scale: {x: 2, y: 2 } // } - var newTransformAnimation: common.PropertyAnimation = { + let newTransformAnimation: common.PropertyAnimation = { target: propertyAnimations[i].target, property: _transform, value: {}, @@ -510,12 +564,14 @@ export function _resolveAnimationCurve(curve: any): any { return CAMediaTimingFunction.functionWithName(kCAMediaTimingFunctionLinear); case enums.AnimationCurve.spring: return curve; + case enums.AnimationCurve.ease: + return CAMediaTimingFunction.functionWithControlPoints(0.25, 0.1, 0.25, 1.0); default: if (curve instanceof CAMediaTimingFunction) { return curve; } else if (curve instanceof common.CubicBezierAnimationCurve) { - var animationCurve = curve; + let animationCurve = curve; return CAMediaTimingFunction.functionWithControlPoints(animationCurve.x1, animationCurve.y1, animationCurve.x2, animationCurve.y2); } return undefined; @@ -524,12 +580,12 @@ export function _resolveAnimationCurve(curve: any): any { export function _getTransformMismatchErrorMessage(view: viewModule.View): string { // Order is important: translate, rotate, scale - var result: CGAffineTransform = CGAffineTransformIdentity; + let result: CGAffineTransform = CGAffineTransformIdentity; result = CGAffineTransformTranslate(result, view.translateX, view.translateY); result = CGAffineTransformRotate(result, view.rotate * Math.PI / 180); result = CGAffineTransformScale(result, view.scaleX, view.scaleY); - var viewTransform = NSStringFromCGAffineTransform(result); - var nativeTransform = NSStringFromCGAffineTransform(view._nativeView.transform); + let viewTransform = NSStringFromCGAffineTransform(result); + let nativeTransform = NSStringFromCGAffineTransform(view._nativeView.transform); if (viewTransform !== nativeTransform) { return "View and Native transforms do not match. View: " + viewTransform + "; Native: " + nativeTransform; diff --git a/ui/animation/keyframe-animation.d.ts b/ui/animation/keyframe-animation.d.ts new file mode 100644 index 000000000..bbb11551e --- /dev/null +++ b/ui/animation/keyframe-animation.d.ts @@ -0,0 +1,94 @@ +declare module "ui/animation/keyframe-animation" { + +import view = require("ui/core/view"); + + export interface KeyframeDeclaration { + property: string; + value: any; + } + + export interface KeyframeInfo { + duration: number; + curve: any; + declarations: Array; + } + + /** + * Defines animation options for the View.animate method. + */ + export class KeyframeAnimationInfo { + + /** + * The animation name. + */ + name: string; + + /** + * The length of the animation in milliseconds. The default duration is 300 milliseconds. + */ + duration: number; + + /** + * The amount of time, in milliseconds, to delay starting the animation. + */ + delay: number; + + /** + * Specifies how many times the animation should be played. Default is 1. + * iOS animations support fractional iterations, i.e. 1.5. + * To repeat an animation infinitely, use Number.POSITIVE_INFINITY + */ + iterations: number; + + /** + * An optional animation curve. Possible values are contained in the [AnimationCurve enumeration](../enums/AnimationCurve/README.md). + * Alternatively, you can pass an instance of type UIViewAnimationCurve for iOS or android.animation.TimeInterpolator for Android. + */ + curve: any; + + /** + * Determines whether the animation values will be applied on the animated object after the animation finishes. + */ + isForwards: boolean; + + /** + * If true the animation will be played backwards. + */ + isReverse: boolean; + + /** + * Return animation keyframes. + */ + keyframes: Array; + } + + export class KeyframeAnimation { + + /** + * The amount of time, in milliseconds, to delay starting the animation. + */ + delay: number; + + /** + * Specifies how many times the animation should be played. Default is 1. + * iOS animations support fractional iterations, i.e. 1.5. + * To repeat an animation infinitely, use Number.POSITIVE_INFINITY + */ + iterations: number; + + /** + * Returns true if the application is currently running. + */ + isPlaying: boolean; + + /** + * Plays the animation. + */ + public play: (view: view.View) => Promise; + + /** + * Creates a keyframe animation from animation definition. + */ + public static keyframeAnimationFromInfo(info: KeyframeAnimationInfo, valueSourceModifier: number); + } +} \ No newline at end of file diff --git a/ui/animation/keyframe-animation.ts b/ui/animation/keyframe-animation.ts new file mode 100644 index 000000000..589ccfd4e --- /dev/null +++ b/ui/animation/keyframe-animation.ts @@ -0,0 +1,196 @@ +import definition = require("ui/animation/keyframe-animation"); +import view = require("ui/core/view"); +import enums = require("ui/enums"); +import style = require("ui/styling/style"); + +export class KeyframeDeclaration implements definition.KeyframeDeclaration { + public property: string; + public value: any; +} + +export class KeyframeInfo implements definition.KeyframeInfo { + public duration: number; + public curve: any; + public declarations: Array; +} + +export class KeyframeAnimationInfo implements definition.KeyframeAnimationInfo { + public name: string = ""; + public duration: number = 0.3; + public delay: number = 0; + public iterations: number = 1; + public curve: any = enums.AnimationCurve.ease; + public isForwards: boolean = false; + public isReverse: boolean = false; + public keyframes: Array; +} + +export class KeyframeAnimation { + public animations: Array; + public delay: number = 0; + public iterations: number = 1; + + private _resolve; + private _reject; + private _isPlaying: boolean; + private _isForwards: boolean; + + public static keyframeAnimationFromInfo(info: KeyframeAnimationInfo, valueSourceModifier: number) { + 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, valueSourceModifier); + } + } + else { + for (let index = 0; index < length; index ++) { + let keyframe = info.keyframes[index]; + startDuration = KeyframeAnimation.parseKeyframe(info, keyframe, animations, startDuration, valueSourceModifier); + } + 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, valueSourceModifier: 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; + animation["valueSource"] = valueSourceModifier; + animations.push(animation); + return startDuration; + } + + public get isPlaying(): boolean { + return this._isPlaying; + } + + public play(view: 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; + + if (this.delay !== 0) { + let that = this; + setTimeout(function (){ that.animate(view, 0, that.iterations); }, that.delay, that); + } + else { + this.animate(view, 0, this.iterations); + } + + return animationFinishedPromise; + } + + private animate(view: view.View, index: number, iterations: number) { + if (index === 0) { + let animation = this.animations[0]; + let modifier = animation["valueSource"]; + + if ("backgroundColor" in animation) { + view.style._setValue(style.backgroundColorProperty, animation["backgroundColor"], modifier); + } + if ("scale" in animation) { + view.style._setValue(style.scaleXProperty, animation["scale"].x, modifier); + view.style._setValue(style.scaleYProperty, animation["scale"].y, modifier); + } + if ("translate" in animation) { + view.style._setValue(style.translateXProperty, animation["translate"].x, modifier); + view.style._setValue(style.translateYProperty, animation["translate"].y, modifier); + } + if ("rotate" in animation) { + view.style._setValue(style.rotateProperty, animation["rotate"], modifier); + } + if ("opacity" in animation) { + view.style._setValue(style.opacityProperty, animation["opacity"], modifier); + } + + let that = this; + setTimeout(function () { that.animate(view, 1, iterations); }, 1, that); + } + 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]; + let modifier = animation["valueSource"]; + if ("backgroundColor" in animation) { + view.style._resetValue(style.backgroundColorProperty, modifier); + } + if ("scale" in animation) { + view.style._resetValue(style.scaleXProperty, modifier); + view.style._resetValue(style.scaleYProperty, modifier); + } + if ("translate" in animation) { + view.style._resetValue(style.translateXProperty, modifier); + view.style._resetValue(style.translateYProperty, modifier); + } + if ("rotate" in animation) { + view.style._resetValue(style.rotateProperty, modifier); + } + if ("opacity" in animation) { + view.style._resetValue(style.opacityProperty, modifier); + } + } + this._resolveAnimationFinishedPromise(); + } + } + else { + view.animate(this.animations[index]).then(() => { + this.animate(view, index + 1, iterations); + }); + } + } + + public _resolveAnimationFinishedPromise() { + this._isPlaying = false; + this._resolve(); + } + + public _rejectAnimationFinishedPromise() { + this._isPlaying = false; + this._reject(new Error("Animation cancelled.")); + } +} diff --git a/ui/button/button.android.ts b/ui/button/button.android.ts index d80a2d163..0e0c2716c 100644 --- a/ui/button/button.android.ts +++ b/ui/button/button.android.ts @@ -36,6 +36,24 @@ export class Button extends common.Button { } } })); + + this._android.setOnTouchListener(new android.view.View.OnTouchListener( + { + get owner() { + return that.get(); + }, + + onTouch: function(v, ev) { + if (ev.getAction() === 0) { // down + this.owner._goToVisualState("highlighted"); + } + else if (ev.getAction() === 1) { // up + this.owner._goToVisualState("normal"); + } + return false; + } + } + )); } public _onTextPropertyChanged(data: dependencyObservable.PropertyChangeData) { diff --git a/ui/button/button.ios.ts b/ui/button/button.ios.ts index d93ce2eff..8f7b1eb6f 100644 --- a/ui/button/button.ios.ts +++ b/ui/button/button.ios.ts @@ -6,6 +6,7 @@ import view = require("ui/core/view"); import utils = require("utils/utils"); import enums = require("ui/enums"); import dependencyObservable = require("ui/core/dependency-observable"); +import styleScope = require("../styling/style-scope"); class TapHandlerImpl extends NSObject { private _owner: WeakRef