Bug 1416106 - Part 10: Implement easing hit. r=gl
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Wed, 14 Feb 2018 23:18:13 +0900
changeset 404004 f20d1625e15bd501a5e36a85f9b30544ae35114a
parent 404003 5df3265bb6097c870156423561b9d85598e3e83b
child 404005 d717511990c418ad5bb5df663ffe12295163cfc6
push id99924
push userebalazs@mozilla.com
push dateThu, 15 Feb 2018 20:43:51 +0000
treeherdermozilla-inbound@a7d2a49f46fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1416106
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1416106 - Part 10: Implement easing hit. r=gl MozReview-Commit-ID: 5d6f1dysdxm
devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js
devtools/client/inspector/animation/components/keyframes-graph/ComputedStylePath.js
devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraphPath.js
devtools/client/inspector/animation/utils/graph-helper.js
devtools/client/themes/animation.css
--- a/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js
@@ -62,16 +62,58 @@ class ColorPath extends ComputedStylePat
     const { baseValue, maxDistance } = this.state;
     const value = getRGBA(computedStyle);
     return getRGBADistance(baseValue, value) / maxDistance;
   }
 
   /**
    * Overide parent's method.
    */
+  renderEasingHint() {
+    const {
+      easingHintStrokeWidth,
+      graphHeight,
+      totalDuration,
+      values,
+    } = this.props;
+
+    const hints = [];
+
+    for (let i = 0; i < values.length - 1; i++) {
+      const startKeyframe = values[i];
+      const endKeyframe = values[i + 1];
+      const startTime = startKeyframe.offset * totalDuration;
+      const endTime = endKeyframe.offset * totalDuration;
+
+      const g = dom.g(
+        {
+          className: "hint"
+        },
+        dom.title({}, startKeyframe.easing),
+        dom.rect(
+          {
+            x: startTime,
+            y: -graphHeight,
+            height: graphHeight,
+            width: endTime - startTime,
+            style: {
+              "stroke-width": easingHintStrokeWidth,
+            },
+          }
+        )
+      );
+      hints.push(g);
+    }
+
+    return hints;
+  }
+
+  /**
+   * Overide parent's method.
+   */
   renderPathSegments(segments) {
     for (const segment of segments) {
       segment.y = 1;
     }
 
     const lastSegment = segments[segments.length - 1];
     const id = `color-property-${ LINEAR_GRADIENT_ID_COUNT++ }`;
     const path = super.renderPathSegments(segments, { fill: `url(#${ id })` });
--- a/devtools/client/inspector/animation/components/keyframes-graph/ComputedStylePath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/ComputedStylePath.js
@@ -36,16 +36,17 @@ const {
  *          e.g. 0
  *   @return {Number}
  *          e.g. 0 (should be 0 - 1.0)
  */
 class ComputedStylePath extends PureComponent {
   static get propTypes() {
     return {
       componentWidth: PropTypes.number.isRequired,
+      easingHintStrokeWidth: PropTypes.number.isRequired,
       graphHeight: PropTypes.number.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       totalDuration: PropTypes.number.isRequired,
       values: PropTypes.array.isRequired,
     };
   }
 
   /**
@@ -106,32 +107,92 @@ class ComputedStylePath extends PureComp
     for (const segment of segments) {
       segment.x += offset;
     }
 
     return segments;
   }
 
   /**
+   * Render easing hint from given path segments.
+   *
+   * @param {Array} segments
+   *        Path segments.
+   * @return {Element}
+   *         Element which represents easing hint.
+   */
+  renderEasingHint(segments) {
+    const {
+      easingHintStrokeWidth,
+      totalDuration,
+      values,
+    } = this.props;
+
+    const hints = [];
+
+    for (let i = 0, indexOfSegments = 0; i < values.length - 1; i++) {
+      const startKeyframe = values[i];
+      const endKeyframe = values[i + 1];
+      const endTime = endKeyframe.offset * totalDuration;
+      const hintSegments = [];
+
+      for (; indexOfSegments < segments.length; indexOfSegments++) {
+        const segment = segments[indexOfSegments];
+        hintSegments.push(segment);
+
+        if (startKeyframe.offset === endKeyframe.offset) {
+          hintSegments.push(segments[++indexOfSegments]);
+          break;
+        } else if (segment.x === endTime) {
+          break;
+        }
+      }
+
+      const g = dom.g(
+        {
+          className: "hint"
+        },
+        dom.title({}, startKeyframe.easing),
+        dom.path(
+          {
+            d: `M${ hintSegments[0].x },${ hintSegments[0].y } ` +
+               toPathString(hintSegments),
+            style: {
+              "stroke-width": easingHintStrokeWidth,
+            }
+          }
+        )
+      );
+
+      hints.push(g);
+    }
+
+    return hints;
+  }
+
+  /**
    * Render graph. This method returns React dom.
    *
    * @return {Element}
    */
   renderGraph() {
     const { values } = this.props;
 
     const segments = [];
 
     for (let i = 0; i < values.length - 1; i++) {
       const startValue = values[i];
       const endValue = values[i + 1];
       segments.push(...this.getPathSegments(startValue, endValue));
     }
 
-    return this.renderPathSegments(segments);
+    return [
+      this.renderPathSegments(segments),
+      this.renderEasingHint(segments)
+    ];
   }
 
   /**
    * Return react dom fron given path segments.
    *
    * @param {Array} segments
    * @param {Object} style
    * @return {Element}
--- a/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraphPath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraphPath.js
@@ -9,16 +9,17 @@ const dom = require("devtools/client/sha
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 
 const ColorPath = createFactory(require("./ColorPath"));
 const DiscretePath = createFactory(require("./DiscretePath"));
 const DistancePath = createFactory(require("./DistancePath"));
 
 const {
+  DEFAULT_EASING_HINT_STROKE_WIDTH,
   DEFAULT_GRAPH_HEIGHT,
   DEFAULT_KEYFRAMES_GRAPH_DURATION,
 } = require("../../utils/graph-helper");
 
 class KeyframesGraphPath extends PureComponent {
   static get propTypes() {
     return {
       getComputedStyle: PropTypes.func.isRequired,
@@ -28,16 +29,17 @@ class KeyframesGraphPath extends PureCom
       values: PropTypes.array.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
+      componentHeight: 0,
       componentWidth: 0,
     };
   }
 
   componentDidMount() {
     this.updateState();
   }
 
@@ -49,45 +51,55 @@ class KeyframesGraphPath extends PureCom
         return DiscretePath;
       default :
         return DistancePath;
     }
   }
 
   updateState() {
     const thisEl = ReactDOM.findDOMNode(this);
-    this.setState({ componentWidth: thisEl.parentNode.clientWidth });
+    this.setState({
+      componentHeight: thisEl.parentNode.clientHeight,
+      componentWidth: thisEl.parentNode.clientWidth,
+    });
   }
 
   render() {
     const {
       getComputedStyle,
       property,
       simulateAnimation,
       type,
       values,
     } = this.props;
-    const { componentWidth } = this.state;
+    const {
+      componentHeight,
+      componentWidth,
+    } = this.state;
 
     if (!componentWidth) {
       return dom.svg();
     }
 
     const pathComponent = this.getPathComponent(type);
+    const strokeWidthInViewBox =
+      DEFAULT_EASING_HINT_STROKE_WIDTH / 2 / componentHeight * DEFAULT_GRAPH_HEIGHT;
 
     return dom.svg(
       {
         className: "keyframes-graph-path",
         preserveAspectRatio: "none",
-        viewBox: `0 -${ DEFAULT_GRAPH_HEIGHT } `
-                 + `${ DEFAULT_KEYFRAMES_GRAPH_DURATION } ${ DEFAULT_GRAPH_HEIGHT }`,
+        viewBox: `0 -${ DEFAULT_GRAPH_HEIGHT + strokeWidthInViewBox } ` +
+                 `${ DEFAULT_KEYFRAMES_GRAPH_DURATION } ` +
+                 `${ DEFAULT_GRAPH_HEIGHT + strokeWidthInViewBox * 2 }`,
       },
       pathComponent(
         {
           componentWidth,
+          easingHintStrokeWidth: DEFAULT_EASING_HINT_STROKE_WIDTH,
           getComputedStyle,
           graphHeight: DEFAULT_GRAPH_HEIGHT,
           property,
           simulateAnimation,
           totalDuration: DEFAULT_KEYFRAMES_GRAPH_DURATION,
           values,
         }
       )
--- a/devtools/client/inspector/animation/utils/graph-helper.js
+++ b/devtools/client/inspector/animation/utils/graph-helper.js
@@ -18,16 +18,18 @@ const DEFAULT_MIN_PROGRESS_THRESHOLD = 0
 // DEFAULT_DURATION_RESOLUTION in order to draw the way the animation progresses.
 // But depending on the timing-function, we may be not able to make the graph
 // smoothly progress if this resolution is not high enough.
 // So, if the difference of animation progress between 2 divisions is more than
 // DEFAULT_MIN_PROGRESS_THRESHOLD * DEFAULT_GRAPH_HEIGHT, then createPathSegments
 // re-divides by DEFAULT_DURATION_RESOLUTION.
 // DEFAULT_DURATION_RESOLUTION shoud be integer and more than 2.
 const DEFAULT_DURATION_RESOLUTION = 4;
+// Stroke width for easing hint.
+const DEFAULT_EASING_HINT_STROKE_WIDTH = 5;
 
 /**
  * The helper class for creating summary graph.
  */
 class SummaryGraphHelper {
   /**
    * Constructor.
    *
@@ -255,14 +257,15 @@ function toPathString(segments) {
   segments.forEach(segment => {
     pathString += `L${ segment.x },${ segment.y } `;
   });
   return pathString;
 }
 
 exports.createPathSegments = createPathSegments;
 exports.DEFAULT_DURATION_RESOLUTION = DEFAULT_DURATION_RESOLUTION;
+exports.DEFAULT_EASING_HINT_STROKE_WIDTH = DEFAULT_EASING_HINT_STROKE_WIDTH;
 exports.DEFAULT_GRAPH_HEIGHT = DEFAULT_GRAPH_HEIGHT;
 exports.DEFAULT_KEYFRAMES_GRAPH_DURATION = DEFAULT_KEYFRAMES_GRAPH_DURATION;
 exports.getPreferredProgressThresholdByKeyframes =
   getPreferredProgressThresholdByKeyframes;
 exports.SummaryGraphHelper = SummaryGraphHelper;
 exports.toPathString = toPathString;
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -355,17 +355,17 @@
 
 .animated-property-name.warning span {
   text-decoration: underline dotted;
 }
 
 /* Keyframes Graph */
 .keyframes-graph {
   height: 100%;
-  padding-top: 5px;
+  padding-top: 3px;
   width: calc(100% - var(--sidebar-width) - var(--graph-right-offset));
 }
 
 .keyframes-graph-path {
   height: 100%;
   width: 100%;
 }
 
@@ -385,16 +385,37 @@
   fill: #ea800088;
   stroke: #ea8000;
 }
 
 .keyframes-graph-path .color-path path {
   stroke: none;
 }
 
+.keyframes-graph .keyframes-graph-path .hint path {
+  fill: none;
+  stroke-linecap: round;
+  stroke-opacity: 0;
+}
+
+.keyframes-graph-path .hint path:hover {
+  stroke-opacity: 1;
+}
+
+.keyframes-graph-path .hint rect {
+  fill-opacity: 0.1;
+  stroke: #00b0bd;
+  stroke-opacity: 0;
+  vector-effect: non-scaling-stroke;
+}
+
+.keyframes-graph-path .hint rect:hover {
+  stroke-opacity: 1;
+}
+
 /* No Animation Panel */
 .animation-error-message {
   overflow: auto;
 }
 
 .animation-error-message > p {
   white-space: pre;
 }