Bug 1416106 - Part 7: Implement color graph. r?gl draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Wed, 14 Feb 2018 23:18:12 +0900
changeset 755232 b6ed11306ed800347000d521079cc16eab89d6d8
parent 755231 ad00e160086dc2c1d14a96f500685e12fa2e7335
child 755233 5cf82cd2d674ffe2a455e82e4d6b5a6abf9ea9fa
push id99127
push userbmo:dakatsuka@mozilla.com
push dateThu, 15 Feb 2018 00:47:03 +0000
reviewersgl
bugs1416106
milestone60.0a1
Bug 1416106 - Part 7: Implement color graph. r?gl MozReview-Commit-ID: 4ek6LXtsmKc
devtools/client/inspector/animation/components/AnimatedPropertyItem.js
devtools/client/inspector/animation/components/AnimatedPropertyList.js
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/KeyframesGraph.js
devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraphPath.js
devtools/client/inspector/animation/components/keyframes-graph/moz.build
--- a/devtools/client/inspector/animation/components/AnimatedPropertyItem.js
+++ b/devtools/client/inspector/animation/components/AnimatedPropertyItem.js
@@ -12,41 +12,44 @@ const AnimatedPropertyName = createFacto
 const KeyframesGraph = createFactory(require("./keyframes-graph/KeyframesGraph"));
 
 class AnimatedPropertyItem extends PureComponent {
   static get propTypes() {
     return {
       property: PropTypes.string.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       state: PropTypes.object.isRequired,
+      type: PropTypes.string.isRequired,
       values: PropTypes.array.isRequired,
     };
   }
 
   render() {
     const {
       property,
       simulateAnimation,
       state,
+      type,
       values,
     } = this.props;
 
     return dom.li(
       {
         className: "animated-property-item"
       },
       AnimatedPropertyName(
         {
           property,
           state,
         }
       ),
       KeyframesGraph(
         {
           simulateAnimation,
+          type,
           values,
         }
       )
     );
   }
 }
 
 module.exports = AnimatedPropertyItem;
--- a/devtools/client/inspector/animation/components/AnimatedPropertyList.js
+++ b/devtools/client/inspector/animation/components/AnimatedPropertyList.js
@@ -49,45 +49,49 @@ class AnimatedPropertyList extends PureC
   }
 
   async updateKeyframesList(animation) {
     const {
       getAnimatedPropertyMap,
       emitEventForTest,
     } = this.props;
     const animatedPropertyMap = await getAnimatedPropertyMap(animation);
+    const animationTypes = await animation.getAnimationTypes(animatedPropertyMap.keys());
 
-    this.setState({ animatedPropertyMap });
+    this.setState({ animatedPropertyMap, animationTypes });
 
     emitEventForTest("animation-keyframes-rendered");
   }
 
   render() {
     const {
       simulateAnimation,
     } = this.props;
     const {
       animatedPropertyMap,
+      animationTypes,
     } = this.state;
 
     if (!animatedPropertyMap) {
       return null;
     }
 
     return dom.ul(
       {
         className: "animated-property-list"
       },
       [...animatedPropertyMap.entries()].map(([property, values]) => {
         const state = this.getPropertyState(property);
+        const type = animationTypes[property];
         return AnimatedPropertyItem(
           {
             property,
             simulateAnimation,
             state,
+            type,
             values,
           }
         );
       })
     );
   }
 }
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js
@@ -0,0 +1,142 @@
+/* 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 dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+const {colorUtils} = require("devtools/shared/css/color.js");
+
+const ComputedStylePath = require("./ComputedStylePath");
+
+/* Count for linearGradient ID */
+let LINEAR_GRADIENT_ID_COUNT = 0;
+
+class ColorPath extends ComputedStylePath {
+  constructor(props) {
+    super(props);
+
+    this.state = this.propToState(props);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    this.setState(this.propToState(nextProps));
+  }
+
+  getPropertyName() {
+    return "color";
+  }
+
+  getPropertyValue(keyframe) {
+    return keyframe.value;
+  }
+
+  propToState({ values }) {
+    const maxObject = { distance: 0 };
+
+    for (let i = 0; i < values.length - 1; i++) {
+      const value1 = getRGBA(values[i].value);
+      for (let j = i + 1; j < values.length; j++) {
+        const value2 = getRGBA(values[j].value);
+        const distance = getRGBADistance(value1, value2);
+
+        if (maxObject.distance >= distance) {
+          continue;
+        }
+
+        maxObject.distance = distance;
+        maxObject.value1 = value1;
+        maxObject.value2 = value2;
+      }
+    }
+
+    const maxDistance = maxObject.distance;
+    const baseValue =
+      maxObject.value1 < maxObject.value2 ? maxObject.value1 : maxObject.value2;
+
+    return { baseValue, maxDistance };
+  }
+
+  toSegmentValue(computedStyle) {
+    const { baseValue, maxDistance } = this.state;
+    const value = getRGBA(computedStyle);
+    return getRGBADistance(baseValue, value) / maxDistance;
+  }
+
+  /**
+   * 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 })` });
+    const linearGradient = dom.linearGradient(
+      { id },
+      segments.map(segment => {
+        return dom.stop(
+          {
+            "stopColor": segment.computedStyle,
+            "offset": segment.x / lastSegment.x,
+          }
+        );
+      })
+    );
+
+    return [path, linearGradient];
+  }
+
+  render() {
+    return dom.g(
+      {
+        className: "color-path",
+      },
+      super.renderGraph()
+    );
+  }
+}
+
+/**
+ * Parse given RGBA string.
+ *
+ * @param {String} colorString
+ *        e.g. rgb(0, 0, 0) or rgba(0, 0, 0, 0.5) and so on.
+ * @return {Object}
+ *         RGBA {r: r, g: g, b: b, a: a}.
+ */
+function getRGBA(colorString) {
+  const color = new colorUtils.CssColor(colorString);
+  return color.getRGBATuple();
+}
+
+/**
+ * Return the distance from give two RGBA.
+ *
+ * @param {Object} rgba1
+ *        RGBA (format is same to getRGBA)
+ * @param {Object} rgba2
+ *        RGBA (format is same to getRGBA)
+ * @return {Number}
+ *         The range is 0 - 1.0.
+ */
+function getRGBADistance(rgba1, rgba2) {
+  const startA = rgba1.a;
+  const startR = rgba1.r * startA;
+  const startG = rgba1.g * startA;
+  const startB = rgba1.b * startA;
+  const endA = rgba2.a;
+  const endR = rgba2.r * endA;
+  const endG = rgba2.g * endA;
+  const endB = rgba2.b * endA;
+  const diffA = startA - endA;
+  const diffR = startR - endR;
+  const diffG = startG - endG;
+  const diffB = startB - endB;
+  return Math.sqrt(diffA * diffA + diffR * diffR + diffG * diffG + diffB * diffB);
+}
+
+module.exports = ColorPath;
--- a/devtools/client/inspector/animation/components/keyframes-graph/ComputedStylePath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/ComputedStylePath.js
@@ -128,26 +128,27 @@ class ComputedStylePath extends PureComp
 
     return this.renderPathSegments(segments);
   }
 
   /**
    * Return react dom fron given path segments.
    *
    * @param {Array} segments
+   * @param {Object} style
    * @return {Element}
    */
-  renderPathSegments(segments) {
+  renderPathSegments(segments, style) {
     const { graphHeight } = this.props;
 
     for (const segment of segments) {
       segment.y *= graphHeight;
     }
 
     let d = `M${ segments[0].x },0 `;
     d += toPathString(segments);
     d += `L${ segments[segments.length - 1].x },0 Z`;
 
-    return dom.path({ d });
+    return dom.path({ d, style });
   }
 }
 
 module.exports = ComputedStylePath;
--- a/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraph.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraph.js
@@ -9,33 +9,36 @@ const dom = require("devtools/client/sha
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const KeyframesGraphPath = createFactory(require("./KeyframesGraphPath"));
 
 class KeyframesGraph extends PureComponent {
   static get propTypes() {
     return {
       simulateAnimation: PropTypes.func.isRequired,
+      type: PropTypes.string.isRequired,
       values: PropTypes.array.isRequired,
     };
   }
 
   render() {
     const {
       simulateAnimation,
+      type,
       values,
     } = this.props;
 
     return dom.div(
       {
         className: "keyframes-graph",
       },
       KeyframesGraphPath(
         {
           simulateAnimation,
+          type,
           values,
         }
       )
     );
   }
 }
 
 module.exports = KeyframesGraph;
--- a/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraphPath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraphPath.js
@@ -4,67 +4,81 @@
 
 "use strict";
 
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 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 DistancePath = createFactory(require("./DistancePath"));
 
 const {
   DEFAULT_GRAPH_HEIGHT,
   DEFAULT_KEYFRAMES_GRAPH_DURATION,
 } = require("../../utils/graph-helper");
 
 class KeyframesGraphPath extends PureComponent {
   static get propTypes() {
     return {
       simulateAnimation: PropTypes.func.isRequired,
+      type: PropTypes.string.isRequired,
       values: PropTypes.array.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
       componentWidth: 0,
     };
   }
 
   componentDidMount() {
     this.updateState();
   }
 
+  getPathComponent(type) {
+    switch (type) {
+      case "color" :
+        return ColorPath;
+      default :
+        return DistancePath;
+    }
+  }
+
   updateState() {
     const thisEl = ReactDOM.findDOMNode(this);
     this.setState({ componentWidth: thisEl.parentNode.clientWidth });
   }
 
   render() {
     const {
       simulateAnimation,
+      type,
       values,
     } = this.props;
     const { componentWidth } = this.state;
 
     if (!componentWidth) {
       return dom.svg();
     }
 
+    const pathComponent = this.getPathComponent(type);
+
     return dom.svg(
       {
         className: "keyframes-graph-path",
         preserveAspectRatio: "none",
         viewBox: `0 -${ DEFAULT_GRAPH_HEIGHT } `
                  + `${ DEFAULT_KEYFRAMES_GRAPH_DURATION } ${ DEFAULT_GRAPH_HEIGHT }`,
       },
-      DistancePath(
+      pathComponent(
         {
           componentWidth,
           graphHeight: DEFAULT_GRAPH_HEIGHT,
           simulateAnimation,
           totalDuration: DEFAULT_KEYFRAMES_GRAPH_DURATION,
           values,
         }
       )
--- a/devtools/client/inspector/animation/components/keyframes-graph/moz.build
+++ b/devtools/client/inspector/animation/components/keyframes-graph/moz.build
@@ -1,10 +1,11 @@
 # 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/.
 
 DevToolsModules(
+    'ColorPath.js',
     'ComputedStylePath.js',
     'DistancePath.js',
     'KeyframesGraph.js',
     'KeyframesGraphPath.js',
 )