blob: e5c0a102b07b636997f943c10eb985329814f07b [file] [log] [blame]
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, scope, testing) {
function groupChildDuration(node) {
return node._timing.delay + node.activeDuration + node._timing.endDelay;
}
function constructor(children, timingInput, id) {
this._id = id;
this._parent = null;
this.children = children || [];
this._reparent(this.children);
timingInput = shared.numericTimingToObject(timingInput);
this._timingInput = shared.cloneTimingInput(timingInput);
this._timing = shared.normalizeTimingInput(timingInput, true);
this.timing = shared.makeTiming(timingInput, true, this);
this.timing._effect = this;
if (this._timing.duration === 'auto') {
this._timing.duration = this.activeDuration;
}
}
window.SequenceEffect = function() {
constructor.apply(this, arguments);
};
window.GroupEffect = function() {
constructor.apply(this, arguments);
};
constructor.prototype = {
_isAncestor: function(effect) {
var a = this;
while (a !== null) {
if (a == effect)
return true;
a = a._parent;
}
return false;
},
_rebuild: function() {
// Re-calculate durations for ancestors with specified duration 'auto'.
var node = this;
while (node) {
if (node.timing.duration === 'auto') {
node._timing.duration = node.activeDuration;
}
node = node._parent;
}
if (this._animation) {
this._animation._rebuildUnderlyingAnimation();
}
},
_reparent: function(newChildren) {
scope.removeMulti(newChildren);
for (var i = 0; i < newChildren.length; i++) {
newChildren[i]._parent = this;
}
},
_putChild: function(args, isAppend) {
var message = isAppend ? 'Cannot append an ancestor or self' : 'Cannot prepend an ancestor or self';
for (var i = 0; i < args.length; i++) {
if (this._isAncestor(args[i])) {
throw {
type: DOMException.HIERARCHY_REQUEST_ERR,
name: 'HierarchyRequestError',
message: message
};
}
}
var oldParents = [];
for (var i = 0; i < args.length; i++) {
isAppend ? this.children.push(args[i]) : this.children.unshift(args[i]);
}
this._reparent(args);
this._rebuild();
},
append: function() {
this._putChild(arguments, true);
},
prepend: function() {
this._putChild(arguments, false);
},
get parent() {
return this._parent;
},
get firstChild() {
return this.children.length ? this.children[0] : null;
},
get lastChild() {
return this.children.length ? this.children[this.children.length - 1] : null;
},
clone: function() {
var clonedTiming = shared.cloneTimingInput(this._timingInput);
var clonedChildren = [];
for (var i = 0; i < this.children.length; i++) {
clonedChildren.push(this.children[i].clone());
}
return (this instanceof GroupEffect) ?
new GroupEffect(clonedChildren, clonedTiming) :
new SequenceEffect(clonedChildren, clonedTiming);
},
remove: function() {
scope.removeMulti([this]);
}
};
window.SequenceEffect.prototype = Object.create(constructor.prototype);
Object.defineProperty(
window.SequenceEffect.prototype,
'activeDuration',
{
get: function() {
var total = 0;
this.children.forEach(function(child) {
total += groupChildDuration(child);
});
return Math.max(total, 0);
}
});
window.GroupEffect.prototype = Object.create(constructor.prototype);
Object.defineProperty(
window.GroupEffect.prototype,
'activeDuration',
{
get: function() {
var max = 0;
this.children.forEach(function(child) {
max = Math.max(max, groupChildDuration(child));
});
return max;
}
});
scope.newUnderlyingAnimationForGroup = function(group) {
var underlyingAnimation;
var timing = null;
var ticker = function(tf) {
var animation = underlyingAnimation._wrapper;
if (!animation) {
return;
}
if (animation.playState == 'pending') {
return;
}
if (!animation.effect) {
return;
}
if (tf == null) {
animation._removeChildAnimations();
return;
}
// If the group has a negative playback rate and is not fill backwards/both, then it should go
// out of effect when it reaches the start of its active interval (tf == 0). If it is fill
// backwards/both then it should stay in effect. calculateIterationProgress will return 0 in the
// backwards-filling case, and null otherwise.
if (tf == 0 && animation.playbackRate < 0) {
if (!timing) {
timing = shared.normalizeTimingInput(animation.effect.timing);
}
tf = shared.calculateIterationProgress(shared.calculateActiveDuration(timing), -1, timing);
if (isNaN(tf) || tf == null) {
animation._forEachChild(function(child) {
child.currentTime = -1;
});
animation._removeChildAnimations();
return;
}
}
};
var underlyingEffect = new KeyframeEffect(null, [], group._timing, group._id);
underlyingEffect.onsample = ticker;
underlyingAnimation = scope.timeline._play(underlyingEffect);
return underlyingAnimation;
};
scope.bindAnimationForGroup = function(animation) {
animation._animation._wrapper = animation;
animation._isGroup = true;
scope.awaitStartTime(animation);
animation._constructChildAnimations();
animation._setExternalAnimation(animation);
};
scope.groupChildDuration = groupChildDuration;
})(webAnimationsShared, webAnimationsNext, webAnimationsTesting);