diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj index 4f26631e7..8c8503fa0 100644 --- a/CrossPlatformModules.csproj +++ b/CrossPlatformModules.csproj @@ -79,6 +79,9 @@ data-binding.xml + + + @@ -107,6 +110,9 @@ + + Designer + Always @@ -1858,6 +1864,7 @@ PreserveNewest + @@ -1950,7 +1957,7 @@ False - + \ No newline at end of file diff --git a/apps/tests/ui/animation/animation-tests.ts b/apps/tests/ui/animation/animation-tests.ts index 2b983a335..e8342c87e 100644 --- a/apps/tests/ui/animation/animation-tests.ts +++ b/apps/tests/ui/animation/animation-tests.ts @@ -61,7 +61,7 @@ export var test_AnimatingProperties = function (done) { // } - export var test_CancellingAnimation = function (done) { +export var test_CancellingAnimation = function (done) { var mainPage: pageModule.Page; var label: labelModule.Label; var pageFactory = function (): pageModule.Page { @@ -241,3 +241,146 @@ export var test_ChainingAnimations = function (done) { // ``` // } + +export var test_AnimateOpacity = function (done) { + var mainPage: pageModule.Page; + var label: labelModule.Label; + var pageFactory = function (): pageModule.Page { + label = new labelModule.Label(); + label.text = "label"; + var stackLayout = new stackLayoutModule.StackLayout(); + stackLayout.addChild(label); + mainPage = new pageModule.Page(); + mainPage.content = stackLayout; + return mainPage; + }; + + helper.navigate(pageFactory); + TKUnit.waitUntilReady(() => { return label.isLoaded }); + + label.animate({opacity: 0.75}) + .then(() => { + TKUnit.assert(label.opacity === 0.75); + helper.goBack(); + done(); + }) + .catch((e) => { + helper.goBack(); + done(e); + }); +} + +export var test_AnimateBackgroundColor = function (done) { + var mainPage: pageModule.Page; + var label: labelModule.Label; + var pageFactory = function (): pageModule.Page { + label = new labelModule.Label(); + label.text = "label"; + var stackLayout = new stackLayoutModule.StackLayout(); + stackLayout.addChild(label); + mainPage = new pageModule.Page(); + mainPage.content = stackLayout; + return mainPage; + }; + + helper.navigate(pageFactory); + TKUnit.waitUntilReady(() => { return label.isLoaded }); + var red = new colorModule.Color("Red"); + + label.animate({ backgroundColor: red }) + .then(() => { + TKUnit.assert(label.backgroundColor.equals(red)); + helper.goBack(); + done(); + }) + .catch((e) => { + helper.goBack(); + done(e); + }); +} + +export var test_AnimateTranslate = function (done) { + var mainPage: pageModule.Page; + var label: labelModule.Label; + var pageFactory = function (): pageModule.Page { + label = new labelModule.Label(); + label.text = "label"; + var stackLayout = new stackLayoutModule.StackLayout(); + stackLayout.addChild(label); + mainPage = new pageModule.Page(); + mainPage.content = stackLayout; + return mainPage; + }; + + helper.navigate(pageFactory); + TKUnit.waitUntilReady(() => { return label.isLoaded }); + + label.animate({ translate: {x: 100, y: 200} }) + .then(() => { + TKUnit.assert(label.translateX === 100); + TKUnit.assert(label.translateY === 200); + helper.goBack(); + done(); + }) + .catch((e) => { + helper.goBack(); + done(e); + }); +} + +export var test_AnimateScale = function (done) { + var mainPage: pageModule.Page; + var label: labelModule.Label; + var pageFactory = function (): pageModule.Page { + label = new labelModule.Label(); + label.text = "label"; + var stackLayout = new stackLayoutModule.StackLayout(); + stackLayout.addChild(label); + mainPage = new pageModule.Page(); + mainPage.content = stackLayout; + return mainPage; + }; + + helper.navigate(pageFactory); + TKUnit.waitUntilReady(() => { return label.isLoaded }); + + label.animate({ scale: { x: 1, y: 2 } }) + .then(() => { + TKUnit.assert(label.scaleX === 1); + TKUnit.assert(label.scaleY === 2); + helper.goBack(); + done(); + }) + .catch((e) => { + helper.goBack(); + done(e); + }); +} + +export var test_AnimateRotate = function (done) { + var mainPage: pageModule.Page; + var label: labelModule.Label; + var pageFactory = function (): pageModule.Page { + label = new labelModule.Label(); + label.text = "label"; + var stackLayout = new stackLayoutModule.StackLayout(); + stackLayout.addChild(label); + mainPage = new pageModule.Page(); + mainPage.content = stackLayout; + return mainPage; + }; + + helper.navigate(pageFactory); + TKUnit.waitUntilReady(() => { return label.isLoaded }); + + label.animate({ rotate: 123 }) + .then(() => { + TKUnit.assert(label.rotate === 123); + helper.goBack(); + done(); + }) + .catch((e) => { + helper.goBack(); + done(e); + }); +} diff --git a/apps/transforms/app.ts b/apps/transforms/app.ts new file mode 100644 index 000000000..2baebd169 --- /dev/null +++ b/apps/transforms/app.ts @@ -0,0 +1,8 @@ +import application = require("application"); +import trace = require("trace"); + +trace.enable(); +trace.setCategories(trace.categories.concat(trace.categories.Animation)); + +application.mainModule = "main-page"; +application.start(); diff --git a/apps/transforms/main-page.ts b/apps/transforms/main-page.ts new file mode 100644 index 000000000..94782be09 --- /dev/null +++ b/apps/transforms/main-page.ts @@ -0,0 +1,8 @@ +import observable = require("data/observable"); +import pages = require("ui/page"); +import model = require("./model"); + +export function pageLoaded(args: observable.EventData) { + var page = args.object; + page.bindingContext = new model.ViewModel(); +} \ No newline at end of file diff --git a/apps/transforms/main-page.xml b/apps/transforms/main-page.xml new file mode 100644 index 000000000..abf6e9e8e --- /dev/null +++ b/apps/transforms/main-page.xml @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/apps/transforms/model.ts b/apps/transforms/model.ts new file mode 100644 index 000000000..a103b8a96 --- /dev/null +++ b/apps/transforms/model.ts @@ -0,0 +1,52 @@ +import observable = require("data/observable"); + +export class ViewModel extends observable.Observable { + constructor() { + super(); + } + + private _translateX = 0;; + get translateX(): number { + return this._translateX; + } + set translateX(value: number) { + this._translateX = value; + this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "translateX", value: value }); + } + + private _translateY = 0; + get translateY(): number { + return this._translateY; + } + set translateY(value: number) { + this._translateY = value; + this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "translateY", value: value }); + } + + private _scaleX = 1; + get scaleX(): number { + return this._scaleX; + } + set scaleX(value: number) { + this._scaleX = value; + this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "scaleX", value: value }); + } + + private _scaleY = 1; + get scaleY(): number { + return this._scaleY; + } + set scaleY(value: number) { + this._scaleY = value; + this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "scaleY", value: value }); + } + + private _rotate = 0; + get rotate(): number { + return this._rotate; + } + set rotate(value: number) { + this._rotate = value; + this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "rotate", value: value }); + } +} \ No newline at end of file diff --git a/apps/transforms/package.json b/apps/transforms/package.json new file mode 100644 index 000000000..24aa2dcee --- /dev/null +++ b/apps/transforms/package.json @@ -0,0 +1,2 @@ +{ "name" : "transforms", + "main" : "app.js" } \ No newline at end of file diff --git a/ui/animation/animation.android.ts b/ui/animation/animation.android.ts index 906ce56ea..e3c8b06b2 100644 --- a/ui/animation/animation.android.ts +++ b/ui/animation/animation.android.ts @@ -183,6 +183,7 @@ export class Animation extends common.Animation implements definition.Animation nativeArray = java.lang.reflect.Array.newInstance(floatType, 1); nativeArray[0] = propertyAnimation.value.x * density; animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "translationX", nativeArray)); + propertyUpdateCallbacks.push(() => { propertyAnimation.target.translateX = propertyAnimation.value.x; }); propertyResetCallbacks.push(() => { nativeView.setTranslationX(originalValue); }); } @@ -191,6 +192,7 @@ export class Animation extends common.Animation implements definition.Animation nativeArray = java.lang.reflect.Array.newInstance(floatType, 1); nativeArray[0] = propertyAnimation.value.y * density; animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "translationY", nativeArray)); + propertyUpdateCallbacks.push(() => { propertyAnimation.target.translateY = propertyAnimation.value.y; }); propertyResetCallbacks.push(() => { nativeView.setTranslationY(originalValue); }); } break; @@ -201,6 +203,7 @@ export class Animation extends common.Animation implements definition.Animation nativeArray = java.lang.reflect.Array.newInstance(floatType, 1); nativeArray[0] = propertyAnimation.value; animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "rotation", nativeArray)); + propertyUpdateCallbacks.push(() => { propertyAnimation.target.rotate = propertyAnimation.value; }); propertyResetCallbacks.push(() => { nativeView.setRotation(originalValue); }); } break; @@ -211,6 +214,7 @@ export class Animation extends common.Animation implements definition.Animation nativeArray = java.lang.reflect.Array.newInstance(floatType, 1); nativeArray[0] = propertyAnimation.value.x; animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "scaleX", nativeArray)); + propertyUpdateCallbacks.push(() => { propertyAnimation.target.scaleX = propertyAnimation.value.x; }); propertyResetCallbacks.push(() => { nativeView.setScaleX(originalValue); }); } @@ -219,6 +223,7 @@ export class Animation extends common.Animation implements definition.Animation nativeArray = java.lang.reflect.Array.newInstance(floatType, 1); nativeArray[0] = propertyAnimation.value.y; animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "scaleY", nativeArray)); + propertyUpdateCallbacks.push(() => { propertyAnimation.target.scaleY = propertyAnimation.value.y; }); propertyResetCallbacks.push(() => { nativeView.setScaleY(originalValue); }); } break; diff --git a/ui/animation/animation.ios.ts b/ui/animation/animation.ios.ts index 36c186f42..b2f9a1b0f 100644 --- a/ui/animation/animation.ios.ts +++ b/ui/animation/animation.ios.ts @@ -102,6 +102,28 @@ export class Animation extends common.Animation implements definition.Animation } else if (that._finishedAnimations === that._mergedPropertyAnimations.length) { trace.write(that._finishedAnimations + " animations finished.", trace.categories.Animation); + + // Update our properties on the view. + var i = 0; + var len = that._propertyAnimations.length; + var a: common.PropertyAnimation; + for (; i < len; i++) { + a = that._propertyAnimations[i]; + switch (a.property) { + case common.Properties.translate: + a.target.translateX = a.value.x; + a.target.translateY = a.value.y; + break; + case common.Properties.rotate: + a.target.rotate = a.value; + break; + case common.Properties.scale: + a.target.scaleX = a.value.x; + a.target.scaleY = a.value.y; + break; + } + } + that._resolveAnimationFinishedPromise(); } } diff --git a/ui/core/view-common.ts b/ui/core/view-common.ts index b757d3611..6b21c0d14 100644 --- a/ui/core/view-common.ts +++ b/ui/core/view-common.ts @@ -98,6 +98,36 @@ var cssClassProperty = new dependencyObservable.Property( new proxy.PropertyMetadata(undefined, dependencyObservable.PropertyMetadataSettings.AffectsStyle, onCssClassPropertyChanged) ); +var translateXProperty = new dependencyObservable.Property( + "translateX", + "View", + new proxy.PropertyMetadata(0) + ); + +var translateYProperty = new dependencyObservable.Property( + "translateY", + "View", + new proxy.PropertyMetadata(0) + ); + +var scaleXProperty = new dependencyObservable.Property( + "scaleX", + "View", + new proxy.PropertyMetadata(1) + ); + +var scaleYProperty = new dependencyObservable.Property( + "scaleY", + "View", + new proxy.PropertyMetadata(1) + ); + +var rotateProperty = new dependencyObservable.Property( + "rotate", + "View", + new proxy.PropertyMetadata(0) + ); + var isEnabledProperty = new dependencyObservable.Property( "isEnabled", "View", @@ -116,6 +146,11 @@ export class View extends proxy.ProxyObject implements definition.View { public static idProperty = idProperty; public static cssClassProperty = cssClassProperty; + public static translateXProperty = translateXProperty; + public static translateYProperty = translateYProperty; + public static scaleXProperty = scaleXProperty; + public static scaleYProperty = scaleYProperty; + public static rotateProperty = rotateProperty; public static isEnabledProperty = isEnabledProperty; public static isUserInteractionEnabledProperty = isUserInteractionEnabledProperty; @@ -374,6 +409,41 @@ export class View extends proxy.ProxyObject implements definition.View { //END Style property shortcuts + get translateX(): number { + return this._getValue(View.translateXProperty); + } + set translateX(value: number) { + this._setValue(View.translateXProperty, value); + } + + get translateY(): number { + return this._getValue(View.translateYProperty); + } + set translateY(value: number) { + this._setValue(View.translateYProperty, value); + } + + get scaleX(): number { + return this._getValue(View.scaleXProperty); + } + set scaleX(value: number) { + this._setValue(View.scaleXProperty, value); + } + + get scaleY(): number { + return this._getValue(View.scaleYProperty); + } + set scaleY(value: number) { + this._setValue(View.scaleYProperty, value); + } + + get rotate(): number { + return this._getValue(View.rotateProperty); + } + set rotate(value: number) { + this._setValue(View.rotateProperty, value); + } + get isEnabled(): boolean { return this._getValue(View.isEnabledProperty); } diff --git a/ui/core/view.android.ts b/ui/core/view.android.ts index bcf46ce39..7c38301a4 100644 --- a/ui/core/view.android.ts +++ b/ui/core/view.android.ts @@ -20,6 +20,36 @@ function onIdPropertyChanged(data: dependencyObservable.PropertyChangeData) { } (viewCommon.View.idProperty.metadata).onSetNativeValue = onIdPropertyChanged; +function onTranslateXPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var view = data.object; + view._nativeView.setTranslationX(data.newValue * utils.layout.getDisplayDensity()); +} +(viewCommon.View.translateXProperty.metadata).onSetNativeValue = onTranslateXPropertyChanged; + +function onTranslateYPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var view = data.object; + view._nativeView.setTranslationY(data.newValue * utils.layout.getDisplayDensity()); +} +(viewCommon.View.translateYProperty.metadata).onSetNativeValue = onTranslateYPropertyChanged; + +function onScaleXPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var view = data.object; + view._nativeView.setScaleX(data.newValue); +} +(viewCommon.View.scaleXProperty.metadata).onSetNativeValue = onScaleXPropertyChanged; + +function onScaleYPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var view = data.object; + view._nativeView.setScaleY(data.newValue); +} +(viewCommon.View.scaleYProperty.metadata).onSetNativeValue = onScaleYPropertyChanged; + +function onRotatePropertyChanged(data: dependencyObservable.PropertyChangeData) { + var view = data.object; + view._nativeView.setRotation(data.newValue); +} +(viewCommon.View.rotateProperty.metadata).onSetNativeValue = onRotatePropertyChanged; + function onIsEnabledPropertyChanged(data: dependencyObservable.PropertyChangeData) { var view = data.object; view._nativeView.setEnabled(data.newValue); diff --git a/ui/core/view.d.ts b/ui/core/view.d.ts index a085a25df..6981bbbbf 100644 --- a/ui/core/view.d.ts +++ b/ui/core/view.d.ts @@ -227,6 +227,31 @@ declare module "ui/core/view" { //----------Style property shortcuts---------- + /** + * Gets or sets the translateX affine transform of the view. + */ + translateX: number; + + /** + * Gets or sets the translateY affine transform of the view. + */ + translateY: number; + + /** + * Gets or sets the scaleX affine transform of the view. + */ + scaleX: number; + + /** + * Gets or sets the scaleY affine transform of the view. + */ + scaleY: number; + + /** + * Gets or sets the rotate affine transform of the view. + */ + rotate: number; + /** * Gets or sets a value indicating whether the the view is enabled. This affects the appearance of the view. */ diff --git a/ui/core/view.ios.ts b/ui/core/view.ios.ts index c020387ce..0dad170ad 100644 --- a/ui/core/view.ios.ts +++ b/ui/core/view.ios.ts @@ -18,6 +18,66 @@ function onIdPropertyChanged(data: dependencyObservable.PropertyChangeData) { } (viewCommon.View.idProperty.metadata).onSetNativeValue = onIdPropertyChanged; +function onTranslateXPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var view = data.object; + var newTransform = CGAffineTransformIdentity; + newTransform = CGAffineTransformTranslate(newTransform, data.newValue, view.translateY); + newTransform = CGAffineTransformRotate(newTransform, view.rotate * Math.PI / 180); + newTransform = CGAffineTransformScale(newTransform, view.scaleX, view.scaleY); + if (!CGAffineTransformEqualToTransform(view._nativeView.transform, newTransform)) { + view._nativeView.transform = newTransform; + } +} +(viewCommon.View.translateXProperty.metadata).onSetNativeValue = onTranslateXPropertyChanged; + +function onTranslateYPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var view = data.object; + var newTransform = CGAffineTransformIdentity; + newTransform = CGAffineTransformTranslate(newTransform, view.translateX, data.newValue); + newTransform = CGAffineTransformRotate(newTransform, view.rotate * Math.PI / 180); + newTransform = CGAffineTransformScale(newTransform, view.scaleX, view.scaleY); + if (!CGAffineTransformEqualToTransform(view._nativeView.transform, newTransform)) { + view._nativeView.transform = newTransform; + } +} +(viewCommon.View.translateYProperty.metadata).onSetNativeValue = onTranslateYPropertyChanged; + +function onScaleXPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var view = data.object; + var newTransform = CGAffineTransformIdentity; + newTransform = CGAffineTransformTranslate(newTransform, view.translateX, view.translateY); + newTransform = CGAffineTransformRotate(newTransform, view.rotate * Math.PI / 180); + newTransform = CGAffineTransformScale(newTransform, data.newValue, view.scaleY); + if (!CGAffineTransformEqualToTransform(view._nativeView.transform, newTransform)) { + view._nativeView.transform = newTransform; + } +} +(viewCommon.View.scaleXProperty.metadata).onSetNativeValue = onScaleXPropertyChanged; + +function onScaleYPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var view = data.object; + var newTransform = CGAffineTransformIdentity; + newTransform = CGAffineTransformTranslate(newTransform, view.translateX, view.translateY); + newTransform = CGAffineTransformRotate(newTransform, view.rotate * Math.PI / 180); + newTransform = CGAffineTransformScale(newTransform, view.scaleX, data.newValue); + if (!CGAffineTransformEqualToTransform(view._nativeView.transform, newTransform)) { + view._nativeView.transform = newTransform; + } +} +(viewCommon.View.scaleYProperty.metadata).onSetNativeValue = onScaleYPropertyChanged; + +function onRotatePropertyChanged(data: dependencyObservable.PropertyChangeData) { + var view = data.object; + var newTransform = CGAffineTransformIdentity; + newTransform = CGAffineTransformTranslate(newTransform, view.translateX, view.translateY); + newTransform = CGAffineTransformRotate(newTransform, data.newValue * Math.PI / 180); + newTransform = CGAffineTransformScale(newTransform, view.scaleX, view.scaleY); + if (!CGAffineTransformEqualToTransform(view._nativeView.transform, newTransform)) { + view._nativeView.transform = newTransform; + } +} +(viewCommon.View.rotateProperty.metadata).onSetNativeValue = onRotatePropertyChanged; + function onIsEnabledPropertyChanged(data: dependencyObservable.PropertyChangeData) { var view = data.object; if (!view._nativeView) {