Merge pull request #3596 from NativeScript/cankov/modules30-qsf

Animation properties and some backward compatability with the QSF
This commit is contained in:
Panayot Cankov
2017-02-08 10:54:03 +02:00
committed by GitHub
23 changed files with 446 additions and 172 deletions

View File

@ -0,0 +1,58 @@
@keyframes intro {
0% {
opacity: 0;
transform: translate(-100, 0);
}
100% {
opacity: 1;
transform: translate(0, 0);
}
}
@keyframes outro {
0% {
opacity: 1;
transform: translate(0, 0);
}
100% {
opacity: 0;
transform: translate(-100, 0);
}
}
.intro {
animation-name: intro;
animation-duration: 3.0;
animation-fill-mode: forwards;
animation-iteration-count: 1;
animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
}
.outro {
animation-name: outro;
animation-duration: 3.0;
animation-fill-mode: forwards;
animation-iteration-count: 1;
animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
}
.gone {
opacity: 0;
transform: translate(-100, 0);
}
@keyframes play {
0% { transform: translate(0, 0); }
33% { transform: translate(100, 0); }
66% { transform: translate(100, 100); }
100% { transform: translate(0, 100); }
}
.idle {
transform: translate(0, 0);
}
.play {
animation-name: play;
animation-duration: 3.0;
animation-fill-mode: forwards;
animation-iteration-count: 1;
animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
}

View File

@ -0,0 +1,13 @@
import { View, EventData } from "ui/core/view";
export function setClass(args: EventData) {
const btn = (<View & { tag: any }>args.object);
const img = btn.page.getViewById<View>("img");
img.className = btn.tag;
}
export function setImg2Class(args: EventData) {
const btn = (<View & { tag: any }>args.object);
const img2 = btn.page.getViewById<View>("img2");
img2.className = btn.tag;
}

View File

@ -0,0 +1,18 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded" id="mainPage">
<StackLayout orientation="vertical">
<Button text="class: gone" tag="gone" tap="setClass" />
<Button text="class: intro" tag="intro" tap="setClass" />
<Button text="class: outro" tag="outro" tap="setClass" />
<Button text="clear class" tag="none" tap="setClass" />
<GridLayout horizontalAlignment="center" verticalAlignment="middle" backgroundColor="blue" width="150" height="50">
<GridLayout id="img" class="gone" backgroundColor="green" width="50" height="50" horizontalAlignment="right" />
</GridLayout>
<Button text="class: idle" tag="idle" tap="setImg2Class" />
<Button text="class: play" tag="play" tap="setImg2Class" />
<GridLayout horizontalAlignment="center" verticalAlignment="middle" backgroundColor="gray" width="150" height="150">
<GridLayout id="img2" class="idle" backgroundColor="green" width="50" height="50" horizontalAlignment="left" verticalAlignment="top" />
</GridLayout>
</StackLayout>
</Page>

View File

@ -4,12 +4,47 @@ import * as buttonModule from "ui/button";
import * as abs from "ui/layouts/absolute-layout"; import * as abs from "ui/layouts/absolute-layout";
import * as animationModule from "ui/animation"; import * as animationModule from "ui/animation";
import * as colorModule from "color"; import * as colorModule from "color";
import * as model from "./model";
import * as enums from "ui/enums"; import * as enums from "ui/enums";
import * as frame from "ui/frame"; import * as frame from "ui/frame";
import * as trace from "trace"; import * as trace from "trace";
var vm = new model.ViewModel(); export class ViewModel extends observable.Observable {
constructor() {
super();
this._duration = 3000;
this._iterations = 1;
}
private _playSequentially: boolean;
get playSequentially(): boolean {
return this._playSequentially;
}
set playSequentially(value: boolean) {
this._playSequentially = value;
this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "playSequentially", value: value });
}
private _duration: number;
get duration(): number {
return this._duration;
}
set duration(value: number) {
this._duration = value;
this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "duration", value: value });
}
private _iterations: number;
get iterations(): number {
return this._iterations;
}
set iterations(value: number) {
this._iterations = value;
this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "iterations", value: value });
}
}
var vm = new ViewModel();
var page: pages.Page; var page: pages.Page;
var panel: abs.AbsoluteLayout; var panel: abs.AbsoluteLayout;
@ -27,8 +62,8 @@ export function pageLoaded(args: observable.EventData) {
button2 = page.getViewById<buttonModule.Button>("button2"); button2 = page.getViewById<buttonModule.Button>("button2");
button3 = page.getViewById<buttonModule.Button>("button3"); button3 = page.getViewById<buttonModule.Button>("button3");
trace.enable(); // trace.enable();
trace.addCategories(trace.categories.concat(trace.categories.Animation)); // trace.addCategories(trace.categories.concat(trace.categories.Animation));
} }
export function onSlideOut(args: observable.EventData) { export function onSlideOut(args: observable.EventData) {

View File

@ -18,7 +18,6 @@
<Button text="In" tap="onSlideIn" width="40" marginLeft="5" marginRight="5" /> <Button text="In" tap="onSlideIn" width="40" marginLeft="5" marginRight="5" />
<Button text="Single" tap="onSingle" width="70" marginLeft="5" marginRight="5" /> <Button text="Single" tap="onSingle" width="70" marginLeft="5" marginRight="5" />
<Button text="Cancel" tap="onCancel" width="70" marginLeft="5" marginRight="5" /> <Button text="Cancel" tap="onCancel" width="70" marginLeft="5" marginRight="5" />
<Button text="Opacity" tap="onOpacity" width="70" marginLeft="5" marginRight="5" />
</StackLayout> </StackLayout>
<StackLayout orientation="horizontal" marginTop="5" marginBottom="5" horizontalAlignment="center" paddingLeft="5" paddingRight="5">> <StackLayout orientation="horizontal" marginTop="5" marginBottom="5" horizontalAlignment="center" paddingLeft="5" paddingRight="5">>

View File

@ -1,37 +0,0 @@
import observable = require("data/observable");
export class ViewModel extends observable.Observable {
constructor() {
super();
this._duration = 3000;
this._iterations = 1;
}
private _playSequentially: boolean;
get playSequentially(): boolean {
return this._playSequentially;
}
set playSequentially(value: boolean) {
this._playSequentially = value;
this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "playSequentially", value: value });
}
private _duration: number;
get duration(): number {
return this._duration;
}
set duration(value: number) {
this._duration = value;
this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "duration", value: value });
}
private _iterations: number;
get iterations(): number {
return this._iterations;
}
set iterations(value: number) {
this._iterations = value;
this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "iterations", value: value });
}
}

View File

@ -43,8 +43,9 @@
<Label class="title" text="Animations" /> <Label class="title" text="Animations" />
<StackLayout> <StackLayout>
<Button tag="animations/configurable" text="configurable" tap="itemTap" /> <Button tag="animations/js-configurable" text="js-configurable" tap="itemTap" />
<Button tag="animations/opacity" text="opacity" tap="itemTap" /> <Button tag="animations/js-opacity" text="js-opacity" tap="itemTap" />
<Button tag="animations/css-keyframes" text="css-keyframes" tap="itemTap" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>

View File

@ -3,7 +3,7 @@ import {
AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise, AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise,
opacityProperty, backgroundColorProperty, rotateProperty, opacityProperty, backgroundColorProperty, rotateProperty,
translateXProperty, translateYProperty, translateXProperty, translateYProperty,
scaleXProperty, scaleYProperty, Color, traceWrite, traceEnabled, traceCategories scaleXProperty, scaleYProperty, Color, traceWrite, traceEnabled, traceCategories, unsetValue
} from "./animation-common"; } from "./animation-common";
import { CacheLayerType, layout } from "utils/utils"; import { CacheLayerType, layout } from "utils/utils";
@ -12,7 +12,7 @@ import lazy from "utils/lazy";
export * from "./animation-common"; export * from "./animation-common";
interface AnimationDefinitionInternal extends AnimationDefinition { interface AnimationDefinitionInternal extends AnimationDefinition {
valueSource?: number; valueSource?: "animation" | "keyframe";
} }
let argbEvaluator: android.animation.ArgbEvaluator; let argbEvaluator: android.animation.ArgbEvaluator;
@ -88,7 +88,7 @@ export class Animation extends AnimationBase {
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; private _valueSource: "animation" | "keyframe";
constructor(animationDefinitions: Array<AnimationDefinitionInternal>, playSequentially?: boolean) { constructor(animationDefinitions: Array<AnimationDefinitionInternal>, playSequentially?: boolean) {
super(animationDefinitions, playSequentially); super(animationDefinitions, playSequentially);
@ -243,7 +243,7 @@ export class Animation extends AnimationBase {
} }
} }
// let valueSource = this._valueSource !== undefined ? this._valueSource : dependencyObservable.ValueSource.Local; let setLocal = this._valueSource === "animation";
switch (propertyAnimation.property) { switch (propertyAnimation.property) {
case Properties.opacity: case Properties.opacity:
@ -251,10 +251,17 @@ export class Animation extends AnimationBase {
nativeArray = Array.create("float", 1); nativeArray = Array.create("float", 1);
nativeArray[0] = propertyAnimation.value; nativeArray[0] = propertyAnimation.value;
propertyUpdateCallbacks.push(checkAnimation(() => { propertyUpdateCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[opacityProperty.cssName] = propertyAnimation.value; propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value;
})); }));
propertyResetCallbacks.push(checkAnimation(() => { propertyResetCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[opacityProperty.cssName] = originalValue1; if (setLocal) {
propertyAnimation.target.style[opacityProperty.name] = originalValue1;
} else {
propertyAnimation.target.style[opacityProperty.keyframe] = originalValue1;
}
if (propertyAnimation.target.nativeView) {
propertyAnimation.target[opacityProperty.native] = propertyAnimation.target.style.opacity;
}
})); }));
animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "alpha", nativeArray)); animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "alpha", nativeArray));
break; break;
@ -274,10 +281,17 @@ export class Animation extends AnimationBase {
})); }));
propertyUpdateCallbacks.push(checkAnimation(() => { propertyUpdateCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[backgroundColorProperty.cssName] = propertyAnimation.value; propertyAnimation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = propertyAnimation.value;
})); }));
propertyResetCallbacks.push(checkAnimation(() => { propertyResetCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[backgroundColorProperty.cssName] = originalValue1; if (setLocal) {
propertyAnimation.target.style[backgroundColorProperty.name] = originalValue1;
} else {
propertyAnimation.target.style[backgroundColorProperty.keyframe] = originalValue1;
if (propertyAnimation.target.nativeView) {
propertyAnimation.target[backgroundColorProperty.native] = propertyAnimation.target.style.backgroundColor;
}
}
})); }));
animators.push(animator); animators.push(animator);
break; break;
@ -295,17 +309,26 @@ export class Animation extends AnimationBase {
xyObjectAnimators[1] = android.animation.ObjectAnimator.ofFloat(nativeView, "translationY", nativeArray); xyObjectAnimators[1] = android.animation.ObjectAnimator.ofFloat(nativeView, "translationY", nativeArray);
xyObjectAnimators[1].setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations)); xyObjectAnimators[1].setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
originalValue1 = nativeView.getTranslationX(); originalValue1 = nativeView.getTranslationX() / density;
originalValue2 = nativeView.getTranslationY(); originalValue2 = nativeView.getTranslationY() / density;
propertyUpdateCallbacks.push(checkAnimation(() => { propertyUpdateCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[translateXProperty.cssName] = propertyAnimation.value.x; propertyAnimation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = propertyAnimation.value.x;
propertyAnimation.target.style[translateYProperty.cssName] = propertyAnimation.value.y; propertyAnimation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = propertyAnimation.value.y;
})); }));
propertyResetCallbacks.push(checkAnimation(() => { propertyResetCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[translateXProperty.cssName] = originalValue1; if (setLocal) {
propertyAnimation.target.style[translateYProperty.cssName] = originalValue2; propertyAnimation.target.style[translateXProperty.name] = originalValue1;
propertyAnimation.target.style[translateYProperty.name] = originalValue2;
} else {
propertyAnimation.target.style[translateXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[translateYProperty.keyframe] = originalValue2;
if (propertyAnimation.target.nativeView) {
propertyAnimation.target[translateXProperty.native] = propertyAnimation.target.style.translateX;
propertyAnimation.target[translateYProperty.native] = propertyAnimation.target.style.translateY;
}
}
})); }));
animatorSet = new android.animation.AnimatorSet(); animatorSet = new android.animation.AnimatorSet();
@ -331,13 +354,22 @@ export class Animation extends AnimationBase {
originalValue2 = nativeView.getScaleY(); originalValue2 = nativeView.getScaleY();
propertyUpdateCallbacks.push(checkAnimation(() => { propertyUpdateCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[scaleXProperty.cssName] = propertyAnimation.value.x; propertyAnimation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = propertyAnimation.value.x;
propertyAnimation.target.style[scaleYProperty.cssName] = propertyAnimation.value.y; propertyAnimation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = propertyAnimation.value.y;
})); }));
propertyResetCallbacks.push(checkAnimation(() => { propertyResetCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[scaleXProperty.cssName] = originalValue1; if (setLocal) {
propertyAnimation.target.style[scaleYProperty.cssName] = originalValue2; propertyAnimation.target.style[scaleXProperty.name] = originalValue1;
propertyAnimation.target.style[scaleYProperty.name] = originalValue2;
} else {
propertyAnimation.target.style[scaleXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[scaleYProperty.keyframe] = originalValue2;
if (propertyAnimation.target.nativeView) {
propertyAnimation.target[scaleXProperty.native] = propertyAnimation.target.style.scaleX;
propertyAnimation.target[scaleYProperty.native] = propertyAnimation.target.style.scaleY;
}
}
})); }));
animatorSet = new android.animation.AnimatorSet(); animatorSet = new android.animation.AnimatorSet();
@ -351,10 +383,17 @@ export class Animation extends AnimationBase {
nativeArray = Array.create("float", 1); nativeArray = Array.create("float", 1);
nativeArray[0] = propertyAnimation.value; nativeArray[0] = propertyAnimation.value;
propertyUpdateCallbacks.push(checkAnimation(() => { propertyUpdateCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[rotateProperty.cssName] = propertyAnimation.value; propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value;
})); }));
propertyResetCallbacks.push(checkAnimation(() => { propertyResetCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[rotateProperty.cssName] = originalValue1; if (setLocal) {
propertyAnimation.target.style[rotateProperty.name] = originalValue1;
} else {
propertyAnimation.target.style[rotateProperty.keyframe] = originalValue1;
if (propertyAnimation.target.nativeView) {
propertyAnimation.target[rotateProperty.native] = propertyAnimation.target.style.rotate;
}
}
})); }));
animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "rotation", nativeArray)); animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "rotation", nativeArray));
break; break;

View File

@ -2,7 +2,7 @@ import { AnimationDefinition } from "ui/animation";
import { import {
AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise, View, opacityProperty, backgroundColorProperty, rotateProperty, AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise, View, opacityProperty, backgroundColorProperty, rotateProperty,
translateXProperty, translateYProperty, translateXProperty, translateYProperty,
scaleXProperty, scaleYProperty, traceEnabled, traceWrite, traceCategories scaleXProperty, scaleYProperty, traceEnabled, traceWrite, traceCategories, Length
} from "./animation-common"; } from "./animation-common";
import { ios } from "utils/utils"; import { ios } from "utils/utils";
@ -33,7 +33,7 @@ interface PropertyAnimationInfo extends PropertyAnimation {
} }
interface AnimationDefinitionInternal extends AnimationDefinition { interface AnimationDefinitionInternal extends AnimationDefinition {
valueSource?: number; valueSource?: "animation" | "keyframe";
} }
interface IOSView extends View { interface IOSView extends View {
@ -51,9 +51,9 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate {
private _finishedCallback: Function; private _finishedCallback: Function;
private _propertyAnimation: PropertyAnimationInfo; private _propertyAnimation: PropertyAnimationInfo;
private _valueSource: number; private _valueSource: "animation" | "keyframe";
public static initWithFinishedCallback(finishedCallback: Function, propertyAnimation: PropertyAnimationInfo, valueSource: number): AnimationDelegateImpl { public static initWithFinishedCallback(finishedCallback: Function, propertyAnimation: PropertyAnimationInfo, valueSource: "animation" | "keyframe"): AnimationDelegateImpl {
let delegate = <AnimationDelegateImpl>AnimationDelegateImpl.new(); let delegate = <AnimationDelegateImpl>AnimationDelegateImpl.new();
delegate._finishedCallback = finishedCallback; delegate._finishedCallback = finishedCallback;
delegate._propertyAnimation = propertyAnimation; delegate._propertyAnimation = propertyAnimation;
@ -63,40 +63,39 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate {
animationDidStart(anim: CAAnimation): void { animationDidStart(anim: CAAnimation): void {
let value = this._propertyAnimation.value; let value = this._propertyAnimation.value;
// let valueSource = this._valueSource || dependencyObservable.ValueSource.Local; let setLocal = this._valueSource === "animation";
let setLocal = true;
let targetStyle = this._propertyAnimation.target.style; let targetStyle = this._propertyAnimation.target.style;
(<IOSView>this._propertyAnimation.target)._suspendPresentationLayerUpdates(); (<IOSView>this._propertyAnimation.target)._suspendPresentationLayerUpdates();
switch (this._propertyAnimation.property) { switch (this._propertyAnimation.property) {
case Properties.backgroundColor: case Properties.backgroundColor:
targetStyle[setLocal ? backgroundColorProperty.name : backgroundColorProperty.cssName] = value; targetStyle[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = value;
break; break;
case Properties.opacity: case Properties.opacity:
targetStyle[setLocal ? opacityProperty.name : opacityProperty.cssName] = value; targetStyle[setLocal ? opacityProperty.name : opacityProperty.keyframe] = value;
break; break;
case Properties.rotate: case Properties.rotate:
targetStyle[setLocal ? rotateProperty.name : rotateProperty.cssName] = value; targetStyle[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value;
break; break;
case Properties.translate: case Properties.translate:
targetStyle[setLocal ? translateXProperty.name : translateXProperty.cssName] = value; targetStyle[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value;
targetStyle[setLocal ? translateYProperty.name : translateYProperty.cssName] = value; targetStyle[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value;
break; break;
case Properties.scale: case Properties.scale:
targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.cssName] = value.x === 0 ? 0.001 : value.x; targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x === 0 ? 0.001 : value.x;
targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.cssName] = value.y === 0 ? 0.001 : value.y; targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y === 0 ? 0.001 : value.y;
break; break;
case _transform: case _transform:
if (value[Properties.translate] !== undefined) { if (value[Properties.translate] !== undefined) {
targetStyle[setLocal ? translateXProperty.name : translateXProperty.cssName] = value[Properties.translate].x; targetStyle[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value[Properties.translate].x;
targetStyle[setLocal ? translateYProperty.name : translateYProperty.cssName] = value[Properties.translate].y; targetStyle[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value[Properties.translate].y;
} }
if (value[Properties.scale] !== undefined) { if (value[Properties.scale] !== undefined) {
let x = value[Properties.scale].x; let x = value[Properties.scale].x;
let y = value[Properties.scale].y; let y = value[Properties.scale].y;
targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.cssName] = x === 0 ? 0.001 : x; targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = x === 0 ? 0.001 : x;
targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.cssName] = y === 0 ? 0.001 : y; targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = y === 0 ? 0.001 : y;
} }
break; break;
} }
@ -147,7 +146,7 @@ export class Animation extends AnimationBase {
private _finishedAnimations: number; private _finishedAnimations: number;
private _cancelledAnimations: number; private _cancelledAnimations: number;
private _mergedPropertyAnimations: Array<PropertyAnimationInfo>; private _mergedPropertyAnimations: Array<PropertyAnimationInfo>;
private _valueSource: number; private _valueSource: "animation" | "keyframe";
constructor(animationDefinitions: Array<AnimationDefinitionInternal>, playSequentially?: boolean) { constructor(animationDefinitions: Array<AnimationDefinitionInternal>, playSequentially?: boolean) {
super(animationDefinitions, playSequentially); super(animationDefinitions, playSequentially);
@ -233,7 +232,7 @@ export class Animation extends AnimationBase {
return _resolveAnimationCurve(curve); return _resolveAnimationCurve(curve);
} }
private static _createiOSAnimationFunction(propertyAnimations: Array<PropertyAnimation>, index: number, playSequentially: boolean, valueSource: number, finishedCallback: (cancelled?: boolean) => void): Function { private static _createiOSAnimationFunction(propertyAnimations: Array<PropertyAnimation>, index: number, playSequentially: boolean, valueSource: "animation" | "keyframe", finishedCallback: (cancelled?: boolean) => void): Function {
return (cancelled?: boolean) => { return (cancelled?: boolean) => {
if (cancelled && finishedCallback) { if (cancelled && finishedCallback) {
@ -256,7 +255,7 @@ export class Animation extends AnimationBase {
} }
} }
private static _getNativeAnimationArguments(animation: PropertyAnimationInfo, valueSource: number): AnimationInfo { private static _getNativeAnimationArguments(animation: PropertyAnimationInfo, valueSource: "animation" | "keyframe"): AnimationInfo {
let nativeView = <UIView>animation.target._nativeView; let nativeView = <UIView>animation.target._nativeView;
let propertyNameToAnimate = animation.property; let propertyNameToAnimate = animation.property;
@ -266,16 +265,13 @@ export class Animation extends AnimationBase {
let tempRotate = (animation.target.rotate || 0) * Math.PI / 180; let tempRotate = (animation.target.rotate || 0) * Math.PI / 180;
let abs; let abs;
let setLocal = true; let setLocal = valueSource === "animation";
// if (valueSource === undefined) {
// valueSource = dependencyObservable.ValueSource.Local;
// }
switch (animation.property) { switch (animation.property) {
case Properties.backgroundColor: case Properties.backgroundColor:
animation._originalValue = animation.target.backgroundColor; animation._originalValue = animation.target.backgroundColor;
animation._propertyResetCallback = (value, valueSource) => { animation._propertyResetCallback = (value, valueSource) => {
animation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.cssName] = value; animation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = value;
}; };
originalValue = nativeView.layer.backgroundColor; originalValue = nativeView.layer.backgroundColor;
if (nativeView instanceof UILabel) { if (nativeView instanceof UILabel) {
@ -286,14 +282,14 @@ export class Animation extends AnimationBase {
case Properties.opacity: case Properties.opacity:
animation._originalValue = animation.target.opacity; animation._originalValue = animation.target.opacity;
animation._propertyResetCallback = (value, valueSource) => { animation._propertyResetCallback = (value, valueSource) => {
animation.target.style[setLocal ? opacityProperty.name : opacityProperty.cssName] = value; animation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = value;
}; };
originalValue = nativeView.layer.opacity; originalValue = nativeView.layer.opacity;
break; break;
case Properties.rotate: case Properties.rotate:
animation._originalValue = animation.target.rotate !== undefined ? animation.target.rotate : 0; animation._originalValue = animation.target.rotate !== undefined ? animation.target.rotate : 0;
animation._propertyResetCallback = (value, valueSource) => { animation._propertyResetCallback = (value, valueSource) => {
animation.target.style[setLocal ? rotateProperty.name : rotateProperty.cssName] = value; animation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value;
}; };
propertyNameToAnimate = "transform.rotation"; propertyNameToAnimate = "transform.rotation";
originalValue = nativeView.layer.valueForKeyPath("transform.rotation"); originalValue = nativeView.layer.valueForKeyPath("transform.rotation");
@ -309,8 +305,8 @@ export class Animation extends AnimationBase {
case Properties.translate: case Properties.translate:
animation._originalValue = { x: animation.target.translateX, y: animation.target.translateY }; animation._originalValue = { x: animation.target.translateX, y: animation.target.translateY };
animation._propertyResetCallback = (value, valueSource) => { animation._propertyResetCallback = (value, valueSource) => {
animation.target.style[setLocal ? translateXProperty.name : translateXProperty.cssName] = value.x; animation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.x;
animation.target.style[setLocal ? translateYProperty.name : translateYProperty.cssName] = value.y; animation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.y;
}; };
propertyNameToAnimate = "transform"; propertyNameToAnimate = "transform";
originalValue = NSValue.valueWithCATransform3D(nativeView.layer.transform); originalValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
@ -325,8 +321,8 @@ export class Animation extends AnimationBase {
} }
animation._originalValue = { x: animation.target.scaleX, y: animation.target.scaleY }; animation._originalValue = { x: animation.target.scaleX, y: animation.target.scaleY };
animation._propertyResetCallback = (value, valueSource) => { animation._propertyResetCallback = (value, valueSource) => {
animation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.cssName] = value.x; animation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x;
animation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.cssName] = value.y; animation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y;
}; };
propertyNameToAnimate = "transform"; propertyNameToAnimate = "transform";
originalValue = NSValue.valueWithCATransform3D(nativeView.layer.transform); originalValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
@ -339,10 +335,10 @@ export class Animation extends AnimationBase {
xt: animation.target.translateX, yt: animation.target.translateY xt: animation.target.translateX, yt: animation.target.translateY
}; };
animation._propertyResetCallback = (value, valueSource) => { animation._propertyResetCallback = (value, valueSource) => {
animation.target.style[setLocal ? translateXProperty.name : translateXProperty.cssName] = value.xt; animation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.xt;
animation.target.style[setLocal ? translateYProperty.name : translateYProperty.cssName] = value.yt; animation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.yt;
animation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.cssName] = value.xs; animation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.xs;
animation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.cssName] = value.ys; animation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.ys;
}; };
propertyNameToAnimate = "transform"; propertyNameToAnimate = "transform";
value = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation)); value = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation));
@ -381,7 +377,7 @@ export class Animation extends AnimationBase {
}; };
} }
private static _createNativeAnimation(propertyAnimations: Array<PropertyAnimation>, index: number, playSequentially: boolean, args: AnimationInfo, animation: PropertyAnimation, valueSource: number, finishedCallback: (cancelled?: boolean) => void) { private static _createNativeAnimation(propertyAnimations: Array<PropertyAnimation>, index: number, playSequentially: boolean, args: AnimationInfo, animation: PropertyAnimation, valueSource: "animation" | "keyframe", finishedCallback: (cancelled?: boolean) => void) {
let nativeView = <UIView>animation.target._nativeView; let nativeView = <UIView>animation.target._nativeView;
let nativeAnimation = CABasicAnimation.animationWithKeyPath(args.propertyNameToAnimate); let nativeAnimation = CABasicAnimation.animationWithKeyPath(args.propertyNameToAnimate);
@ -415,7 +411,7 @@ export class Animation extends AnimationBase {
} }
} }
private static _createNativeSpringAnimation(propertyAnimations: Array<PropertyAnimationInfo>, index: number, playSequentially: boolean, args: AnimationInfo, animation: PropertyAnimationInfo, valueSource: number, finishedCallback: (cancelled?: boolean) => void) { private static _createNativeSpringAnimation(propertyAnimations: Array<PropertyAnimationInfo>, index: number, playSequentially: boolean, args: AnimationInfo, animation: PropertyAnimationInfo, valueSource: "animation" | "keyframe", finishedCallback: (cancelled?: boolean) => void) {
let nativeView = <UIView>animation.target._nativeView; let nativeView = <UIView>animation.target._nativeView;
@ -591,7 +587,7 @@ export class Animation extends AnimationBase {
export function _getTransformMismatchErrorMessage(view: View): string { export function _getTransformMismatchErrorMessage(view: View): string {
// Order is important: translate, rotate, scale // Order is important: translate, rotate, scale
let result: CGAffineTransform = CGAffineTransformIdentity; let result: CGAffineTransform = CGAffineTransformIdentity;
result = CGAffineTransformTranslate(result, view.translateX || 0, view.translateY || 0); result = CGAffineTransformTranslate(result, Length.toDevicePixels(view.translateX || 0, 0), Length.toDevicePixels(view.translateY || 0, 0));
result = CGAffineTransformRotate(result, (view.rotate || 0) * Math.PI / 180); result = CGAffineTransformRotate(result, (view.rotate || 0) * Math.PI / 180);
result = CGAffineTransformScale(result, view.scaleX || 1, view.scaleY || 1); result = CGAffineTransformScale(result, view.scaleX || 1, view.scaleY || 1);
let viewTransform = NSStringFromCGAffineTransform(result); let viewTransform = NSStringFromCGAffineTransform(result);

View File

@ -5,7 +5,18 @@ import {
KeyframeAnimation as KeyframeAnimationDefinition KeyframeAnimation as KeyframeAnimationDefinition
} from "ui/animation/keyframe-animation"; } from "ui/animation/keyframe-animation";
import { View, unsetValue } from "ui/core/view"; import {
View,
backgroundColorProperty,
scaleXProperty,
scaleYProperty,
translateXProperty,
translateYProperty,
rotateProperty,
opacityProperty,
unsetValue,
Color
} from "ui/core/view";
import { Animation } from "ui/animation"; import { Animation } from "ui/animation";
export class KeyframeDeclaration implements KeyframeDeclarationDefinition { export class KeyframeDeclaration implements KeyframeDeclarationDefinition {
@ -30,8 +41,20 @@ export class KeyframeAnimationInfo implements KeyframeAnimationInfoDefinition {
public keyframes: Array<KeyframeInfo>; public keyframes: Array<KeyframeInfo>;
} }
interface Keyframe {
backgroundColor?: Color;
scale?: { x: number, y: number };
translate?: { x: number, y: number };
rotate?: number;
opacity?: number;
valueSource?: "keyframe" | "animation";
duration?: number;
curve?: any;
forceLayer?: boolean;
}
export class KeyframeAnimation implements KeyframeAnimationDefinition { export class KeyframeAnimation implements KeyframeAnimationDefinition {
public animations: Array<Object>; public animations: Array<Keyframe>;
public delay: number = 0; public delay: number = 0;
public iterations: number = 1; public iterations: number = 1;
@ -43,7 +66,7 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
private _target: View; private _target: View;
public static keyframeAnimationFromInfo(info: KeyframeAnimationInfo) { public static keyframeAnimationFromInfo(info: KeyframeAnimationInfo) {
let animations = new Array<Object>(); let animations = new Array<Keyframe>();
let length = info.keyframes.length; let length = info.keyframes.length;
let startDuration = 0; let startDuration = 0;
if (info.isReverse) { if (info.isReverse) {
@ -81,7 +104,7 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
} }
private static parseKeyframe(info: KeyframeAnimationInfo, keyframe: KeyframeInfo, animations: Array<Object>, startDuration: number): number { private static parseKeyframe(info: KeyframeAnimationInfo, keyframe: KeyframeInfo, animations: Array<Object>, startDuration: number): number {
let animation = {}; let animation: Keyframe = {};
for (let declaration of keyframe.declarations) { for (let declaration of keyframe.declarations) {
animation[declaration.property] = declaration.value; animation[declaration.property] = declaration.value;
} }
@ -93,9 +116,10 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
duration = (info.duration * duration) - startDuration; duration = (info.duration * duration) - startDuration;
startDuration += duration; startDuration += duration;
} }
animation["duration"] = info.isReverse ? info.duration - duration : duration; animation.duration = info.isReverse ? info.duration - duration : duration;
animation["curve"] = keyframe.curve; animation.curve = keyframe.curve;
animation["forceLayer"] = true; animation.forceLayer = true;
animation.valueSource = "keyframe";
animations.push(animation); animations.push(animation);
return startDuration; return startDuration;
} }
@ -153,21 +177,21 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
let animation = this.animations[0]; let animation = this.animations[0];
if ("backgroundColor" in animation) { if ("backgroundColor" in animation) {
view.style[`css-background-color`] = animation["backgroundColor"]; view.style[backgroundColorProperty.keyframe] = animation.backgroundColor;
} }
if ("scale" in animation) { if ("scale" in animation) {
view.style["css-scaleX"] = animation["scale"].x; view.style[scaleXProperty.keyframe] = animation.scale.x;
view.style["css-scaleY"] = animation["scale"].y; view.style[scaleYProperty.keyframe] = animation.scale.y;
} }
if ("translate" in animation) { if ("translate" in animation) {
view.style["css-translateX"] = animation["translate"].x; view.style[translateXProperty.keyframe] = animation.translate.x;
view.style["css-translateY"] = animation["translate"].y; view.style[translateYProperty.keyframe] = animation.translate.y;
} }
if ("rotate" in animation) { if ("rotate" in animation) {
view.style["css-rotate"] = animation["rotate"]; view.style[rotateProperty.keyframe] = animation.rotate;
} }
if ("opacity" in animation) { if ("opacity" in animation) {
view.style["css-opacity"] = animation["opacity"]; view.style[opacityProperty.keyframe] = animation.opacity;
} }
setTimeout(() => this.animate(view, 1, iterations), 1); setTimeout(() => this.animate(view, 1, iterations), 1);
@ -212,21 +236,21 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
private _resetAnimationValues(view: View, animation: Object) { private _resetAnimationValues(view: View, animation: Object) {
if ("backgroundColor" in animation) { if ("backgroundColor" in animation) {
view.style[`css-background-color`] = unsetValue; view.style[backgroundColorProperty.keyframe] = unsetValue;
} }
if ("scale" in animation) { if ("scale" in animation) {
view.style["css-scaleX"] = animation["scale"].x; view.style[scaleXProperty.keyframe] = unsetValue;
view.style["css-scaleY"] = animation["scale"].y; view.style[scaleYProperty.keyframe] = unsetValue;
} }
if ("translate" in animation) { if ("translate" in animation) {
view.style["css-translateX"] = animation["translate"].x; view.style[translateXProperty.keyframe] = unsetValue;
view.style["css-translateY"] = animation["translate"].y; view.style[translateYProperty.keyframe] = unsetValue;
} }
if ("rotate" in animation) { if ("rotate" in animation) {
view.style["css-rotate"] = animation["rotate"]; view.style[rotateProperty.keyframe] = unsetValue;
} }
if ("opacity" in animation) { if ("opacity" in animation) {
view.style["css-opacity"] = animation["opacity"]; view.style[opacityProperty.keyframe] = unsetValue;
} }
} }
} }

View File

@ -9,6 +9,9 @@ export const unsetValue: any = new Object();
let symbolPropertyMap = {}; let symbolPropertyMap = {};
let cssSymbolPropertyMap = {}; let cssSymbolPropertyMap = {};
let cssSymbolResetMap = {};
let inheritableProperties = new Array<InheritedProperty<any, any>>(); let inheritableProperties = new Array<InheritedProperty<any, any>>();
let inheritableCssProperties = new Array<InheritedCssProperty<any, any>>(); let inheritableCssProperties = new Array<InheritedCssProperty<any, any>>();
@ -35,7 +38,8 @@ const enum ValueSource {
Default = 0, Default = 0,
Inherited = 1, Inherited = 1,
Css = 2, Css = 2,
Local = 3 Local = 3,
Keyframe = 4
} }
export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<U>, definitions.Property<T, U> { export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<U>, definitions.Property<T, U> {
@ -359,7 +363,7 @@ export class CssProperty<T extends Style, U> implements definitions.CssProperty<
const name = options.name; const name = options.name;
this.name = name; this.name = name;
this.cssName = `css-${options.cssName}`; this.cssName = `css:${options.cssName}`;
this.cssLocalName = options.cssName; this.cssLocalName = options.cssName;
const key = Symbol(name + ":propertyKey"); const key = Symbol(name + ":propertyKey");
@ -541,6 +545,106 @@ export class CssProperty<T extends Style, U> implements definitions.CssProperty<
} }
} }
export class CssAnimationProperty<T extends Style, U> {
public readonly name: string;
public readonly cssName: string;
public readonly native: symbol;
public readonly register: (cls: { prototype }) => void;
public readonly keyframe: string;
public readonly defaultValueKey: symbol;
constructor(private options: definitions.CssAnimationPropertyOptions<T, U>) {
const { valueConverter, equalityComparer, valueChanged, defaultValue } = options;
const propertyName = options.name;
this.name = propertyName;
this.cssName = (options.cssName || propertyName);
const cssName = "css:" + (options.cssName || propertyName);
const keyframeName = "keyframe:" + propertyName;
this.keyframe = keyframeName;
const defaultName = "default:" + propertyName;
const defaultValueKey = Symbol(defaultName);
this.defaultValueKey = defaultValueKey;
const cssValue = Symbol(cssName);
const styleValue = Symbol(propertyName);
const keyframeValue = Symbol(keyframeName);
const computedValue = Symbol("computed-value:" + propertyName);
const computedSource = Symbol("computed-source:" + propertyName);
const native = this.native = Symbol("native:" + propertyName);
const eventName = propertyName + "Change";
function descriptor(symbol: symbol, propertySource: ValueSource, enumerable: boolean, configurable: boolean, getsComputed: boolean): PropertyDescriptor {
return { enumerable, configurable,
get: getsComputed ? function(this: T) { return this[computedValue]; } : function(this: T) { return this[symbol]; },
set(this: T, value: U) {
let prev = this[computedValue];
if (value === unsetValue) {
this[symbol] = unsetValue;
if (this[computedSource] == propertySource) {
// Fallback to lower value source.
if (this[styleValue] !== unsetValue) {
this[computedSource] = ValueSource.Local;
this[computedValue] = this[styleValue];
} else if (this[cssValue] !== unsetValue) {
this[computedSource] = ValueSource.Css;
this[computedValue] = this[cssValue];
} else {
this[computedSource] = ValueSource.Default;
this[computedValue] = defaultValue;
}
}
} else {
if (valueConverter && typeof value === "string") {
value = valueConverter(value);
}
this[symbol] = value;
if (this[computedSource] <= propertySource) {
this[computedSource] = propertySource;
this[computedValue] = value;
}
}
let next = this[computedValue];
if (prev !== next && (!equalityComparer || !equalityComparer(prev, next))) {
valueChanged && valueChanged(this, prev, next);
this.view.nativeView && (this.view[native] = next);
this.hasListeners(eventName) && this.notify({ eventName, object: this, propertyName, value });
}
}
}
}
const defaultPropertyDescriptor = descriptor(defaultValueKey, ValueSource.Default, false, false, false);
const cssPropertyDescriptor = descriptor(cssValue, ValueSource.Css, false, false, false);
const stylePropertyDescriptor = descriptor(styleValue, ValueSource.Local, true, true, true);
const keyframePropertyDescriptor = descriptor(keyframeValue, ValueSource.Keyframe, false, false, false);
cssSymbolResetMap[cssValue] = cssName;
cssSymbolResetMap[keyframeValue] = keyframeName;
symbolPropertyMap[computedValue] = this;
this.register = (cls: { prototype: T }) => {
cls.prototype[defaultValueKey] = options.defaultValue;
cls.prototype[computedValue] = options.defaultValue;
cls.prototype[computedSource] = ValueSource.Default;
cls.prototype[cssValue] = unsetValue;
cls.prototype[styleValue] = unsetValue;
cls.prototype[keyframeValue] = unsetValue;
Object.defineProperty(cls.prototype, defaultName, defaultPropertyDescriptor);
Object.defineProperty(cls.prototype, cssName, cssPropertyDescriptor);
Object.defineProperty(cls.prototype, propertyName, stylePropertyDescriptor);
Object.defineProperty(cls.prototype, keyframeName, keyframePropertyDescriptor);
}
}
}
export class InheritedCssProperty<T extends Style, U> extends CssProperty<T, U> implements definitions.InheritedCssProperty<T, U> { export class InheritedCssProperty<T extends Style, U> extends CssProperty<T, U> implements definitions.InheritedCssProperty<T, U> {
public setInheritedValue: (value: U) => void; public setInheritedValue: (value: U) => void;
@ -690,7 +794,7 @@ export class ShorthandProperty<T extends Style, P> implements definitions.Shorth
const key = Symbol(this.name + ":propertyKey"); const key = Symbol(this.name + ":propertyKey");
this.key = key; this.key = key;
this.cssName = `css-${options.cssName}`; this.cssName = `css:${options.cssName}`;
this.cssLocalName = `${options.cssName}`; this.cssLocalName = `${options.cssName}`;
const converter = options.converter; const converter = options.converter;
@ -872,12 +976,12 @@ export function clearInheritedProperties(view: ViewBase): void {
export function resetCSSProperties(style: Style): void { export function resetCSSProperties(style: Style): void {
let symbols = (<any>Object).getOwnPropertySymbols(style); let symbols = (<any>Object).getOwnPropertySymbols(style);
for (let symbol of symbols) { for (let symbol of symbols) {
const cssProperty = cssSymbolPropertyMap[symbol]; let cssProperty;
if (!cssProperty) { if (cssProperty = cssSymbolPropertyMap[symbol]) {
continue;
}
style[cssProperty.cssName] = unsetValue; style[cssProperty.cssName] = unsetValue;
} else if (cssProperty = cssSymbolResetMap[symbol]) {
style[cssProperty] = unsetValue;
}
} }
} }

View File

@ -4,7 +4,7 @@ import { Source } from "utils/debug";
import { Background } from "ui/styling/background"; import { Background } from "ui/styling/background";
import { import {
ViewBase, getEventOrGestureName, EventData, Style, unsetValue, ViewBase, getEventOrGestureName, EventData, Style, unsetValue,
Property, CssProperty, ShorthandProperty, InheritedCssProperty, Property, CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty,
gestureFromString, isIOS, traceEnabled, traceWrite, traceCategories, makeParser, makeValidator gestureFromString, isIOS, traceEnabled, traceWrite, traceCategories, makeParser, makeValidator
} from "./view-base"; } from "./view-base";
import { observe as gestureObserve, GesturesObserver, GestureTypes, GestureEventData } from "ui/gestures"; import { observe as gestureObserve, GesturesObserver, GestureTypes, GestureEventData } from "ui/gestures";
@ -426,17 +426,17 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
this.style.rotate = value; this.style.rotate = value;
} }
get translateX(): number { get translateX(): Length {
return this.style.translateX; return this.style.translateX;
} }
set translateX(value: number) { set translateX(value: Length) {
this.style.translateX = value; this.style.translateX = value;
} }
get translateY(): number { get translateY(): Length {
return this.style.translateY; return this.style.translateY;
} }
set translateY(value: number) { set translateY(value: Length) {
this.style.translateY = value; this.style.translateY = value;
} }
@ -1352,19 +1352,19 @@ function convertToPaddings(this: void, value: string | Length): [CssProperty<any
} }
} }
export const rotateProperty = new CssProperty<Style, number>({ name: "rotate", cssName: "rotate", defaultValue: 0, valueConverter: (v) => parseFloat(v) }); export const rotateProperty = new CssAnimationProperty<Style, number>({ name: "rotate", cssName: "rotate", defaultValue: 0, valueConverter: parseFloat });
rotateProperty.register(Style); rotateProperty.register(Style);
export const scaleXProperty = new CssProperty<Style, number>({ name: "scaleX", cssName: "scaleX", defaultValue: 1, valueConverter: (v) => parseFloat(v) }); export const scaleXProperty = new CssAnimationProperty<Style, number>({ name: "scaleX", cssName: "scaleX", defaultValue: 1, valueConverter: parseFloat });
scaleXProperty.register(Style); scaleXProperty.register(Style);
export const scaleYProperty = new CssProperty<Style, number>({ name: "scaleY", cssName: "scaleY", defaultValue: 1, valueConverter: (v) => parseFloat(v) }); export const scaleYProperty = new CssAnimationProperty<Style, number>({ name: "scaleY", cssName: "scaleY", defaultValue: 1, valueConverter: parseFloat });
scaleYProperty.register(Style); scaleYProperty.register(Style);
export const translateXProperty = new CssProperty<Style, number>({ name: "translateX", cssName: "translateX", defaultValue: 0, valueConverter: (v) => parseFloat(v) }); export const translateXProperty = new CssAnimationProperty<Style, Length>({ name: "translateX", cssName: "translateX", defaultValue: 0, valueConverter: Length.parse, equalityComparer: Length.equals });
translateXProperty.register(Style); translateXProperty.register(Style);
export const translateYProperty = new CssProperty<Style, number>({ name: "translateY", cssName: "translateY", defaultValue: 0, valueConverter: (v) => parseFloat(v) }); export const translateYProperty = new CssAnimationProperty<Style, Length>({ name: "translateY", cssName: "translateY", defaultValue: 0, valueConverter: Length.parse, equalityComparer: Length.equals });
translateYProperty.register(Style); translateYProperty.register(Style);
const transformProperty = new ShorthandProperty<Style, string>({ const transformProperty = new ShorthandProperty<Style, string>({
@ -1545,7 +1545,7 @@ export const backgroundImageProperty = new CssProperty<Style, string>({
}); });
backgroundImageProperty.register(Style); backgroundImageProperty.register(Style);
export const backgroundColorProperty = new CssProperty<Style, Color>({ export const backgroundColorProperty = new CssAnimationProperty<Style, Color>({
name: "backgroundColor", cssName: "background-color", valueChanged: (target, oldValue, newValue) => { name: "backgroundColor", cssName: "background-color", valueChanged: (target, oldValue, newValue) => {
let background = target.backgroundInternal; let background = target.backgroundInternal;
target.backgroundInternal = background.withColor(newValue); target.backgroundInternal = background.withColor(newValue);
@ -1929,7 +1929,7 @@ function opacityConverter(value: any): number {
throw new Error(`Opacity should be between [0, 1]. Value: ${newValue}`); throw new Error(`Opacity should be between [0, 1]. Value: ${newValue}`);
} }
export const opacityProperty = new CssProperty<Style, number>({ name: "opacity", cssName: "opacity", defaultValue: 1, valueConverter: opacityConverter }); export const opacityProperty = new CssAnimationProperty<Style, number>({ name: "opacity", cssName: "opacity", defaultValue: 1, valueConverter: opacityConverter });
opacityProperty.register(Style); opacityProperty.register(Style);
export const colorProperty = new InheritedCssProperty<Style, Color>({ name: "color", cssName: "color", equalityComparer: Color.equals, valueConverter: (v) => new Color(v) }); export const colorProperty = new InheritedCssProperty<Style, Color>({ name: "color", cssName: "color", equalityComparer: Color.equals, valueConverter: (v) => new Color(v) });

View File

@ -389,18 +389,18 @@ export class View extends ViewCommon {
org.nativescript.widgets.ViewHelper.setScaleY(this.nativeView, float(value)); org.nativescript.widgets.ViewHelper.setScaleY(this.nativeView, float(value));
} }
get [translateXProperty.native](): number { get [translateXProperty.native](): Length | number {
return org.nativescript.widgets.ViewHelper.getTranslateX(this.nativeView); return org.nativescript.widgets.ViewHelper.getTranslateX(this.nativeView);
} }
set [translateXProperty.native](value: number) { set [translateXProperty.native](value: Length) {
org.nativescript.widgets.ViewHelper.setTranslateX(this.nativeView, float(value)); org.nativescript.widgets.ViewHelper.setTranslateX(this.nativeView, Length.toDevicePixels(value, 0));
} }
get [translateYProperty.native](): number { get [translateYProperty.native](): Length | number {
return org.nativescript.widgets.ViewHelper.getTranslateY(this.nativeView); return org.nativescript.widgets.ViewHelper.getTranslateY(this.nativeView);
} }
set [translateYProperty.native](value: number) { set [translateYProperty.native](value: Length) {
org.nativescript.widgets.ViewHelper.setTranslateY(this.nativeView, float(value)); org.nativescript.widgets.ViewHelper.setTranslateY(this.nativeView, Length.toDevicePixels(value, 0));
} }
get [zIndexProperty.native](): number { get [zIndexProperty.native](): number {

View File

@ -1,7 +1,7 @@
declare module "ui/core/view" { declare module "ui/core/view" {
import { GestureTypes, GesturesObserver, GestureEventData, TouchGestureEventData, TouchAction } from "ui/gestures"; import { GestureTypes, GesturesObserver, GestureEventData, TouchGestureEventData, TouchAction } from "ui/gestures";
import { import {
ViewBase, Property, CssProperty, InheritedCssProperty, Style, EventData, ShorthandProperty ViewBase, Property, CssProperty, CssAnimationProperty, InheritedCssProperty, Style, EventData, ShorthandProperty
} from "ui/core/view-base"; } from "ui/core/view-base";
import { Background } from "ui/styling/background"; import { Background } from "ui/styling/background";
import { Font, FontWeight, FontStyle } from "ui/styling/font"; import { Font, FontWeight, FontStyle } from "ui/styling/font";
@ -293,12 +293,12 @@ declare module "ui/core/view" {
/** /**
* Gets or sets the translateX affine transform of the view. * Gets or sets the translateX affine transform of the view.
*/ */
translateX: number; translateX: Length;
/** /**
* Gets or sets the translateY affine transform of the view. * Gets or sets the translateY affine transform of the view.
*/ */
translateY: number; translateY: Length;
/** /**
* Gets or sets the scaleX affine transform of the view. * Gets or sets the scaleX affine transform of the view.
@ -702,16 +702,16 @@ declare module "ui/core/view" {
export const isEnabledProperty: Property<View, boolean>; export const isEnabledProperty: Property<View, boolean>;
export const isUserInteractionEnabledProperty: Property<View, boolean>; export const isUserInteractionEnabledProperty: Property<View, boolean>;
export const rotateProperty: CssProperty<Style, number>; export const rotateProperty: CssAnimationProperty<Style, number>;
export const scaleXProperty: CssProperty<Style, number>; export const scaleXProperty: CssAnimationProperty<Style, number>;
export const scaleYProperty: CssProperty<Style, number>; export const scaleYProperty: CssAnimationProperty<Style, number>;
export const translateXProperty: CssProperty<Style, number>; export const translateXProperty: CssAnimationProperty<Style, Length>;
export const translateYProperty: CssProperty<Style, number>; export const translateYProperty: CssAnimationProperty<Style, Length>;
export const clipPathProperty: CssProperty<Style, string>; export const clipPathProperty: CssProperty<Style, string>;
export const colorProperty: InheritedCssProperty<Style, Color>; export const colorProperty: InheritedCssProperty<Style, Color>;
export const backgroundColorProperty: CssProperty<Style, Color>; export const backgroundColorProperty: CssAnimationProperty<Style, Color>;
export const backgroundImageProperty: CssProperty<Style, string>; export const backgroundImageProperty: CssProperty<Style, string>;
export const backgroundRepeatProperty: CssProperty<Style, BackgroundRepeat>; export const backgroundRepeatProperty: CssProperty<Style, BackgroundRepeat>;
export const backgroundSizeProperty: CssProperty<Style, string>; export const backgroundSizeProperty: CssProperty<Style, string>;
@ -737,7 +737,7 @@ declare module "ui/core/view" {
export const zIndexProperty: CssProperty<Style, number>; export const zIndexProperty: CssProperty<Style, number>;
export const visibilityProperty: CssProperty<Style, Visibility>; export const visibilityProperty: CssProperty<Style, Visibility>;
export const opacityProperty: CssProperty<Style, number>; export const opacityProperty: CssAnimationProperty<Style, number>;
export const minWidthProperty: CssProperty<Style, Length>; export const minWidthProperty: CssProperty<Style, Length>;
export const minHeightProperty: CssProperty<Style, Length>; export const minHeightProperty: CssProperty<Style, Length>;

View File

@ -4,7 +4,7 @@ import {
ViewCommon, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty, visibilityProperty, opacityProperty, ViewCommon, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty, visibilityProperty, opacityProperty,
rotateProperty, scaleXProperty, scaleYProperty, rotateProperty, scaleXProperty, scaleYProperty,
translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty, translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty,
clipPathProperty, layout, traceEnabled, traceWrite, traceCategories, Background, Visibility clipPathProperty, layout, traceEnabled, traceWrite, traceCategories, Background, Visibility, Length
} from "./view-common"; } from "./view-common";
export * from "./view-common"; export * from "./view-common";
@ -232,8 +232,8 @@ export class View extends ViewCommon {
} }
public updateNativeTransform() { public updateNativeTransform() {
let translateX = this.translateX || 0; let translateX = Length.toDevicePixels(this.translateX || 0, 0);
let translateY = this.translateY || 0; let translateY = Length.toDevicePixels(this.translateY || 0, 0);
let scaleX = this.scaleX || 1; let scaleX = this.scaleX || 1;
let scaleY = this.scaleY || 1; let scaleY = this.scaleY || 1;
let rotate = this.rotate || 0; let rotate = this.rotate || 0;
@ -363,17 +363,17 @@ export class View extends ViewCommon {
this.updateNativeTransform(); this.updateNativeTransform();
} }
get [translateXProperty.native](): number { get [translateXProperty.native](): Length | number {
return 0; return 0;
} }
set [translateXProperty.native](value: number) { set [translateXProperty.native](value: Length) {
this.updateNativeTransform(); this.updateNativeTransform();
} }
get [translateYProperty.native](): number { get [translateYProperty.native](): Length | number {
return 0; return 0;
} }
set [translateYProperty.native](value: number) { set [translateYProperty.native](value: Length) {
this.updateNativeTransform(); this.updateNativeTransform();
} }

View File

@ -217,6 +217,27 @@ declare module "ui/core/properties" {
readonly cssName: string; readonly cssName: string;
} }
export interface CssAnimationPropertyOptions<T, U> {
readonly name: string;
readonly cssName?: string;
readonly defaultValue?: U;
readonly equalityComparer?: (x: U, y: U) => boolean;
readonly valueChanged?: (target: T, oldValue: U, newValue: U) => void;
readonly valueConverter?: (value: string) => U;
}
export class CssAnimationProperty<T extends Style, U> {
constructor(options: CssAnimationPropertyOptions<T, U>);
public readonly name: string;
public readonly cssName: string;
public readonly native: symbol;
readonly keyframe: string;
public register(cls: { prototype: T }): void;
}
export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<U> { export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<U> {
constructor(options: PropertyOptions<T, U>); constructor(options: PropertyOptions<T, U>);

View File

@ -113,7 +113,7 @@ imageSourceProperty.register(ImageBase);
export const srcProperty = new Property<ImageBase, any>({ name: "src"}); export const srcProperty = new Property<ImageBase, any>({ name: "src"});
srcProperty.register(ImageBase); srcProperty.register(ImageBase);
export const loadModeProperty = new Property<ImageBase, "sync" | "async">({ name: "loadMode", defaultValue: "async" }); export const loadModeProperty = new Property<ImageBase, "sync" | "async">({ name: "loadMode", defaultValue: "sync" });
loadModeProperty.register(ImageBase); loadModeProperty.register(ImageBase);
export const isLoadingProperty = new Property<ImageBase, boolean>({ name: "isLoading", defaultValue: false, valueConverter: booleanConverter }); export const isLoadingProperty = new Property<ImageBase, boolean>({ name: "isLoading", defaultValue: false, valueConverter: booleanConverter });

View File

@ -25,9 +25,12 @@ export class Image extends ImageBase {
} }
private setTintColor(value: Color) { private setTintColor(value: Color) {
if (value !== null && this._ios.image && !this._templateImageWasCreated) { if (value && this._ios.image && !this._templateImageWasCreated) {
this._ios.image = this._ios.image.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate); this._ios.image = this._ios.image.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
this._templateImageWasCreated = true; this._templateImageWasCreated = true;
} else if (this._ios.image && this._templateImageWasCreated) {
this._templateImageWasCreated = false;
this._ios.image = this._ios.image.imageWithRenderingMode(UIImageRenderingMode.Automatic);
} }
this._ios.tintColor = value ? value.ios : null; this._ios.tintColor = value ? value.ios : null;
} }

View File

@ -49,8 +49,8 @@ export class CssState {
let style = view.style; let style = view.style;
ruleset.declarations.forEach(d => { ruleset.declarations.forEach(d => {
try { try {
// Use the "css-" prefixed name, so that CSS value source is set. // Use the "css:" prefixed name, so that CSS value source is set.
let cssPropName = `css-${d.property}`; let cssPropName = `css:${d.property}`;
if (cssPropName in style) { if (cssPropName in style) {
style[cssPropName] = d.value; style[cssPropName] = d.value;
} else { } else {

View File

@ -46,8 +46,8 @@ declare module "ui/styling/style" {
public rotate: number; public rotate: number;
public scaleX: number; public scaleX: number;
public scaleY: number; public scaleY: number;
public translateX: number; public translateX: Length;
public translateY: number; public translateY: Length;
public clipPath: string; public clipPath: string;
public color: Color; public color: Color;