@ -0,0 +1,57 @@
|
||||
import {AnimationPlayer} from './animation_player';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {ListWrapper, StringMapWrapper, Map} from '../facade/collection';
|
||||
|
||||
export class ActiveAnimationPlayersMap {
|
||||
private _map = new Map<any, {[key: string]: AnimationPlayer}>();
|
||||
private _allPlayers: AnimationPlayer[] = [];
|
||||
|
||||
get length(): number {
|
||||
return this.getAllPlayers().length;
|
||||
}
|
||||
|
||||
find(element: any, animationName: string): AnimationPlayer {
|
||||
var playersByAnimation = this._map.get(element);
|
||||
if (isPresent(playersByAnimation)) {
|
||||
return playersByAnimation[animationName];
|
||||
}
|
||||
}
|
||||
|
||||
findAllPlayersByElement(element: any): AnimationPlayer[] {
|
||||
var players = [];
|
||||
StringMapWrapper.forEach(this._map.get(element), player => players.push(player));
|
||||
return players;
|
||||
}
|
||||
|
||||
set(element: any, animationName: string, player: AnimationPlayer): void {
|
||||
var playersByAnimation = this._map.get(element);
|
||||
if (!isPresent(playersByAnimation)) {
|
||||
playersByAnimation = {};
|
||||
}
|
||||
var existingEntry = playersByAnimation[animationName];
|
||||
if (isPresent(existingEntry)) {
|
||||
this.remove(element, animationName);
|
||||
}
|
||||
playersByAnimation[animationName] = player;
|
||||
this._allPlayers.push(player);
|
||||
this._map.set(element, playersByAnimation);
|
||||
}
|
||||
|
||||
getAllPlayers(): AnimationPlayer[] {
|
||||
return this._allPlayers;
|
||||
}
|
||||
|
||||
remove(element: any, animationName: string): void {
|
||||
var playersByAnimation = this._map.get(element);
|
||||
if (isPresent(playersByAnimation)) {
|
||||
var player = playersByAnimation[animationName];
|
||||
delete playersByAnimation[animationName];
|
||||
var index = this._allPlayers.indexOf(player);
|
||||
ListWrapper.removeAt(this._allPlayers, index);
|
||||
|
||||
if (StringMapWrapper.isEmpty(playersByAnimation)) {
|
||||
this._map.delete(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export const FILL_STYLE_FLAG = 'true'; // TODO (matsko): change to boolean
|
||||
export const ANY_STATE = '*';
|
||||
export const EMPTY_STATE = 'void';
|
15
modules/@angular/core/src/animation/animation_driver.ts
Normal file
15
modules/@angular/core/src/animation/animation_driver.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import {NoOpAnimationPlayer, AnimationPlayer} from './animation_player';
|
||||
import {AnimationKeyframe} from './animation_keyframe';
|
||||
import {AnimationStyles} from './animation_styles';
|
||||
|
||||
export abstract class AnimationDriver {
|
||||
abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer;
|
||||
}
|
||||
|
||||
export class NoOpAnimationDriver extends AnimationDriver {
|
||||
animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer {
|
||||
return new NoOpAnimationPlayer();
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
import {AnimationPlayer} from './animation_player';
|
||||
import {isPresent, scheduleMicroTask} from '../facade/lang';
|
||||
import {Math} from '../facade/math';
|
||||
|
||||
export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
private _subscriptions: Function[] = [];
|
||||
private _finished = false;
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
|
||||
constructor(private _players: AnimationPlayer[]) {
|
||||
var count = 0;
|
||||
var total = this._players.length;
|
||||
if (total == 0) {
|
||||
scheduleMicroTask(() => this._onFinish());
|
||||
} else {
|
||||
this._players.forEach(player => {
|
||||
player.parentPlayer = this;
|
||||
player.onDone(() => {
|
||||
if (++count >= total) {
|
||||
this._onFinish();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _onFinish() {
|
||||
if (!this._finished) {
|
||||
this._finished = true;
|
||||
if (!isPresent(this.parentPlayer)) {
|
||||
this.destroy();
|
||||
}
|
||||
this._subscriptions.forEach(subscription => subscription());
|
||||
this._subscriptions = [];
|
||||
}
|
||||
}
|
||||
|
||||
onDone(fn: Function): void { this._subscriptions.push(fn); }
|
||||
|
||||
play() { this._players.forEach(player => player.play()); }
|
||||
|
||||
pause(): void { this._players.forEach(player => player.pause()); }
|
||||
|
||||
restart(): void { this._players.forEach(player => player.restart()); }
|
||||
|
||||
finish(): void {
|
||||
this._onFinish();
|
||||
this._players.forEach(player => player.finish());
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this._onFinish();
|
||||
this._players.forEach(player => player.destroy());
|
||||
}
|
||||
|
||||
reset(): void { this._players.forEach(player => player.reset()); }
|
||||
|
||||
setPosition(p): void {
|
||||
this._players.forEach(player => {
|
||||
player.setPosition(p);
|
||||
});
|
||||
}
|
||||
|
||||
getPosition(): number {
|
||||
var min = 0;
|
||||
this._players.forEach(player => {
|
||||
var p = player.getPosition();
|
||||
min = Math.min(p, min);
|
||||
});
|
||||
return min;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import {AnimationStyles} from './animation_styles';
|
||||
|
||||
export class AnimationKeyframe {
|
||||
constructor(public offset: number, public styles: AnimationStyles) {}
|
||||
}
|
39
modules/@angular/core/src/animation/animation_player.ts
Normal file
39
modules/@angular/core/src/animation/animation_player.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import {scheduleMicroTask} from '../facade/lang';
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
|
||||
export abstract class AnimationPlayer {
|
||||
abstract onDone(fn: Function): void;
|
||||
abstract play(): void;
|
||||
abstract pause(): void;
|
||||
abstract restart(): void;
|
||||
abstract finish(): void;
|
||||
abstract destroy(): void;
|
||||
abstract reset(): void;
|
||||
abstract setPosition(p): void;
|
||||
abstract getPosition(): number;
|
||||
get parentPlayer(): AnimationPlayer { throw new BaseException('NOT IMPLEMENTED: Base Class'); }
|
||||
set parentPlayer(player: AnimationPlayer) { throw new BaseException('NOT IMPLEMENTED: Base Class'); }
|
||||
}
|
||||
|
||||
export class NoOpAnimationPlayer implements AnimationPlayer {
|
||||
private _subscriptions = [];
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
constructor() {
|
||||
scheduleMicroTask(() => this._onFinish());
|
||||
}
|
||||
_onFinish() {
|
||||
this._subscriptions.forEach(entry => { entry(); });
|
||||
this._subscriptions = [];
|
||||
}
|
||||
onDone(fn: Function): void { this._subscriptions.push(fn); }
|
||||
play(): void {}
|
||||
pause(): void {}
|
||||
restart(): void {}
|
||||
finish(): void {
|
||||
this._onFinish();
|
||||
}
|
||||
destroy(): void {}
|
||||
reset(): void {}
|
||||
setPosition(p): void {}
|
||||
getPosition(): number { return 0; }
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {NoOpAnimationPlayer, AnimationPlayer} from './animation_player';
|
||||
import {scheduleMicroTask} from '../facade/lang';
|
||||
|
||||
export class AnimationSequencePlayer implements AnimationPlayer {
|
||||
private _currentIndex: number = 0;
|
||||
private _activePlayer: AnimationPlayer;
|
||||
private _subscriptions: Function[] = [];
|
||||
private _finished = false;
|
||||
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
|
||||
constructor(private _players: AnimationPlayer[]) {
|
||||
this._players.forEach(player => {
|
||||
player.parentPlayer = this;
|
||||
});
|
||||
this._onNext(false);
|
||||
}
|
||||
|
||||
private _onNext(start: boolean) {
|
||||
if (this._finished) return;
|
||||
|
||||
if (this._players.length == 0) {
|
||||
this._activePlayer = new NoOpAnimationPlayer();
|
||||
scheduleMicroTask(() => this._onFinish());
|
||||
} else if (this._currentIndex >= this._players.length) {
|
||||
this._activePlayer = new NoOpAnimationPlayer();
|
||||
this._onFinish();
|
||||
} else {
|
||||
var player = this._players[this._currentIndex++];
|
||||
player.onDone(() => this._onNext(true));
|
||||
|
||||
this._activePlayer = player;
|
||||
if (start) {
|
||||
player.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _onFinish() {
|
||||
if (!this._finished) {
|
||||
this._finished = true;
|
||||
if (!isPresent(this.parentPlayer)) {
|
||||
this.destroy();
|
||||
}
|
||||
this._subscriptions.forEach(subscription => subscription());
|
||||
this._subscriptions = [];
|
||||
}
|
||||
}
|
||||
|
||||
onDone(fn: Function): void { this._subscriptions.push(fn); }
|
||||
|
||||
play(): void { this._activePlayer.play(); }
|
||||
|
||||
pause(): void { this._activePlayer.pause(); }
|
||||
|
||||
restart(): void {
|
||||
if (this._players.length > 0) {
|
||||
this.reset();
|
||||
this._players[0].restart();
|
||||
}
|
||||
}
|
||||
|
||||
reset(): void { this._players.forEach(player => player.reset()); }
|
||||
|
||||
finish(): void {
|
||||
this._onFinish();
|
||||
this._players.forEach(player => player.finish());
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this._onFinish();
|
||||
this._players.forEach(player => player.destroy());
|
||||
}
|
||||
|
||||
setPosition(p): void {
|
||||
this._players[0].setPosition(p);
|
||||
}
|
||||
|
||||
getPosition(): number {
|
||||
return this._players[0].getPosition();
|
||||
}
|
||||
}
|
113
modules/@angular/core/src/animation/animation_style_util.ts
Normal file
113
modules/@angular/core/src/animation/animation_style_util.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import {isPresent, isArray} from '../facade/lang';
|
||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {AUTO_STYLE} from './metadata';
|
||||
import {FILL_STYLE_FLAG} from './animation_constants';
|
||||
|
||||
export class AnimationStyleUtil {
|
||||
static balanceStyles(previousStyles: {[key: string]: string|number},
|
||||
newStyles: {[key: string]: string|number},
|
||||
nullValue = null): {[key: string]: string|number} {
|
||||
var finalStyles: {[key: string]: string|number} = {};
|
||||
|
||||
StringMapWrapper.forEach(newStyles, (value, prop) => {
|
||||
finalStyles[prop] = value;
|
||||
});
|
||||
|
||||
StringMapWrapper.forEach(previousStyles, (value, prop) => {
|
||||
if (!isPresent(finalStyles[prop])) {
|
||||
finalStyles[prop] = nullValue;
|
||||
}
|
||||
});
|
||||
|
||||
return finalStyles;
|
||||
}
|
||||
static balanceKeyframes(collectedStyles: {[key: string]: string|number},
|
||||
finalStateStyles: {[key: string]: string|number},
|
||||
keyframes: any[]): any[] {
|
||||
var limit = keyframes.length - 1;
|
||||
var firstKeyframe = keyframes[0];
|
||||
|
||||
// phase 1: copy all the styles from the first keyframe into the lookup map
|
||||
var flatenedFirstKeyframeStyles = AnimationStyleUtil.flattenStyles(firstKeyframe.styles.styles);
|
||||
|
||||
var extraFirstKeyframeStyles = {};
|
||||
var hasExtraFirstStyles = false;
|
||||
StringMapWrapper.forEach(collectedStyles, (value, prop) => {
|
||||
// if the style is already defined in the first keyframe then
|
||||
// we do not replace it.
|
||||
if (!flatenedFirstKeyframeStyles[prop]) {
|
||||
flatenedFirstKeyframeStyles[prop] = value;
|
||||
extraFirstKeyframeStyles[prop] = value;
|
||||
hasExtraFirstStyles = true;
|
||||
}
|
||||
});
|
||||
|
||||
var keyframeCollectedStyles = StringMapWrapper.merge({}, flatenedFirstKeyframeStyles);
|
||||
|
||||
// phase 2: normalize the final keyframe
|
||||
var finalKeyframe = keyframes[limit];
|
||||
ListWrapper.insert(finalKeyframe.styles.styles, 0, finalStateStyles);
|
||||
|
||||
var flatenedFinalKeyframeStyles = AnimationStyleUtil.flattenStyles(finalKeyframe.styles.styles);
|
||||
var extraFinalKeyframeStyles = {};
|
||||
var hasExtraFinalStyles = false;
|
||||
StringMapWrapper.forEach(keyframeCollectedStyles, (value, prop) => {
|
||||
if (!isPresent(flatenedFinalKeyframeStyles[prop])) {
|
||||
extraFinalKeyframeStyles[prop] = AUTO_STYLE;
|
||||
hasExtraFinalStyles = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasExtraFinalStyles) {
|
||||
finalKeyframe.styles.styles.push(extraFinalKeyframeStyles);
|
||||
}
|
||||
|
||||
StringMapWrapper.forEach(flatenedFinalKeyframeStyles, (value, prop) => {
|
||||
if (!isPresent(flatenedFirstKeyframeStyles[prop])) {
|
||||
extraFirstKeyframeStyles[prop] = AUTO_STYLE;
|
||||
hasExtraFirstStyles = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasExtraFirstStyles) {
|
||||
firstKeyframe.styles.styles.push(extraFirstKeyframeStyles);
|
||||
}
|
||||
|
||||
return keyframes;
|
||||
}
|
||||
|
||||
static clearStyles(styles: {[key: string]: string|number}): {[key: string]: string|number} {
|
||||
var finalStyles: {[key: string]: string|number} = {};
|
||||
StringMapWrapper.keys(styles).forEach(key => {
|
||||
finalStyles[key] = null;
|
||||
});
|
||||
return finalStyles;
|
||||
}
|
||||
|
||||
static collectAndResolveStyles(collection: {[key: string]: string|number}, styles: {[key: string]: string|number}[]) {
|
||||
return styles.map(entry => {
|
||||
var stylesObj = {};
|
||||
StringMapWrapper.forEach(entry, (value, prop) => {
|
||||
if (value == FILL_STYLE_FLAG) {
|
||||
value = collection[prop];
|
||||
if (!isPresent(value)) {
|
||||
value = AUTO_STYLE;
|
||||
}
|
||||
}
|
||||
collection[prop] = value;
|
||||
stylesObj[prop] = value;
|
||||
});
|
||||
return stylesObj;
|
||||
});
|
||||
}
|
||||
|
||||
static flattenStyles(styles: {[key: string]: string|number}[]) {
|
||||
var finalStyles = {};
|
||||
styles.forEach(entry => {
|
||||
StringMapWrapper.forEach(entry, (value, prop) => {
|
||||
finalStyles[prop] = value;
|
||||
});
|
||||
});
|
||||
return finalStyles;
|
||||
}
|
||||
}
|
3
modules/@angular/core/src/animation/animation_styles.ts
Normal file
3
modules/@angular/core/src/animation/animation_styles.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export class AnimationStyles {
|
||||
constructor(public styles: {[key: string]: string | number}[]) {}
|
||||
}
|
116
modules/@angular/core/src/animation/metadata.ts
Normal file
116
modules/@angular/core/src/animation/metadata.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import {isPresent, isArray, isString, isStringMap, NumberWrapper} from '../facade/lang';
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
|
||||
export const AUTO_STYLE = "*";
|
||||
|
||||
export class AnimationEntryMetadata {
|
||||
constructor(public name: string, public definitions: AnimationStateMetadata[]) {}
|
||||
}
|
||||
|
||||
export abstract class AnimationStateMetadata {}
|
||||
|
||||
export class AnimationStateDeclarationMetadata extends AnimationStateMetadata {
|
||||
constructor(public stateNameExpr: string, public styles: AnimationStyleMetadata) { super(); }
|
||||
}
|
||||
|
||||
export class AnimationStateTransitionMetadata extends AnimationStateMetadata {
|
||||
constructor(public stateChangeExpr: string, public animation: AnimationMetadata) { super(); }
|
||||
}
|
||||
|
||||
export abstract class AnimationMetadata {}
|
||||
|
||||
export class AnimationKeyframesSequenceMetadata extends AnimationMetadata {
|
||||
constructor(public steps: AnimationStyleMetadata[]) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export class AnimationStyleMetadata extends AnimationMetadata {
|
||||
constructor(public styles: Array<string|{[key: string]: string | number}>, public offset: number = null) { super(); }
|
||||
}
|
||||
|
||||
export class AnimationAnimateMetadata extends AnimationMetadata {
|
||||
constructor(public timings: string | number,
|
||||
public styles: AnimationStyleMetadata|AnimationKeyframesSequenceMetadata) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AnimationWithStepsMetadata extends AnimationMetadata {
|
||||
constructor() { super(); }
|
||||
get steps(): AnimationMetadata[] { throw new BaseException('NOT IMPLEMENTED: Base Class'); }
|
||||
}
|
||||
|
||||
export class AnimationSequenceMetadata extends AnimationWithStepsMetadata {
|
||||
constructor(private _steps: AnimationMetadata[]) { super(); }
|
||||
get steps(): AnimationMetadata[] { return this._steps; }
|
||||
}
|
||||
|
||||
export class AnimationGroupMetadata extends AnimationWithStepsMetadata {
|
||||
constructor(private _steps: AnimationMetadata[]) { super(); }
|
||||
get steps(): AnimationMetadata[] { return this._steps; }
|
||||
}
|
||||
|
||||
export function animate(timing: string | number,
|
||||
styles: AnimationStyleMetadata|AnimationKeyframesSequenceMetadata = null): AnimationAnimateMetadata {
|
||||
var stylesEntry = styles;
|
||||
if (!isPresent(stylesEntry)) {
|
||||
var EMPTY_STYLE: {[key: string]: string|number} = {};
|
||||
stylesEntry = new AnimationStyleMetadata([EMPTY_STYLE], 1);
|
||||
}
|
||||
return new AnimationAnimateMetadata(timing, stylesEntry);
|
||||
}
|
||||
|
||||
export function group(steps: AnimationMetadata[]): AnimationGroupMetadata {
|
||||
return new AnimationGroupMetadata(steps);
|
||||
}
|
||||
|
||||
export function sequence(steps: AnimationMetadata[]): AnimationSequenceMetadata {
|
||||
return new AnimationSequenceMetadata(steps);
|
||||
}
|
||||
|
||||
export function style(tokens: string|{[key: string]: string | number}|Array<string|{[key: string]: string | number}>): AnimationStyleMetadata {
|
||||
var input: Array<{[key: string]: string | number}|string>;
|
||||
var offset: number = null;
|
||||
if (isString(tokens)) {
|
||||
input = [<string>tokens];
|
||||
} else {
|
||||
if (isArray(tokens)) {
|
||||
input = <Array<{[key: string]: string | number}>>tokens;
|
||||
} else {
|
||||
input = [<{[key: string]: string | number}>tokens];
|
||||
}
|
||||
input.forEach(entry => {
|
||||
var entryOffset = entry['offset'];
|
||||
if (isPresent(entryOffset)) {
|
||||
offset = offset == null ? NumberWrapper.parseFloat(entryOffset) : offset;
|
||||
}
|
||||
});
|
||||
}
|
||||
return new AnimationStyleMetadata(input, offset);
|
||||
}
|
||||
|
||||
export function state(stateNameExpr: string, styles: AnimationStyleMetadata): AnimationStateDeclarationMetadata {
|
||||
return new AnimationStateDeclarationMetadata(stateNameExpr, styles);
|
||||
}
|
||||
|
||||
export function keyframes(steps: AnimationStyleMetadata|AnimationStyleMetadata[]): AnimationKeyframesSequenceMetadata {
|
||||
var stepData = isArray(steps)
|
||||
? <AnimationStyleMetadata[]>steps
|
||||
: [<AnimationStyleMetadata>steps];
|
||||
return new AnimationKeyframesSequenceMetadata(stepData);
|
||||
}
|
||||
|
||||
export function transition(stateChangeExpr: string, animationData: AnimationMetadata|AnimationMetadata[]): AnimationStateTransitionMetadata {
|
||||
var animation = isArray(animationData)
|
||||
? new AnimationSequenceMetadata(<AnimationMetadata[]>animationData)
|
||||
: <AnimationMetadata>animationData;
|
||||
return new AnimationStateTransitionMetadata(stateChangeExpr, animation);
|
||||
}
|
||||
|
||||
export function trigger(name: string, animation: AnimationMetadata|AnimationMetadata[]): AnimationEntryMetadata {
|
||||
var entry = isArray(animation)
|
||||
? <AnimationMetadata[]>animation
|
||||
: [<AnimationMetadata>animation];
|
||||
return new AnimationEntryMetadata(name, entry);
|
||||
}
|
Reference in New Issue
Block a user