mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 19:26:42 +08:00
feat: Add 3D rotation to view - takeover of PR# 5950 (#8136)
* feat: add 3d rotation * chore: fix build errors * chore: fix tslint errors * chore: add @types/chai dev dep * chore: unused import cleanup * chore: update tests for x,y rotation * chore: rebase upstream/master * fix: iOS Affine Transform test verification * feat(css): Added optional css-tree parser (#8076) * feat(css): Added optional css-tree parser * test: css-tree parser compat tests * test: more css-tree compat tests * feat(dialogs): Setting the size of popup dialog thru dialog options (#8041) * Added iOS specific height and width attributes to ShowModalOptions * Set the height and width of the popup dialog to the presenting controller * dialog options ios attributes presentationStyle, height & width are made optional * Updated NativeScript.api.md for public API changes * Update with git properties * Public API * CLA update * fix: use iOS native-helper for 3d-rotate * test: Fix tests using _getTransformMismatchError * fix: view.__hasTransfrom not set updating properly * test: fix css-animations test page Co-authored-by: Alexander Vakrilov <alexander.vakrilov@gmail.com> Co-authored-by: Darin Dimitrov <darin.dimitrov@gmail.com> Co-authored-by: Shailesh Lolam <slolam@live.com> Co-authored-by: Dimitar Topuzov <dtopuzov@gmail.com>
This commit is contained in:

committed by
Alexander Vakrilov

parent
8550c3293d
commit
e8f5ac8522
@ -243,7 +243,8 @@ export interface AnimationDefinition {
|
|||||||
|
|
||||||
opacity?: number;
|
opacity?: number;
|
||||||
|
|
||||||
rotate?: number;
|
// Warning: (ae-forgotten-export) The symbol "Point3D" needs to be exported by the entry point index.d.ts
|
||||||
|
rotate?: number | Point3D;
|
||||||
|
|
||||||
scale?: Pair;
|
scale?: Pair;
|
||||||
|
|
||||||
@ -2086,6 +2087,8 @@ export class Style extends Observable {
|
|||||||
// (undocumented)
|
// (undocumented)
|
||||||
public paddingTop: Length;
|
public paddingTop: Length;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
public perspective: number;
|
||||||
|
// (undocumented)
|
||||||
public placeholderColor: Color;
|
public placeholderColor: Color;
|
||||||
// Warning: (ae-forgotten-export) The symbol "PropertyBagClass" needs to be exported by the entry point index.d.ts
|
// Warning: (ae-forgotten-export) The symbol "PropertyBagClass" needs to be exported by the entry point index.d.ts
|
||||||
public readonly PropertyBag: PropertyBagClass;
|
public readonly PropertyBag: PropertyBagClass;
|
||||||
@ -2094,6 +2097,10 @@ export class Style extends Observable {
|
|||||||
// (undocumented)
|
// (undocumented)
|
||||||
public rotate: number;
|
public rotate: number;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
public rotateX: number;
|
||||||
|
// (undocumented)
|
||||||
|
public rotateY: number;
|
||||||
|
// (undocumented)
|
||||||
public scaleX: number;
|
public scaleX: number;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
public scaleY: number;
|
public scaleY: number;
|
||||||
@ -2702,12 +2709,15 @@ export abstract class View extends ViewBase {
|
|||||||
opacity: number;
|
opacity: number;
|
||||||
originX: number;
|
originX: number;
|
||||||
originY: number;
|
originY: number;
|
||||||
|
perspective: number;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
_redrawNativeBackground(value: any): void;
|
_redrawNativeBackground(value: any): void;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
_removeAnimation(animation: Animation): boolean;
|
_removeAnimation(animation: Animation): boolean;
|
||||||
public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number;
|
public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number;
|
||||||
rotate: number;
|
rotate: number;
|
||||||
|
rotateX: number;
|
||||||
|
rotateY: number;
|
||||||
scaleX: number;
|
scaleX: number;
|
||||||
scaleY: number;
|
scaleY: number;
|
||||||
_setCurrentLayoutBounds(left: number, top: number, right: number, bottom: number): { boundsChanged: boolean, sizeChanged: boolean };
|
_setCurrentLayoutBounds(left: number, top: number, right: number, bottom: number): { boundsChanged: boolean, sizeChanged: boolean };
|
||||||
|
40
e2e/animation/app/3d-rotate/page.ts
Normal file
40
e2e/animation/app/3d-rotate/page.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { EventData, Page } from "tns-core-modules/ui/page";
|
||||||
|
import { View } from "tns-core-modules/ui/core/view";
|
||||||
|
import { Point3D } from "tns-core-modules/ui/animation/animation";
|
||||||
|
|
||||||
|
let view: View;
|
||||||
|
|
||||||
|
export function pageLoaded(args: EventData) {
|
||||||
|
const page = <Page>args.object;
|
||||||
|
view = page.getViewById<View>("view");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onAnimateX(args: EventData) {
|
||||||
|
rotate({ x: 360, y: 0, z: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onAnimateY(args: EventData) {
|
||||||
|
rotate({ x: 0, y: 360, z: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onAnimateZ(args: EventData) {
|
||||||
|
rotate({ x: 0, y: 0, z: 360 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onAnimateXYZ(args: EventData) {
|
||||||
|
rotate({ x: 360, y: 360, z: 360 });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rotate(rotate: Point3D) {
|
||||||
|
await view.animate({
|
||||||
|
rotate,
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
view.rotate = 0;
|
||||||
|
view.rotateX = 0;
|
||||||
|
view.rotateY = 0;
|
||||||
|
}
|
24
e2e/animation/app/3d-rotate/page.xml
Normal file
24
e2e/animation/app/3d-rotate/page.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
|
||||||
|
<ActionBar title="Rotate" />
|
||||||
|
|
||||||
|
<GridLayout rows="auto auto auto auto *" columns="* * *">
|
||||||
|
<Image src="~/res/icon_100x100.png" width="30" height="30" col="0" row="0" rotateX="60"/>
|
||||||
|
<Image src="~/res/icon_100x100.png" width="30" height="30" col="1" row="0" rotateY="60"/>
|
||||||
|
<Image src="~/res/icon_100x100.png" width="30" height="30" col="2" row="0" rotate="60"/>
|
||||||
|
|
||||||
|
<Button text="X" tap="onAnimateX" col="0" row="1"/>
|
||||||
|
<Button text="Y" tap="onAnimateY" col="1" row="1"/>
|
||||||
|
<Button text="Z" tap="onAnimateZ" col="2" row="1"/>
|
||||||
|
|
||||||
|
<Image src="~/res/icon_100x100.png" width="60" height="60" horizontalAlignment="center"
|
||||||
|
colSpan="3" row="2" rotate="60" rotateX="60" rotateY="60"/>
|
||||||
|
|
||||||
|
<Button text="XYZ" tap="onAnimateXYZ" row="3" colSpan="3"/>
|
||||||
|
|
||||||
|
<AbsoluteLayout width="300" height="300" clipToBounds="true" backgroundColor="LightGray" row="4" colSpan="3">
|
||||||
|
<Image id="view" src="~/res/icon_100x100.png"
|
||||||
|
width="100" height="100"
|
||||||
|
left="100" top="100"/>
|
||||||
|
</AbsoluteLayout>
|
||||||
|
</GridLayout>
|
||||||
|
</Page>
|
75
e2e/animation/app/css-animations/3d-rotate/page.css
Normal file
75
e2e/animation/app/css-animations/3d-rotate/page.css
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
.rotate-x {
|
||||||
|
rotateX: 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rotate-y {
|
||||||
|
rotateY: 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rotate-z {
|
||||||
|
rotate: 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.original {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-x {
|
||||||
|
animation-name: rotateX;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-y {
|
||||||
|
animation-name: rotateY;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-z {
|
||||||
|
animation-name: rotateZ;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-xyz-3d {
|
||||||
|
animation-name: rotateXYZ3D;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-xyz {
|
||||||
|
animation-name: rotateXYZ;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotateX {
|
||||||
|
from { transform: none; }
|
||||||
|
50% { transform: rotateX(60) }
|
||||||
|
to { transform: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotateY {
|
||||||
|
from { transform: none; }
|
||||||
|
50% { transform: rotateY(60) }
|
||||||
|
to { transform: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotateZ {
|
||||||
|
from { transform: none; }
|
||||||
|
50% { transform: rotate(60) }
|
||||||
|
to { transform: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotateXYZ3D {
|
||||||
|
from { transform: none; }
|
||||||
|
50% { transform: rotate3d(60, 60, 60) }
|
||||||
|
to { transform: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotateXYZ {
|
||||||
|
from { transform: none; }
|
||||||
|
50% { transform: rotateX(60) rotateY(60) rotate(60) }
|
||||||
|
to { transform: none; }
|
||||||
|
}
|
35
e2e/animation/app/css-animations/3d-rotate/page.ts
Normal file
35
e2e/animation/app/css-animations/3d-rotate/page.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { EventData, Page } from "tns-core-modules/ui/page";
|
||||||
|
import { View } from "tns-core-modules/ui/core/view";
|
||||||
|
import { Point3D } from "tns-core-modules/ui/animation/animation";
|
||||||
|
|
||||||
|
let view: View;
|
||||||
|
|
||||||
|
export function pageLoaded(args: EventData) {
|
||||||
|
const page = <Page>args.object;
|
||||||
|
view = page.getViewById<View>("view");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onAnimateX(args: EventData) {
|
||||||
|
view.className = "original";
|
||||||
|
view.className = "animate-x";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onAnimateY(args: EventData) {
|
||||||
|
view.className = "original";
|
||||||
|
view.className = "animate-y";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onAnimateZ(args: EventData) {
|
||||||
|
view.className = "original";
|
||||||
|
view.className = "animate-z";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onAnimateXYZ3D(args: EventData) {
|
||||||
|
view.className = "original";
|
||||||
|
view.className = "animate-xyz-3d";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onAnimateXYZ(args: EventData) {
|
||||||
|
view.className = "original";
|
||||||
|
view.className = "animate-xyz";
|
||||||
|
}
|
22
e2e/animation/app/css-animations/3d-rotate/page.xml
Normal file
22
e2e/animation/app/css-animations/3d-rotate/page.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
|
||||||
|
<ActionBar title="Rotate" />
|
||||||
|
|
||||||
|
<GridLayout rows="auto auto auto *" columns="* * *">
|
||||||
|
<Image src="~/res/icon_100x100.png" width="30" height="30" col="0" row="0" class="rotate-x"/>
|
||||||
|
<Image src="~/res/icon_100x100.png" width="30" height="30" col="1" row="0" class="rotate-y"/>
|
||||||
|
<Image src="~/res/icon_100x100.png" width="30" height="30" col="2" row="0" class="rotate-z"/>
|
||||||
|
|
||||||
|
<Button text="X" tap="onAnimateX" col="0" row="1"/>
|
||||||
|
<Button text="Y" tap="onAnimateY" col="1" row="1"/>
|
||||||
|
<Button text="Z" tap="onAnimateZ" col="2" row="1"/>
|
||||||
|
|
||||||
|
<Button text="XYZ" tap="onAnimateXYZ" row="2" col="0"/>
|
||||||
|
<Button text="XYZ-3D" tap="onAnimateXYZ3D" row="2" col="1"/>
|
||||||
|
|
||||||
|
<AbsoluteLayout width="300" height="300" clipToBounds="true" backgroundColor="LightGray" row="3" colSpan="3">
|
||||||
|
<Image id="view" src="~/res/icon_100x100.png"
|
||||||
|
width="100" height="100"
|
||||||
|
left="100" top="100" />
|
||||||
|
</AbsoluteLayout>
|
||||||
|
</GridLayout>
|
||||||
|
</Page>
|
@ -11,6 +11,6 @@ export function pageLoaded(args: EventData) {
|
|||||||
export function onButtonTap(args: EventData) {
|
export function onButtonTap(args: EventData) {
|
||||||
const clickedButton = <Button>args.object;
|
const clickedButton = <Button>args.object;
|
||||||
|
|
||||||
const destination = clickedButton.text + "/page";
|
const destination = "css-animations/" + clickedButton.text + "/page";
|
||||||
currentFrame.navigate(destination);
|
currentFrame.navigate(destination);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<Button text="settings" tap="onButtonTap"/>
|
<Button text="settings" tap="onButtonTap"/>
|
||||||
<Button text="visual-states" tap="onButtonTap"/>
|
<Button text="visual-states" tap="onButtonTap"/>
|
||||||
<Button text="initial-animation" tap="onButtonTap"/>
|
<Button text="initial-animation" tap="onButtonTap"/>
|
||||||
|
<Button text="3d-rotate" tap="onButtonTap"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</Page>
|
</Page>
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
<Button text="slide-in-effect" tap="onButtonTap" />
|
<Button text="slide-in-effect" tap="onButtonTap" />
|
||||||
<Button text="infinite" tap="onButtonTap" />
|
<Button text="infinite" tap="onButtonTap" />
|
||||||
<Button text="animation-curves" tap="onButtonTap" />
|
<Button text="animation-curves" tap="onButtonTap" />
|
||||||
<Button text="css-animations" tap="onButtonTap" />
|
<Button text="css-animations" tap="onButtonTap" />
|
||||||
|
<Button text="3d-rotate" tap="onButtonTap" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
@ -16,12 +16,15 @@ const TRANSFORM_MATRIXES = {
|
|||||||
0, 1, y,
|
0, 1, y,
|
||||||
0, 0, 1,
|
0, 0, 1,
|
||||||
],
|
],
|
||||||
"rotate": angleInDeg => {
|
"rotate": ({ x, y, z }) => {
|
||||||
const angleInRad = degreesToRadians(angleInDeg);
|
// TODO: Handle rotations over X and Y axis
|
||||||
|
const radZ = degreesToRadians(z);
|
||||||
|
const cosZ = Math.cos(radZ);
|
||||||
|
const sinZ = Math.sin(radZ);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Math.cos(angleInRad), -Math.sin(angleInRad), 0,
|
cosZ, -sinZ, 0,
|
||||||
Math.sin(angleInRad), Math.cos(angleInRad), 0,
|
sinZ, cosZ, 0,
|
||||||
0, 0, 1,
|
0, 0, 1,
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
@ -43,6 +46,7 @@ export function multiplyAffine2d(m1: number[], m2: number[]): number[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Decompose rotations over X and Y axis
|
||||||
export function decompose2DTransformMatrix(matrix: number[])
|
export function decompose2DTransformMatrix(matrix: number[])
|
||||||
: TransformFunctionsInfo {
|
: TransformFunctionsInfo {
|
||||||
|
|
||||||
@ -52,7 +56,7 @@ export function decompose2DTransformMatrix(matrix: number[])
|
|||||||
const determinant = A * D - B * C;
|
const determinant = A * D - B * C;
|
||||||
const translate = { x: E || 0, y: F || 0 };
|
const translate = { x: E || 0, y: F || 0 };
|
||||||
|
|
||||||
// rewrite with obj desctructuring using the identity matrix
|
// rewrite with obj destructuring using the identity matrix
|
||||||
let rotate = 0;
|
let rotate = 0;
|
||||||
let scale = { x: 1, y: 1 };
|
let scale = { x: 1, y: 1 };
|
||||||
if (A || B) {
|
if (A || B) {
|
||||||
@ -67,7 +71,7 @@ export function decompose2DTransformMatrix(matrix: number[])
|
|||||||
|
|
||||||
rotate = radiansToDegrees(rotate);
|
rotate = radiansToDegrees(rotate);
|
||||||
|
|
||||||
return { translate, rotate, scale };
|
return { translate, rotate: { x: 0, y: 0, z: rotate }, scale };
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyTransformMatrix(matrix: number[]) {
|
function verifyTransformMatrix(matrix: number[]) {
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"tslib": "1.10.0"
|
"tslib": "1.10.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/chai": "~4.2.5",
|
||||||
"@types/node": "~10.12.18",
|
"@types/node": "~10.12.18",
|
||||||
"tns-platform-declarations": "next"
|
"tns-platform-declarations": "next"
|
||||||
},
|
},
|
||||||
@ -58,4 +59,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,18 +1,19 @@
|
|||||||
// Types.
|
// Types.
|
||||||
import {
|
import {
|
||||||
CubicBezierAnimationCurve as CubicBezierAnimationCurveDefinition,
|
CubicBezierAnimationCurve as CubicBezierAnimationCurveDefinition,
|
||||||
Animation as AnimationBaseDefinition
|
Animation as AnimationBaseDefinition,
|
||||||
|
Point3D
|
||||||
} from ".";
|
} from ".";
|
||||||
import {
|
import {
|
||||||
AnimationDefinition, AnimationPromise as AnimationPromiseDefinition,
|
AnimationDefinition, AnimationPromise as AnimationPromiseDefinition,
|
||||||
Pair, PropertyAnimation
|
Pair, PropertyAnimation
|
||||||
} from "./animation-interfaces";
|
} from "./animation-interfaces";
|
||||||
|
|
||||||
// Requires.
|
// Requires.
|
||||||
import { Color } from "../../color";
|
import { Color } from "../../color";
|
||||||
import {
|
import {
|
||||||
isEnabled as traceEnabled, write as traceWrite,
|
isEnabled as traceEnabled, write as traceWrite,
|
||||||
categories as traceCategories, messageType as traceType
|
categories as traceCategories, messageType as traceType
|
||||||
} from "../../trace";
|
} from "../../trace";
|
||||||
import { PercentLength } from "../styling/style-properties";
|
import { PercentLength } from "../styling/style-properties";
|
||||||
|
|
||||||
@ -150,24 +151,30 @@ export abstract class AnimationBase implements AnimationBaseDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let item in animationDefinition) {
|
for (let item in animationDefinition) {
|
||||||
if (animationDefinition[item] === undefined) {
|
const value = animationDefinition[item];
|
||||||
|
if (value === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((item === Properties.opacity ||
|
if ((item === Properties.opacity ||
|
||||||
item === Properties.rotate ||
|
|
||||||
item === "duration" ||
|
item === "duration" ||
|
||||||
item === "delay" ||
|
item === "delay" ||
|
||||||
item === "iterations") && typeof animationDefinition[item] !== "number") {
|
item === "iterations") && typeof value !== "number") {
|
||||||
throw new Error(`Property ${item} must be valid number. Value: ${animationDefinition[item]}`);
|
throw new Error(`Property ${item} must be valid number. Value: ${value}`);
|
||||||
} else if ((item === Properties.scale || item === Properties.translate) &&
|
} else if ((item === Properties.scale || item === Properties.translate) &&
|
||||||
(typeof (<Pair>animationDefinition[item]).x !== "number" || typeof (<Pair>animationDefinition[item]).y !== "number")) {
|
(typeof (<Pair>value).x !== "number" || typeof (<Pair>value).y !== "number")) {
|
||||||
throw new Error(`Property ${item} must be valid Pair. Value: ${animationDefinition[item]}`);
|
throw new Error(`Property ${item} must be valid Pair. Value: ${value}`);
|
||||||
} else if (item === Properties.backgroundColor && !Color.isValid(animationDefinition.backgroundColor)) {
|
} else if (item === Properties.backgroundColor && !Color.isValid(animationDefinition.backgroundColor)) {
|
||||||
throw new Error(`Property ${item} must be valid color. Value: ${animationDefinition[item]}`);
|
throw new Error(`Property ${item} must be valid color. Value: ${value}`);
|
||||||
} else if (item === Properties.width || item === Properties.height) {
|
} else if (item === Properties.width || item === Properties.height) {
|
||||||
// Coerce input into a PercentLength object in case it's a string.
|
// Coerce input into a PercentLength object in case it's a string.
|
||||||
animationDefinition[item] = PercentLength.parse(<any>animationDefinition[item]);
|
animationDefinition[item] = PercentLength.parse(<any>value);
|
||||||
|
} else if (item === Properties.rotate) {
|
||||||
|
const rotate: number | Point3D = value;
|
||||||
|
if ((typeof rotate !== "number") &&
|
||||||
|
!(typeof rotate.x === "number" && typeof rotate.y === "number" && typeof rotate.z === "number")) {
|
||||||
|
throw new Error(`Property ${rotate} must be valid number or Point3D. Value: ${value}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,10 +235,18 @@ export abstract class AnimationBase implements AnimationBaseDefinition {
|
|||||||
|
|
||||||
// rotate
|
// rotate
|
||||||
if (animationDefinition.rotate !== undefined) {
|
if (animationDefinition.rotate !== undefined) {
|
||||||
|
// Make sure the value of the rotation property is always Point3D
|
||||||
|
let rotationValue: Point3D;
|
||||||
|
if (typeof animationDefinition.rotate === "number") {
|
||||||
|
rotationValue = { x: 0, y: 0, z: animationDefinition.rotate };
|
||||||
|
} else {
|
||||||
|
rotationValue = animationDefinition.rotate;
|
||||||
|
}
|
||||||
|
|
||||||
propertyAnimations.push({
|
propertyAnimations.push({
|
||||||
target: animationDefinition.target,
|
target: animationDefinition.target,
|
||||||
property: Properties.rotate,
|
property: Properties.rotate,
|
||||||
value: animationDefinition.rotate,
|
value: rotationValue,
|
||||||
duration: animationDefinition.duration,
|
duration: animationDefinition.duration,
|
||||||
delay: animationDefinition.delay,
|
delay: animationDefinition.delay,
|
||||||
iterations: animationDefinition.iterations,
|
iterations: animationDefinition.iterations,
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
traceEnabled, traceCategories, traceType
|
traceEnabled, traceCategories, traceType
|
||||||
} from "./animation-common";
|
} from "./animation-common";
|
||||||
import {
|
import {
|
||||||
opacityProperty, backgroundColorProperty, rotateProperty,
|
opacityProperty, backgroundColorProperty, rotateProperty, rotateXProperty, rotateYProperty,
|
||||||
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty,
|
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty,
|
||||||
heightProperty, widthProperty, PercentLength
|
heightProperty, widthProperty, PercentLength
|
||||||
} from "../styling/style-properties";
|
} from "../styling/style-properties";
|
||||||
@ -91,6 +91,36 @@ export function _resolveAnimationCurve(curve: string | CubicBezierAnimationCurve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAndroidRepeatCount(iterations: number): number {
|
||||||
|
return (iterations === Number.POSITIVE_INFINITY) ? android.view.animation.Animation.INFINITE : iterations - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createObjectAnimator(nativeView: android.view.View, propertyName: string, value: number): android.animation.ObjectAnimator {
|
||||||
|
let arr = Array.create("float", 1);
|
||||||
|
arr[0] = value;
|
||||||
|
|
||||||
|
return android.animation.ObjectAnimator.ofFloat(nativeView, propertyName, arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAnimationSet(animators: android.animation.ObjectAnimator[], iterations: number): android.animation.AnimatorSet {
|
||||||
|
iterations = getAndroidRepeatCount(iterations);
|
||||||
|
|
||||||
|
const animatorSet = new android.animation.AnimatorSet();
|
||||||
|
const animatorsArray = Array.create(android.animation.Animator, animators.length);
|
||||||
|
|
||||||
|
animators.forEach((animator, index) => {
|
||||||
|
animatorsArray[index] = animator;
|
||||||
|
|
||||||
|
//TODO: not sure if we have to do that for each animator
|
||||||
|
animatorsArray[index].setRepeatCount(iterations);
|
||||||
|
});
|
||||||
|
|
||||||
|
animatorSet.playTogether(animatorsArray);
|
||||||
|
animatorSet.setupStartValues();
|
||||||
|
|
||||||
|
return animatorSet;
|
||||||
|
}
|
||||||
|
|
||||||
export class Animation extends AnimationBase {
|
export class Animation extends AnimationBase {
|
||||||
private _animatorListener: android.animation.Animator.AnimatorListener;
|
private _animatorListener: android.animation.Animator.AnimatorListener;
|
||||||
private _nativeAnimatorsArray: any;
|
private _nativeAnimatorsArray: any;
|
||||||
@ -264,16 +294,14 @@ export class Animation extends AnimationBase {
|
|||||||
|
|
||||||
this._target = propertyAnimation.target;
|
this._target = propertyAnimation.target;
|
||||||
|
|
||||||
let nativeArray;
|
|
||||||
const nativeView = <android.view.View>propertyAnimation.target.nativeViewProtected;
|
const nativeView = <android.view.View>propertyAnimation.target.nativeViewProtected;
|
||||||
const animators = new Array<android.animation.Animator>();
|
const animators = new Array<android.animation.Animator>();
|
||||||
const propertyUpdateCallbacks = new Array<Function>();
|
const propertyUpdateCallbacks = new Array<Function>();
|
||||||
const propertyResetCallbacks = new Array<Function>();
|
const propertyResetCallbacks = new Array<Function>();
|
||||||
let originalValue1;
|
let originalValue1;
|
||||||
let originalValue2;
|
let originalValue2;
|
||||||
|
let originalValue3;
|
||||||
const density = layout.getDisplayDensity();
|
const density = layout.getDisplayDensity();
|
||||||
let xyObjectAnimators: any;
|
|
||||||
let animatorSet: android.animation.AnimatorSet;
|
|
||||||
|
|
||||||
let key = propertyKeys[propertyAnimation.property];
|
let key = propertyKeys[propertyAnimation.property];
|
||||||
if (key) {
|
if (key) {
|
||||||
@ -295,8 +323,6 @@ export class Animation extends AnimationBase {
|
|||||||
opacityProperty._initDefaultNativeValue(style);
|
opacityProperty._initDefaultNativeValue(style);
|
||||||
|
|
||||||
originalValue1 = nativeView.getAlpha();
|
originalValue1 = nativeView.getAlpha();
|
||||||
nativeArray = Array.create("float", 1);
|
|
||||||
nativeArray[0] = propertyAnimation.value;
|
|
||||||
propertyUpdateCallbacks.push(checkAnimation(() => {
|
propertyUpdateCallbacks.push(checkAnimation(() => {
|
||||||
propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value;
|
propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value;
|
||||||
}));
|
}));
|
||||||
@ -310,7 +336,7 @@ export class Animation extends AnimationBase {
|
|||||||
propertyAnimation.target[opacityProperty.setNative](propertyAnimation.target.style.opacity);
|
propertyAnimation.target[opacityProperty.setNative](propertyAnimation.target.style.opacity);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "alpha", nativeArray));
|
animators.push(createObjectAnimator(nativeView, "alpha", propertyAnimation.value));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Properties.backgroundColor:
|
case Properties.backgroundColor:
|
||||||
@ -318,7 +344,7 @@ export class Animation extends AnimationBase {
|
|||||||
|
|
||||||
ensureArgbEvaluator();
|
ensureArgbEvaluator();
|
||||||
originalValue1 = propertyAnimation.target.backgroundColor;
|
originalValue1 = propertyAnimation.target.backgroundColor;
|
||||||
nativeArray = Array.create(java.lang.Object, 2);
|
const nativeArray = Array.create(java.lang.Object, 2);
|
||||||
nativeArray[0] = propertyAnimation.target.backgroundColor ? java.lang.Integer.valueOf((<Color>propertyAnimation.target.backgroundColor).argb) : java.lang.Integer.valueOf(-1);
|
nativeArray[0] = propertyAnimation.target.backgroundColor ? java.lang.Integer.valueOf((<Color>propertyAnimation.target.backgroundColor).argb) : java.lang.Integer.valueOf(-1);
|
||||||
nativeArray[1] = java.lang.Integer.valueOf((<Color>propertyAnimation.value).argb);
|
nativeArray[1] = java.lang.Integer.valueOf((<Color>propertyAnimation.value).argb);
|
||||||
let animator = android.animation.ValueAnimator.ofObject(argbEvaluator, nativeArray);
|
let animator = android.animation.ValueAnimator.ofObject(argbEvaluator, nativeArray);
|
||||||
@ -350,18 +376,6 @@ export class Animation extends AnimationBase {
|
|||||||
translateXProperty._initDefaultNativeValue(style);
|
translateXProperty._initDefaultNativeValue(style);
|
||||||
translateYProperty._initDefaultNativeValue(style);
|
translateYProperty._initDefaultNativeValue(style);
|
||||||
|
|
||||||
xyObjectAnimators = Array.create(android.animation.Animator, 2);
|
|
||||||
|
|
||||||
nativeArray = Array.create("float", 1);
|
|
||||||
nativeArray[0] = propertyAnimation.value.x * density;
|
|
||||||
xyObjectAnimators[0] = android.animation.ObjectAnimator.ofFloat(nativeView, "translationX", nativeArray);
|
|
||||||
xyObjectAnimators[0].setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
|
|
||||||
|
|
||||||
nativeArray = Array.create("float", 1);
|
|
||||||
nativeArray[0] = propertyAnimation.value.y * density;
|
|
||||||
xyObjectAnimators[1] = android.animation.ObjectAnimator.ofFloat(nativeView, "translationY", nativeArray);
|
|
||||||
xyObjectAnimators[1].setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
|
|
||||||
|
|
||||||
originalValue1 = nativeView.getTranslationX() / density;
|
originalValue1 = nativeView.getTranslationX() / density;
|
||||||
originalValue2 = nativeView.getTranslationY() / density;
|
originalValue2 = nativeView.getTranslationY() / density;
|
||||||
|
|
||||||
@ -385,28 +399,17 @@ export class Animation extends AnimationBase {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
animatorSet = new android.animation.AnimatorSet();
|
animators.push(
|
||||||
animatorSet.playTogether(xyObjectAnimators);
|
createAnimationSet([
|
||||||
animatorSet.setupStartValues();
|
createObjectAnimator(nativeView, "translationX", propertyAnimation.value.x * density),
|
||||||
animators.push(animatorSet);
|
createObjectAnimator(nativeView, "translationY", propertyAnimation.value.y * density)
|
||||||
|
], propertyAnimation.iterations));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Properties.scale:
|
case Properties.scale:
|
||||||
scaleXProperty._initDefaultNativeValue(style);
|
scaleXProperty._initDefaultNativeValue(style);
|
||||||
scaleYProperty._initDefaultNativeValue(style);
|
scaleYProperty._initDefaultNativeValue(style);
|
||||||
|
|
||||||
xyObjectAnimators = Array.create(android.animation.Animator, 2);
|
|
||||||
|
|
||||||
nativeArray = Array.create("float", 1);
|
|
||||||
nativeArray[0] = propertyAnimation.value.x;
|
|
||||||
xyObjectAnimators[0] = android.animation.ObjectAnimator.ofFloat(nativeView, "scaleX", nativeArray);
|
|
||||||
xyObjectAnimators[0].setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
|
|
||||||
|
|
||||||
nativeArray = Array.create("float", 1);
|
|
||||||
nativeArray[0] = propertyAnimation.value.y;
|
|
||||||
xyObjectAnimators[1] = android.animation.ObjectAnimator.ofFloat(nativeView, "scaleY", nativeArray);
|
|
||||||
xyObjectAnimators[1].setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
|
|
||||||
|
|
||||||
originalValue1 = nativeView.getScaleX();
|
originalValue1 = nativeView.getScaleX();
|
||||||
originalValue2 = nativeView.getScaleY();
|
originalValue2 = nativeView.getScaleY();
|
||||||
|
|
||||||
@ -430,34 +433,53 @@ export class Animation extends AnimationBase {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
animatorSet = new android.animation.AnimatorSet();
|
animators.push(
|
||||||
animatorSet.playTogether(xyObjectAnimators);
|
createAnimationSet([
|
||||||
animatorSet.setupStartValues();
|
createObjectAnimator(nativeView, "scaleX", propertyAnimation.value.x),
|
||||||
animators.push(animatorSet);
|
createObjectAnimator(nativeView, "scaleY", propertyAnimation.value.y)
|
||||||
|
], propertyAnimation.iterations));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Properties.rotate:
|
case Properties.rotate:
|
||||||
rotateProperty._initDefaultNativeValue(style);
|
rotateProperty._initDefaultNativeValue(style);
|
||||||
|
rotateXProperty._initDefaultNativeValue(style);
|
||||||
|
rotateYProperty._initDefaultNativeValue(style);
|
||||||
|
|
||||||
|
originalValue1 = nativeView.getRotationX();
|
||||||
|
originalValue2 = nativeView.getRotationY();
|
||||||
|
originalValue3 = nativeView.getRotation();
|
||||||
|
|
||||||
originalValue1 = nativeView.getRotation();
|
|
||||||
nativeArray = Array.create("float", 1);
|
|
||||||
nativeArray[0] = propertyAnimation.value;
|
|
||||||
propertyUpdateCallbacks.push(checkAnimation(() => {
|
propertyUpdateCallbacks.push(checkAnimation(() => {
|
||||||
propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value;
|
propertyAnimation.target.style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = propertyAnimation.value.x;
|
||||||
|
propertyAnimation.target.style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = propertyAnimation.value.y;
|
||||||
|
propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value.z;
|
||||||
}));
|
}));
|
||||||
propertyResetCallbacks.push(checkAnimation(() => {
|
propertyResetCallbacks.push(checkAnimation(() => {
|
||||||
if (setLocal) {
|
if (setLocal) {
|
||||||
propertyAnimation.target.style[rotateProperty.name] = originalValue1;
|
propertyAnimation.target.style[rotateXProperty.name] = originalValue1;
|
||||||
|
propertyAnimation.target.style[rotateYProperty.name] = originalValue2;
|
||||||
|
propertyAnimation.target.style[rotateProperty.name] = originalValue3;
|
||||||
} else {
|
} else {
|
||||||
propertyAnimation.target.style[rotateProperty.keyframe] = originalValue1;
|
propertyAnimation.target.style[rotateXProperty.keyframe] = originalValue1;
|
||||||
|
propertyAnimation.target.style[rotateYProperty.keyframe] = originalValue2;
|
||||||
|
propertyAnimation.target.style[rotateProperty.keyframe] = originalValue3;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (propertyAnimation.target.nativeViewProtected) {
|
if (propertyAnimation.target.nativeViewProtected) {
|
||||||
propertyAnimation.target[rotateProperty.setNative](propertyAnimation.target.style.rotate);
|
propertyAnimation.target[rotateProperty.setNative](propertyAnimation.target.style.rotate);
|
||||||
|
propertyAnimation.target[rotateXProperty.setNative](propertyAnimation.target.style.rotateX);
|
||||||
|
propertyAnimation.target[rotateYProperty.setNative](propertyAnimation.target.style.rotateY);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "rotation", nativeArray));
|
|
||||||
|
animators.push(
|
||||||
|
createAnimationSet([
|
||||||
|
createObjectAnimator(nativeView, "rotationX", propertyAnimation.value.x),
|
||||||
|
createObjectAnimator(nativeView, "rotationY", propertyAnimation.value.y),
|
||||||
|
createObjectAnimator(nativeView, "rotation", propertyAnimation.value.z)
|
||||||
|
], propertyAnimation.iterations));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Properties.width:
|
case Properties.width:
|
||||||
case Properties.height: {
|
case Properties.height: {
|
||||||
|
|
||||||
@ -465,7 +487,7 @@ export class Animation extends AnimationBase {
|
|||||||
const extentProperty = isVertical ? heightProperty : widthProperty;
|
const extentProperty = isVertical ? heightProperty : widthProperty;
|
||||||
|
|
||||||
extentProperty._initDefaultNativeValue(style);
|
extentProperty._initDefaultNativeValue(style);
|
||||||
nativeArray = Array.create("float", 2);
|
const nativeArray = Array.create("float", 2);
|
||||||
let toValue = propertyAnimation.value;
|
let toValue = propertyAnimation.value;
|
||||||
let parent = propertyAnimation.target.parent as View;
|
let parent = propertyAnimation.target.parent as View;
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
@ -473,7 +495,7 @@ export class Animation extends AnimationBase {
|
|||||||
}
|
}
|
||||||
const parentExtent: number = isVertical ? parent.getMeasuredHeight() : parent.getMeasuredWidth();
|
const parentExtent: number = isVertical ? parent.getMeasuredHeight() : parent.getMeasuredWidth();
|
||||||
toValue = PercentLength.toDevicePixels(toValue, parentExtent, parentExtent) / screen.mainScreen.scale;
|
toValue = PercentLength.toDevicePixels(toValue, parentExtent, parentExtent) / screen.mainScreen.scale;
|
||||||
const nativeHeight: number = isVertical ? nativeView.getHeight() : nativeView.getWidth();
|
let nativeHeight: number = isVertical ? nativeView.getHeight() : nativeView.getWidth();
|
||||||
const targetStyle: string = setLocal ? extentProperty.name : extentProperty.keyframe;
|
const targetStyle: string = setLocal ? extentProperty.name : extentProperty.keyframe;
|
||||||
originalValue1 = nativeHeight / screen.mainScreen.scale;
|
originalValue1 = nativeHeight / screen.mainScreen.scale;
|
||||||
nativeArray[0] = originalValue1;
|
nativeArray[0] = originalValue1;
|
||||||
@ -516,7 +538,7 @@ export class Animation extends AnimationBase {
|
|||||||
|
|
||||||
// Repeat Count
|
// Repeat Count
|
||||||
if (propertyAnimation.iterations !== undefined && animators[i] instanceof android.animation.ValueAnimator) {
|
if (propertyAnimation.iterations !== undefined && animators[i] instanceof android.animation.ValueAnimator) {
|
||||||
(<android.animation.ValueAnimator>animators[i]).setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
|
(<android.animation.ValueAnimator>animators[i]).setRepeatCount(getAndroidRepeatCount(propertyAnimation.iterations));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolator
|
// Interpolator
|
||||||
@ -533,8 +555,4 @@ export class Animation extends AnimationBase {
|
|||||||
this._propertyUpdateCallbacks = this._propertyUpdateCallbacks.concat(propertyUpdateCallbacks);
|
this._propertyUpdateCallbacks = this._propertyUpdateCallbacks.concat(propertyUpdateCallbacks);
|
||||||
this._propertyResetCallbacks = this._propertyResetCallbacks.concat(propertyResetCallbacks);
|
this._propertyResetCallbacks = this._propertyResetCallbacks.concat(propertyResetCallbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _getAndroidRepeatCount(iterations: number): number {
|
|
||||||
return (iterations === Number.POSITIVE_INFINITY) ? android.view.animation.Animation.INFINITE : iterations - 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
18
nativescript-core/ui/animation/animation.d.ts
vendored
18
nativescript-core/ui/animation/animation.d.ts
vendored
@ -47,7 +47,7 @@ export interface AnimationDefinition {
|
|||||||
/**
|
/**
|
||||||
* Animates the rotate affine transform of the view. Value should be a number specifying the rotation amount in degrees.
|
* Animates the rotate affine transform of the view. Value should be a number specifying the rotation amount in degrees.
|
||||||
*/
|
*/
|
||||||
rotate?: number;
|
rotate?: number | Point3D;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of the animation in milliseconds. The default duration is 300 milliseconds.
|
* The length of the animation in milliseconds. The default duration is 300 milliseconds.
|
||||||
@ -97,14 +97,24 @@ export type Transformation = {
|
|||||||
/**
|
/**
|
||||||
* Defines possible css transformations
|
* Defines possible css transformations
|
||||||
*/
|
*/
|
||||||
export type TransformationType = "rotate" |
|
export type TransformationType =
|
||||||
|
"rotate" | "rotateX" | "rotateY" |
|
||||||
"translate" | "translateX" | "translateY" |
|
"translate" | "translateX" | "translateY" |
|
||||||
"scale" | "scaleX" | "scaleY";
|
"scale" | "scaleX" | "scaleY";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines possible css transformation values
|
* Defines possible css transformation values
|
||||||
*/
|
*/
|
||||||
export type TransformationValue = Pair | number;
|
export type TransformationValue = Point3D | Pair | number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a point in 3d space (x, y and z) for rotation in 3d animations.
|
||||||
|
*/
|
||||||
|
export interface Point3D {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a pair of values (horizontal and vertical) for translate and scale animations.
|
* Defines a pair of values (horizontal and vertical) for translate and scale animations.
|
||||||
@ -119,7 +129,7 @@ export interface Pair {
|
|||||||
*/
|
*/
|
||||||
export type TransformFunctionsInfo = {
|
export type TransformFunctionsInfo = {
|
||||||
translate: Pair,
|
translate: Pair,
|
||||||
rotate: number,
|
rotate: Point3D,
|
||||||
scale: Pair,
|
scale: Pair,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
// Types
|
// Types
|
||||||
import {
|
import {
|
||||||
AnimationDefinitionInternal, AnimationPromise, IOSView,
|
AnimationDefinitionInternal, AnimationPromise, IOSView,
|
||||||
PropertyAnimation, PropertyAnimationInfo
|
PropertyAnimation, PropertyAnimationInfo
|
||||||
} from "./animation-common";
|
} from "./animation-common";
|
||||||
import { View } from "../core/view";
|
import { View } from "../core/view";
|
||||||
|
|
||||||
// Requires
|
// Requires
|
||||||
import {
|
import {
|
||||||
AnimationBase, Properties, CubicBezierAnimationCurve,
|
AnimationBase, Properties, CubicBezierAnimationCurve,
|
||||||
traceWrite, traceEnabled, traceCategories, traceType
|
traceWrite, traceEnabled, traceCategories, traceType
|
||||||
} from "./animation-common";
|
} from "./animation-common";
|
||||||
import {
|
import {
|
||||||
opacityProperty, backgroundColorProperty, rotateProperty,
|
opacityProperty, backgroundColorProperty, rotateProperty, rotateXProperty, rotateYProperty,
|
||||||
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty,
|
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty,
|
||||||
heightProperty, widthProperty, PercentLength
|
heightProperty, widthProperty, PercentLength
|
||||||
} from "../styling/style-properties";
|
} from "../styling/style-properties";
|
||||||
|
|
||||||
|
import { ios as iosNativeHelper } from "../../utils/native-helper";
|
||||||
|
|
||||||
import { screen } from "../../platform";
|
import { screen } from "../../platform";
|
||||||
|
|
||||||
export * from "./animation-common";
|
export * from "./animation-common";
|
||||||
@ -27,6 +29,7 @@ let FLT_MAX = 340282346638528859811704183484516925440.000000;
|
|||||||
|
|
||||||
class AnimationInfo {
|
class AnimationInfo {
|
||||||
public propertyNameToAnimate: string;
|
public propertyNameToAnimate: string;
|
||||||
|
public subPropertiesToAnimate?: string[];
|
||||||
public fromValue: any;
|
public fromValue: any;
|
||||||
public toValue: any;
|
public toValue: any;
|
||||||
public duration: number;
|
public duration: number;
|
||||||
@ -69,7 +72,9 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate {
|
|||||||
targetStyle[setLocal ? opacityProperty.name : opacityProperty.keyframe] = value;
|
targetStyle[setLocal ? opacityProperty.name : opacityProperty.keyframe] = value;
|
||||||
break;
|
break;
|
||||||
case Properties.rotate:
|
case Properties.rotate:
|
||||||
targetStyle[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value;
|
targetStyle[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = value.x;
|
||||||
|
targetStyle[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value.y;
|
||||||
|
targetStyle[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value.z;
|
||||||
break;
|
break;
|
||||||
case Properties.translate:
|
case Properties.translate:
|
||||||
targetStyle[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.x;
|
targetStyle[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.x;
|
||||||
@ -90,6 +95,11 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate {
|
|||||||
targetStyle[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value[Properties.translate].x;
|
targetStyle[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value[Properties.translate].x;
|
||||||
targetStyle[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value[Properties.translate].y;
|
targetStyle[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value[Properties.translate].y;
|
||||||
}
|
}
|
||||||
|
if (value[Properties.rotate] !== undefined) {
|
||||||
|
targetStyle[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = value[Properties.rotate].x;
|
||||||
|
targetStyle[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value[Properties.rotate].y;
|
||||||
|
targetStyle[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value[Properties.rotate].z;
|
||||||
|
}
|
||||||
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;
|
||||||
@ -267,24 +277,23 @@ export class Animation extends AnimationBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static _getNativeAnimationArguments(animation: PropertyAnimationInfo, valueSource: "animation" | "keyframe"): AnimationInfo {
|
private static _getNativeAnimationArguments(animation: PropertyAnimationInfo, valueSource: "animation" | "keyframe"): AnimationInfo {
|
||||||
|
const view = animation.target;
|
||||||
let nativeView = <UIView>animation.target.nativeViewProtected;
|
const style = view.style;
|
||||||
let propertyNameToAnimate = animation.property;
|
const nativeView = <UIView>view.nativeViewProtected;
|
||||||
let toValue = animation.value;
|
const parent = view.parent as View;
|
||||||
let fromValue;
|
|
||||||
const parent = animation.target.parent as View;
|
|
||||||
const screenScale: number = screen.mainScreen.scale;
|
const screenScale: number = screen.mainScreen.scale;
|
||||||
|
|
||||||
let tempRotate = (animation.target.rotate || 0) * Math.PI / 180;
|
let propertyNameToAnimate = animation.property;
|
||||||
let abs;
|
let subPropertyNameToAnimate;
|
||||||
|
let toValue = animation.value;
|
||||||
|
let fromValue;
|
||||||
let setLocal = valueSource === "animation";
|
let setLocal = valueSource === "animation";
|
||||||
|
|
||||||
switch (animation.property) {
|
switch (animation.property) {
|
||||||
case Properties.backgroundColor:
|
case Properties.backgroundColor:
|
||||||
animation._originalValue = animation.target.backgroundColor;
|
animation._originalValue = view.backgroundColor;
|
||||||
animation._propertyResetCallback = (value, valueSource) => {
|
animation._propertyResetCallback = (value, valueSource) => {
|
||||||
animation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = value;
|
style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = value;
|
||||||
};
|
};
|
||||||
fromValue = nativeView.layer.backgroundColor;
|
fromValue = nativeView.layer.backgroundColor;
|
||||||
if (nativeView instanceof UILabel) {
|
if (nativeView instanceof UILabel) {
|
||||||
@ -293,33 +302,50 @@ export class Animation extends AnimationBase {
|
|||||||
toValue = toValue.CGColor;
|
toValue = toValue.CGColor;
|
||||||
break;
|
break;
|
||||||
case Properties.opacity:
|
case Properties.opacity:
|
||||||
animation._originalValue = animation.target.opacity;
|
animation._originalValue = view.opacity;
|
||||||
animation._propertyResetCallback = (value, valueSource) => {
|
animation._propertyResetCallback = (value, valueSource) => {
|
||||||
animation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = value;
|
style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = value;
|
||||||
};
|
};
|
||||||
fromValue = nativeView.layer.opacity;
|
fromValue = nativeView.layer.opacity;
|
||||||
break;
|
break;
|
||||||
case Properties.rotate:
|
case Properties.rotate:
|
||||||
animation._originalValue = animation.target.rotate !== undefined ? animation.target.rotate : 0;
|
animation._originalValue = { x: view.rotateX, y: view.rotateY, z: view.rotate };
|
||||||
animation._propertyResetCallback = (value, valueSource) => {
|
animation._propertyResetCallback = (value, valueSource) => {
|
||||||
animation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value;
|
style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value.z;
|
||||||
|
style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = value.x;
|
||||||
|
style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value.y;
|
||||||
};
|
};
|
||||||
|
|
||||||
propertyNameToAnimate = "transform.rotation";
|
propertyNameToAnimate = "transform.rotation";
|
||||||
fromValue = nativeView.layer.valueForKeyPath("transform.rotation");
|
subPropertyNameToAnimate = ["x", "y", "z"];
|
||||||
|
fromValue = {
|
||||||
|
x: nativeView.layer.valueForKeyPath("transform.rotation.x"),
|
||||||
|
y: nativeView.layer.valueForKeyPath("transform.rotation.y"),
|
||||||
|
z: nativeView.layer.valueForKeyPath("transform.rotation.z")
|
||||||
|
};
|
||||||
|
|
||||||
|
if (animation.target.rotateX !== undefined && animation.target.rotateX !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
|
||||||
|
fromValue.x = animation.target.rotateX * Math.PI / 180;
|
||||||
|
}
|
||||||
|
if (animation.target.rotateY !== undefined && animation.target.rotateY !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
|
||||||
|
fromValue.y = animation.target.rotateY * Math.PI / 180;
|
||||||
|
}
|
||||||
if (animation.target.rotate !== undefined && animation.target.rotate !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
|
if (animation.target.rotate !== undefined && animation.target.rotate !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
|
||||||
fromValue = animation.target.rotate * Math.PI / 180;
|
fromValue.z = animation.target.rotate * Math.PI / 180;
|
||||||
}
|
|
||||||
toValue = toValue * Math.PI / 180;
|
|
||||||
abs = fabs(fromValue - toValue);
|
|
||||||
if (abs < 0.001 && fromValue !== tempRotate) {
|
|
||||||
fromValue = tempRotate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Respect only value.z for back-compat until 3D rotations are implemented
|
||||||
|
toValue = {
|
||||||
|
x: toValue.x * Math.PI / 180,
|
||||||
|
y: toValue.y * Math.PI / 180,
|
||||||
|
z: toValue.z * Math.PI / 180
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
case Properties.translate:
|
case Properties.translate:
|
||||||
animation._originalValue = { x: animation.target.translateX, y: animation.target.translateY };
|
animation._originalValue = { x: view.translateX, y: view.translateY };
|
||||||
animation._propertyResetCallback = (value, valueSource) => {
|
animation._propertyResetCallback = (value, valueSource) => {
|
||||||
animation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.x;
|
style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.x;
|
||||||
animation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.y;
|
style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.y;
|
||||||
};
|
};
|
||||||
propertyNameToAnimate = "transform";
|
propertyNameToAnimate = "transform";
|
||||||
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
|
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
|
||||||
@ -332,10 +358,10 @@ export class Animation extends AnimationBase {
|
|||||||
if (toValue.y === 0) {
|
if (toValue.y === 0) {
|
||||||
toValue.y = 0.001;
|
toValue.y = 0.001;
|
||||||
}
|
}
|
||||||
animation._originalValue = { x: animation.target.scaleX, y: animation.target.scaleY };
|
animation._originalValue = { x: view.scaleX, y: view.scaleY };
|
||||||
animation._propertyResetCallback = (value, valueSource) => {
|
animation._propertyResetCallback = (value, valueSource) => {
|
||||||
animation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x;
|
style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x;
|
||||||
animation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y;
|
style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y;
|
||||||
};
|
};
|
||||||
propertyNameToAnimate = "transform";
|
propertyNameToAnimate = "transform";
|
||||||
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
|
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
|
||||||
@ -344,14 +370,18 @@ export class Animation extends AnimationBase {
|
|||||||
case _transform:
|
case _transform:
|
||||||
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
|
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
|
||||||
animation._originalValue = {
|
animation._originalValue = {
|
||||||
xs: animation.target.scaleX, ys: animation.target.scaleY,
|
xs: view.scaleX, ys: view.scaleY,
|
||||||
xt: animation.target.translateX, yt: animation.target.translateY
|
xt: view.translateX, yt: view.translateY,
|
||||||
|
rx: view.rotateX, ry: view.rotateY, rz: view.rotate
|
||||||
};
|
};
|
||||||
animation._propertyResetCallback = (value, valueSource) => {
|
animation._propertyResetCallback = (value, valueSource) => {
|
||||||
animation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.xt;
|
style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.xt;
|
||||||
animation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.yt;
|
style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.yt;
|
||||||
animation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.xs;
|
style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.xs;
|
||||||
animation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.ys;
|
style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.ys;
|
||||||
|
style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = value.rx;
|
||||||
|
style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value.ry;
|
||||||
|
style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value.rz;
|
||||||
};
|
};
|
||||||
propertyNameToAnimate = "transform";
|
propertyNameToAnimate = "transform";
|
||||||
toValue = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation));
|
toValue = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation));
|
||||||
@ -373,10 +403,10 @@ export class Animation extends AnimationBase {
|
|||||||
toValue = NSValue.valueWithCGRect(
|
toValue = NSValue.valueWithCGRect(
|
||||||
CGRectMake(currentBounds.origin.x, currentBounds.origin.y, extentX, extentY)
|
CGRectMake(currentBounds.origin.x, currentBounds.origin.y, extentX, extentY)
|
||||||
);
|
);
|
||||||
animation._originalValue = animation.target.height;
|
animation._originalValue = view.height;
|
||||||
animation._propertyResetCallback = (value, valueSource) => {
|
animation._propertyResetCallback = (value, valueSource) => {
|
||||||
const prop = isHeight ? heightProperty : widthProperty;
|
const prop = isHeight ? heightProperty : widthProperty;
|
||||||
animation.target.style[setLocal ? prop.name : prop.keyframe] = value;
|
style[setLocal ? prop.name : prop.keyframe] = value;
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -406,6 +436,7 @@ export class Animation extends AnimationBase {
|
|||||||
return {
|
return {
|
||||||
propertyNameToAnimate: propertyNameToAnimate,
|
propertyNameToAnimate: propertyNameToAnimate,
|
||||||
fromValue: fromValue,
|
fromValue: fromValue,
|
||||||
|
subPropertiesToAnimate: subPropertyNameToAnimate,
|
||||||
toValue: toValue,
|
toValue: toValue,
|
||||||
duration: duration,
|
duration: duration,
|
||||||
repeatCount: repeatCount,
|
repeatCount: repeatCount,
|
||||||
@ -416,18 +447,12 @@ export class Animation extends AnimationBase {
|
|||||||
private static _createNativeAnimation(propertyAnimations: Array<PropertyAnimation>, index: number, playSequentially: boolean, args: AnimationInfo, animation: PropertyAnimation, valueSource: "animation" | "keyframe", 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.nativeViewProtected;
|
let nativeView = <UIView>animation.target.nativeViewProtected;
|
||||||
let nativeAnimation = CABasicAnimation.animationWithKeyPath(args.propertyNameToAnimate);
|
let nativeAnimation;
|
||||||
nativeAnimation.fromValue = args.fromValue;
|
|
||||||
nativeAnimation.toValue = args.toValue;
|
if (args.subPropertiesToAnimate) {
|
||||||
nativeAnimation.duration = args.duration;
|
nativeAnimation = this._createGroupAnimation(args, animation);
|
||||||
if (args.repeatCount !== undefined) {
|
} else {
|
||||||
nativeAnimation.repeatCount = args.repeatCount;
|
nativeAnimation = this._createBasicAnimation(args, animation);
|
||||||
}
|
|
||||||
if (args.delay !== undefined) {
|
|
||||||
nativeAnimation.beginTime = CACurrentMediaTime() + args.delay;
|
|
||||||
}
|
|
||||||
if (animation.curve !== undefined) {
|
|
||||||
nativeAnimation.timingFunction = animation.curve;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let animationDelegate = AnimationDelegateImpl.initWithFinishedCallback(finishedCallback, animation, valueSource);
|
let animationDelegate = AnimationDelegateImpl.initWithFinishedCallback(finishedCallback, animation, valueSource);
|
||||||
@ -447,6 +472,44 @@ export class Animation extends AnimationBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static _createGroupAnimation(args: AnimationInfo, animation: PropertyAnimation) {
|
||||||
|
let groupAnimation = CAAnimationGroup.new();
|
||||||
|
groupAnimation.duration = args.duration;
|
||||||
|
const animations = NSMutableArray.alloc<CAAnimation>().initWithCapacity(3);
|
||||||
|
|
||||||
|
args.subPropertiesToAnimate.forEach(property => {
|
||||||
|
const basicAnimationArgs = { ...args };
|
||||||
|
basicAnimationArgs.propertyNameToAnimate = `${args.propertyNameToAnimate}.${property}`;
|
||||||
|
basicAnimationArgs.fromValue = args.fromValue[property];
|
||||||
|
basicAnimationArgs.toValue = args.toValue[property];
|
||||||
|
|
||||||
|
const basicAnimation = this._createBasicAnimation(basicAnimationArgs, animation);
|
||||||
|
animations.addObject(basicAnimation);
|
||||||
|
});
|
||||||
|
|
||||||
|
groupAnimation.animations = animations;
|
||||||
|
|
||||||
|
return groupAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _createBasicAnimation(args: AnimationInfo, animation: PropertyAnimation) {
|
||||||
|
let basicAnimation = CABasicAnimation.animationWithKeyPath(args.propertyNameToAnimate);
|
||||||
|
basicAnimation.fromValue = args.fromValue;
|
||||||
|
basicAnimation.toValue = args.toValue;
|
||||||
|
basicAnimation.duration = args.duration;
|
||||||
|
if (args.repeatCount !== undefined) {
|
||||||
|
basicAnimation.repeatCount = args.repeatCount;
|
||||||
|
}
|
||||||
|
if (args.delay !== undefined) {
|
||||||
|
basicAnimation.beginTime = CACurrentMediaTime() + args.delay;
|
||||||
|
}
|
||||||
|
if (animation.curve !== undefined) {
|
||||||
|
basicAnimation.timingFunction = animation.curve;
|
||||||
|
}
|
||||||
|
|
||||||
|
return basicAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
private static _createNativeSpringAnimation(propertyAnimations: Array<PropertyAnimationInfo>, index: number, playSequentially: boolean, args: AnimationInfo, animation: PropertyAnimationInfo, valueSource: "animation" | "keyframe", 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.nativeViewProtected;
|
let nativeView = <UIView>animation.target.nativeViewProtected;
|
||||||
@ -490,9 +553,6 @@ export class Animation extends AnimationBase {
|
|||||||
animation.target[animation.property] = value;
|
animation.target[animation.property] = value;
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case Properties.rotate:
|
|
||||||
nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate);
|
|
||||||
break;
|
|
||||||
case _transform:
|
case _transform:
|
||||||
animation._originalValue = nativeView.layer.transform;
|
animation._originalValue = nativeView.layer.transform;
|
||||||
nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate);
|
nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate);
|
||||||
@ -508,6 +568,11 @@ export class Animation extends AnimationBase {
|
|||||||
animation.target.translateX = animation.value[Properties.translate].x;
|
animation.target.translateX = animation.value[Properties.translate].x;
|
||||||
animation.target.translateY = animation.value[Properties.translate].y;
|
animation.target.translateY = animation.value[Properties.translate].y;
|
||||||
}
|
}
|
||||||
|
if (animation.value[Properties.rotate] !== undefined) {
|
||||||
|
animation.target.rotateX = animation.value[Properties.rotate].x;
|
||||||
|
animation.target.rotateY = animation.value[Properties.rotate].y;
|
||||||
|
animation.target.rotate = animation.value[Properties.rotate].z;
|
||||||
|
}
|
||||||
if (animation.value[Properties.scale] !== undefined) {
|
if (animation.value[Properties.scale] !== undefined) {
|
||||||
animation.target.scaleX = animation.value[Properties.scale].x;
|
animation.target.scaleX = animation.value[Properties.scale].x;
|
||||||
animation.target.scaleY = animation.value[Properties.scale].y;
|
animation.target.scaleY = animation.value[Properties.scale].y;
|
||||||
@ -631,19 +696,41 @@ export class Animation extends AnimationBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function _getTransformMismatchErrorMessage(view: View): string {
|
export function _getTransformMismatchErrorMessage(view: View): string {
|
||||||
// Order is important: translate, rotate, scale
|
const expectedTransform = calculateTransform(view);
|
||||||
let result: CGAffineTransform = CGAffineTransformIdentity;
|
const expectedTransformString = getCATransform3DString(expectedTransform);
|
||||||
const tx = view.translateX;
|
const actualTransformString = getCATransform3DString(view.nativeViewProtected.layer.transform);
|
||||||
const ty = view.translateY;
|
|
||||||
result = CGAffineTransformTranslate(result, tx, ty);
|
|
||||||
result = CGAffineTransformRotate(result, (view.rotate || 0) * Math.PI / 180);
|
|
||||||
result = CGAffineTransformScale(result, view.scaleX || 1, view.scaleY || 1);
|
|
||||||
let viewTransform = NSStringFromCGAffineTransform(result);
|
|
||||||
let nativeTransform = NSStringFromCGAffineTransform(view.nativeViewProtected.transform);
|
|
||||||
|
|
||||||
if (viewTransform !== nativeTransform) {
|
if (actualTransformString !== expectedTransformString) {
|
||||||
return "View and Native transforms do not match. View: " + viewTransform + "; Native: " + nativeTransform;
|
return "View and Native transforms do not match.\nActual: " + actualTransformString + ";\nExpected: " + expectedTransformString;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calculateTransform(view: View): CATransform3D {
|
||||||
|
const scaleX = view.scaleX || 1e-6;
|
||||||
|
const scaleY = view.scaleY || 1e-6;
|
||||||
|
const perspective = view.perspective || 300;
|
||||||
|
|
||||||
|
// Order is important: translate, rotate, scale
|
||||||
|
let expectedTransform = new CATransform3D(CATransform3DIdentity);
|
||||||
|
|
||||||
|
// Only set perspective if there is 3D rotation
|
||||||
|
if (view.rotateX || view.rotateY) {
|
||||||
|
expectedTransform.m34 = -1 / perspective;
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedTransform = CATransform3DTranslate(expectedTransform, view.translateX, view.translateY, 0);
|
||||||
|
expectedTransform = iosNativeHelper.applyRotateTransform(expectedTransform, view.rotateX, view.rotateY, view.rotate);
|
||||||
|
expectedTransform = CATransform3DScale(expectedTransform, scaleX, scaleY, 1);
|
||||||
|
|
||||||
|
return expectedTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCATransform3DString(t: CATransform3D) {
|
||||||
|
return `[
|
||||||
|
${t.m11}, ${t.m12}, ${t.m13}, ${t.m14},
|
||||||
|
${t.m21}, ${t.m22}, ${t.m23}, ${t.m24},
|
||||||
|
${t.m31}, ${t.m32}, ${t.m33}, ${t.m34},
|
||||||
|
${t.m41}, ${t.m42}, ${t.m43}, ${t.m44}]`;
|
||||||
|
}
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
backgroundColorProperty,
|
backgroundColorProperty,
|
||||||
scaleXProperty, scaleYProperty,
|
scaleXProperty, scaleYProperty,
|
||||||
translateXProperty, translateYProperty,
|
translateXProperty, translateYProperty,
|
||||||
rotateProperty, opacityProperty,
|
rotateProperty, opacityProperty, rotateXProperty, rotateYProperty,
|
||||||
widthProperty, heightProperty, PercentLength
|
widthProperty, heightProperty, PercentLength
|
||||||
} from "../styling/style-properties";
|
} from "../styling/style-properties";
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ interface Keyframe {
|
|||||||
backgroundColor?: Color;
|
backgroundColor?: Color;
|
||||||
scale?: { x: number, y: number };
|
scale?: { x: number, y: number };
|
||||||
translate?: { x: number, y: number };
|
translate?: { x: number, y: number };
|
||||||
rotate?: number;
|
rotate?: { x: number, y: number, z: number };
|
||||||
opacity?: number;
|
opacity?: number;
|
||||||
width?: PercentLength;
|
width?: PercentLength;
|
||||||
height?: PercentLength;
|
height?: PercentLength;
|
||||||
@ -213,7 +213,9 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
|
|||||||
view.style[translateYProperty.keyframe] = animation.translate.y;
|
view.style[translateYProperty.keyframe] = animation.translate.y;
|
||||||
}
|
}
|
||||||
if ("rotate" in animation) {
|
if ("rotate" in animation) {
|
||||||
view.style[rotateProperty.keyframe] = animation.rotate;
|
view.style[rotateXProperty.keyframe] = animation.rotate.x;
|
||||||
|
view.style[rotateYProperty.keyframe] = animation.rotate.y;
|
||||||
|
view.style[rotateProperty.keyframe] = animation.rotate.z;
|
||||||
}
|
}
|
||||||
if ("opacity" in animation) {
|
if ("opacity" in animation) {
|
||||||
view.style[opacityProperty.keyframe] = animation.opacity;
|
view.style[opacityProperty.keyframe] = animation.opacity;
|
||||||
|
@ -697,6 +697,27 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
|||||||
this.style.rotate = value;
|
this.style.rotate = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get rotateX(): number {
|
||||||
|
return this.style.rotateX;
|
||||||
|
}
|
||||||
|
set rotateX(value: number) {
|
||||||
|
this.style.rotateX = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get rotateY(): number {
|
||||||
|
return this.style.rotateY;
|
||||||
|
}
|
||||||
|
set rotateY(value: number) {
|
||||||
|
this.style.rotateY = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get perspective(): number {
|
||||||
|
return this.style.perspective;
|
||||||
|
}
|
||||||
|
set perspective(value: number) {
|
||||||
|
this.style.perspective = value;
|
||||||
|
}
|
||||||
|
|
||||||
get translateX(): dip {
|
get translateX(): dip {
|
||||||
return this.style.translateX;
|
return this.style.translateX;
|
||||||
}
|
}
|
||||||
|
@ -10,17 +10,18 @@ import {
|
|||||||
} from "./view-common";
|
} from "./view-common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Length, PercentLength, Visibility, HorizontalAlignment, VerticalAlignment,
|
perspectiveProperty, Length, PercentLength, Visibility, HorizontalAlignment, VerticalAlignment,
|
||||||
visibilityProperty, opacityProperty, horizontalAlignmentProperty, verticalAlignmentProperty,
|
visibilityProperty, opacityProperty, horizontalAlignmentProperty, verticalAlignmentProperty,
|
||||||
minWidthProperty, minHeightProperty, widthProperty, heightProperty,
|
minWidthProperty, minHeightProperty, widthProperty, heightProperty,
|
||||||
marginLeftProperty, marginTopProperty, marginRightProperty, marginBottomProperty,
|
marginLeftProperty, marginTopProperty, marginRightProperty, marginBottomProperty,
|
||||||
rotateProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty,
|
rotateProperty, rotateXProperty, rotateYProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty,
|
||||||
zIndexProperty, backgroundInternalProperty, androidElevationProperty, androidDynamicElevationOffsetProperty
|
zIndexProperty, backgroundInternalProperty, androidElevationProperty, androidDynamicElevationOffsetProperty
|
||||||
} from "../../styling/style-properties";
|
} from "../../styling/style-properties";
|
||||||
|
|
||||||
import { Background, ad as androidBackground } from "../../styling/background";
|
import { Background, ad as androidBackground } from "../../styling/background";
|
||||||
import { profile } from "../../../profiling";
|
import { profile } from "../../../profiling";
|
||||||
import { topmost } from "../../frame/frame-stack";
|
import { topmost } from "../../frame/frame-stack";
|
||||||
|
import { screen } from "../../../platform";
|
||||||
import { AndroidActivityBackPressedEventData, android as androidApp } from "../../../application";
|
import { AndroidActivityBackPressedEventData, android as androidApp } from "../../../application";
|
||||||
import { device } from "../../../platform";
|
import { device } from "../../../platform";
|
||||||
import lazy from "../../../utils/lazy";
|
import lazy from "../../../utils/lazy";
|
||||||
@ -911,6 +912,20 @@ export class View extends ViewCommon {
|
|||||||
org.nativescript.widgets.ViewHelper.setRotate(this.nativeViewProtected, float(value));
|
org.nativescript.widgets.ViewHelper.setRotate(this.nativeViewProtected, float(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[rotateXProperty.setNative](value: number) {
|
||||||
|
org.nativescript.widgets.ViewHelper.setRotateX(this.nativeViewProtected, float(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[rotateYProperty.setNative](value: number) {
|
||||||
|
org.nativescript.widgets.ViewHelper.setRotateY(this.nativeViewProtected, float(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[perspectiveProperty.setNative](value: number) {
|
||||||
|
const scale = screen.mainScreen.scale;
|
||||||
|
const distance = value * scale;
|
||||||
|
org.nativescript.widgets.ViewHelper.setPerspective(this.nativeViewProtected, float(distance));
|
||||||
|
}
|
||||||
|
|
||||||
[scaleXProperty.setNative](value: number) {
|
[scaleXProperty.setNative](value: number) {
|
||||||
org.nativescript.widgets.ViewHelper.setScaleX(this.nativeViewProtected, float(value));
|
org.nativescript.widgets.ViewHelper.setScaleX(this.nativeViewProtected, float(value));
|
||||||
}
|
}
|
||||||
|
23
nativescript-core/ui/core/view/view.d.ts
vendored
23
nativescript-core/ui/core/view/view.d.ts
vendored
@ -75,6 +75,11 @@ export interface Point {
|
|||||||
* Represents the y coordinate of the location.
|
* Represents the y coordinate of the location.
|
||||||
*/
|
*/
|
||||||
y: number;
|
y: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the z coordinate of the location.
|
||||||
|
*/
|
||||||
|
z?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -315,10 +320,26 @@ export abstract class View extends ViewBase {
|
|||||||
opacity: number;
|
opacity: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets or sets the rotate affine transform of the view.
|
* Gets or sets the rotate affine transform of the view along the Z axis.
|
||||||
*/
|
*/
|
||||||
rotate: number;
|
rotate: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or sets the rotate affine transform of the view along the X axis.
|
||||||
|
*/
|
||||||
|
rotateX: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or sets the rotate affine transform of the view along the Y axis.
|
||||||
|
*/
|
||||||
|
rotateY: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or sets the distance of the camera form the view perspective.
|
||||||
|
* Usually needed when rotating the view over the X or Y axis.
|
||||||
|
*/
|
||||||
|
perspective: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets or sets the translateX affine transform of the view in device independent pixels.
|
* Gets or sets the translateX affine transform of the view in device independent pixels.
|
||||||
*/
|
*/
|
||||||
|
@ -9,10 +9,12 @@ import {
|
|||||||
import { ios } from "./view-helper";
|
import { ios } from "./view-helper";
|
||||||
import { ios as iosBackground, Background } from "../../styling/background";
|
import { ios as iosBackground, Background } from "../../styling/background";
|
||||||
import { ios as iosUtils } from "../../../utils/utils";
|
import { ios as iosUtils } from "../../../utils/utils";
|
||||||
|
import { ios as iosNativeHelper } from "../../../utils/native-helper";
|
||||||
import {
|
import {
|
||||||
Visibility,
|
perspectiveProperty, Visibility,
|
||||||
visibilityProperty, opacityProperty,
|
visibilityProperty, opacityProperty,
|
||||||
rotateProperty, scaleXProperty, scaleYProperty,
|
rotateProperty, rotateXProperty, rotateYProperty,
|
||||||
|
scaleXProperty, scaleYProperty,
|
||||||
translateXProperty, translateYProperty, zIndexProperty,
|
translateXProperty, translateYProperty, zIndexProperty,
|
||||||
backgroundInternalProperty, clipPathProperty
|
backgroundInternalProperty, clipPathProperty
|
||||||
} from "../../styling/style-properties";
|
} from "../../styling/style-properties";
|
||||||
@ -156,6 +158,7 @@ export class View extends ViewCommon implements ViewDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public _setNativeViewFrame(nativeView: UIView, frame: CGRect): void {
|
public _setNativeViewFrame(nativeView: UIView, frame: CGRect): void {
|
||||||
|
|
||||||
let oldFrame = this._cachedFrame || nativeView.frame;
|
let oldFrame = this._cachedFrame || nativeView.frame;
|
||||||
if (!CGRectEqualToRect(oldFrame, frame)) {
|
if (!CGRectEqualToRect(oldFrame, frame)) {
|
||||||
if (traceEnabled()) {
|
if (traceEnabled()) {
|
||||||
@ -166,8 +169,8 @@ export class View extends ViewCommon implements ViewDefinition {
|
|||||||
let transform = null;
|
let transform = null;
|
||||||
if (this._hasTransfrom) {
|
if (this._hasTransfrom) {
|
||||||
// Always set identity transform before setting frame;
|
// Always set identity transform before setting frame;
|
||||||
transform = nativeView.transform;
|
transform = nativeView.layer.transform;
|
||||||
nativeView.transform = CGAffineTransformIdentity;
|
nativeView.layer.transform = CATransform3DIdentity;
|
||||||
nativeView.frame = frame;
|
nativeView.frame = frame;
|
||||||
} else {
|
} else {
|
||||||
nativeView.frame = frame;
|
nativeView.frame = frame;
|
||||||
@ -177,10 +180,10 @@ export class View extends ViewCommon implements ViewDefinition {
|
|||||||
if (adjustedFrame) {
|
if (adjustedFrame) {
|
||||||
nativeView.frame = adjustedFrame;
|
nativeView.frame = adjustedFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._hasTransfrom) {
|
if (this._hasTransfrom) {
|
||||||
// re-apply the transform after the frame is adjusted
|
// re-apply the transform after the frame is adjusted
|
||||||
nativeView.transform = transform;
|
nativeView.layer.transform = transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
const boundsOrigin = nativeView.bounds.origin;
|
const boundsOrigin = nativeView.bounds.origin;
|
||||||
@ -348,18 +351,25 @@ export class View extends ViewCommon implements ViewDefinition {
|
|||||||
public updateNativeTransform() {
|
public updateNativeTransform() {
|
||||||
const scaleX = this.scaleX || 1e-6;
|
const scaleX = this.scaleX || 1e-6;
|
||||||
const scaleY = this.scaleY || 1e-6;
|
const scaleY = this.scaleY || 1e-6;
|
||||||
const rotate = this.rotate || 0;
|
const perspective = this.perspective || 300;
|
||||||
let newTransform = CGAffineTransformIdentity;
|
|
||||||
newTransform = CGAffineTransformTranslate(newTransform, this.translateX, this.translateY);
|
let transform = new CATransform3D(CATransform3DIdentity);
|
||||||
newTransform = CGAffineTransformRotate(newTransform, rotate * Math.PI / 180);
|
|
||||||
newTransform = CGAffineTransformScale(newTransform, scaleX, scaleY);
|
// Only set perspective if there is 3D rotation
|
||||||
if (!CGAffineTransformEqualToTransform(this.nativeViewProtected.transform, newTransform)) {
|
if (this.rotateX || this.rotateY) {
|
||||||
|
transform.m34 = -1 / perspective;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform = CATransform3DTranslate(transform, this.translateX, this.translateY, 0);
|
||||||
|
transform = iosNativeHelper.applyRotateTransform(transform, this.rotateX, this.rotateY, this.rotate);
|
||||||
|
transform = CATransform3DScale(transform, scaleX, scaleY, 1);
|
||||||
|
if (!CATransform3DEqualToTransform(this.nativeViewProtected.layer.transform, transform)) {
|
||||||
const updateSuspended = this._isPresentationLayerUpdateSuspeneded();
|
const updateSuspended = this._isPresentationLayerUpdateSuspeneded();
|
||||||
if (!updateSuspended) {
|
if (!updateSuspended) {
|
||||||
CATransaction.begin();
|
CATransaction.begin();
|
||||||
}
|
}
|
||||||
this.nativeViewProtected.transform = newTransform;
|
this.nativeViewProtected.layer.transform = transform;
|
||||||
this._hasTransfrom = this.nativeViewProtected && !CGAffineTransformEqualToTransform(this.nativeViewProtected.transform, CGAffineTransformIdentity);
|
this._hasTransfrom = this.nativeViewProtected && !CATransform3DEqualToTransform(this.nativeViewProtected.transform3D, CATransform3DIdentity);
|
||||||
if (!updateSuspended) {
|
if (!updateSuspended) {
|
||||||
CATransaction.commit();
|
CATransaction.commit();
|
||||||
}
|
}
|
||||||
@ -579,6 +589,27 @@ export class View extends ViewCommon implements ViewDefinition {
|
|||||||
this.updateNativeTransform();
|
this.updateNativeTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[rotateXProperty.getDefault](): number {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
[rotateXProperty.setNative](value: number) {
|
||||||
|
this.updateNativeTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
[rotateYProperty.getDefault](): number {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
[rotateYProperty.setNative](value: number) {
|
||||||
|
this.updateNativeTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
[perspectiveProperty.getDefault](): number {
|
||||||
|
return 300;
|
||||||
|
}
|
||||||
|
[perspectiveProperty.setNative](value: number) {
|
||||||
|
this.updateNativeTransform();
|
||||||
|
}
|
||||||
|
|
||||||
[scaleXProperty.getDefault](): number {
|
[scaleXProperty.getDefault](): number {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -472,6 +472,15 @@ function convertToPaddings(this: void, value: string | Length): [CssProperty<any
|
|||||||
export const rotateProperty = new CssAnimationProperty<Style, number>({ name: "rotate", cssName: "rotate", defaultValue: 0, valueConverter: parseFloat });
|
export const rotateProperty = new CssAnimationProperty<Style, number>({ name: "rotate", cssName: "rotate", defaultValue: 0, valueConverter: parseFloat });
|
||||||
rotateProperty.register(Style);
|
rotateProperty.register(Style);
|
||||||
|
|
||||||
|
export const rotateXProperty = new CssAnimationProperty<Style, number>({ name: "rotateX", cssName: "rotatex", defaultValue: 0, valueConverter: parseFloat });
|
||||||
|
rotateXProperty.register(Style);
|
||||||
|
|
||||||
|
export const rotateYProperty = new CssAnimationProperty<Style, number>({ name: "rotateY", cssName: "rotatey", defaultValue: 0, valueConverter: parseFloat });
|
||||||
|
rotateYProperty.register(Style);
|
||||||
|
|
||||||
|
export const perspectiveProperty = new CssAnimationProperty<Style, number>({ name: "perspective", cssName: "perspective", defaultValue: 1000, valueConverter: parseFloat });
|
||||||
|
perspectiveProperty.register(Style);
|
||||||
|
|
||||||
export const scaleXProperty = new CssAnimationProperty<Style, number>({ name: "scaleX", cssName: "scaleX", defaultValue: 1, valueConverter: parseFloat });
|
export const scaleXProperty = new CssAnimationProperty<Style, number>({ name: "scaleX", cssName: "scaleX", defaultValue: 1, valueConverter: parseFloat });
|
||||||
scaleXProperty.register(Style);
|
scaleXProperty.register(Style);
|
||||||
|
|
||||||
@ -500,6 +509,8 @@ const transformProperty = new ShorthandProperty<Style, string>({
|
|||||||
let translateX = this.translateX;
|
let translateX = this.translateX;
|
||||||
let translateY = this.translateY;
|
let translateY = this.translateY;
|
||||||
let rotate = this.rotate;
|
let rotate = this.rotate;
|
||||||
|
let rotateX = this.rotateX;
|
||||||
|
let rotateY = this.rotateY;
|
||||||
let result = "";
|
let result = "";
|
||||||
if (translateX !== 0 || translateY !== 0) {
|
if (translateX !== 0 || translateY !== 0) {
|
||||||
result += `translate(${translateX}, ${translateY}) `;
|
result += `translate(${translateX}, ${translateY}) `;
|
||||||
@ -507,7 +518,8 @@ const transformProperty = new ShorthandProperty<Style, string>({
|
|||||||
if (scaleX !== 1 || scaleY !== 1) {
|
if (scaleX !== 1 || scaleY !== 1) {
|
||||||
result += `scale(${scaleX}, ${scaleY}) `;
|
result += `scale(${scaleX}, ${scaleY}) `;
|
||||||
}
|
}
|
||||||
if (rotate !== 0) {
|
if (rotateX !== 0 || rotateY !== 0 || rotate !== 0) {
|
||||||
|
result += `rotate(${rotateX}, ${rotateY}, ${rotate}) `;
|
||||||
result += `rotate (${rotate})`;
|
result += `rotate (${rotate})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,13 +531,16 @@ transformProperty.register(Style);
|
|||||||
|
|
||||||
const IDENTITY_TRANSFORMATION = {
|
const IDENTITY_TRANSFORMATION = {
|
||||||
translate: { x: 0, y: 0 },
|
translate: { x: 0, y: 0 },
|
||||||
rotate: 0,
|
rotate: { x: 0, y: 0, z: 0 },
|
||||||
scale: { x: 1, y: 1 },
|
scale: { x: 1, y: 1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const TRANSFORM_SPLITTER = new RegExp(/\s*(.+?)\((.*?)\)/g);
|
const TRANSFORM_SPLITTER = new RegExp(/\s*(.+?)\((.*?)\)/g);
|
||||||
const TRANSFORMATIONS = Object.freeze([
|
const TRANSFORMATIONS = Object.freeze([
|
||||||
"rotate",
|
"rotate",
|
||||||
|
"rotateX",
|
||||||
|
"rotateY",
|
||||||
|
"rotate3d",
|
||||||
"translate",
|
"translate",
|
||||||
"translate3d",
|
"translate3d",
|
||||||
"translateX",
|
"translateX",
|
||||||
@ -547,7 +562,28 @@ const STYLE_TRANSFORMATION_MAP = Object.freeze({
|
|||||||
"translateX": ({ x }) => ({ property: "translate", value: { x, y: IDENTITY_TRANSFORMATION.translate.y } }),
|
"translateX": ({ x }) => ({ property: "translate", value: { x, y: IDENTITY_TRANSFORMATION.translate.y } }),
|
||||||
"translateY": ({ y }) => ({ property: "translate", value: { y, x: IDENTITY_TRANSFORMATION.translate.x } }),
|
"translateY": ({ y }) => ({ property: "translate", value: { y, x: IDENTITY_TRANSFORMATION.translate.x } }),
|
||||||
|
|
||||||
"rotate": value => ({ property: "rotate", value }),
|
"rotate3d": value => ({ property: "rotate", value }),
|
||||||
|
"rotateX": (x) => ({
|
||||||
|
property: "rotate", value: {
|
||||||
|
x,
|
||||||
|
y: IDENTITY_TRANSFORMATION.rotate.y,
|
||||||
|
z: IDENTITY_TRANSFORMATION.rotate.z
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
"rotateY": (y) => ({
|
||||||
|
property: "rotate", value: {
|
||||||
|
x: IDENTITY_TRANSFORMATION.rotate.x,
|
||||||
|
y,
|
||||||
|
z: IDENTITY_TRANSFORMATION.rotate.z
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
"rotate": (z) => ({
|
||||||
|
property: "rotate", value: {
|
||||||
|
x: IDENTITY_TRANSFORMATION.rotate.x,
|
||||||
|
y: IDENTITY_TRANSFORMATION.rotate.y,
|
||||||
|
z
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
function convertToTransform(value: string): [CssProperty<any, any>, any][] {
|
function convertToTransform(value: string): [CssProperty<any, any>, any][] {
|
||||||
@ -564,7 +600,9 @@ function convertToTransform(value: string): [CssProperty<any, any>, any][] {
|
|||||||
[scaleXProperty, scale.x],
|
[scaleXProperty, scale.x],
|
||||||
[scaleYProperty, scale.y],
|
[scaleYProperty, scale.y],
|
||||||
|
|
||||||
[rotateProperty, rotate],
|
[rotateProperty, rotate.z],
|
||||||
|
[rotateXProperty, rotate.x],
|
||||||
|
[rotateYProperty, rotate.y],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -619,13 +657,13 @@ function normalizeTransformation({ property, value }: Transformation): Transform
|
|||||||
function convertTransformValue(property: string, stringValue: string)
|
function convertTransformValue(property: string, stringValue: string)
|
||||||
: TransformationValue {
|
: TransformationValue {
|
||||||
|
|
||||||
const [x, y = x] = stringValue.split(",").map(parseFloat);
|
const [x, y = x, z = y] = stringValue.split(",").map(parseFloat);
|
||||||
|
|
||||||
if (property === "rotate") {
|
if (property === "rotate" || property === "rotateX" || property === "rotateY") {
|
||||||
return stringValue.slice(-3) === "rad" ? radiansToDegrees(x) : x;
|
return stringValue.slice(-3) === "rad" ? radiansToDegrees(x) : x;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { x, y };
|
return { x, y, z };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background properties.
|
// Background properties.
|
||||||
|
@ -525,6 +525,8 @@ export class CssState {
|
|||||||
const view = this.viewRef.get();
|
const view = this.viewRef.get();
|
||||||
if (view) {
|
if (view) {
|
||||||
view.style["keyframe:rotate"] = unsetValue;
|
view.style["keyframe:rotate"] = unsetValue;
|
||||||
|
view.style["keyframe:rotateX"] = unsetValue;
|
||||||
|
view.style["keyframe:rotateY"] = unsetValue;
|
||||||
view.style["keyframe:scaleX"] = unsetValue;
|
view.style["keyframe:scaleX"] = unsetValue;
|
||||||
view.style["keyframe:scaleY"] = unsetValue;
|
view.style["keyframe:scaleY"] = unsetValue;
|
||||||
view.style["keyframe:translateX"] = unsetValue;
|
view.style["keyframe:translateX"] = unsetValue;
|
||||||
|
@ -53,6 +53,9 @@ export class Style extends Observable {
|
|||||||
public backgroundInternal: Background;
|
public backgroundInternal: Background;
|
||||||
|
|
||||||
public rotate: number;
|
public rotate: number;
|
||||||
|
public rotateX: number;
|
||||||
|
public rotateY: number;
|
||||||
|
public perspective: number;
|
||||||
public scaleX: number;
|
public scaleX: number;
|
||||||
public scaleY: number;
|
public scaleY: number;
|
||||||
public translateX: dip;
|
public translateX: dip;
|
||||||
|
@ -86,6 +86,10 @@ export class Style extends Observable implements StyleDefinition {
|
|||||||
public backgroundInternal: Background;
|
public backgroundInternal: Background;
|
||||||
|
|
||||||
public rotate: number;
|
public rotate: number;
|
||||||
|
public rotateX: number;
|
||||||
|
public rotateY: number;
|
||||||
|
public perspective: number;
|
||||||
|
|
||||||
public scaleX: number;
|
public scaleX: number;
|
||||||
public scaleY: number;
|
public scaleY: number;
|
||||||
public translateX: dip;
|
public translateX: dip;
|
||||||
|
11
nativescript-core/utils/native-helper.d.ts
vendored
11
nativescript-core/utils/native-helper.d.ts
vendored
@ -155,5 +155,14 @@ export module ios {
|
|||||||
*/
|
*/
|
||||||
export function getVisibleViewController(rootViewController: any/* UIViewController*/): any/* UIViewController*/;
|
export function getVisibleViewController(rootViewController: any/* UIViewController*/): any/* UIViewController*/;
|
||||||
|
|
||||||
export class UIDocumentInteractionControllerDelegateImpl {}
|
/**
|
||||||
|
*
|
||||||
|
* @param transform Applies a rotation transform over X,Y and Z axis
|
||||||
|
* @param x Rotation over X axis in degrees
|
||||||
|
* @param y Rotation over Y axis in degrees
|
||||||
|
* @param z Rotation over Z axis in degrees
|
||||||
|
*/
|
||||||
|
export function applyRotateTransform(transform: any /* CATransform3D*/, x: number, y: number, z: number): any /* CATransform3D*/;
|
||||||
|
|
||||||
|
export class UIDocumentInteractionControllerDelegateImpl { }
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import {
|
|||||||
write as traceWrite
|
write as traceWrite
|
||||||
} from "../trace";
|
} from "../trace";
|
||||||
|
|
||||||
|
const radToDeg = Math.PI / 180;
|
||||||
|
|
||||||
function isOrientationLandscape(orientation: number) {
|
function isOrientationLandscape(orientation: number) {
|
||||||
return orientation === UIDeviceOrientation.LandscapeLeft /* 3 */ ||
|
return orientation === UIDeviceOrientation.LandscapeLeft /* 3 */ ||
|
||||||
orientation === UIDeviceOrientation.LandscapeRight /* 4 */;
|
orientation === UIDeviceOrientation.LandscapeRight /* 4 */;
|
||||||
@ -114,23 +116,39 @@ export module ios {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyRotateTransform(transform: CATransform3D, x: number, y: number, z: number): CATransform3D {
|
||||||
|
if (x) {
|
||||||
|
transform = CATransform3DRotate(transform, x * radToDeg, 1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y) {
|
||||||
|
transform = CATransform3DRotate(transform, y * radToDeg, 0, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (z) {
|
||||||
|
transform = CATransform3DRotate(transform, z * radToDeg, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
export class UIDocumentInteractionControllerDelegateImpl extends NSObject implements UIDocumentInteractionControllerDelegate {
|
export class UIDocumentInteractionControllerDelegateImpl extends NSObject implements UIDocumentInteractionControllerDelegate {
|
||||||
public static ObjCProtocols = [UIDocumentInteractionControllerDelegate];
|
public static ObjCProtocols = [UIDocumentInteractionControllerDelegate];
|
||||||
|
|
||||||
public getViewController(): UIViewController {
|
public getViewController(): UIViewController {
|
||||||
const app = UIApplication.sharedApplication;
|
const app = UIApplication.sharedApplication;
|
||||||
|
|
||||||
return app.keyWindow.rootViewController;
|
return app.keyWindow.rootViewController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController) {
|
public documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController) {
|
||||||
return this.getViewController();
|
return this.getViewController();
|
||||||
}
|
}
|
||||||
|
|
||||||
public documentInteractionControllerViewForPreview(controller: UIDocumentInteractionController) {
|
public documentInteractionControllerViewForPreview(controller: UIDocumentInteractionController) {
|
||||||
return this.getViewController().view;
|
return this.getViewController().view;
|
||||||
}
|
}
|
||||||
|
|
||||||
public documentInteractionControllerRectForPreview(controller: UIDocumentInteractionController): CGRect {
|
public documentInteractionControllerRectForPreview(controller: UIDocumentInteractionController): CGRect {
|
||||||
return this.getViewController().view.frame;
|
return this.getViewController().view.frame;
|
||||||
}
|
}
|
||||||
|
1
nativescript-core/utils/utils.d.ts
vendored
1
nativescript-core/utils/utils.d.ts
vendored
@ -251,6 +251,7 @@ export module ios {
|
|||||||
* Returns the visible UIViewController.
|
* Returns the visible UIViewController.
|
||||||
*/
|
*/
|
||||||
export function getVisibleViewController(rootViewController: any/* UIViewController*/): any/* UIViewController*/;
|
export function getVisibleViewController(rootViewController: any/* UIViewController*/): any/* UIViewController*/;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +14,7 @@ export function openFile(filePath: string): boolean {
|
|||||||
const path = filePath.replace("~", appPath);
|
const path = filePath.replace("~", appPath);
|
||||||
|
|
||||||
const controller = UIDocumentInteractionController.interactionControllerWithURL(NSURL.fileURLWithPath(path));
|
const controller = UIDocumentInteractionController.interactionControllerWithURL(NSURL.fileURLWithPath(path));
|
||||||
controller.delegate = <UIDocumentInteractionControllerDelegate> new ios.UIDocumentInteractionControllerDelegateImpl();
|
controller.delegate = <UIDocumentInteractionControllerDelegate>new ios.UIDocumentInteractionControllerDelegateImpl();
|
||||||
|
|
||||||
return controller.presentPreviewAnimated(true);
|
return controller.presentPreviewAnimated(true);
|
||||||
}
|
}
|
||||||
@ -48,4 +48,4 @@ export function openUrl(location: string): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mainScreenScale = UIScreen.mainScreen.scale;
|
mainScreenScale = UIScreen.mainScreen.scale;
|
@ -433,6 +433,7 @@ function animateExtentAndAssertExpected(along: "height" | "width", value: Percen
|
|||||||
expectedNumber,
|
expectedNumber,
|
||||||
`PercentLength.toDevicePixels(${inputString}) should be "${expectedNumber}" but is "${observedNumber}"`
|
`PercentLength.toDevicePixels(${inputString}) should be "${expectedNumber}" but is "${observedNumber}"`
|
||||||
);
|
);
|
||||||
|
|
||||||
assertIOSNativeTransformIsCorrect(label);
|
assertIOSNativeTransformIsCorrect(label);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ export function test_ReadTransformAllSet() {
|
|||||||
const animation = createAnimationFromCSS(css, "test");
|
const animation = createAnimationFromCSS(css, "test");
|
||||||
const { rotate, scale, translate } = getTransformsValues(animation.keyframes[0].declarations);
|
const { rotate, scale, translate } = getTransformsValues(animation.keyframes[0].declarations);
|
||||||
|
|
||||||
TKUnit.assertAreClose(rotate, 10, DELTA);
|
TKUnit.assertAreClose(rotate.z, 10, DELTA);
|
||||||
|
|
||||||
TKUnit.assertAreClose(scale.x, 5, SCALE_DELTA);
|
TKUnit.assertAreClose(scale.x, 5, SCALE_DELTA);
|
||||||
TKUnit.assertAreClose(scale.y, 1, SCALE_DELTA);
|
TKUnit.assertAreClose(scale.y, 1, SCALE_DELTA);
|
||||||
@ -136,7 +136,7 @@ export function test_ReadTransformNone() {
|
|||||||
const animation = createAnimationFromCSS(css, "test");
|
const animation = createAnimationFromCSS(css, "test");
|
||||||
const { rotate, scale, translate } = getTransformsValues(animation.keyframes[0].declarations);
|
const { rotate, scale, translate } = getTransformsValues(animation.keyframes[0].declarations);
|
||||||
|
|
||||||
TKUnit.assertEqual(rotate, 0);
|
TKUnit.assertEqual(rotate.z, 0);
|
||||||
|
|
||||||
TKUnit.assertEqual(scale.x, 1);
|
TKUnit.assertEqual(scale.x, 1);
|
||||||
TKUnit.assertEqual(scale.y, 1);
|
TKUnit.assertEqual(scale.y, 1);
|
||||||
@ -271,7 +271,7 @@ export function test_ReadRotate() {
|
|||||||
const { rotate } = getTransforms(animation.keyframes[0].declarations);
|
const { rotate } = getTransforms(animation.keyframes[0].declarations);
|
||||||
|
|
||||||
TKUnit.assertEqual(rotate.property, "rotate");
|
TKUnit.assertEqual(rotate.property, "rotate");
|
||||||
TKUnit.assertAreClose(rotate.value, 5, DELTA);
|
TKUnit.assertAreClose(rotate.value.z, 5, DELTA);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_ReadRotateDeg() {
|
export function test_ReadRotateDeg() {
|
||||||
@ -280,7 +280,7 @@ export function test_ReadRotateDeg() {
|
|||||||
const { rotate } = getTransforms(animation.keyframes[0].declarations);
|
const { rotate } = getTransforms(animation.keyframes[0].declarations);
|
||||||
|
|
||||||
TKUnit.assertEqual(rotate.property, "rotate");
|
TKUnit.assertEqual(rotate.property, "rotate");
|
||||||
TKUnit.assertAreClose(rotate.value, 45, DELTA);
|
TKUnit.assertAreClose(rotate.value.z, 45, DELTA);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_ReadRotateRad() {
|
export function test_ReadRotateRad() {
|
||||||
@ -289,7 +289,7 @@ export function test_ReadRotateRad() {
|
|||||||
const { rotate } = getTransforms(animation.keyframes[0].declarations);
|
const { rotate } = getTransforms(animation.keyframes[0].declarations);
|
||||||
|
|
||||||
TKUnit.assertEqual(rotate.property, "rotate");
|
TKUnit.assertEqual(rotate.property, "rotate");
|
||||||
TKUnit.assertAreClose(rotate.value, 45, DELTA);
|
TKUnit.assertAreClose(rotate.value.z, 45, DELTA);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_ReadAnimationWithUnsortedKeyframes() {
|
export function test_ReadAnimationWithUnsortedKeyframes() {
|
||||||
|
@ -576,6 +576,14 @@
|
|||||||
public static getRotate(view: android.view.View): number;
|
public static getRotate(view: android.view.View): number;
|
||||||
public static setRotate(view: android.view.View, value: number): void;
|
public static setRotate(view: android.view.View, value: number): void;
|
||||||
|
|
||||||
|
public static getRotateX(view: android.view.View): number;
|
||||||
|
public static setRotateX(view: android.view.View, value: number): void;
|
||||||
|
|
||||||
|
public static getRotateY(view: android.view.View): number;
|
||||||
|
public static setRotateY(view: android.view.View, value: number): void;
|
||||||
|
|
||||||
|
public static setPerspective(view: android.view.View, value: number): void;
|
||||||
|
|
||||||
public static getScaleX(view: android.view.View): number;
|
public static getScaleX(view: android.view.View): number;
|
||||||
public static setScaleX(view: android.view.View, value: number): void;
|
public static setScaleX(view: android.view.View, value: number): void;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user