summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/animation/components/graph/TimingPath.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/inspector/animation/components/graph/TimingPath.js')
-rw-r--r--devtools/client/inspector/animation/components/graph/TimingPath.js450
1 files changed, 450 insertions, 0 deletions
diff --git a/devtools/client/inspector/animation/components/graph/TimingPath.js b/devtools/client/inspector/animation/components/graph/TimingPath.js
new file mode 100644
index 0000000000..7949527988
--- /dev/null
+++ b/devtools/client/inspector/animation/components/graph/TimingPath.js
@@ -0,0 +1,450 @@
+/* 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 <path> element list.
+ *
+ * @param {Object} state
+ * State of animation.
+ * @param {SummaryGraphHelper} helper
+ * Instance of SummaryGraphHelper.
+ * @return {Array}
+ * list of <path> 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 <path> element to given pathList.
+ *
+ * @param {Array} pathList
+ * Add rendered <path> 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 <path> element to given pathList.
+ * This section is only useful in cases where iterationStart has decimals.
+ *
+ * @param {Array} pathList
+ * Add rendered <path> 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 <path> elements to given pathList.
+ *
+ * @param {Array} pathList
+ * Add rendered <path> 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 <path> element to given pathList.
+ * This section is only useful in cases where iterationStart has decimals.
+ *
+ * @param {Array} pathList
+ * Add rendered <path> 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 <path> elements to given pathList.
+ *
+ * @param {Array} pathList
+ * Add rendered <path> 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 <path> 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 <path> element to given pathList.
+ *
+ * @param {Array} pathList
+ * Add rendered <path> 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 <path> element to given pathList.
+ *
+ * @param {Array} pathList
+ * Add rendered <path> 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;