Bug 1406285 - Part 10: Implement negative delay path. r?gl draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Thu, 18 Jan 2018 13:02:10 +0900
changeset 721939 7181e42d485e583e0bd4a334dacc8daf85c1c4ad
parent 721938 bf31e1538ec7d55482878ece3af1f4624635d52f
child 721940 86d72dacb95d0c6d5ae63d43fa13c017ba71bca0
push id96003
push userbmo:dakatsuka@mozilla.com
push dateThu, 18 Jan 2018 05:23:36 +0000
reviewersgl
bugs1406285
milestone59.0a1
Bug 1406285 - Part 10: Implement negative delay path. r?gl MozReview-Commit-ID: 4bje1aOBHth
devtools/client/inspector/animation/components/graph/NegativeDelayPath.js
devtools/client/inspector/animation/components/graph/NegativePath.js
devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
devtools/client/inspector/animation/components/graph/moz.build
devtools/client/themes/animation.css
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/graph/NegativeDelayPath.js
@@ -0,0 +1,41 @@
+/* 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 PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+const NegativePath = require("./NegativePath");
+
+class NegativeDelayPath extends NegativePath {
+  static get propTypes() {
+    return {
+      animation: PropTypes.object.isRequired,
+      durationPerPixel: PropTypes.number.isRequired,
+      keyframes: PropTypes.object.isRequired,
+      simulateAnimation: PropTypes.func.isRequired,
+      totalDuration: PropTypes.number.isRequired,
+    };
+  }
+
+  constructor(props) {
+    props.className = "animation-negative-delay-path";
+    super(props);
+  }
+
+  renderGraph(state, helper) {
+    const startTime = state.delay;
+    const endTime = 0;
+    const segments = helper.createPathSegments(startTime, endTime);
+
+    return dom.path(
+      {
+        d: helper.toPathString(segments),
+      }
+    );
+  }
+}
+
+module.exports = NegativeDelayPath;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/graph/NegativePath.js
@@ -0,0 +1,90 @@
+/* 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("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+const { SummaryGraphHelper, toPathString } = require("../../utils/graph-helper");
+
+class NegativePath extends PureComponent {
+  static get propTypes() {
+    return {
+      animation: PropTypes.object.isRequired,
+      className: PropTypes.string.isRequired,
+      durationPerPixel: PropTypes.number.isRequired,
+      keyframes: PropTypes.object.isRequired,
+      simulateAnimation: PropTypes.func.isRequired,
+      totalDuration: PropTypes.number.isRequired,
+    };
+  }
+
+  render() {
+    const {
+      animation,
+      className,
+      durationPerPixel,
+      keyframes,
+      simulateAnimation,
+      totalDuration,
+    } = this.props;
+
+    const { state } = animation;
+    const effectTiming = Object.assign({}, state, {
+      fill: "both",
+      iterations: state.iterationCount ? state.iterationCount : Infinity
+    });
+
+    // Create new keyframes for opacity as computed style.
+    // The reason why we use computed value instead of computed timing progress is to
+    // include the easing in keyframes as well. Although the computed timing progress
+    // is not affected by the easing in keyframes at all, computed value reflects that.
+    const frames = keyframes.map(keyframe => {
+      return {
+        opacity: keyframe.offset,
+        offset: keyframe.offset,
+        easing: keyframe.easing
+      };
+    });
+
+    const simulatedAnimation = simulateAnimation(frames, effectTiming, true);
+    const simulatedElement = simulatedAnimation.effect.target;
+    const win = simulatedElement.ownerGlobal;
+
+    // Set the underlying opacity to zero so that if we sample the animation's output
+    // during the delay phase and it is not filling backwards, we get zero.
+    simulatedElement.style.opacity = 0;
+
+    const getValueFunc = time => {
+      simulatedAnimation.currentTime = time;
+      return win.getComputedStyle(simulatedElement).opacity;
+    };
+
+    const toPathStringFunc = segments => {
+      const firstSegment = segments[0];
+      let pathString = `M${ firstSegment.x },0 `;
+      pathString += toPathString(segments);
+      const lastSegment = segments[segments.length - 1];
+      pathString += `L${ lastSegment.x },0 Z`;
+      return pathString;
+    };
+
+    const helper = new SummaryGraphHelper(state, keyframes,
+                                          totalDuration, durationPerPixel,
+                                          getValueFunc, toPathStringFunc);
+    const offset = state.previousStartTime ? state.previousStartTime : 0;
+
+    return dom.g(
+      {
+        className,
+        transform: `translate(${ offset })`
+      },
+      this.renderGraph(state, helper)
+    );
+  }
+}
+
+module.exports = NegativePath;
--- a/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
+++ b/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
@@ -6,16 +6,17 @@
 
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 
 const ComputedTimingPath = createFactory(require("./ComputedTimingPath"));
 const EffectTimingPath = createFactory(require("./EffectTimingPath"));
+const NegativeDelayPath = createFactory(require("./NegativeDelayPath"));
 const { DEFAULT_GRAPH_HEIGHT } = require("../../utils/graph-helper");
 
 // Minimum opacity for semitransparent fill color for keyframes's easing graph.
 const MIN_KEYFRAMES_EASING_OPACITY = 0.5;
 
 class SummaryGraphPath extends PureComponent {
   static get propTypes() {
     return {
@@ -177,23 +178,37 @@ class SummaryGraphPath extends PureCompo
             keyframes,
             opacity,
             simulateAnimation,
             totalDuration,
           }
         )
       ),
       animation.state.easing !== "linear" ?
-      EffectTimingPath(
-        {
-          animation,
-          durationPerPixel,
-          simulateAnimation,
-          totalDuration,
-        }
-      )
+        EffectTimingPath(
+          {
+            animation,
+            durationPerPixel,
+            simulateAnimation,
+            totalDuration,
+          }
+        )
+      :
+      null,
+      animation.state.delay < 0 ?
+        keyframesList.map(keyframes => {
+          return NegativeDelayPath(
+            {
+              animation,
+              durationPerPixel,
+              keyframes,
+              simulateAnimation,
+              totalDuration,
+            }
+          );
+        })
       :
       null
     );
   }
 }
 
 module.exports = SummaryGraphPath;
--- a/devtools/client/inspector/animation/components/graph/moz.build
+++ b/devtools/client/inspector/animation/components/graph/moz.build
@@ -2,12 +2,14 @@
 # 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/.
 
 DevToolsModules(
     'ComputedTimingPath.js',
     'DelaySign.js',
     'EffectTimingPath.js',
     'EndDelaySign.js',
+    'NegativeDelayPath.js',
+    'NegativePath.js',
     'SummaryGraph.js',
     'SummaryGraphPath.js',
     'TimingPath.js'
 )
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -114,16 +114,24 @@
   transform: scale(1, -1);
   vector-effect: non-scaling-stroke;
 }
 
 .animation-effect-timing-path path.infinity:nth-child(n+2) {
   opacity: 0.3;
 }
 
+.animation-negative-delay-path path {
+  fill: none;
+  stroke: var(--theme-graphs-grey);
+  stroke-dasharray: 2px 2px;
+  transform: scale(1, -1);
+  vector-effect: non-scaling-stroke;
+}
+
 .animation-delay-sign,
 .animation-end-delay-sign {
   background-color: var(--theme-graphs-grey);
   height: 3px;
   position: absolute;
   top: calc(100% - 1.5px);
 }