/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { PureComponent, } = require("resource://devtools/client/shared/vendor/react.js"); const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); // Show max 10 iterations for infinite animations // to give users a clue that the animation does repeat. const MAX_INFINITE_ANIMATIONS_ITERATIONS = 10; class TimingPath extends PureComponent { /** * Render a graph of given parameters and return as element list. * * @param {Object} state * State of animation. * @param {SummaryGraphHelper} helper * Instance of SummaryGraphHelper. * @return {Array} * list of element. */ renderGraph(state, helper) { // Starting time of main iteration. let mainIterationStartTime = 0; let iterationStart = state.iterationStart; let iterationCount = state.iterationCount ? state.iterationCount : Infinity; const pathList = []; // Append delay. if (state.delay > 0) { this.renderDelay(pathList, state, helper); mainIterationStartTime = state.delay; } else { const negativeDelayCount = -state.delay / state.duration; // Move to forward the starting point for negative delay. iterationStart += negativeDelayCount; // Consume iteration count by negative delay. if (iterationCount !== Infinity) { iterationCount -= negativeDelayCount; } } if (state.duration === Infinity) { this.renderInfinityDuration( pathList, state, mainIterationStartTime, helper ); return pathList; } // Append 1st section of iterations, // This section is only useful in cases where iterationStart has decimals. // e.g. // if { iterationStart: 0.25, iterations: 3 }, firstSectionCount is 0.75. const firstSectionCount = iterationStart % 1 === 0 ? 0 : Math.min(1 - (iterationStart % 1), iterationCount); if (firstSectionCount) { this.renderFirstIteration( pathList, state, mainIterationStartTime, firstSectionCount, helper ); } if (iterationCount === Infinity) { // If the animation repeats infinitely, // we fill the remaining area with iteration paths. this.renderInfinity( pathList, state, mainIterationStartTime, firstSectionCount, helper ); } else { // Otherwise, we show remaining iterations, endDelay and fill. // Append forwards fill-mode. if (state.fill === "both" || state.fill === "forwards") { this.renderForwardsFill( pathList, state, mainIterationStartTime, iterationCount, helper ); } // Append middle section of iterations. // e.g. // if { iterationStart: 0.25, iterations: 3 }, middleSectionCount is 2. const middleSectionCount = Math.floor(iterationCount - firstSectionCount); this.renderMiddleIterations( pathList, state, mainIterationStartTime, firstSectionCount, middleSectionCount, helper ); // Append last section of iterations, if there is remaining iteration. // e.g. // if { iterationStart: 0.25, iterations: 3 }, lastSectionCount is 0.25. const lastSectionCount = iterationCount - middleSectionCount - firstSectionCount; if (lastSectionCount) { this.renderLastIteration( pathList, state, mainIterationStartTime, firstSectionCount, middleSectionCount, lastSectionCount, helper ); } // Append endDelay. if (state.endDelay > 0) { this.renderEndDelay( pathList, state, mainIterationStartTime, iterationCount, helper ); } } return pathList; } /** * Render 'delay' part in animation and add a element to given pathList. * * @param {Array} pathList * Add rendered element to this array. * @param {Object} state * State of animation. * @param {SummaryGraphHelper} helper * Instance of SummaryGraphHelper. */ renderDelay(pathList, state, helper) { const startSegment = helper.getSegment(0); const endSegment = { x: state.delay, y: startSegment.y }; const segments = [startSegment, endSegment]; pathList.push( dom.path({ className: "animation-delay-path", d: helper.toPathString(segments), }) ); } /** * Render 1st section of iterations and add a element to given pathList. * This section is only useful in cases where iterationStart has decimals. * * @param {Array} pathList * Add rendered element to this array. * @param {Object} state * State of animation. * @param {Number} mainIterationStartTime * Start time of main iteration. * @param {Number} firstSectionCount * Iteration count of first section. * @param {SummaryGraphHelper} helper * Instance of SummaryGraphHelper. */ renderFirstIteration( pathList, state, mainIterationStartTime, firstSectionCount, helper ) { const startTime = mainIterationStartTime; const endTime = startTime + firstSectionCount * state.duration; const segments = helper.createPathSegments(startTime, endTime); pathList.push( dom.path({ className: "animation-iteration-path", d: helper.toPathString(segments), }) ); } /** * Render middle iterations and add elements to given pathList. * * @param {Array} pathList * Add rendered elements to this array. * @param {Object} state * State of animation. * @param {Number} mainIterationStartTime * Starting time of main iteration. * @param {Number} firstSectionCount * Iteration count of first section. * @param {Number} middleSectionCount * Iteration count of middle section. * @param {SummaryGraphHelper} helper * Instance of SummaryGraphHelper. */ renderMiddleIterations( pathList, state, mainIterationStartTime, firstSectionCount, middleSectionCount, helper ) { const offset = mainIterationStartTime + firstSectionCount * state.duration; for (let i = 0; i < middleSectionCount; i++) { // Get the path segments of each iteration. const startTime = offset + i * state.duration; const endTime = startTime + state.duration; const segments = helper.createPathSegments(startTime, endTime); pathList.push( dom.path({ className: "animation-iteration-path", d: helper.toPathString(segments), }) ); } } /** * Render last section of iterations and add a element to given pathList. * This section is only useful in cases where iterationStart has decimals. * * @param {Array} pathList * Add rendered elements to this array. * @param {Object} state * State of animation. * @param {Number} mainIterationStartTime * Starting time of main iteration. * @param {Number} firstSectionCount * Iteration count of first section. * @param {Number} middleSectionCount * Iteration count of middle section. * @param {Number} lastSectionCount * Iteration count of last section. * @param {SummaryGraphHelper} helper * Instance of SummaryGraphHelper. */ renderLastIteration( pathList, state, mainIterationStartTime, firstSectionCount, middleSectionCount, lastSectionCount, helper ) { const startTime = mainIterationStartTime + (firstSectionCount + middleSectionCount) * state.duration; const endTime = startTime + lastSectionCount * state.duration; const segments = helper.createPathSegments(startTime, endTime); pathList.push( dom.path({ className: "animation-iteration-path", d: helper.toPathString(segments), }) ); } /** * Render infinity iterations and add elements to given pathList. * * @param {Array} pathList * Add rendered elements to this array. * @param {Object} state * State of animation. * @param {Number} mainIterationStartTime * Starting time of main iteration. * @param {Number} firstSectionCount * Iteration count of first section. * @param {SummaryGraphHelper} helper * Instance of SummaryGraphHelper. */ renderInfinity( pathList, state, mainIterationStartTime, firstSectionCount, helper ) { // Calculate the number of iterations to display, // with a maximum of MAX_INFINITE_ANIMATIONS_ITERATIONS let uncappedInfinityIterationCount = (helper.totalDuration - firstSectionCount * state.duration) / state.duration; // If there is a small floating point error resulting in, e.g. 1.0000001 // ceil will give us 2 so round first. uncappedInfinityIterationCount = parseFloat( uncappedInfinityIterationCount.toPrecision(6) ); const infinityIterationCount = Math.min( MAX_INFINITE_ANIMATIONS_ITERATIONS, Math.ceil(uncappedInfinityIterationCount) ); // Append first full iteration path. const firstStartTime = mainIterationStartTime + firstSectionCount * state.duration; const firstEndTime = firstStartTime + state.duration; const firstSegments = helper.createPathSegments( firstStartTime, firstEndTime ); pathList.push( dom.path({ className: "animation-iteration-path", d: helper.toPathString(firstSegments), }) ); // Append other iterations. We can copy first segments. const isAlternate = state.direction.match(/alternate/); for (let i = 1; i < infinityIterationCount; i++) { const startTime = firstStartTime + i * state.duration; let segments; if (isAlternate && i % 2) { // Copy as reverse. segments = firstSegments.map(segment => { return { x: firstEndTime - segment.x + startTime, y: segment.y }; }); } else { // Copy as is. segments = firstSegments.map(segment => { return { x: segment.x - firstStartTime + startTime, y: segment.y }; }); } pathList.push( dom.path({ className: "animation-iteration-path infinity", d: helper.toPathString(segments), }) ); } } /** * Render infinity duration. * * @param {Array} pathList * Add rendered elements to this array. * @param {Object} state * State of animation. * @param {Number} mainIterationStartTime * Starting time of main iteration. * @param {SummaryGraphHelper} helper * Instance of SummaryGraphHelper. */ renderInfinityDuration(pathList, state, mainIterationStartTime, helper) { const startSegment = helper.getSegment(mainIterationStartTime); const endSegment = { x: helper.totalDuration, y: startSegment.y }; const segments = [startSegment, endSegment]; pathList.push( dom.path({ className: "animation-iteration-path infinity-duration", d: helper.toPathString(segments), }) ); } /** * Render 'endDelay' part in animation and add a element to given pathList. * * @param {Array} pathList * Add rendered element to this array. * @param {Object} state * State of animation. * @param {Number} mainIterationStartTime * Starting time of main iteration. * @param {Number} iterationCount * Iteration count of whole animation. * @param {SummaryGraphHelper} helper * Instance of SummaryGraphHelper. */ renderEndDelay( pathList, state, mainIterationStartTime, iterationCount, helper ) { const startTime = mainIterationStartTime + iterationCount * state.duration; const startSegment = helper.getSegment(startTime); const endSegment = { x: startTime + state.endDelay, y: startSegment.y }; pathList.push( dom.path({ className: "animation-enddelay-path", d: helper.toPathString([startSegment, endSegment]), }) ); } /** * Render 'fill' for forwards part in animation and * add a element to given pathList. * * @param {Array} pathList * Add rendered element to this array. * @param {Object} state * State of animation. * @param {Number} mainIterationStartTime * Starting time of main iteration. * @param {Number} iterationCount * Iteration count of whole animation. * @param {SummaryGraphHelper} helper * Instance of SummaryGraphHelper. */ renderForwardsFill( pathList, state, mainIterationStartTime, iterationCount, helper ) { const startTime = mainIterationStartTime + iterationCount * state.duration + (state.endDelay > 0 ? state.endDelay : 0); const startSegment = helper.getSegment(startTime); const endSegment = { x: helper.totalDuration, y: startSegment.y }; pathList.push( dom.path({ className: "animation-fill-forwards-path", d: helper.toPathString([startSegment, endSegment]), }) ); } } module.exports = TimingPath;