implemented CSS animations

fixing animation tests
This commit is contained in:
Tsvetan Raikov
2016-03-24 18:42:58 +02:00
parent f42b13322a
commit 073cc94926
30 changed files with 1750 additions and 256 deletions

View File

@ -87,7 +87,7 @@ allTests["SEARCH-BAR"] = require('./ui/search-bar/search-bar-tests');
allTests["CONNECTIVITY"] = require("./connectivity-tests"); allTests["CONNECTIVITY"] = require("./connectivity-tests");
allTests["SEGMENTED-BAR"] = require("./ui/segmented-bar/segmented-bar-tests"); allTests["SEGMENTED-BAR"] = require("./ui/segmented-bar/segmented-bar-tests");
allTests["ANIMATION"] = require("./ui/animation/animation-tests"); allTests["ANIMATION"] = require("./ui/animation/animation-tests");
allTests["CSS-ANIMATION"] = require("./ui/animation/css-animation-tests");
if (!isRunningOnEmulator()) { if (!isRunningOnEmulator()) {
allTests["LOCATION"] = require("./location-tests"); allTests["LOCATION"] = require("./location-tests");
} }

View File

@ -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 (<any>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);
};

View File

@ -0,0 +1,5 @@
@keyframes test {
from { background-color: red; }
50% { background-color: yellow; }
to { background-color: green; }
}

View File

@ -179,7 +179,6 @@ export var test_isAddedToNativeVisualTree_IsUpdated = function () {
views[1]._addView(newButton); views[1]._addView(newButton);
TKUnit.assert(newButton._isAddedToNativeVisualTree); TKUnit.assert(newButton._isAddedToNativeVisualTree);
views[1]._removeView(newButton); views[1]._removeView(newButton);
TKUnit.assert(!newButton._isAddedToNativeVisualTree); TKUnit.assert(!newButton._isAddedToNativeVisualTree);
} }

View File

@ -233,6 +233,7 @@
"apps/tests/ui/action-bar/action-bar-tests.ios.ts", "apps/tests/ui/action-bar/action-bar-tests.ios.ts",
"apps/tests/ui/activity-indicator/activity-indicator-tests.ts", "apps/tests/ui/activity-indicator/activity-indicator-tests.ts",
"apps/tests/ui/animation/animation-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/bindable-tests.ts",
"apps/tests/ui/binding-expressions-tests.ts", "apps/tests/ui/binding-expressions-tests.ts",
"apps/tests/ui/bindingContext_testPage.ts", "apps/tests/ui/bindingContext_testPage.ts",
@ -498,6 +499,8 @@
"ui/animation/animation.android.ts", "ui/animation/animation.android.ts",
"ui/animation/animation.d.ts", "ui/animation/animation.d.ts",
"ui/animation/animation.ios.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.d.ts",
"ui/border/border.ts", "ui/border/border.ts",
"ui/builder/binding-builder.d.ts", "ui/builder/binding-builder.d.ts",

View File

@ -5,18 +5,19 @@ import color = require("color");
import trace = require("trace"); import trace = require("trace");
import types = require("utils/types"); import types = require("utils/types");
import enums = require("ui/enums"); import enums = require("ui/enums");
import styleModule = require("ui/styling/style");
global.moduleMerge(common, exports); global.moduleMerge(common, exports);
var argbEvaluator: android.animation.ArgbEvaluator; let argbEvaluator: android.animation.ArgbEvaluator;
function ensureArgbEvaluator() { function ensureArgbEvaluator() {
if (!argbEvaluator) { if (!argbEvaluator) {
argbEvaluator = new android.animation.ArgbEvaluator(); argbEvaluator = new android.animation.ArgbEvaluator();
} }
} }
var keyPrefix = "ui.animation."; let keyPrefix = "ui.animation.";
var propertyKeys = {}; let propertyKeys = {};
propertyKeys[common.Properties.backgroundColor] = Symbol(keyPrefix + common.Properties.backgroundColor); propertyKeys[common.Properties.backgroundColor] = Symbol(keyPrefix + common.Properties.backgroundColor);
propertyKeys[common.Properties.opacity] = Symbol(keyPrefix + common.Properties.opacity); propertyKeys[common.Properties.opacity] = Symbol(keyPrefix + common.Properties.opacity);
propertyKeys[common.Properties.rotate] = Symbol(keyPrefix + common.Properties.rotate); 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<android.animation.Animator>; private _animators: Array<android.animation.Animator>;
private _propertyUpdateCallbacks: Array<Function>; private _propertyUpdateCallbacks: Array<Function>;
private _propertyResetCallbacks: Array<Function>; private _propertyResetCallbacks: Array<Function>;
private _valueSource: number;
public play(): definition.AnimationPromise { public play(): definition.AnimationPromise {
var animationFinishedPromise = super.play(); var animationFinishedPromise = super.play();
var i: number; let i: number;
var length: number; let length: number;
this._animators = new Array<android.animation.Animator>(); this._animators = new Array<android.animation.Animator>();
this._propertyUpdateCallbacks = new Array<Function>(); this._propertyUpdateCallbacks = new Array<Function>();
@ -78,7 +80,11 @@ export class Animation extends common.Animation implements definition.Animation
constructor(animationDefinitions: Array<definition.AnimationDefinition>, playSequentially?: boolean) { constructor(animationDefinitions: Array<definition.AnimationDefinition>, playSequentially?: boolean) {
super(animationDefinitions, playSequentially); super(animationDefinitions, playSequentially);
var that = this; if (animationDefinitions.length > 0 && (<any>animationDefinitions[0]).valueSource !== undefined) {
this._valueSource = (<any>animationDefinitions[0]).valueSource;
}
let that = this;
this._animatorListener = new android.animation.Animator.AnimatorListener({ this._animatorListener = new android.animation.Animator.AnimatorListener({
onAnimationStart: function (animator: android.animation.Animator): void { onAnimationStart: function (animator: android.animation.Animator): void {
trace.write("MainAnimatorListener.onAndroidAnimationStart(" + animator +")", trace.categories.Animation); trace.write("MainAnimatorListener.onAndroidAnimationStart(" + animator +")", trace.categories.Animation);
@ -103,8 +109,8 @@ export class Animation extends common.Animation implements definition.Animation
return; return;
} }
var i = 0; let i = 0;
var length = this._propertyUpdateCallbacks.length; let length = this._propertyUpdateCallbacks.length;
for (; i < length; i++) { for (; i < length; i++) {
this._propertyUpdateCallbacks[i](); this._propertyUpdateCallbacks[i]();
} }
@ -112,8 +118,8 @@ export class Animation extends common.Animation implements definition.Animation
} }
private _onAndroidAnimationCancel() { private _onAndroidAnimationCancel() {
var i = 0; let i = 0;
var length = this._propertyResetCallbacks.length; let length = this._propertyResetCallbacks.length;
for (; i < length; i++) { for (; i < length; i++) {
this._propertyResetCallbacks[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!"); throw new Error("Animation value cannot be null or undefined!");
} }
var nativeArray; let nativeArray;
var nativeView: android.view.View = (<android.view.View>propertyAnimation.target._nativeView); let nativeView: android.view.View = (<android.view.View>propertyAnimation.target._nativeView);
var animators = new Array<android.animation.Animator>(); let animators = new Array<android.animation.Animator>();
var propertyUpdateCallbacks = new Array<Function>(); let propertyUpdateCallbacks = new Array<Function>();
var propertyResetCallbacks = new Array<Function>(); let propertyResetCallbacks = new Array<Function>();
var originalValue1; let originalValue1;
var originalValue2; let originalValue2;
var density = utils.layout.getDisplayDensity(); let density = utils.layout.getDisplayDensity();
var xyObjectAnimators: any; let xyObjectAnimators: any;
var animatorSet: android.animation.AnimatorSet; let animatorSet: android.animation.AnimatorSet;
var key = propertyKeys[propertyAnimation.property]; let key = propertyKeys[propertyAnimation.property];
if (key) { if (key) {
propertyAnimation.target[key] = propertyAnimation; propertyAnimation.target[key] = propertyAnimation;
} }
@ -159,13 +165,22 @@ export class Animation extends common.Animation implements definition.Animation
} }
} }
let valueSource = this._valueSource;
switch (propertyAnimation.property) { switch (propertyAnimation.property) {
case common.Properties.opacity: case common.Properties.opacity:
originalValue1 = nativeView.getAlpha(); originalValue1 = nativeView.getAlpha();
nativeArray = (<any>Array).create("float", 1); nativeArray = (<any>Array).create("float", 1);
nativeArray[0] = propertyAnimation.value; 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); })); propertyResetCallbacks.push(checkAnimation(() => { nativeView.setAlpha(originalValue1); }));
animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "alpha", nativeArray)); animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "alpha", nativeArray));
break; break;
@ -176,15 +191,23 @@ export class Animation extends common.Animation implements definition.Animation
nativeArray = (<any>Array).create(java.lang.Object, 2); nativeArray = (<any>Array).create(java.lang.Object, 2);
nativeArray[0] = propertyAnimation.target.backgroundColor ? java.lang.Integer.valueOf((<color.Color>propertyAnimation.target.backgroundColor).argb) : java.lang.Integer.valueOf(-1); nativeArray[0] = propertyAnimation.target.backgroundColor ? java.lang.Integer.valueOf((<color.Color>propertyAnimation.target.backgroundColor).argb) : java.lang.Integer.valueOf(-1);
nativeArray[1] = java.lang.Integer.valueOf((<color.Color>propertyAnimation.value).argb); nativeArray[1] = java.lang.Integer.valueOf((<color.Color>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({ animator.addUpdateListener(new android.animation.ValueAnimator.AnimatorUpdateListener({
onAnimationUpdate(animator: android.animation.ValueAnimator) { onAnimationUpdate(animator: android.animation.ValueAnimator) {
var argb = (<java.lang.Integer>animator.getAnimatedValue()).intValue(); let argb = (<java.lang.Integer>animator.getAnimatedValue()).intValue();
propertyAnimation.target.backgroundColor = new color.Color(argb); propertyAnimation.target.style._setValue(styleModule.backgroundColorProperty, new color.Color(argb), valueSource);
} }
})); }));
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; })); propertyUpdateCallbacks.push(checkAnimation(() => { propertyAnimation.target.backgroundColor = propertyAnimation.value; }));
}
propertyResetCallbacks.push(checkAnimation(() => { nativeView.setBackground(originalValue1); })); propertyResetCallbacks.push(checkAnimation(() => { nativeView.setBackground(originalValue1); }));
animators.push(animator); animators.push(animator);
break; break;
@ -205,10 +228,18 @@ export class Animation extends common.Animation implements definition.Animation
originalValue1 = nativeView.getTranslationX(); originalValue1 = nativeView.getTranslationX();
originalValue2 = nativeView.getTranslationY(); originalValue2 = nativeView.getTranslationY();
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(() => { propertyUpdateCallbacks.push(checkAnimation(() => {
propertyAnimation.target.translateX = propertyAnimation.value.x; propertyAnimation.target.translateX = propertyAnimation.value.x;
propertyAnimation.target.translateY = propertyAnimation.value.y; propertyAnimation.target.translateY = propertyAnimation.value.y;
})); }));
}
propertyResetCallbacks.push(checkAnimation(() => { propertyResetCallbacks.push(checkAnimation(() => {
nativeView.setTranslationX(originalValue1); nativeView.setTranslationX(originalValue1);
@ -237,10 +268,18 @@ export class Animation extends common.Animation implements definition.Animation
originalValue1 = nativeView.getScaleX(); originalValue1 = nativeView.getScaleX();
originalValue2 = nativeView.getScaleY(); originalValue2 = nativeView.getScaleY();
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(() => { propertyUpdateCallbacks.push(checkAnimation(() => {
propertyAnimation.target.scaleX = propertyAnimation.value.x; propertyAnimation.target.scaleX = propertyAnimation.value.x;
propertyAnimation.target.scaleY = propertyAnimation.value.y; propertyAnimation.target.scaleY = propertyAnimation.value.y;
})); }));
}
propertyResetCallbacks.push(checkAnimation(() => { propertyResetCallbacks.push(checkAnimation(() => {
nativeView.setScaleY(originalValue1); nativeView.setScaleY(originalValue1);
@ -257,7 +296,14 @@ export class Animation extends common.Animation implements definition.Animation
originalValue1 = nativeView.getRotation(); originalValue1 = nativeView.getRotation();
nativeArray = (<any>Array).create("float", 1); nativeArray = (<any>Array).create("float", 1);
nativeArray[0] = propertyAnimation.value; nativeArray[0] = 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; })); propertyUpdateCallbacks.push(checkAnimation(() => { propertyAnimation.target.rotate = propertyAnimation.value; }));
}
propertyResetCallbacks.push(checkAnimation(() => { nativeView.setRotation(originalValue1); })); propertyResetCallbacks.push(checkAnimation(() => { nativeView.setRotation(originalValue1); }));
animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "rotation", nativeArray)); animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "rotation", nativeArray));
break; break;
@ -266,8 +312,8 @@ export class Animation extends common.Animation implements definition.Animation
throw new Error("Cannot animate " + propertyAnimation.property); throw new Error("Cannot animate " + propertyAnimation.property);
} }
var i = 0; let i = 0;
var length = animators.length; let length = animators.length;
for (; i < length; i++) { for (; i < length; i++) {
// Duration // Duration
@ -302,11 +348,11 @@ export class Animation extends common.Animation implements definition.Animation
} }
} }
var easeIn = new android.view.animation.AccelerateInterpolator(1); let easeIn = new android.view.animation.AccelerateInterpolator(1);
var easeOut = new android.view.animation.DecelerateInterpolator(1); let easeOut = new android.view.animation.DecelerateInterpolator(1);
var easeInOut = new android.view.animation.AccelerateDecelerateInterpolator(); let easeInOut = new android.view.animation.AccelerateDecelerateInterpolator();
var linear = new android.view.animation.LinearInterpolator(); let linear = new android.view.animation.LinearInterpolator();
var bounce = new android.view.animation.BounceInterpolator(); let bounce = new android.view.animation.BounceInterpolator();
export function _resolveAnimationCurve(curve: any): any { export function _resolveAnimationCurve(curve: any): any {
switch (curve) { switch (curve) {
case enums.AnimationCurve.easeIn: case enums.AnimationCurve.easeIn:
@ -324,11 +370,13 @@ export function _resolveAnimationCurve(curve: any): any {
case enums.AnimationCurve.spring: case enums.AnimationCurve.spring:
trace.write("Animation curve resolved to android.view.animation.BounceInterpolator().", trace.categories.Animation); trace.write("Animation curve resolved to android.view.animation.BounceInterpolator().", trace.categories.Animation);
return bounce; return bounce;
case enums.AnimationCurve.ease:
return (<any>android).support.v4.view.animation.PathInterpolatorCompat.create(0.25, 0.1, 0.25, 1.0);
default: default:
trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation); trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation);
if (curve instanceof common.CubicBezierAnimationCurve) { if (curve instanceof common.CubicBezierAnimationCurve) {
var animationCurve = <common.CubicBezierAnimationCurve>curve; let animationCurve = <common.CubicBezierAnimationCurve>curve;
var interpolator = (<any>android).support.v4.view.animation.PathInterpolatorCompat.create(animationCurve.x1, animationCurve.y1, animationCurve.x2, animationCurve.y2); let interpolator = (<any>android).support.v4.view.animation.PathInterpolatorCompat.create(animationCurve.x1, animationCurve.y1, animationCurve.x2, animationCurve.y2);
return interpolator; return interpolator;
} }
return curve; return curve;

View File

@ -3,13 +3,15 @@ import common = require("./animation-common");
import viewModule = require("ui/core/view"); import viewModule = require("ui/core/view");
import trace = require("trace"); import trace = require("trace");
import enums = require("ui/enums"); import enums = require("ui/enums");
import style = require("ui/styling/style");
import dependencyObservable = require("ui/core/dependency-observable");
global.moduleMerge(common, exports); global.moduleMerge(common, exports);
var _transform = "_transform"; let _transform = "_transform";
var _skip = "_skip"; let _skip = "_skip";
var FLT_MAX = 340282346638528859811704183484516925440.000000; let FLT_MAX = 340282346638528859811704183484516925440.000000;
declare var CASpringAnimation:any; declare var CASpringAnimation:any;
@ -29,19 +31,54 @@ class AnimationDelegateImpl extends NSObject {
private _finishedCallback: Function; private _finishedCallback: Function;
private _propertyAnimation: common.PropertyAnimation; 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>AnimationDelegateImpl.new(); let delegate = <AnimationDelegateImpl>AnimationDelegateImpl.new();
delegate._finishedCallback = finishedCallback; delegate._finishedCallback = finishedCallback;
delegate._propertyAnimation = propertyAnimation; delegate._propertyAnimation = propertyAnimation;
delegate._valueSource = valueSource;
return delegate; return delegate;
} }
animationDidStart(anim: CAAnimation): void { animationDidStart(anim: CAAnimation): void {
var value = this._propertyAnimation.value; let value = this._propertyAnimation.value;
(<any>this._propertyAnimation.target)._suspendPresentationLayerUpdates(); (<any>this._propertyAnimation.target)._suspendPresentationLayerUpdates();
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) { switch (this._propertyAnimation.property) {
case common.Properties.backgroundColor: case common.Properties.backgroundColor:
this._propertyAnimation.target.backgroundColor = value; this._propertyAnimation.target.backgroundColor = value;
@ -52,6 +89,14 @@ class AnimationDelegateImpl extends NSObject {
case common.Properties.rotate: case common.Properties.rotate:
this._propertyAnimation.target.rotate = value; this._propertyAnimation.target.rotate = value;
break; 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: case _transform:
if (value[common.Properties.translate] !== undefined) { if (value[common.Properties.translate] !== undefined) {
this._propertyAnimation.target.translateX = value[common.Properties.translate].x; this._propertyAnimation.target.translateX = value[common.Properties.translate].x;
@ -63,6 +108,7 @@ class AnimationDelegateImpl extends NSObject {
} }
break; break;
} }
}
(<any>this._propertyAnimation.target)._resumePresentationLayerUpdates(); (<any>this._propertyAnimation.target)._resumePresentationLayerUpdates();
} }
@ -71,11 +117,6 @@ class AnimationDelegateImpl extends NSObject {
if (this._finishedCallback) { if (this._finishedCallback) {
this._finishedCallback(!finished); this._finishedCallback(!finished);
} }
if (!finished) {
if ((<any>this._propertyAnimation)._propertyResetCallback) {
(<any>this._propertyAnimation)._propertyResetCallback((<any>this._propertyAnimation)._originalValue);
}
}
if (finished && this.nextAnimation) { if (finished && this.nextAnimation) {
this.nextAnimation(); this.nextAnimation();
} }
@ -87,9 +128,10 @@ export class Animation extends common.Animation implements definition.Animation
private _finishedAnimations: number; private _finishedAnimations: number;
private _cancelledAnimations: number; private _cancelledAnimations: number;
private _mergedPropertyAnimations: Array<common.PropertyAnimation>; private _mergedPropertyAnimations: Array<common.PropertyAnimation>;
private _valueSource: number;
public play(): definition.AnimationPromise { public play(): definition.AnimationPromise {
var animationFinishedPromise = super.play(); let animationFinishedPromise = super.play();
this._finishedAnimations = 0; this._finishedAnimations = 0;
this._cancelledAnimations = 0; this._cancelledAnimations = 0;
this._iOSAnimationFunction(); this._iOSAnimationFunction();
@ -99,22 +141,34 @@ export class Animation extends common.Animation implements definition.Animation
public cancel(): void { public cancel(): void {
super.cancel(); super.cancel();
var i = 0; let i = 0;
var length = this._mergedPropertyAnimations.length; let length = this._mergedPropertyAnimations.length;
for (; i < length; i++) { for (; i < length; i++) {
(<UIView>this._mergedPropertyAnimations[i].target._nativeView).layer.removeAllAnimations(); (<UIView>this._mergedPropertyAnimations[i].target._nativeView).layer.removeAllAnimations();
if ((<any>this._mergedPropertyAnimations[i])._propertyResetCallback) {
(<any>this._mergedPropertyAnimations[i])._propertyResetCallback((<any>this._mergedPropertyAnimations[i])._originalValue);
}
} }
} }
constructor(animationDefinitions: Array<definition.AnimationDefinition>, playSequentially?: boolean) { constructor(animationDefinitions: Array<definition.AnimationDefinition>, playSequentially?: boolean) {
super(animationDefinitions, playSequentially); super(animationDefinitions, playSequentially);
if (animationDefinitions.length > 0 && (<any>animationDefinitions[0]).valueSource !== undefined) {
this._valueSource = (<any>animationDefinitions[0]).valueSource;
}
if (!playSequentially) {
trace.write("Non-merged Property Animations: " + this._propertyAnimations.length, trace.categories.Animation); trace.write("Non-merged Property Animations: " + this._propertyAnimations.length, trace.categories.Animation);
this._mergedPropertyAnimations = Animation._mergeAffineTransformAnimations(this._propertyAnimations); this._mergedPropertyAnimations = Animation._mergeAffineTransformAnimations(this._propertyAnimations);
trace.write("Merged Property Animations: " + this._mergedPropertyAnimations.length, trace.categories.Animation); trace.write("Merged Property Animations: " + this._mergedPropertyAnimations.length, trace.categories.Animation);
}
else {
this._mergedPropertyAnimations = this._propertyAnimations;
}
var that = this; let that = this;
var animationFinishedCallback = (cancelled: boolean) => { let animationFinishedCallback = (cancelled: boolean) => {
if (that._playSequentially) { 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. // This function will be called by the last animation when done or by another animation if the user cancels them halfway through.
if (cancelled) { 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<common.PropertyAnimation>, index: number, playSequentially: boolean, finishedCallback: (cancelled?: boolean) => void): Function { private static _createiOSAnimationFunction(propertyAnimations: Array<common.PropertyAnimation>, index: number, playSequentially: boolean, valueSource: number, finishedCallback: (cancelled?: boolean) => void): Function {
return (cancelled?: boolean) => { return (cancelled?: boolean) => {
if (cancelled && finishedCallback) { if (cancelled && finishedCallback) {
@ -156,34 +210,34 @@ export class Animation extends common.Animation implements definition.Animation
return; return;
} }
var animation = propertyAnimations[index]; let animation = propertyAnimations[index];
var args = Animation._getNativeAnimationArguments(animation); let args = Animation._getNativeAnimationArguments(animation, valueSource);
if (animation.curve === enums.AnimationCurve.spring) { if (animation.curve === enums.AnimationCurve.spring) {
Animation._createNativeSpringAnimation(propertyAnimations, index, playSequentially, args, animation, finishedCallback); Animation._createNativeSpringAnimation(propertyAnimations, index, playSequentially, args, animation, valueSource, finishedCallback);
} }
else { 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 = <UIView>animation.target._nativeView; let nativeView = <UIView>animation.target._nativeView;
var presentationLayer = nativeView.layer.presentationLayer(); let presentationLayer = nativeView.layer.presentationLayer();
var propertyNameToAnimate = animation.property; let propertyNameToAnimate = animation.property;
var value = animation.value; let value = animation.value;
var originalValue; let originalValue;
var tempRotate = animation.target.rotate * Math.PI / 180; let tempRotate = animation.target.rotate * Math.PI / 180;
var abs let abs;
switch (animation.property) { switch (animation.property) {
case common.Properties.backgroundColor: case common.Properties.backgroundColor:
(<any>animation)._originalValue = animation.target.backgroundColor; (<any>animation)._originalValue = animation.target.backgroundColor;
(<any>animation)._propertyResetCallback = (value) => { animation.target.backgroundColor = value }; (<any>animation)._propertyResetCallback = (value) => { animation.target.backgroundColor = value; };
if (presentationLayer != null) { if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = presentationLayer.backgroundColor; originalValue = presentationLayer.backgroundColor;
} }
else { else {
@ -197,8 +251,8 @@ export class Animation extends common.Animation implements definition.Animation
break; break;
case common.Properties.opacity: case common.Properties.opacity:
(<any>animation)._originalValue = animation.target.opacity; (<any>animation)._originalValue = animation.target.opacity;
(<any>animation)._propertyResetCallback = (value) => { animation.target.opacity = value }; (<any>animation)._propertyResetCallback = (value) => { animation.target.opacity = value; };
if (presentationLayer != null) { if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = presentationLayer.opacity; originalValue = presentationLayer.opacity;
} }
else { else {
@ -207,9 +261,9 @@ export class Animation extends common.Animation implements definition.Animation
break; break;
case common.Properties.rotate: case common.Properties.rotate:
(<any>animation)._originalValue = animation.target.rotate; (<any>animation)._originalValue = animation.target.rotate;
(<any>animation)._propertyResetCallback = (value) => { animation.target.rotate = value }; (<any>animation)._propertyResetCallback = (value) => { animation.target.rotate = value; };
propertyNameToAnimate = "transform.rotation"; propertyNameToAnimate = "transform.rotation";
if (presentationLayer != null) { if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = presentationLayer.valueForKeyPath("transform.rotation"); originalValue = presentationLayer.valueForKeyPath("transform.rotation");
} }
else { else {
@ -224,8 +278,8 @@ export class Animation extends common.Animation implements definition.Animation
case common.Properties.translate: case common.Properties.translate:
(<any>animation)._originalValue = { x:animation.target.translateX, y:animation.target.translateY }; (<any>animation)._originalValue = { x:animation.target.translateX, y:animation.target.translateY };
(<any>animation)._propertyResetCallback = (value) => { animation.target.translateX = value.x; animation.target.translateY = value.y; }; (<any>animation)._propertyResetCallback = (value) => { animation.target.translateX = value.x; animation.target.translateY = value.y; };
propertyNameToAnimate = "transform" propertyNameToAnimate = "transform";
if (presentationLayer != null) { if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform); originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform);
} }
else { else {
@ -236,8 +290,8 @@ export class Animation extends common.Animation implements definition.Animation
case common.Properties.scale: case common.Properties.scale:
(<any>animation)._originalValue = { x:animation.target.scaleX, y:animation.target.scaleY }; (<any>animation)._originalValue = { x:animation.target.scaleX, y:animation.target.scaleY };
(<any>animation)._propertyResetCallback = (value) => { animation.target.scaleX = value.x; animation.target.scaleY = value.y; }; (<any>animation)._propertyResetCallback = (value) => { animation.target.scaleX = value.x; animation.target.scaleY = value.y; };
propertyNameToAnimate = "transform" propertyNameToAnimate = "transform";
if (presentationLayer != null) { if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform); originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform);
} }
else { 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)); value = NSValue.valueWithCATransform3D(CATransform3DScale(nativeView.layer.transform, value.x, value.y, 1));
break; break;
case _transform: case _transform:
if (presentationLayer != null) { if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform); originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform);
} }
else { else {
@ -260,24 +314,24 @@ export class Animation extends common.Animation implements definition.Animation
animation.target.scaleX = value.xs; animation.target.scaleX = value.xs;
animation.target.scaleY = value.ys; animation.target.scaleY = value.ys;
}; };
propertyNameToAnimate = "transform" propertyNameToAnimate = "transform";
value = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation)); value = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation));
break; break;
default: default:
throw new Error("Cannot animate " + animation.property); throw new Error("Cannot animate " + animation.property);
} }
var duration = 0.3; let duration = 0.3;
if (animation.duration !== undefined) { if (animation.duration !== undefined) {
duration = animation.duration / 1000.0; duration = animation.duration / 1000.0;
} }
var delay = undefined; let delay = undefined;
if (animation.delay) { if (animation.delay) {
delay = animation.delay / 1000.0; delay = animation.delay / 1000.0;
} }
var repeatCount = undefined; let repeatCount = undefined;
if (animation.iterations !== undefined) { if (animation.iterations !== undefined) {
if (animation.iterations === Number.POSITIVE_INFINITY) { if (animation.iterations === Number.POSITIVE_INFINITY) {
repeatCount = FLT_MAX; repeatCount = FLT_MAX;
@ -297,10 +351,10 @@ export class Animation extends common.Animation implements definition.Animation
}; };
} }
private static _createNativeAnimation(propertyAnimations: Array<common.PropertyAnimation>, index: number, playSequentially: boolean, args: AnimationInfo, animation: common.PropertyAnimation, finishedCallback: (cancelled?: boolean) => void) { private static _createNativeAnimation(propertyAnimations: Array<common.PropertyAnimation>, index: number, playSequentially: boolean, args: AnimationInfo, animation: common.PropertyAnimation, valueSource: number, finishedCallback: (cancelled?: boolean) => void) {
var nativeView = <UIView>animation.target._nativeView; let nativeView = <UIView>animation.target._nativeView;
var nativeAnimation = CABasicAnimation.animationWithKeyPath(args.propertyNameToAnimate); let nativeAnimation = CABasicAnimation.animationWithKeyPath(args.propertyNameToAnimate);
nativeAnimation.fromValue = args.fromValue; nativeAnimation.fromValue = args.fromValue;
nativeAnimation.toValue = args.toValue; nativeAnimation.toValue = args.toValue;
nativeAnimation.duration = args.duration; nativeAnimation.duration = args.duration;
@ -314,14 +368,14 @@ export class Animation extends common.Animation implements definition.Animation
nativeAnimation.timingFunction = animation.curve; nativeAnimation.timingFunction = animation.curve;
} }
var animationDelegate = AnimationDelegateImpl.initWithFinishedCallback(finishedCallback, animation); let animationDelegate = AnimationDelegateImpl.initWithFinishedCallback(finishedCallback, animation, valueSource);
nativeAnimation.setValueForKey(animationDelegate, "delegate"); nativeAnimation.setValueForKey(animationDelegate, "delegate");
nativeView.layer.addAnimationForKey(nativeAnimation, args.propertyNameToAnimate); nativeView.layer.addAnimationForKey(nativeAnimation, args.propertyNameToAnimate);
var callback = undefined; let callback = undefined;
if (index+1 < propertyAnimations.length) { if (index + 1 < propertyAnimations.length) {
callback = Animation._createiOSAnimationFunction(propertyAnimations, index+1, playSequentially, finishedCallback); callback = Animation._createiOSAnimationFunction(propertyAnimations, index + 1, playSequentially, valueSource, finishedCallback);
if (!playSequentially) { if (!playSequentially) {
callback(); callback();
} }
@ -331,14 +385,14 @@ export class Animation extends common.Animation implements definition.Animation
} }
} }
private static _createNativeSpringAnimation(propertyAnimations: Array<common.PropertyAnimation>, index: number, playSequentially: boolean, args: AnimationInfo, animation: common.PropertyAnimation, finishedCallback: (cancelled?: boolean) => void) { private static _createNativeSpringAnimation(propertyAnimations: Array<common.PropertyAnimation>, index: number, playSequentially: boolean, args: AnimationInfo, animation: common.PropertyAnimation, valueSource: number, finishedCallback: (cancelled?: boolean) => void) {
var nativeView = <UIView>animation.target._nativeView; let nativeView = <UIView>animation.target._nativeView;
var callback = undefined; let callback = undefined;
var nextAnimation; let nextAnimation;
if (index + 1 < propertyAnimations.length) { if (index + 1 < propertyAnimations.length) {
callback = Animation._createiOSAnimationFunction(propertyAnimations, index + 1, playSequentially, finishedCallback); callback = Animation._createiOSAnimationFunction(propertyAnimations, index + 1, playSequentially, valueSource, finishedCallback);
if (!playSequentially) { if (!playSequentially) {
callback(); callback();
} }
@ -347,7 +401,7 @@ export class Animation extends common.Animation implements definition.Animation
} }
} }
var delay = 0; let delay = 0;
if (args.delay) { if (args.delay) {
delay = args.delay; delay = args.delay;
} }
@ -396,7 +450,7 @@ export class Animation extends common.Animation implements definition.Animation
} }
} }
if (finishedCallback) { if (finishedCallback) {
var cancelled = !finished; let cancelled = !finished;
finishedCallback(cancelled); finishedCallback(cancelled);
} }
if (finished && nextAnimation) { if (finished && nextAnimation) {
@ -406,18 +460,18 @@ export class Animation extends common.Animation implements definition.Animation
} }
private static _createNativeAffineTransform(animation: common.PropertyAnimation): CATransform3D { private static _createNativeAffineTransform(animation: common.PropertyAnimation): CATransform3D {
var value = animation.value; let value = animation.value;
var result:CATransform3D = CATransform3DIdentity; let result:CATransform3D = CATransform3DIdentity;
if (value[common.Properties.translate] !== undefined) { if (value[common.Properties.translate] !== undefined) {
var x = value[common.Properties.translate].x; let x = value[common.Properties.translate].x;
var y = value[common.Properties.translate].y; let y = value[common.Properties.translate].y;
result = CATransform3DTranslate(result, x, y, 0); result = CATransform3DTranslate(result, x, y, 0);
} }
if (value[common.Properties.scale] !== undefined) { if (value[common.Properties.scale] !== undefined) {
var x = value[common.Properties.scale].x; let x = value[common.Properties.scale].x;
var y = value[common.Properties.scale].y; let y = value[common.Properties.scale].y;
result = CATransform3DScale(result, x, y, 1); 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) { private static _canBeMerged(animation1: common.PropertyAnimation, animation2: common.PropertyAnimation) {
var result = let result =
Animation._isAffineTransform(animation1.property) && Animation._isAffineTransform(animation1.property) &&
Animation._isAffineTransform(animation2.property) && Animation._isAffineTransform(animation2.property) &&
animation1.target === animation2.target && animation1.target === animation2.target &&
@ -443,11 +497,11 @@ export class Animation extends common.Animation implements definition.Animation
} }
private static _mergeAffineTransformAnimations(propertyAnimations: Array<common.PropertyAnimation>): Array<common.PropertyAnimation> { private static _mergeAffineTransformAnimations(propertyAnimations: Array<common.PropertyAnimation>): Array<common.PropertyAnimation> {
var result = new Array<common.PropertyAnimation>(); let result = new Array<common.PropertyAnimation>();
var i = 0; let i = 0;
var j; let j;
var length = propertyAnimations.length; let length = propertyAnimations.length;
for (; i < length; i++) { for (; i < length; i++) {
if (propertyAnimations[i][_skip]) { if (propertyAnimations[i][_skip]) {
continue; continue;
@ -465,7 +519,7 @@ export class Animation extends common.Animation implements definition.Animation
// rotate: 90, // rotate: 90,
// scale: {x: 2, y: 2 } // scale: {x: 2, y: 2 }
// } // }
var newTransformAnimation: common.PropertyAnimation = { let newTransformAnimation: common.PropertyAnimation = {
target: propertyAnimations[i].target, target: propertyAnimations[i].target,
property: _transform, property: _transform,
value: {}, value: {},
@ -510,12 +564,14 @@ export function _resolveAnimationCurve(curve: any): any {
return CAMediaTimingFunction.functionWithName(kCAMediaTimingFunctionLinear); return CAMediaTimingFunction.functionWithName(kCAMediaTimingFunctionLinear);
case enums.AnimationCurve.spring: case enums.AnimationCurve.spring:
return curve; return curve;
case enums.AnimationCurve.ease:
return CAMediaTimingFunction.functionWithControlPoints(0.25, 0.1, 0.25, 1.0);
default: default:
if (curve instanceof CAMediaTimingFunction) { if (curve instanceof CAMediaTimingFunction) {
return curve; return curve;
} }
else if (curve instanceof common.CubicBezierAnimationCurve) { else if (curve instanceof common.CubicBezierAnimationCurve) {
var animationCurve = <common.CubicBezierAnimationCurve>curve; let animationCurve = <common.CubicBezierAnimationCurve>curve;
return CAMediaTimingFunction.functionWithControlPoints(animationCurve.x1, animationCurve.y1, animationCurve.x2, animationCurve.y2); return CAMediaTimingFunction.functionWithControlPoints(animationCurve.x1, animationCurve.y1, animationCurve.x2, animationCurve.y2);
} }
return undefined; return undefined;
@ -524,12 +580,12 @@ export function _resolveAnimationCurve(curve: any): any {
export function _getTransformMismatchErrorMessage(view: viewModule.View): string { export function _getTransformMismatchErrorMessage(view: viewModule.View): string {
// Order is important: translate, rotate, scale // Order is important: translate, rotate, scale
var result: CGAffineTransform = CGAffineTransformIdentity; let result: CGAffineTransform = CGAffineTransformIdentity;
result = CGAffineTransformTranslate(result, view.translateX, view.translateY); result = CGAffineTransformTranslate(result, view.translateX, view.translateY);
result = CGAffineTransformRotate(result, view.rotate * Math.PI / 180); result = CGAffineTransformRotate(result, view.rotate * Math.PI / 180);
result = CGAffineTransformScale(result, view.scaleX, view.scaleY); result = CGAffineTransformScale(result, view.scaleX, view.scaleY);
var viewTransform = NSStringFromCGAffineTransform(result); let viewTransform = NSStringFromCGAffineTransform(result);
var nativeTransform = NSStringFromCGAffineTransform(view._nativeView.transform); let nativeTransform = NSStringFromCGAffineTransform(view._nativeView.transform);
if (viewTransform !== nativeTransform) { if (viewTransform !== nativeTransform) {
return "View and Native transforms do not match. View: " + viewTransform + "; Native: " + nativeTransform; return "View and Native transforms do not match. View: " + viewTransform + "; Native: " + nativeTransform;

94
ui/animation/keyframe-animation.d.ts vendored Normal file
View File

@ -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<KeyframeDeclaration>;
}
/**
* 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<KeyframeInfo>;
}
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<void>;
/**
* Creates a keyframe animation from animation definition.
*/
public static keyframeAnimationFromInfo(info: KeyframeAnimationInfo, valueSourceModifier: number);
}
}

View File

@ -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<KeyframeDeclaration>;
}
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<KeyframeInfo>;
}
export class KeyframeAnimation {
public animations: Array<Object>;
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<Object>();
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<Object>, 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<void> {
if (this._isPlaying) {
throw new Error("Animation is already playing.");
}
let animationFinishedPromise = new Promise<void>((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."));
}
}

View File

@ -36,6 +36,24 @@ export class Button extends common.Button {
} }
} }
})); }));
this._android.setOnTouchListener(new android.view.View.OnTouchListener(
<utils.Owned & android.view.View.IOnTouchListener>{
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) { public _onTextPropertyChanged(data: dependencyObservable.PropertyChangeData) {

View File

@ -6,6 +6,7 @@ import view = require("ui/core/view");
import utils = require("utils/utils"); import utils = require("utils/utils");
import enums = require("ui/enums"); import enums = require("ui/enums");
import dependencyObservable = require("ui/core/dependency-observable"); import dependencyObservable = require("ui/core/dependency-observable");
import styleScope = require("../styling/style-scope");
class TapHandlerImpl extends NSObject { class TapHandlerImpl extends NSObject {
private _owner: WeakRef<Button>; private _owner: WeakRef<Button>;
@ -47,6 +48,22 @@ export class Button extends common.Button {
}); });
} }
public onLoaded() {
super.onLoaded();
if (this.parent !== null && this.page !== null) {
let rootPage = this.page;
let scope: styleScope.StyleScope = (<any>rootPage)._getStyleScope();
if (scope.getVisualStates(this) !== undefined) {
this._stateChangedHandler.start();
}
}
}
public onUnloaded() {
super.onUnloaded();
this._stateChangedHandler.stop();
}
get ios(): UIButton { get ios(): UIButton {
return this._ios; return this._ios;
} }

View File

@ -10,5 +10,9 @@
* @param callback A callback called when a visual state of the UIControl is changed. * @param callback A callback called when a visual state of the UIControl is changed.
*/ */
constructor(control: any /* UIControl */, callback: (state: string) => void); constructor(control: any /* UIControl */, callback: (state: string) => void);
start();
stop();
} }
} }

View File

@ -21,22 +21,31 @@ export class ControlStateChangeListener implements definition.ControlStateChange
private _observer: NSObject; private _observer: NSObject;
private _states: string[]; private _states: string[];
private _control: UIControl; private _control: UIControl;
private _observing: boolean = false;
private _callback: (state: string) => void; private _callback: (state: string) => void;
constructor(control: UIControl, callback: (state: string) => void) { constructor(control: UIControl, callback: (state: string) => void) {
this._observer = ObserverClass.alloc(); this._observer = ObserverClass.alloc();
this._observer["_owner"] = this; this._observer["_owner"] = this;
// TODO: Commenting for now, needs revision later since we must detach the observers upon control deallocation
//control.addObserverForKeyPathOptionsContext(this._observer, "selected", NSKeyValueObservingOptions.NSKeyValueObservingOptionNew, null);
//control.addObserverForKeyPathOptionsContext(this._observer, "enabled", NSKeyValueObservingOptions.NSKeyValueObservingOptionNew, null);
//control.addObserverForKeyPathOptionsContext(this._observer, "highlighted", NSKeyValueObservingOptions.NSKeyValueObservingOptionNew, null);
this._control = control; this._control = control;
this._callback = callback; this._callback = callback;
} }
public start() {
if (!this._observing) {
this._control.addObserverForKeyPathOptionsContext(this._observer, "highlighted", NSKeyValueObservingOptions.NSKeyValueObservingOptionNew, null);
this._observing = true;
}
}
public stop() {
if (this._observing) {
this._observing = false;
this._control.removeObserverForKeyPath(this._observer, "highlighted");
}
}
private _onEnabledChanged() { private _onEnabledChanged() {
this._updateState(); this._updateState();
} }
@ -52,11 +61,8 @@ export class ControlStateChangeListener implements definition.ControlStateChange
private _updateState() { private _updateState() {
var state = visualStateConstants.Normal; var state = visualStateConstants.Normal;
if (this._control.highlighted) { if (this._control.highlighted) {
state = visualStateConstants.Pressed; state = "highlighted";
} else if (this._control.highlighted) {
// TODO:
} }
this._callback(state); this._callback(state);
} }
} }

View File

@ -636,6 +636,71 @@ export class ViewStyler implements style.Styler {
(<android.view.View>view._nativeView).setPadding(left, top, right, bottom); (<android.view.View>view._nativeView).setPadding(left, top, right, bottom);
} }
// Rotate
private static setRotateProperty(view: View, newValue: any) {
view.rotate = newValue;
}
private static resetRotateProperty(view: View, nativeValue: any) {
view.rotate = nativeValue;
}
private static getRotateProperty(view: View): any {
return view.rotate;
}
//ScaleX
private static setScaleXProperty(view: View, newValue: any) {
view.scaleX = newValue;
}
private static resetScaleXProperty(view: View, nativeValue: any) {
view.scaleX = nativeValue;
}
private static getScaleXProperty(view: View): any {
return view.scaleX;
}
//ScaleY
private static setScaleYProperty(view: View, newValue: any) {
view.scaleY = newValue;
}
private static resetScaleYProperty(view: View, nativeValue: any) {
view.scaleY = nativeValue;
}
private static getScaleYProperty(view: View): any {
return view.scaleY;
}
//TranslateX
private static setTranslateXProperty(view: View, newValue: any) {
view.translateX = newValue;
}
private static resetTranslateXProperty(view: View, nativeValue: any) {
view.translateX = nativeValue;
}
private static getTranslateXProperty(view: View): any {
return view.translateX;
}
//TranslateY
private static setTranslateYProperty(view: View, newValue: any) {
view.translateY = newValue;
}
private static resetTranslateYProperty(view: View, nativeValue: any) {
view.translateY = nativeValue;
}
private static getTranslateYProperty(view: View): any {
return view.translateY;
}
public static registerHandlers() { public static registerHandlers() {
style.registerHandler(style.visibilityProperty, new style.StylePropertyChangedHandler( style.registerHandler(style.visibilityProperty, new style.StylePropertyChangedHandler(
ViewStyler.setVisibilityProperty, ViewStyler.setVisibilityProperty,
@ -679,6 +744,31 @@ export class ViewStyler implements style.Styler {
style.registerHandler(style.nativePaddingsProperty, new style.StylePropertyChangedHandler( style.registerHandler(style.nativePaddingsProperty, new style.StylePropertyChangedHandler(
ViewStyler.setPaddingProperty, ViewStyler.setPaddingProperty,
ViewStyler.resetPaddingProperty), "LayoutBase"); ViewStyler.resetPaddingProperty), "LayoutBase");
style.registerHandler(style.rotateProperty, new style.StylePropertyChangedHandler(
ViewStyler.setRotateProperty,
ViewStyler.resetRotateProperty,
ViewStyler.getRotateProperty));
style.registerHandler(style.scaleXProperty, new style.StylePropertyChangedHandler(
ViewStyler.setScaleXProperty,
ViewStyler.resetScaleXProperty,
ViewStyler.getScaleXProperty));
style.registerHandler(style.scaleYProperty, new style.StylePropertyChangedHandler(
ViewStyler.setScaleYProperty,
ViewStyler.resetScaleYProperty,
ViewStyler.getScaleYProperty));
style.registerHandler(style.translateXProperty, new style.StylePropertyChangedHandler(
ViewStyler.setTranslateXProperty,
ViewStyler.resetTranslateXProperty,
ViewStyler.getTranslateXProperty));
style.registerHandler(style.translateYProperty, new style.StylePropertyChangedHandler(
ViewStyler.setTranslateYProperty,
ViewStyler.resetTranslateYProperty,
ViewStyler.getTranslateYProperty));
} }
} }

View File

@ -517,6 +517,71 @@ export class ViewStyler implements style.Styler {
return 0; return 0;
} }
// Rotate
private static setRotateProperty(view: View, newValue: any) {
view.rotate = newValue;
}
private static resetRotateProperty(view: View, nativeValue: any) {
view.rotate = nativeValue;
}
private static getRotateProperty(view: View): any {
return view.rotate;
}
//ScaleX
private static setScaleXProperty(view: View, newValue: any) {
view.scaleX = newValue;
}
private static resetScaleXProperty(view: View, nativeValue: any) {
view.scaleX = nativeValue;
}
private static getScaleXProperty(view: View): any {
return view.scaleX;
}
//ScaleY
private static setScaleYProperty(view: View, newValue: any) {
view.scaleY = newValue;
}
private static resetScaleYProperty(view: View, nativeValue: any) {
view.scaleY = nativeValue;
}
private static getScaleYProperty(view: View): any {
return view.scaleY;
}
//TranslateX
private static setTranslateXProperty(view: View, newValue: any) {
view.translateX = newValue;
}
private static resetTranslateXProperty(view: View, nativeValue: any) {
view.translateX = nativeValue;
}
private static getTranslateXProperty(view: View): any {
return view.translateX;
}
//TranslateY
private static setTranslateYProperty(view: View, newValue: any) {
view.translateY = newValue;
}
private static resetTranslateYProperty(view: View, nativeValue: any) {
view.translateY = nativeValue;
}
private static getTranslateYProperty(view: View): any {
return view.translateY;
}
public static registerHandlers() { public static registerHandlers() {
style.registerHandler(style.backgroundInternalProperty, new style.StylePropertyChangedHandler( style.registerHandler(style.backgroundInternalProperty, new style.StylePropertyChangedHandler(
ViewStyler.setBackgroundInternalProperty, ViewStyler.setBackgroundInternalProperty,
@ -545,6 +610,31 @@ export class ViewStyler implements style.Styler {
ViewStyler.setBorderRadiusProperty, ViewStyler.setBorderRadiusProperty,
ViewStyler.resetBorderRadiusProperty, ViewStyler.resetBorderRadiusProperty,
ViewStyler.getBorderRadiusProperty)); ViewStyler.getBorderRadiusProperty));
style.registerHandler(style.rotateProperty, new style.StylePropertyChangedHandler(
ViewStyler.setRotateProperty,
ViewStyler.resetRotateProperty,
ViewStyler.getRotateProperty));
style.registerHandler(style.scaleXProperty, new style.StylePropertyChangedHandler(
ViewStyler.setScaleXProperty,
ViewStyler.resetScaleXProperty,
ViewStyler.getScaleXProperty));
style.registerHandler(style.scaleYProperty, new style.StylePropertyChangedHandler(
ViewStyler.setScaleYProperty,
ViewStyler.resetScaleYProperty,
ViewStyler.getScaleYProperty));
style.registerHandler(style.translateXProperty, new style.StylePropertyChangedHandler(
ViewStyler.setTranslateXProperty,
ViewStyler.resetTranslateXProperty,
ViewStyler.getTranslateXProperty));
style.registerHandler(style.translateYProperty, new style.StylePropertyChangedHandler(
ViewStyler.setTranslateYProperty,
ViewStyler.resetTranslateYProperty,
ViewStyler.getTranslateYProperty));
} }
} }

7
ui/enums/enums.d.ts vendored
View File

@ -523,7 +523,12 @@
/** /**
* Represents an animation curve type. * Represents an animation curve type.
*/ */
module AnimationCurve { export module AnimationCurve {
/**
* Default value. Specifies a transition effect with a slow start, then fast, then end slowly (equivalent to cubic-bezier(0.25,0.1,0.25,1))
*/
export var ease: string;
/** /**
* An ease-in curve causes the animation to begin slowly, and then speed up as it progresses. * An ease-in curve causes the animation to begin slowly, and then speed up as it progresses.

View File

@ -1,5 +1,3 @@
import animationModule = require("ui/animation");
export module KeyboardType { export module KeyboardType {
export var datetime = "datetime"; export var datetime = "datetime";
export var phone = "phone"; export var phone = "phone";
@ -159,13 +157,17 @@ export module BackgroundRepeat {
export var noRepeat: string = "no-repeat"; export var noRepeat: string = "no-repeat";
} }
var animationModule;
export module AnimationCurve { export module AnimationCurve {
export var ease = "ease";
export var easeIn = "easeIn"; export var easeIn = "easeIn";
export var easeOut = "easeOut"; export var easeOut = "easeOut";
export var easeInOut = "easeInOut"; export var easeInOut = "easeInOut";
export var linear = "linear"; export var linear = "linear";
export var spring = "spring"; export var spring = "spring";
export function cubicBezier(x1: number, y1: number, x2: number, y2: number): animationModule.CubicBezierAnimationCurve { export function cubicBezier(x1: number, y1: number, x2: number, y2: number): Object {
animationModule = animationModule || require("ui/animation");
return new animationModule.CubicBezierAnimationCurve(x1, y1 ,x2, y2); return new animationModule.CubicBezierAnimationCurve(x1, y1 ,x2, y2);
} }
} }

View File

@ -138,6 +138,21 @@ export class ListView extends common.ListView {
} }
} }
get _childrenCount(): number {
let keys = Object.keys(this._realizedItems);
return keys.length;
}
public _eachChildView(callback: (child: viewModule.View) => boolean): void {
let keys = Object.keys(this._realizedItems);
let length = keys.length;
for (let i = 0; i < length; i++) {
let key = keys[i];
let view: viewModule.View = this._realizedItems[key];
callback(view);
}
}
public _getRealizedView(convertView: android.view.View, index: number) { public _getRealizedView(convertView: android.view.View, index: number) {
if (!convertView) { if (!convertView) {
return this._getItemTemplateContent(index); return this._getItemTemplateContent(index);

View File

@ -241,6 +241,16 @@ export class ListView extends common.ListView {
return this._ios; return this._ios;
} }
get _childrenCount(): number {
return this._map.size;
}
public _eachChildView(callback: (child: view.View) => boolean): void {
this._map.forEach(function(view, key) {
callback(view);
}, this._map);
}
public scrollToIndex(index: number) { public scrollToIndex(index: number) {
if (this._ios) { if (this._ios) {
this._ios.scrollToRowAtIndexPathAtScrollPositionAnimated(NSIndexPath.indexPathForItemInSection(index, 0), this._ios.scrollToRowAtIndexPathAtScrollPositionAnimated(NSIndexPath.indexPathForItemInSection(index, 0),

View File

@ -8,6 +8,7 @@ import * as style from "../styling/style";
import * as fileSystemModule from "file-system"; import * as fileSystemModule from "file-system";
import * as frameModule from "ui/frame"; import * as frameModule from "ui/frame";
import proxy = require("ui/core/proxy"); import proxy = require("ui/core/proxy");
import keyframeAnimation = require("ui/animation/keyframe-animation");
var fs: typeof fileSystemModule; var fs: typeof fileSystemModule;
function ensureFS() { function ensureFS() {
@ -175,6 +176,15 @@ export class Page extends ContentView implements dts.Page {
} }
} }
public removeCssSelectors(selectorExpression: string) {
this._styleScope.removeSelectors(selectorExpression);
this._refreshCss();
}
public getKeyframeAnimationWithName(animationName: string): keyframeAnimation.KeyframeAnimationInfo {
return this._styleScope.getKeyframeAnimationWithName(animationName);
}
get frame(): frameModule.Frame { get frame(): frameModule.Frame {
return <frameModule.Frame>this.parent; return <frameModule.Frame>this.parent;
} }

12
ui/page/page.d.ts vendored
View File

@ -8,6 +8,7 @@ declare module "ui/page" {
import frame = require("ui/frame"); import frame = require("ui/frame");
import actionBar = require("ui/action-bar"); import actionBar = require("ui/action-bar");
import dependencyObservable = require("ui/core/dependency-observable"); import dependencyObservable = require("ui/core/dependency-observable");
import keyframeAnimation = require("ui/animation/keyframe-animation");
//@private //@private
import styleScope = require("ui/styling/style-scope"); import styleScope = require("ui/styling/style-scope");
@ -120,6 +121,17 @@ declare module "ui/page" {
*/ */
addCssFile(cssFileName: string): void; addCssFile(cssFileName: string): void;
/**
* Removes all selectors matching the specified selector expression.
* @param selectorExpression - A valid selector expression.
*/
removeCssSelectors(selectorExpression: string): void;
/**
* Returns a CSS keyframe animation with the specified name, if it exists.
*/
getKeyframeAnimationWithName(animationName: string): keyframeAnimation.KeyframeAnimationInfo;
/** /**
* A property that is used to pass a data from another page (while navigate to). * A property that is used to pass a data from another page (while navigate to).
*/ */

View File

@ -1,5 +1,6 @@
import enums = require("ui/enums"); import enums = require("ui/enums");
import color = require("color"); import color = require("color");
import types = require("utils/types");
export function colorConverter(value: string): color.Color { export function colorConverter(value: string): color.Color {
return new color.Color(value); return new color.Color(value);
@ -73,3 +74,96 @@ export function opacityConverter(value: string): number {
return result; return result;
} }
export function timeConverter(value: string): number {
var result = parseFloat(value);
if (value.indexOf("ms") === -1) {
result = result*1000;
}
result = Math.max(0.0, result);
return result;
}
export function bezieArgumentConverter(value: string): number {
var result = parseFloat(value);
result = Math.max(0.0, result);
result = Math.min(1.0, result);
return result;
}
export function animationTimingFunctionConverter(value: string): Object {
let result: Object = enums.AnimationCurve.ease;
switch (value) {
case "ease":
result = enums.AnimationCurve.ease;
break;
case "linear":
result = enums.AnimationCurve.linear;
break;
case "ease-in":
result = enums.AnimationCurve.easeIn;
break;
case "ease-out":
result = enums.AnimationCurve.easeOut;
break;
case "ease-in-out":
result = enums.AnimationCurve.easeInOut;
break;
case "spring":
result = enums.AnimationCurve.spring;
break;
default:
if (value.indexOf("cubic-bezier(") === 0) {
let bezierArr = value.substring(13).split(/[,]+/);
if (bezierArr.length !== 4) {
throw new Error("Invalid value for animation: " + value);
}
result = enums.AnimationCurve.cubicBezier(bezieArgumentConverter(bezierArr[0]),
bezieArgumentConverter(bezierArr[1]),
bezieArgumentConverter(bezierArr[2]),
bezieArgumentConverter(bezierArr[3]));
}
else {
throw new Error("Invalid value for animation: " + value);
}
break;
}
return result;
}
export function transformConverter(value: any): Object {
if (value === "none") {
let operations = {};
operations[value] = value;
return operations;
}
else if (types.isString(value)) {
let operations = {};
let operator = "";
let pos = 0;
while (pos < value.length) {
if (value[pos] === " " || value[pos] === ",") {
pos ++;
}
else if (value[pos] === "(") {
let start = pos + 1;
while (pos < value.length && value[pos] !== ")") {
pos ++;
}
let operand = value.substring(start, pos);
operations[operator] = operand.trim();
operator = "";
pos ++;
}
else {
operator += value[pos ++];
}
}
return operations;
}
else {
return undefined;
}
}

View File

@ -0,0 +1,202 @@
import animationModule = require("ui/animation");
import keyframeAnimationModule = require("ui/animation/keyframe-animation");
import cssParser = require("css");
import converters = require("../styling/converters");
import types = require("utils/types");
import colorModule = require("color");
import styleProperty = require("ui/styling/style-property");
interface TransformInfo {
scale: animationModule.Pair;
translate: animationModule.Pair;
}
let animationProperties = {
"animation-name": (info, declaration) => info.name = declaration.value,
"animation-duration": (info, declaration) => info.duration = converters.timeConverter(declaration.value),
"animation-delay": (info, declaration) => info.delay = converters.timeConverter(declaration.value),
"animation-timing-function": (info, declaration) => info.curve = converters.animationTimingFunctionConverter(declaration.value),
"animation-iteration-count": (info, declaration) => declaration.value === "infinite" ? info.iterations = Number.MAX_VALUE : info.iterations = converters.numberConverter(declaration.value),
"animation-direction": (info, declaration) => info.isReverse = declaration.value === "reverse",
"animation-fill-mode": (info, declaration) => info.isForwards = declaration.value === "forwards"
};
export class CssAnimationParser {
public static keyframeAnimationsFromCSSDeclarations(declarations: cssParser.Declaration[]): Array<keyframeAnimationModule.KeyframeAnimationInfo> {
let animations: Array<keyframeAnimationModule.KeyframeAnimationInfo> = new Array<keyframeAnimationModule.KeyframeAnimationInfo>();
let animationInfo: keyframeAnimationModule.KeyframeAnimationInfo = undefined;
if (declarations === null || declarations === undefined) {
return undefined;
}
for (let declaration of declarations) {
if (declaration.property === "animation") {
CssAnimationParser.keyframeAnimationsFromCSSProperty(declaration.value, animations);
}
else {
let propertyHandler = animationProperties[declaration.property];
if (propertyHandler) {
if (animationInfo === undefined) {
animationInfo = new keyframeAnimationModule.KeyframeAnimationInfo();
animations.push(animationInfo);
}
propertyHandler(animationInfo, declaration);
}
}
}
return animations.length === 0 ? undefined : animations;
}
public static keyframesArrayFromCSS(cssKeyframes: Object): Array<keyframeAnimationModule.KeyframeInfo> {
let parsedKeyframes = new Array<keyframeAnimationModule.KeyframeInfo>();
for (let keyframe of (<any>cssKeyframes).keyframes) {
let declarations = CssAnimationParser.parseKeyframeDeclarations(keyframe);
for (let time of keyframe.values) {
if (time === "from") {
time = 0;
}
else if (time === "to") {
time = 1;
}
else {
time = parseFloat(time) / 100;
if (time < 0) {
time = 0;
}
if (time > 100) {
time = 100;
}
}
let current = parsedKeyframes[time];
if (current === undefined) {
current = <keyframeAnimationModule.KeyframeInfo>{};
current.duration = time;
parsedKeyframes[time] = current;
}
for (let declaration of <any>keyframe.declarations) {
if (declaration.property === "animation-timing-function") {
current.curve = converters.animationTimingFunctionConverter(declaration.value);
}
}
current.declarations = declarations;
}
}
let array = new Array();
for (let parsedKeyframe in parsedKeyframes) {
array.push(parsedKeyframes[parsedKeyframe]);
}
array.sort(function (a, b) { return a.duration - b.duration; });
return array;
}
private static keyframeAnimationsFromCSSProperty(value: any, animations: Array<keyframeAnimationModule.KeyframeAnimationInfo>) {
if (types.isString(value)) {
let values = value.split(/[,]+/);
for (let parsedValue of values) {
let animationInfo = new keyframeAnimationModule.KeyframeAnimationInfo();
let arr = (<string>parsedValue).trim().split(/[ ]+/);
if (arr.length > 0) {
animationInfo.name = arr[0];
}
if (arr.length > 1) {
animationInfo.duration = converters.timeConverter(arr[1]);
}
if (arr.length > 2) {
animationInfo.curve = converters.animationTimingFunctionConverter(arr[2]);
}
if (arr.length > 3) {
animationInfo.delay = converters.timeConverter(arr[3]);
}
if (arr.length > 4) {
animationInfo.iterations = parseInt(arr[4]);
}
if (arr.length > 5) {
animationInfo.isReverse = arr[4] === "reverse";
}
if (arr.length > 6) {
animationInfo.isForwards = arr[5] === "forwards";
}
if (arr.length > 7) {
throw new Error("Invalid value for animation: " + value);
}
animations.push(animationInfo);
}
}
}
private static parseKeyframeDeclarations(keyframe: Object): Array<keyframeAnimationModule.KeyframeDeclaration> {
let declarations = {};
let transforms = { scale: undefined, translate: undefined };
for (let declaration of (<any>keyframe).declarations) {
let property = styleProperty.getPropertyByCssName(declaration.property);
if (property) {
let val = declaration.value;
if (property.name === "opacity") {
val = parseFloat(val);
}
else if (property.name === "backgroundColor") {
val = new colorModule.Color(val);
}
declarations[property.name] = val;
}
else {
let pairs = styleProperty.getShorthandPairs(declaration.property, declaration.value);
if (pairs) {
for (let j = 0; j < pairs.length; j++) {
let pair = pairs[j];
if (!this.preprocessAnimationValues(pair, transforms)) {
declarations[pair.property.name] = pair.value;
}
}
}
}
}
if (transforms.scale !== undefined) {
declarations["scale"] = transforms.scale;
}
if (transforms.translate !== undefined) {
declarations["translate"] = transforms.translate;
}
let array = new Array<keyframeAnimationModule.KeyframeDeclaration>();
for (let declaration in declarations) {
let keyframeDeclaration = <keyframeAnimationModule.KeyframeDeclaration>{};
keyframeDeclaration.property = declaration;
keyframeDeclaration.value = declarations[declaration];
array.push(keyframeDeclaration);
}
return array;
}
private static preprocessAnimationValues(pair: styleProperty.KeyValuePair<styleProperty.Property, any>, transforms: TransformInfo) {
if (pair.property.name === "scaleX") {
if (transforms.scale === undefined) {
transforms.scale = { x: 1, y: 1 };
}
transforms.scale.x = pair.value;
return true;
}
if (pair.property.name === "scaleY") {
if (transforms.scale === undefined) {
transforms.scale = { x: 1, y: 1 };
}
transforms.scale.y = pair.value;
return true;
}
if (pair.property.name === "translateX") {
if (transforms.translate === undefined) {
transforms.translate = { x: 0, y: 0 };
}
transforms.translate.x = pair.value;
return true;
}
if (pair.property.name === "translateY") {
if (transforms.translate === undefined) {
transforms.translate = { x: 0, y: 0 };
}
transforms.translate.y = pair.value;
return true;
}
return false;
}
}

View File

@ -2,6 +2,7 @@
import view = require("ui/core/view"); import view = require("ui/core/view");
import cssParser = require("css"); import cssParser = require("css");
import styleProperty = require("ui/styling/style-property"); import styleProperty = require("ui/styling/style-property");
import keyframeAnimation = require("ui/animation/keyframe-animation");
export class CssSelector { export class CssSelector {
constructor(expression: string, declarations: cssParser.Declaration[]); constructor(expression: string, declarations: cssParser.Declaration[]);
@ -13,9 +14,11 @@
specificity: number; specificity: number;
animations: Array<keyframeAnimation.KeyframeAnimationInfo>;
matches(view: view.View): boolean; matches(view: view.View): boolean;
apply(view: view.View); apply(view: view.View, valueSourceModifier: number);
eachSetter(callback: (property: styleProperty.Property, resolvedValue: any) => void); eachSetter(callback: (property: styleProperty.Property, resolvedValue: any) => void);
} }

View File

@ -5,14 +5,18 @@ import * as trace from "trace";
import * as styleProperty from "ui/styling/style-property"; import * as styleProperty from "ui/styling/style-property";
import * as types from "utils/types"; import * as types from "utils/types";
import * as utils from "utils/utils"; import * as utils from "utils/utils";
import keyframeAnimation = require("ui/animation/keyframe-animation");
import cssAnimationParser = require("./css-animation-parser");
import {getSpecialPropertySetter} from "ui/builder/special-properties"; import {getSpecialPropertySetter} from "ui/builder/special-properties";
var ID_SPECIFICITY = 1000000; let ID_SPECIFICITY = 1000000;
var ATTR_SPECIFITY = 10000; let ATTR_SPECIFITY = 10000;
var CLASS_SPECIFICITY = 100; let CLASS_SPECIFICITY = 100;
var TYPE_SPECIFICITY = 1; let TYPE_SPECIFICITY = 1;
export class CssSelector { export class CssSelector {
public animations: Array<keyframeAnimation.KeyframeAnimationInfo>;
private _expression: string; private _expression: string;
private _declarations: cssParser.Declaration[]; private _declarations: cssParser.Declaration[];
private _attrExpression: string; private _attrExpression: string;
@ -22,7 +26,7 @@ export class CssSelector {
let leftSquareBracketIndex = expression.indexOf(LSBRACKET); let leftSquareBracketIndex = expression.indexOf(LSBRACKET);
if (leftSquareBracketIndex > 0) { if (leftSquareBracketIndex > 0) {
// extracts what is inside square brackets ([target = 'test'] will extract "target = 'test'") // extracts what is inside square brackets ([target = 'test'] will extract "target = 'test'")
var paramsRegex = /\[\s*(.*)\s*\]/; let paramsRegex = /\[\s*(.*)\s*\]/;
let attrParams = paramsRegex.exec(expression); let attrParams = paramsRegex.exec(expression);
if (attrParams && attrParams.length > 1) { if (attrParams && attrParams.length > 1) {
this._attrExpression = attrParams[1].trim(); this._attrExpression = attrParams[1].trim();
@ -34,6 +38,7 @@ export class CssSelector {
} }
} }
this._declarations = declarations; this._declarations = declarations;
this.animations = cssAnimationParser.CssAnimationParser.keyframeAnimationsFromCSSDeclarations(declarations);
} }
get expression(): string { get expression(): string {
@ -52,13 +57,18 @@ export class CssSelector {
throw "Specificity property is abstract"; throw "Specificity property is abstract";
} }
protected get valueSourceModifier(): number {
return observable.ValueSource.Css;
}
public matches(view: view.View): boolean { public matches(view: view.View): boolean {
return false; return false;
} }
public apply(view: view.View) { public apply(view: view.View, valueSourceModifier: number) {
let modifier = valueSourceModifier || this.valueSourceModifier;
this.eachSetter((property, value) => { this.eachSetter((property, value) => {
if(types.isString(property)) { if (types.isString(property)) {
let attrHandled = false; let attrHandled = false;
let specialSetter = getSpecialPropertySetter(property); let specialSetter = getSpecialPropertySetter(property);
@ -72,12 +82,20 @@ export class CssSelector {
} }
} else { } else {
try { try {
view.style._setValue(property, value, observable.ValueSource.Css); view.style._setValue(property, value, modifier);
} catch (ex) { } catch (ex) {
trace.write("Error setting property: " + property.name + " view: " + view + " value: " + value + " " + ex, trace.categories.Style, trace.messageType.error); trace.write("Error setting property: " + property.name + " view: " + view + " value: " + value + " " + ex, trace.categories.Style, trace.messageType.error);
} }
} }
}); });
if (this.animations && view.isLoaded) {
for (let animationInfo of this.animations) {
let realAnimation = keyframeAnimation.KeyframeAnimation.keyframeAnimationFromInfo(animationInfo, modifier);
if (realAnimation) {
realAnimation.play(view);
}
}
}
} }
public eachSetter(callback: (property, resolvedValue: any) => void) { public eachSetter(callback: (property, resolvedValue: any) => void) {
@ -93,7 +111,7 @@ export class CssSelector {
callback(property, resolvedValue); callback(property, resolvedValue);
} }
else { else {
var pairs = styleProperty.getShorthandPairs(name, resolvedValue); let pairs = styleProperty.getShorthandPairs(name, resolvedValue);
if (pairs) { if (pairs) {
for (let j = 0; j < pairs.length; j++) { for (let j = 0; j < pairs.length; j++) {
let pair = pairs[j]; let pair = pairs[j];
@ -178,7 +196,7 @@ class CssClassSelector extends CssSelector {
return CLASS_SPECIFICITY; return CLASS_SPECIFICITY;
} }
public matches(view: view.View): boolean { public matches(view: view.View): boolean {
var expectedClass = this.expression; let expectedClass = this.expression;
let result = view._cssClasses.some((cssClass, i, arr) => { return cssClass === expectedClass }); let result = view._cssClasses.some((cssClass, i, arr) => { return cssClass === expectedClass });
if (result && this.attrExpression) { if (result && this.attrExpression) {
return matchesAttr(this.attrExpression, view); return matchesAttr(this.attrExpression, view);
@ -357,10 +375,14 @@ export class CssVisualStateSelector extends CssSelector {
return this._state; return this._state;
} }
protected get valueSourceModifier(): number {
return observable.ValueSource.VisualState;
}
constructor(expression: string, declarations: cssParser.Declaration[]) { constructor(expression: string, declarations: cssParser.Declaration[]) {
super(expression, declarations); super(expression, declarations);
var args = expression.split(COLON); let args = expression.split(COLON);
this._key = args[0]; this._key = args[0];
this._state = args[1]; this._state = args[1];
@ -381,13 +403,13 @@ export class CssVisualStateSelector extends CssSelector {
} }
public matches(view: view.View): boolean { public matches(view: view.View): boolean {
var matches = true; let matches = true;
if (this._isById) { if (this._isById) {
matches = this._match === view.id; matches = this._match === view.id;
} }
if (this._isByClass) { if (this._isByClass) {
var expectedClass = this._match; let expectedClass = this._match;
matches = view._cssClasses.some((cssClass, i, arr) => { return cssClass === expectedClass }); matches = view._cssClasses.some((cssClass, i, arr) => { return cssClass === expectedClass });
} }
@ -407,18 +429,18 @@ export class CssVisualStateSelector extends CssSelector {
} }
} }
var HASH = "#"; let HASH = "#";
var DOT = "."; let DOT = ".";
var COLON = ":"; let COLON = ":";
var SPACE = " "; let SPACE = " ";
var GTHAN = ">"; let GTHAN = ">";
var LSBRACKET = "["; let LSBRACKET = "[";
var RSBRACKET = "]"; let RSBRACKET = "]";
var EQUAL = "="; let EQUAL = "=";
export function createSelector(expression: string, declarations: cssParser.Declaration[]): CssSelector { export function createSelector(expression: string, declarations: cssParser.Declaration[]): CssSelector {
let goodExpr = expression.replace(/>/g, " > ").replace(/\s\s+/g, " "); let goodExpr = expression.replace(/>/g, " > ").replace(/\s\s+/g, " ");
var spaceIndex = goodExpr.indexOf(SPACE); let spaceIndex = goodExpr.indexOf(SPACE);
if (spaceIndex >= 0) { if (spaceIndex >= 0) {
return new CssCompositeSelector(goodExpr, declarations); return new CssCompositeSelector(goodExpr, declarations);
} }
@ -462,6 +484,6 @@ class InlineStyleSelector extends CssSelector {
} }
export function applyInlineSyle(view: view.View, declarations: cssParser.Declaration[]) { export function applyInlineSyle(view: view.View, declarations: cssParser.Declaration[]) {
var localStyleSelector = new InlineStyleSelector(declarations); let localStyleSelector = new InlineStyleSelector(declarations);
localStyleSelector.apply(view); localStyleSelector.apply(view);
} }

View File

@ -3,6 +3,7 @@ declare module "ui/styling/style-scope" {
import view = require("ui/core/view"); import view = require("ui/core/view");
import cssSelector = require("ui/styling/css-selector"); import cssSelector = require("ui/styling/css-selector");
import cssParser = require("css"); import cssParser = require("css");
import keyframeAnimation = require("ui/animation/keyframe-animation");
export class StyleScope { export class StyleScope {
public css: string; public css: string;
@ -14,6 +15,9 @@ declare module "ui/styling/style-scope" {
public applySelectors(view: view.View): void public applySelectors(view: view.View): void
public getVisualStates(view: view.View): Object; public getVisualStates(view: view.View): Object;
public removeSelectors(selectorExpression: string);
public getKeyframeAnimationWithName(animationName: string): keyframeAnimation.KeyframeAnimationInfo;
} }
export function applyInlineSyle(view: view.View, style: string): void; export function applyInlineSyle(view: view.View, style: string): void;

View File

@ -7,6 +7,9 @@ import * as typesModule from "utils/types";
import * as utilsModule from "utils/utils"; import * as utilsModule from "utils/utils";
import * as fileSystemModule from "file-system"; import * as fileSystemModule from "file-system";
import * as visualStateModule from "./visual-state"; import * as visualStateModule from "./visual-state";
import keyframeAnimation = require("ui/animation/keyframe-animation");
import cssAnimationParser = require("./css-animation-parser");
import observable = require("ui/core/dependency-observable");
var types: typeof typesModule; var types: typeof typesModule;
function ensureTypes() { function ensureTypes() {
@ -50,10 +53,12 @@ export class StyleScope {
private _localCssSelectorVersion: number = 0; private _localCssSelectorVersion: number = 0;
private _localCssSelectorsAppliedVersion: number = 0; private _localCssSelectorsAppliedVersion: number = 0;
private _applicationCssSelectorsAppliedVersion: number = 0; private _applicationCssSelectorsAppliedVersion: number = 0;
private _keyframes = {};
get css(): string { get css(): string {
return this._css; return this._css;
} }
set css(value: string) { set css(value: string) {
this._cssFileName = undefined; this._cssFileName = undefined;
this.setCss(value); this.setCss(value);
@ -71,7 +76,7 @@ export class StyleScope {
this._reset(); this._reset();
const parsedSelectors = StyleScope.createSelectorsFromCss(cssString, cssFileName); const parsedSelectors = StyleScope.createSelectorsFromCss(cssString, cssFileName, this._keyframes);
if (append) { if (append) {
this._localCssSelectors.push.apply(this._localCssSelectors, parsedSelectors); this._localCssSelectors.push.apply(this._localCssSelectors, parsedSelectors);
} else { } else {
@ -82,35 +87,51 @@ export class StyleScope {
this.ensureSelectors(); this.ensureSelectors();
} }
public static createSelectorsFromCss(css: string, cssFileName: string): cssSelector.CssSelector[] { public removeSelectors(selectorExpression: string) {
try { for (let i = this._mergedCssSelectors.length - 1; i >= 0; i--) {
var pageCssSyntaxTree = css ? cssParser.parse(css, { source: cssFileName }) : null; let selector = this._mergedCssSelectors[i];
if (selector.expression === selectorExpression) {
var pageCssSelectors = new Array<cssSelector.CssSelector>(); this._mergedCssSelectors.splice(i, 1);
}
if (pageCssSyntaxTree) { }
pageCssSelectors = StyleScope._joinCssSelectorsArrays([pageCssSelectors, StyleScope.createSelectorsFromImports(pageCssSyntaxTree)]);
pageCssSelectors = StyleScope._joinCssSelectorsArrays([pageCssSelectors, StyleScope.createSelectorsFromSyntaxTree(pageCssSyntaxTree)]);
} }
public getKeyframeAnimationWithName(animationName: string): keyframeAnimation.KeyframeAnimationInfo {
let keyframes = this._keyframes[animationName];
if (keyframes !== undefined) {
let animation = new keyframeAnimation.KeyframeAnimationInfo();
animation.keyframes = cssAnimationParser.CssAnimationParser.keyframesArrayFromCSS(keyframes);
return animation;
}
return undefined;
}
public static createSelectorsFromCss(css: string, cssFileName: string, keyframes: Object): cssSelector.CssSelector[] {
try {
let pageCssSyntaxTree = css ? cssParser.parse(css, { source: cssFileName }) : null;
let pageCssSelectors = new Array<cssSelector.CssSelector>();
if (pageCssSyntaxTree) {
pageCssSelectors = StyleScope._joinCssSelectorsArrays([pageCssSelectors, StyleScope.createSelectorsFromImports(pageCssSyntaxTree, keyframes)]);
pageCssSelectors = StyleScope._joinCssSelectorsArrays([pageCssSelectors, StyleScope.createSelectorsFromSyntaxTree(pageCssSyntaxTree, keyframes)]);
}
return pageCssSelectors; return pageCssSelectors;
} catch (e) { } catch (e) {
trace.write("Css styling failed: " + e, trace.categories.Error, trace.messageType.error); trace.write("Css styling failed: " + e, trace.categories.Error, trace.messageType.error);
} }
} }
public static createSelectorsFromImports(tree: cssParser.SyntaxTree): cssSelector.CssSelector[] { public static createSelectorsFromImports(tree: cssParser.SyntaxTree, keyframes: Object): cssSelector.CssSelector[] {
var selectors = new Array<cssSelector.CssSelector>(); let selectors = new Array<cssSelector.CssSelector>();
ensureTypes(); ensureTypes();
if (!types.isNullOrUndefined(tree)) { if (!types.isNullOrUndefined(tree)) {
var imports = tree["stylesheet"]["rules"].filter(r=> r.type === "import"); let imports = tree["stylesheet"]["rules"].filter(r=> r.type === "import");
for (var i = 0; i < imports.length; i++) { for (let i = 0; i < imports.length; i++) {
var importItem = imports[i]["import"]; let importItem = imports[i]["import"];
var match = importItem && (<string>importItem).match(pattern); let match = importItem && (<string>importItem).match(pattern);
var url = match && match[2]; let url = match && match[2];
if (!types.isNullOrUndefined(url)) { if (!types.isNullOrUndefined(url)) {
ensureUtils(); ensureUtils();
@ -118,16 +139,16 @@ export class StyleScope {
if (utils.isFileOrResourcePath(url)) { if (utils.isFileOrResourcePath(url)) {
ensureFS(); ensureFS();
var fileName = types.isString(url) ? url.trim() : ""; let fileName = types.isString(url) ? url.trim() : "";
if (fileName.indexOf("~/") === 0) { if (fileName.indexOf("~/") === 0) {
fileName = fs.path.join(fs.knownFolders.currentApp().path, fileName.replace("~/", "")); fileName = fs.path.join(fs.knownFolders.currentApp().path, fileName.replace("~/", ""));
} }
if (fs.File.exists(fileName)) { if (fs.File.exists(fileName)) {
var file = fs.File.fromPath(fileName); let file = fs.File.fromPath(fileName);
var text = file.readTextSync(); let text = file.readTextSync();
if (text) { if (text) {
selectors = StyleScope._joinCssSelectorsArrays([selectors, StyleScope.createSelectorsFromCss(text, fileName)]); selectors = StyleScope._joinCssSelectorsArrays([selectors, StyleScope.createSelectorsFromCss(text, fileName, keyframes)]);
} }
} }
} }
@ -152,6 +173,7 @@ export class StyleScope {
if (toMerge.length > 0) { if (toMerge.length > 0) {
this._mergedCssSelectors = StyleScope._joinCssSelectorsArrays(toMerge); this._mergedCssSelectors = StyleScope._joinCssSelectorsArrays(toMerge);
this._applyKeyframesOnSelectors();
return true; return true;
} else { } else {
return false; return false;
@ -159,8 +181,8 @@ export class StyleScope {
} }
private static _joinCssSelectorsArrays(arrays: Array<Array<cssSelector.CssSelector>>): Array<cssSelector.CssSelector> { private static _joinCssSelectorsArrays(arrays: Array<Array<cssSelector.CssSelector>>): Array<cssSelector.CssSelector> {
var mergedResult = []; let mergedResult = [];
var i; let i;
for (i = 0; i < arrays.length; i++) { for (i = 0; i < arrays.length; i++) {
if (arrays[i]) { if (arrays[i]) {
mergedResult.push.apply(mergedResult, arrays[i]); mergedResult.push.apply(mergedResult, arrays[i]);
@ -175,8 +197,7 @@ export class StyleScope {
this.ensureSelectors(); this.ensureSelectors();
view.style._beginUpdate(); view.style._beginUpdate();
let i,
var i,
selector: cssSelector.CssSelector, selector: cssSelector.CssSelector,
matchedStateSelectors = new Array<cssSelector.CssVisualStateSelector>() matchedStateSelectors = new Array<cssSelector.CssVisualStateSelector>()
@ -187,14 +208,14 @@ export class StyleScope {
if (selector instanceof cssSelector.CssVisualStateSelector) { if (selector instanceof cssSelector.CssVisualStateSelector) {
matchedStateSelectors.push(<cssSelector.CssVisualStateSelector>selector); matchedStateSelectors.push(<cssSelector.CssVisualStateSelector>selector);
} else { } else {
selector.apply(view); selector.apply(view, observable.ValueSource.Css);
} }
} }
} }
if (matchedStateSelectors.length > 0) { if (matchedStateSelectors.length > 0) {
// Create a key for all matched selectors for this element // Create a key for all matched selectors for this element
var key: string = ""; let key: string = "";
matchedStateSelectors.forEach((s) => key += s.key + "|"); matchedStateSelectors.forEach((s) => key += s.key + "|");
// Associate the view to the created key // Associate the view to the created key
@ -210,7 +231,7 @@ export class StyleScope {
} }
public getVisualStates(view: view.View): Object { public getVisualStates(view: view.View): Object {
var key = this._viewIdToKey[view._domId]; let key = this._viewIdToKey[view._domId];
if (key === undefined) { if (key === undefined) {
return undefined; return undefined;
} }
@ -219,7 +240,7 @@ export class StyleScope {
} }
private _createVisualsStatesForSelectors(key: string, matchedStateSelectors: Array<cssSelector.CssVisualStateSelector>) { private _createVisualsStatesForSelectors(key: string, matchedStateSelectors: Array<cssSelector.CssVisualStateSelector>) {
var i, let i,
allStates = {}, allStates = {},
stateSelector: cssSelector.CssVisualStateSelector; stateSelector: cssSelector.CssVisualStateSelector;
@ -229,25 +250,30 @@ export class StyleScope {
for (i = 0; i < matchedStateSelectors.length; i++) { for (i = 0; i < matchedStateSelectors.length; i++) {
stateSelector = matchedStateSelectors[i]; stateSelector = matchedStateSelectors[i];
var visualState = allStates[stateSelector.state]; let visualState = allStates[stateSelector.state];
if (!visualState) { if (!visualState) {
visualState = new vs.VisualState(); visualState = new vs.VisualState();
allStates[stateSelector.state] = visualState; allStates[stateSelector.state] = visualState;
} }
// add all stateSelectors instead of adding setters
if (stateSelector.animations && stateSelector.animations.length > 0) {
visualState.animatedSelectors.push(stateSelector);
}
else {
stateSelector.eachSetter((property, value) => { stateSelector.eachSetter((property, value) => {
visualState.setters[property.name] = value; visualState.setters[property.name] = value;
}); });
} }
} }
}
private static createSelectorsFromSyntaxTree(ast: cssParser.SyntaxTree): Array<cssSelector.CssSelector> { private static createSelectorsFromSyntaxTree(ast: cssParser.SyntaxTree, keyframes: Object): Array<cssSelector.CssSelector> {
var result: Array<cssSelector.CssSelector> = []; let result: Array<cssSelector.CssSelector> = [];
let rules = ast.stylesheet.rules;
var rules = ast.stylesheet.rules; let rule: cssParser.Rule;
var rule: cssParser.Rule; let i;
var i; let j;
var j;
// Create selectors form AST // Create selectors form AST
for (i = 0; i < rules.length; i++) { for (i = 0; i < rules.length; i++) {
@ -256,10 +282,10 @@ export class StyleScope {
if (rule.type === "rule") { if (rule.type === "rule") {
// Filter comment nodes. // Filter comment nodes.
var filteredDeclarations = []; let filteredDeclarations = [];
if (rule.declarations) { if (rule.declarations) {
for (j = 0; j < rule.declarations.length; j++) { for (j = 0; j < rule.declarations.length; j++) {
var declaration = rule.declarations[j]; let declaration = rule.declarations[j];
if (declaration.type === "declaration") { if (declaration.type === "declaration") {
filteredDeclarations.push({ filteredDeclarations.push({
property: declaration.property.toLowerCase(), property: declaration.property.toLowerCase(),
@ -268,11 +294,12 @@ export class StyleScope {
} }
} }
} }
for (j = 0; j < rule.selectors.length; j++) { for (j = 0; j < rule.selectors.length; j++) {
result.push(cssSelector.createSelector(rule.selectors[j], filteredDeclarations)); result.push(cssSelector.createSelector(rule.selectors[j], filteredDeclarations));
} }
//} }
else if (rule.type === "keyframes") {
keyframes[(<any>rule).name] = rule;
} }
} }
@ -283,12 +310,26 @@ export class StyleScope {
this._statesByKey = {}; this._statesByKey = {};
this._viewIdToKey = {}; this._viewIdToKey = {};
} }
private _applyKeyframesOnSelectors() {
for (let i = this._mergedCssSelectors.length - 1; i >= 0; i--) {
let selector = this._mergedCssSelectors[i];
if (selector.animations !== undefined) {
for (let animation of selector.animations) {
let keyframe = this._keyframes[animation.name];
if (keyframe !== undefined) {
animation.keyframes = cssAnimationParser.CssAnimationParser.keyframesArrayFromCSS(keyframe);
}
}
}
}
}
} }
export function applyInlineSyle(view: view.View, style: string) { export function applyInlineSyle(view: view.View, style: string) {
try { try {
var syntaxTree = cssParser.parse("local { " + style + " }", undefined); let syntaxTree = cssParser.parse("local { " + style + " }", undefined);
var filteredDeclarations = syntaxTree.stylesheet.rules[0].declarations.filter((val, i, arr) => { return val.type === "declaration" }); let filteredDeclarations = syntaxTree.stylesheet.rules[0].declarations.filter((val, i, arr) => { return val.type === "declaration" });
cssSelector.applyInlineSyle(view, filteredDeclarations); cssSelector.applyInlineSyle(view, filteredDeclarations);
} catch (ex) { } catch (ex) {
trace.write("Applying local style failed: " + ex, trace.categories.Error, trace.messageType.error); trace.write("Applying local style failed: " + ex, trace.categories.Error, trace.messageType.error);

11
ui/styling/style.d.ts vendored
View File

@ -35,6 +35,11 @@ declare module "ui/styling/style" {
} }
export class Style extends DependencyObservable implements styling.Style { export class Style extends DependencyObservable implements styling.Style {
public rotate: number;
public translateX: number;
public translateY: number;
public scaleX: number;
public scaleY: number;
public color: Color; public color: Color;
public backgroundColor: Color; public backgroundColor: Color;
public backgroundImage: string; public backgroundImage: string;
@ -89,6 +94,12 @@ declare module "ui/styling/style" {
export function registerNoStylingClass(className); export function registerNoStylingClass(className);
export function getHandler(property: Property, view: View): StylePropertyChangedHandler; export function getHandler(property: Property, view: View): StylePropertyChangedHandler;
// Property registration // Property registration
export var rotateProperty: styleProperty.Property;
export var translateXProperty: styleProperty.Property;
export var translateYProperty: styleProperty.Property;
export var scaleXProperty: styleProperty.Property;
export var scaleYProperty: styleProperty.Property;
export var colorProperty: styleProperty.Property; export var colorProperty: styleProperty.Property;
export var backgroundImageProperty: styleProperty.Property; export var backgroundImageProperty: styleProperty.Property;
export var backgroundColorProperty: styleProperty.Property; export var backgroundColorProperty: styleProperty.Property;

View File

@ -470,6 +470,46 @@ export class Style extends DependencyObservable implements styling.Style {
private _updateCounter = 0; private _updateCounter = 0;
private _nativeSetters = new Map<Property, any>(); private _nativeSetters = new Map<Property, any>();
get rotate(): number {
return this._getValue(rotateProperty);
}
set rotate(value: number) {
this._setValue(rotateProperty, value);
}
get scaleX(): number {
return this._getValue(scaleXProperty);
}
set scaleX(value: number) {
this._setValue(scaleXProperty, value);
}
get scaleY(): number {
return this._getValue(scaleYProperty);
}
set scaleY(value: number) {
this._setValue(scaleYProperty, value);
}
get translateX(): number {
return this._getValue(translateXProperty);
}
set translateX(value: number) {
this._setValue(translateXProperty, value);
}
get translateY(): number {
return this._getValue(translateYProperty);
}
set translateY(value: number) {
this._setValue(translateYProperty, value);
}
get color(): Color { get color(): Color {
return this._getValue(colorProperty); return this._getValue(colorProperty);
} }
@ -934,6 +974,22 @@ export function getHandler(property: Property, view: View): definition.StyleProp
} }
// Property registration // Property registration
export var rotateProperty = new styleProperty.Property("rotate", "rotate",
new PropertyMetadata(undefined, PropertyMetadataSettings.None, null));
export var scaleXProperty = new styleProperty.Property("scaleX", "scaleX",
new PropertyMetadata(undefined, PropertyMetadataSettings.None, null));
export var scaleYProperty = new styleProperty.Property("scaleY", "scaleY",
new PropertyMetadata(undefined, PropertyMetadataSettings.None, null));
export var translateXProperty = new styleProperty.Property("translateX", "translateX",
new PropertyMetadata(undefined, PropertyMetadataSettings.None, null));
export var translateYProperty = new styleProperty.Property("translateY", "translateY",
new PropertyMetadata(undefined, PropertyMetadataSettings.None, null));
export var colorProperty = new styleProperty.Property("color", "color", export var colorProperty = new styleProperty.Property("color", "color",
new PropertyMetadata(undefined, PropertyMetadataSettings.Inheritable, undefined, Color.isValid, Color.equals), new PropertyMetadata(undefined, PropertyMetadataSettings.Inheritable, undefined, Color.isValid, Color.equals),
converters.colorConverter); converters.colorConverter);
@ -1132,10 +1188,65 @@ function onFontChanged(value: any): Array<styleProperty.KeyValuePair<styleProper
return array; return array;
} }
function onTransformChanged(value: any): Array<styleProperty.KeyValuePair<styleProperty.Property, any>> {
var newTransform = converters.transformConverter(value);
var array = new Array<styleProperty.KeyValuePair<styleProperty.Property, any>>();
var values = undefined;
for (var transform in newTransform) {
switch (transform) {
case "scaleX":
array.push({ property: scaleXProperty, value: parseFloat(newTransform[transform]) });
break;
case "scaleY":
array.push({ property: scaleYProperty, value: parseFloat(newTransform[transform]) });
break;
case "scale":
case "scale3d":
values = newTransform[transform].split(",");
if (values.length === 2 || values.length === 3) {
array.push({ property: scaleXProperty, value: parseFloat(values[0]) });
array.push({ property: scaleYProperty, value: parseFloat(values[1]) });
}
break;
case "translateX":
array.push({ property: translateXProperty, value: parseFloat(newTransform[transform]) });
break;
case "translateY":
array.push({ property: translateYProperty, value: parseFloat(newTransform[transform]) });
break;
case "translate":
case "translate3d":
values = newTransform[transform].split(",");
if (values.length === 2 || values.length === 3) {
array.push({ property: translateXProperty, value: parseFloat(values[0]) });
array.push({ property: translateYProperty, value: parseFloat(values[1]) });
}
break;
case "rotate":
let text = newTransform[transform];
let val = parseFloat(text);
if (text.slice(-3) === "rad") {
val = val * (180.0 / Math.PI);
}
array.push({ property: rotateProperty, value: val });
break;
case "none":
array.push({ property: scaleXProperty, value: 1 });
array.push({ property: scaleYProperty, value: 1 });
array.push({ property: translateXProperty, value: 0 });
array.push({ property: translateYProperty, value: 0 });
array.push({ property: rotateProperty, value: 0 });
break;
}
}
return array;
}
// register default shorthand callbacks. // register default shorthand callbacks.
styleProperty.registerShorthandCallback("font", onFontChanged); styleProperty.registerShorthandCallback("font", onFontChanged);
styleProperty.registerShorthandCallback("margin", onMarginChanged); styleProperty.registerShorthandCallback("margin", onMarginChanged);
styleProperty.registerShorthandCallback("padding", onPaddingChanged); styleProperty.registerShorthandCallback("padding", onPaddingChanged);
styleProperty.registerShorthandCallback("transform", onTransformChanged);
var _defaultNativeValuesCache = {}; var _defaultNativeValuesCache = {};

View File

@ -1,31 +1,38 @@
import viewModule = require("ui/core/view"); import viewModule = require("ui/core/view");
import observable = require("ui/core/dependency-observable"); import observable = require("ui/core/dependency-observable");
import styleProperty = require("ui/styling/style-property"); import styleProperty = require("ui/styling/style-property");
import cssSelector = require("ui/styling/css-selector");
import * as visualStateConstants from "ui/styling/visual-state-constants"; import * as visualStateConstants from "ui/styling/visual-state-constants";
export class VisualState { export class VisualState {
private _setters: {}; private _setters: {}; // use css selector instead
private _animatedSelectors: cssSelector.CssVisualStateSelector[];
constructor() { constructor() {
this._setters = {}; this._setters = {};
this._animatedSelectors = [];
} }
get setters(): {} { get setters(): {} {
return this._setters; return this._setters;
} }
get animatedSelectors(): cssSelector.CssVisualStateSelector[] {
return this._animatedSelectors;
}
} }
/** /**
* *
*/ */
export function goToState(view: viewModule.View, state: string): string { export function goToState(view: viewModule.View, state: string): string {
var root = <any>view.page; let root = <any>view.page;
if (!root) { if (!root) {
return undefined; return undefined;
} }
//TODO: this of optimization //TODO: this of optimization
var allStates = root._getStyleScope().getVisualStates(view); let allStates = root._getStyleScope().getVisualStates(view);
if (!allStates) { if (!allStates) {
return undefined; return undefined;
} }
@ -44,8 +51,8 @@ export function goToState(view: viewModule.View, state: string): string {
// Step 2 // Step 2
if (state !== view.visualState) { if (state !== view.visualState) {
var newState: VisualState = allStates[state]; let newState: VisualState = allStates[state];
var oldState: VisualState = allStates[view.visualState]; let oldState: VisualState = allStates[view.visualState];
// Step 3 // Step 3
resetProperties(view, oldState, newState); resetProperties(view, oldState, newState);
@ -62,9 +69,9 @@ function resetProperties(view: viewModule.View, oldState: VisualState, newState:
return; return;
} }
var property: styleProperty.Property; let property: styleProperty.Property;
for (var name in oldState.setters) { for (let name in oldState.setters) {
if (newState && (name in newState.setters)) { if (newState && (name in newState.setters)) {
// Property will be altered by the new state, no need to reset it. // Property will be altered by the new state, no need to reset it.
continue; continue;
@ -75,6 +82,19 @@ function resetProperties(view: viewModule.View, oldState: VisualState, newState:
view.style._resetValue(property, observable.ValueSource.VisualState); view.style._resetValue(property, observable.ValueSource.VisualState);
} }
} }
for (let selector of oldState.animatedSelectors) {
for (let animationInfo of selector.animations) {
for (let keyframe of animationInfo.keyframes) {
for (let declaration of keyframe.declarations) {
property = styleProperty.getPropertyByName(declaration.property);
if (property) {
view.style._resetValue(property, observable.ValueSource.VisualState);
}
}
}
}
}
} }
function applyProperties(view: viewModule.View, state: VisualState) { function applyProperties(view: viewModule.View, state: VisualState) {
@ -82,12 +102,16 @@ function applyProperties(view: viewModule.View, state: VisualState) {
return; return;
} }
var property: styleProperty.Property; let property: styleProperty.Property;
for (var name in state.setters) { for (let name in state.setters) {
property = styleProperty.getPropertyByName(name); property = styleProperty.getPropertyByName(name);
if (property) { if (property) {
view.style._setValue(property, state.setters[name], observable.ValueSource.VisualState); view.style._setValue(property, state.setters[name], observable.ValueSource.VisualState);
} }
} }
for (let selector of state.animatedSelectors) {
selector.apply(view, observable.ValueSource.VisualState);
}
} }