| // 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); |