Skip to content

fix(animations): use parsers from core modules #844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 4 additions & 14 deletions nativescript-angular/animations/animation-player.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { AnimationPlayer } from "@angular/animations";
import {
KeyframeAnimation,
KeyframeAnimationInfo,
} from "tns-core-modules/ui/animation/keyframe-animation";
import { KeyframeAnimation }
from "tns-core-modules/ui/animation/keyframe-animation";

import { NgView } from "../element-registry";
import { Keyframe, getAnimationCurve, parseAnimationKeyframe } from "./utils";
import { Keyframe, createKeyframeAnimation } from "./utils";

export class NativeScriptAnimationPlayer implements AnimationPlayer {
public parentPlayer: AnimationPlayer = null;
Expand Down Expand Up @@ -86,15 +84,7 @@ export class NativeScriptAnimationPlayer implements AnimationPlayer {
}

private initKeyframeAnimation(keyframes: Keyframe[], duration: number, delay: number, easing: string) {
let info = new KeyframeAnimationInfo();
info.isForwards = true;
info.iterations = 1;
info.duration = duration === 0 ? 0.01 : duration;
info.delay = delay;
info.curve = getAnimationCurve(easing);
info.keyframes = keyframes.map(parseAnimationKeyframe);

this.animation = KeyframeAnimation.keyframeAnimationFromInfo(info);
this.animation = createKeyframeAnimation(keyframes, duration, delay, easing);
}

private onFinish() {
Expand Down
165 changes: 40 additions & 125 deletions nativescript-angular/animations/utils.ts
Original file line number Diff line number Diff line change
@@ -1,139 +1,54 @@
import {
KeyframeAnimation,
KeyframeAnimationInfo,
KeyframeDeclaration,
KeyframeInfo,
} from "tns-core-modules/ui/animation/keyframe-animation";
import { CssAnimationProperty } from "tns-core-modules/ui/core/properties";
import { AnimationCurve } from "tns-core-modules/ui/enums";
import { parseKeyframeDeclarations } from "tns-core-modules/ui/styling/css-animation-parser";
import { animationTimingFunctionConverter } from "tns-core-modules/ui/styling/converters";

export interface Keyframe {
[key: string]: string | number;
}

interface Transformation {
property: string;
value: number | { x: number, y: number };
}

const TRANSFORM_MATCHER = new RegExp(/(.+)\((.+)\)/);
const TRANSFORM_SPLITTER = new RegExp(/[\s,]+/);

const STYLE_TRANSFORMATION_MAP = Object.freeze({
"scale": value => ({ property: "scale", value }),
"scale3d": value => ({ property: "scale", value }),
"scaleX": value => ({ property: "scale", value: { x: value, y: 1 } }),
"scaleY": value => ({ property: "scale", value: { x: 1, y: value } }),

"translate": value => ({ property: "translate", value }),
"translate3d": value => ({ property: "translate", value }),
"translateX": value => ({ property: "translate", value: { x: value, y: 0 } }),
"translateY": value => ({ property: "translate", value: { x: 0, y: value } }),

"rotate": value => ({ property: "rotate", value }),

"none": _value => [
{ property: "scale", value: { x: 1, y: 1 } },
{ property: "translate", value: { x: 0, y: 0 } },
{ property: "rotate", value: 0 },
],
});

const STYLE_CURVE_MAP = Object.freeze({
"ease": AnimationCurve.ease,
"linear": AnimationCurve.linear,
"ease-in": AnimationCurve.easeIn,
"ease-out": AnimationCurve.easeOut,
"ease-in-out": AnimationCurve.easeInOut,
"spring": AnimationCurve.spring,
});

export function getAnimationCurve(value: string): any {
if (!value) {
return AnimationCurve.ease;
}

const curve = STYLE_CURVE_MAP[value];
if (curve) {
return curve;
}

const [, property = "", pointsString = ""] = TRANSFORM_MATCHER.exec(value) || [];
const coords = pointsString.split(TRANSFORM_SPLITTER).map(stringToBezieCoords);

if (property !== "cubic-bezier" || coords.length !== 4) {
throw new Error(`Invalid value for animation: ${value}`);
} else {
return (<any>AnimationCurve).cubicBezier(...coords);
offset: number;
}

export function createKeyframeAnimation(
styles: Keyframe[],
duration: number,
delay: number,
easing: string)
: KeyframeAnimation {

const info = createKeyframeAnimationInfo(styles, duration, delay, easing);
return KeyframeAnimation.keyframeAnimationFromInfo(info);
}

const createKeyframeAnimationInfo = (
styles: Keyframe[],
duration: number,
delay: number,
easing: string
): KeyframeAnimationInfo => ({
isForwards: true,
duration: duration || 0.01,
delay,
curve: getCurve(easing),
keyframes: styles.map(parseAnimationKeyframe),
}
}

export function parseAnimationKeyframe(styles: Keyframe) {
let keyframeInfo = <KeyframeInfo>{};
keyframeInfo.duration = <number>styles.offset;
keyframeInfo.declarations = Object.keys(styles).reduce((declarations, prop) => {
let value = styles[prop];

const property = CssAnimationProperty._getByCssName(prop);
if (property) {
if (typeof value === "string" && property._valueConverter) {
value = property._valueConverter(<string>value);
}
declarations.push({ property: property.name, value });
} else if (typeof value === "string" && prop === "transform") {
declarations.push(...parseTransformation(<string>value));
}
);

return declarations;
}, new Array<KeyframeDeclaration>());

return keyframeInfo;
}

function stringToBezieCoords(value: string): number {
let result = parseFloat(value);
if (result < 0) {
return 0;
} else if (result > 1) {
return 1;
}

return result;
}
const getCurve = (value: string) => animationTimingFunctionConverter(value);

function parseTransformation(styleString: string): KeyframeDeclaration[] {
return parseStyle(styleString)
.reduce((transformations, style) => {
const transform = STYLE_TRANSFORMATION_MAP[style.property](style.value);

if (Array.isArray(transform)) {
transformations.push(...transform);
} else if (typeof transform !== "undefined") {
transformations.push(transform);
}

return transformations;
}, new Array<Transformation>());
}

function parseStyle(text: string): Transformation[] {
return text.split(TRANSFORM_SPLITTER).map(stringToTransformation).filter(t => !!t);
}

function stringToTransformation(text: string): Transformation {
const [, property = "", stringValue = ""] = TRANSFORM_MATCHER.exec(text) || [];
if (!property) {
return;
}
const parseAnimationKeyframe = (styles: Keyframe): KeyframeInfo => ({
duration: getKeyframeDuration(styles),
declarations: getDeclarations(styles),
});

const [x, y] = stringValue.split(",").map(parseFloat);
if (x && y) {
return { property, value: {x, y} };
} else {
let value: number = x;
const getKeyframeDuration = (styles: Keyframe): number => styles.offset;

if (stringValue.slice(-3) === "rad") {
value *= 180.0 / Math.PI;
}
function getDeclarations(styles: Keyframe): KeyframeDeclaration[] {
const unparsedDeclarations: KeyframeDeclaration[] =
Object.keys(styles).map(property => ({ property, value: styles[property] }));

return { property, value };
}
return parseKeyframeDeclarations(unparsedDeclarations);
}