mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 19:26:42 +08:00

* 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>
311 lines
11 KiB
TypeScript
311 lines
11 KiB
TypeScript
// Definitions.
|
|
import {
|
|
Keyframes as KeyframesDefinition,
|
|
UnparsedKeyframe as UnparsedKeyframeDefinition,
|
|
KeyframeDeclaration as KeyframeDeclarationDefinition,
|
|
KeyframeInfo as KeyframeInfoDefinition,
|
|
KeyframeAnimationInfo as KeyframeAnimationInfoDefinition,
|
|
KeyframeAnimation as KeyframeAnimationDefinition,
|
|
} from "./keyframe-animation";
|
|
|
|
import { View, Color } from "../core/view";
|
|
|
|
import { AnimationCurve } from "../enums";
|
|
|
|
import { write as traceWrite, categories as traceCategories, messageType as traceType } from "../../trace";
|
|
|
|
// Types.
|
|
import { unsetValue } from "../core/properties";
|
|
import { Animation } from "./animation";
|
|
import {
|
|
backgroundColorProperty,
|
|
scaleXProperty, scaleYProperty,
|
|
translateXProperty, translateYProperty,
|
|
rotateProperty, opacityProperty, rotateXProperty, rotateYProperty,
|
|
widthProperty, heightProperty, PercentLength
|
|
} from "../styling/style-properties";
|
|
|
|
export class Keyframes implements KeyframesDefinition {
|
|
name: string;
|
|
keyframes: Array<UnparsedKeyframe>;
|
|
}
|
|
|
|
export class UnparsedKeyframe implements UnparsedKeyframeDefinition {
|
|
values: Array<any>;
|
|
declarations: Array<KeyframeDeclaration>;
|
|
}
|
|
|
|
export class KeyframeDeclaration implements KeyframeDeclarationDefinition {
|
|
public property: string;
|
|
public value: any;
|
|
}
|
|
|
|
export class KeyframeInfo implements KeyframeInfoDefinition {
|
|
public duration: number;
|
|
public declarations: Array<KeyframeDeclaration>;
|
|
public curve?: any = AnimationCurve.ease;
|
|
}
|
|
|
|
export class KeyframeAnimationInfo implements KeyframeAnimationInfoDefinition {
|
|
public keyframes: Array<KeyframeInfo>;
|
|
public name?: string = "";
|
|
public duration?: number = 0.3;
|
|
public delay?: number = 0;
|
|
public iterations?: number = 1;
|
|
public curve?: any = "ease";
|
|
public isForwards?: boolean = false;
|
|
public isReverse?: boolean = false;
|
|
}
|
|
|
|
interface Keyframe {
|
|
backgroundColor?: Color;
|
|
scale?: { x: number, y: number };
|
|
translate?: { x: number, y: number };
|
|
rotate?: { x: number, y: number, z: number };
|
|
opacity?: number;
|
|
width?: PercentLength;
|
|
height?: PercentLength;
|
|
valueSource?: "keyframe" | "animation";
|
|
duration?: number;
|
|
curve?: any;
|
|
forceLayer?: boolean;
|
|
}
|
|
|
|
export class KeyframeAnimation implements KeyframeAnimationDefinition {
|
|
public animations: Array<Keyframe>;
|
|
public delay: number = 0;
|
|
public iterations: number = 1;
|
|
|
|
private _resolve;
|
|
private _isPlaying: boolean;
|
|
private _isForwards: boolean;
|
|
private _nativeAnimations: Array<Animation>;
|
|
private _target: View;
|
|
|
|
public static keyframeAnimationFromInfo(info: KeyframeAnimationInfo)
|
|
: KeyframeAnimation {
|
|
|
|
const length = info.keyframes.length;
|
|
let animations = new Array<Keyframe>();
|
|
let startDuration = 0;
|
|
|
|
if (info.isReverse) {
|
|
for (let index = length - 1; index >= 0; index--) {
|
|
let keyframe = info.keyframes[index];
|
|
startDuration = KeyframeAnimation.parseKeyframe(info, keyframe, animations, startDuration);
|
|
}
|
|
} else {
|
|
for (let index = 0; index < length; index++) {
|
|
let keyframe = info.keyframes[index];
|
|
startDuration = KeyframeAnimation.parseKeyframe(info, keyframe, animations, startDuration);
|
|
}
|
|
for (let index = length - 1; index > 0; index--) {
|
|
let a1 = animations[index];
|
|
let a2 = animations[index - 1];
|
|
if (a2["curve"] !== undefined) {
|
|
a1["curve"] = a2["curve"];
|
|
a2["curve"] = undefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
animations.map(a => a["curve"] ? a : Object.assign(a, { curve: info.curve }));
|
|
|
|
const animation: KeyframeAnimation = new KeyframeAnimation();
|
|
animation.delay = info.delay;
|
|
animation.iterations = info.iterations;
|
|
animation.animations = animations;
|
|
animation._isForwards = info.isForwards;
|
|
|
|
return animation;
|
|
}
|
|
|
|
private static parseKeyframe(info: KeyframeAnimationInfo, keyframe: KeyframeInfo, animations: Array<Object>, startDuration: number): number {
|
|
let animation: Keyframe = {};
|
|
for (let declaration of keyframe.declarations) {
|
|
animation[declaration.property] = declaration.value;
|
|
}
|
|
|
|
let duration = keyframe.duration;
|
|
if (duration === 0) {
|
|
duration = 0.01;
|
|
} else {
|
|
duration = (info.duration * duration) - startDuration;
|
|
startDuration += duration;
|
|
}
|
|
animation.duration = info.isReverse ? info.duration - duration : duration;
|
|
animation.curve = keyframe.curve;
|
|
animation.forceLayer = true;
|
|
animation.valueSource = "keyframe";
|
|
animations.push(animation);
|
|
|
|
return startDuration;
|
|
}
|
|
|
|
public get isPlaying(): boolean {
|
|
return this._isPlaying;
|
|
}
|
|
|
|
public cancel() {
|
|
if (!this.isPlaying) {
|
|
traceWrite("Keyframe animation is already playing.", traceCategories.Animation, traceType.warn);
|
|
|
|
return;
|
|
}
|
|
|
|
this._isPlaying = false;
|
|
for (let i = this._nativeAnimations.length - 1; i >= 0; i--) {
|
|
let animation = this._nativeAnimations[i];
|
|
if (animation.isPlaying) {
|
|
animation.cancel();
|
|
}
|
|
}
|
|
if (this._nativeAnimations.length > 0) {
|
|
let animation = this._nativeAnimations[0];
|
|
this._resetAnimationValues(this._target, animation);
|
|
}
|
|
this._resetAnimations();
|
|
}
|
|
|
|
public play(view: View): Promise<void> {
|
|
if (this._isPlaying) {
|
|
traceWrite("Keyframe animation is already playing.", traceCategories.Animation, traceType.warn);
|
|
|
|
return new Promise<void>(resolve => {
|
|
resolve();
|
|
});
|
|
}
|
|
|
|
let animationFinishedPromise = new Promise<void>(resolve => {
|
|
this._resolve = resolve;
|
|
});
|
|
|
|
this._isPlaying = true;
|
|
this._nativeAnimations = new Array<Animation>();
|
|
this._target = view;
|
|
|
|
if (this.delay !== 0) {
|
|
setTimeout(() => this.animate(view, 0, this.iterations), this.delay);
|
|
}
|
|
else {
|
|
this.animate(view, 0, this.iterations);
|
|
}
|
|
|
|
return animationFinishedPromise;
|
|
}
|
|
|
|
private animate(view: View, index: number, iterations: number) {
|
|
if (!this._isPlaying) {
|
|
return;
|
|
}
|
|
if (index === 0) {
|
|
let animation = this.animations[0];
|
|
|
|
if ("backgroundColor" in animation) {
|
|
view.style[backgroundColorProperty.keyframe] = animation.backgroundColor;
|
|
}
|
|
if ("scale" in animation) {
|
|
view.style[scaleXProperty.keyframe] = animation.scale.x;
|
|
view.style[scaleYProperty.keyframe] = animation.scale.y;
|
|
}
|
|
if ("translate" in animation) {
|
|
view.style[translateXProperty.keyframe] = animation.translate.x;
|
|
view.style[translateYProperty.keyframe] = animation.translate.y;
|
|
}
|
|
if ("rotate" in animation) {
|
|
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) {
|
|
view.style[opacityProperty.keyframe] = animation.opacity;
|
|
}
|
|
if ("height" in animation) {
|
|
view.style[heightProperty.keyframe] = animation.height;
|
|
}
|
|
if ("width" in animation) {
|
|
view.style[widthProperty.keyframe] = animation.width;
|
|
}
|
|
|
|
setTimeout(() => this.animate(view, 1, iterations), 1);
|
|
}
|
|
else if (index < 0 || index >= this.animations.length) {
|
|
iterations -= 1;
|
|
if (iterations > 0) {
|
|
this.animate(view, 0, iterations);
|
|
}
|
|
else {
|
|
if (this._isForwards === false) {
|
|
let animation = this.animations[this.animations.length - 1];
|
|
this._resetAnimationValues(view, animation);
|
|
}
|
|
this._resolveAnimationFinishedPromise();
|
|
}
|
|
}
|
|
else {
|
|
let animation;
|
|
const cachedAnimation = this._nativeAnimations[index - 1];
|
|
|
|
if (cachedAnimation) {
|
|
animation = cachedAnimation;
|
|
}
|
|
else {
|
|
let animationDef = this.animations[index];
|
|
(<any>animationDef).target = view;
|
|
animation = new Animation([animationDef]);
|
|
this._nativeAnimations.push(animation);
|
|
}
|
|
|
|
const isLastIteration = iterations - 1 <= 0;
|
|
|
|
// Catch the animation cancel to prevent unhandled promise rejection warnings
|
|
animation.play(isLastIteration).then(() => {
|
|
this.animate(view, index + 1, iterations);
|
|
}, (error: any) => {
|
|
traceWrite(typeof error === "string" ? error : error.message, traceCategories.Animation, traceType.warn);
|
|
}).catch((error: any) => {
|
|
traceWrite(typeof error === "string" ? error : error.message, traceCategories.Animation, traceType.warn);
|
|
}); // tslint:disable-line
|
|
}
|
|
}
|
|
|
|
public _resolveAnimationFinishedPromise() {
|
|
this._nativeAnimations = new Array<Animation>();
|
|
this._isPlaying = false;
|
|
this._target = null;
|
|
this._resolve();
|
|
}
|
|
|
|
public _resetAnimations() {
|
|
this._nativeAnimations = new Array<Animation>();
|
|
this._isPlaying = false;
|
|
this._target = null;
|
|
}
|
|
|
|
private _resetAnimationValues(view: View, animation: Object) {
|
|
if ("backgroundColor" in animation) {
|
|
view.style[backgroundColorProperty.keyframe] = unsetValue;
|
|
}
|
|
if ("scale" in animation) {
|
|
view.style[scaleXProperty.keyframe] = unsetValue;
|
|
view.style[scaleYProperty.keyframe] = unsetValue;
|
|
}
|
|
if ("translate" in animation) {
|
|
view.style[translateXProperty.keyframe] = unsetValue;
|
|
view.style[translateYProperty.keyframe] = unsetValue;
|
|
}
|
|
if ("rotate" in animation) {
|
|
view.style[rotateProperty.keyframe] = unsetValue;
|
|
}
|
|
if ("opacity" in animation) {
|
|
view.style[opacityProperty.keyframe] = unsetValue;
|
|
}
|
|
if ("height" in animation) {
|
|
view.style[heightProperty.keyframe] = unsetValue;
|
|
}
|
|
if ("width" in animation) {
|
|
view.style[widthProperty.keyframe] = unsetValue;
|
|
}
|
|
}
|
|
}
|