mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 02:54:11 +08:00

* chore: move tns-core-modules to nativescript-core * chore: preparing compat generate script * chore: add missing definitions * chore: no need for http-request to be private * chore: packages chore * test: generate tests for tns-core-modules * chore: add anroid module for consistency * chore: add .npmignore * chore: added privateModulesWhitelist * chore(webpack): added bundle-entry-points * chore: scripts * chore: tests changed to use @ns/core * test: add scoped-packages test project * test: fix types * test: update test project * chore: build scripts * chore: update build script * chore: npm scripts cleanup * chore: make the compat pgk work with old wp config * test: generate diff friendly tests * chore: create barrel exports * chore: move files after rebase * chore: typedoc config * chore: compat mode * chore: review of barrels * chore: remove tns-core-modules import after rebase * chore: dev workflow setup * chore: update developer-workflow * docs: experiment with API extractor * chore: api-extractor and barrel exports * chore: api-extractor configs * chore: generate d.ts rollup with api-extractor * refactor: move methods inside Frame * chore: fic tests to use Frame static methods * refactor: create Builder class * refactor: use Builder class in tests * refactor: include Style in ui barrel * chore: separate compat build script * chore: fix tslint errors * chore: update NATIVESCRIPT_CORE_ARGS * chore: fix compat pack * chore: fix ui-test-app build with linked modules * chore: Application, ApplicationSettings, Connectivity and Http * chore: export Trace, Profiling and Utils * refactor: Static create methods for ImageSource * chore: fix deprecated usages of ImageSource * chore: move Span and FormattedString to ui * chore: add events-args and ImageSource to index files * chore: check for CLI >= 6.2 when building for IOS * chore: update travis build * chore: copy Pod file to compat package * chore: update error msg ui-tests-app * refactor: Apply suggestions from code review Co-Authored-By: Martin Yankov <m.i.yankov@gmail.com> * chore: typings and refs * chore: add missing d.ts files for public API * chore: adress code review FB * chore: update api-report * chore: dev-workflow for other apps * chore: api update * chore: update api-report
309 lines
10 KiB
TypeScript
309 lines
10 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,
|
|
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?: 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[rotateProperty.keyframe] = animation.rotate;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|