Bug 1337235 - Mouseover interaction for grid outline. r=gl
authorMicah Tigley <tigleym@gmail.com>
Sat, 25 Feb 2017 23:42:45 -0700
changeset 345281 0a15dfecc9fa2b375e5de3ad6f195516b1de2d2f
parent 345276 81c5898574dcd877c0d42b5554ee3f941d6ba55e
child 345282 417a8c548d3944bd91eb26322f2092525f727ff4
push id31436
push userkwierso@gmail.com
push dateThu, 02 Mar 2017 01:18:52 +0000
treeherdermozilla-central@e91de6fb2b3d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1337235
milestone54.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 1337235 - Mouseover interaction for grid outline. r=gl MozReview-Commit-ID: 4qZfle2lDMZ
devtools/client/inspector/layout/components/Grid.js
devtools/client/inspector/layout/components/GridOutline.js
devtools/client/inspector/layout/layout.js
devtools/client/themes/layout.css
devtools/server/actors/highlighters/css-grid.js
--- a/devtools/client/inspector/layout/components/Grid.js
+++ b/devtools/client/inspector/layout/components/Grid.js
@@ -22,16 +22,17 @@ module.exports = createClass({
     getSwatchColorPickerTooltip: PropTypes.func.isRequired,
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
     highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
     setSelectedNode: PropTypes.func.isRequired,
     showGridOutline: PropTypes.bool.isRequired,
     onHideBoxModelHighlighter: PropTypes.func.isRequired,
     onSetGridOverlayColor: PropTypes.func.isRequired,
     onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+    onShowGridAreaHighlight: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
     onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
@@ -42,26 +43,28 @@ module.exports = createClass({
       setSelectedNode,
       showGridOutline,
       onHideBoxModelHighlighter,
       onSetGridOverlayColor,
       onShowBoxModelHighlighterForNode,
       onToggleGridHighlighter,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
+      onShowGridAreaHighlight,
     } = this.props;
 
     return grids.length ?
       dom.div(
         {
           id: "layout-grid-container",
         },
         showGridOutline ?
           GridOutline({
             grids,
+            onShowGridAreaHighlight,
           })
           :
           null,
         dom.div(
           {
             className: "grid-content",
           },
           GridList({
--- a/devtools/client/inspector/layout/components/GridOutline.js
+++ b/devtools/client/inspector/layout/components/GridOutline.js
@@ -18,98 +18,183 @@ const VIEWPORT_HEIGHT = 100;
 const VIEWPORT_WIDTH = 450;
 
 module.exports = createClass({
 
   displayName: "GridOutline",
 
   propTypes: {
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
+    onShowGridAreaHighlight: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   getInitialState() {
     return {
       selectedGrids: [],
     };
   },
 
   componentWillReceiveProps({ grids }) {
     this.setState({
       selectedGrids: grids.filter(grid => grid.highlighted),
     });
   },
 
-  renderGridCell(columnNumber, rowNumber, color) {
-    return dom.rect(
-      {
-        className: "grid-outline-cell",
-        x: columnNumber,
-        y: rowNumber,
-        width: 10,
-        height: 10,
-        stroke: color,
-      }
+  /**
+   * Returns the grid area name if the given grid cell is part of a grid area, otherwise
+   * null.
+   *
+   * @param  {Number} columnNumber
+   *         The column number of the grid cell.
+   * @param  {Number} rowNumber
+   *         The row number of the grid cell.
+   * @param  {Array} areas
+   *         Array of grid areas data stored in the grid fragment.
+   * @return {String} If there is a grid area return area name, otherwise null.
+   */
+  getGridAreaName(columnNumber, rowNumber, areas) {
+    const gridArea = areas.find(area =>
+      (area.rowStart <= rowNumber && area.rowEnd > rowNumber) &&
+      (area.columnStart <= columnNumber && area.columnEnd > columnNumber)
     );
+
+    if (!gridArea) {
+      return null;
+    }
+
+    return gridArea.name;
   },
 
-  renderGridFragment({ color, gridFragments }) {
+  /**
+   * Renders the grid outline for the given grid container object.
+   *
+   * @param  {Object} grid
+   *         A single grid container in the document.
+   */
+  renderGrid(grid) {
+    const { id, color, gridFragments } = grid;
     // TODO: We are drawing the first fragment since only one is currently being stored.
-    // In future we will need to iterate over all fragments of a grid.
-    const { rows, cols } = gridFragments[0];
-    const numOfColLines = cols.lines.length - 1;
-    const numOfRowLines = rows.lines.length - 1;
+    // In the future we will need to iterate over all fragments of a grid.
+    const { rows, cols, areas } = gridFragments[0];
+    const numberOfColumns = cols.lines.length - 1;
+    const numberOfRows = rows.lines.length - 1;
     const rectangles = [];
 
-    // Draw a rectangle that acts as a border for the grid outline
-    const border = this.renderGridOutlineBorder(numOfRowLines, numOfColLines, color);
+    // Draw a rectangle that acts as a border for the grid outline.
+    const border = this.renderGridOutlineBorder(numberOfRows, numberOfColumns, color);
     rectangles.push(border);
 
     let x = 1;
     let y = 1;
     const width = 10;
     const height = 10;
 
-    // Draw the cells within the grid outline border
-    for (let row = 0; row < numOfRowLines; row++) {
-      for (let col = 0; col < numOfColLines; col++) {
-        rectangles.push(this.renderGridCell(x, y, color));
+    // Draw the cells within the grid outline border.
+    for (let rowNumber = 1; rowNumber <= numberOfRows; rowNumber++) {
+      for (let columnNumber = 1; columnNumber <= numberOfColumns; columnNumber++) {
+        const gridAreaName = this.getGridAreaName(columnNumber, rowNumber, areas);
+        const gridCell = this.renderGridCell(id, x, y, rowNumber, columnNumber, color,
+          gridAreaName);
+
+        rectangles.push(gridCell);
         x += width;
       }
 
       x = 1;
       y += height;
     }
 
     return rectangles;
   },
 
-  renderGridOutline(gridFragments) {
+  /**
+   * Renders the grid cell of a grid fragment.
+   *
+   * @param  {Number} id
+   *         The grid id stored on the grid fragment
+   * @param  {Number} x
+   *         The x-position of the grid cell.
+   * @param  {Number} y
+   *         The y-position of the grid cell.
+   * @param  {Number} rowNumber
+   *         The row number of the grid cell.
+   * @param  {Number} columnNumber
+   *         The column number of the grid cell.
+   * @param  {String|null} gridAreaName
+   *         The grid area name or null if the grid cell is not part of a grid area.
+   */
+  renderGridCell(id, x, y, rowNumber, columnNumber, color, gridAreaName) {
+    return dom.rect(
+      {
+        className: "grid-outline-cell",
+        "data-grid-area-name": gridAreaName,
+        "data-grid-id": id,
+        x,
+        y,
+        width: 10,
+        height: 10,
+        fill: "none",
+        stroke: color,
+        onMouseOver: this.onMouseOverCell,
+        onMouseOut: this.onMouseLeaveCell,
+      }
+    );
+  },
+
+  renderGridOutline(grids) {
     return dom.g(
       {
         className: "grid-cell-group",
       },
-      gridFragments.map(gridFragment => this.renderGridFragment(gridFragment))
+      grids.map(grid => this.renderGrid(grid))
     );
   },
 
-  renderGridOutlineBorder(numberOfRows, numberOfCols, color) {
+  renderGridOutlineBorder(numberOfRows, numberOfColumns, color) {
     return dom.rect(
       {
         className: "grid-outline-border",
         x: 1,
         y: 1,
-        width: numberOfCols * 10,
+        width: numberOfColumns * 10,
         height: numberOfRows * 10,
         stroke: color,
       }
     );
   },
 
+  onMouseLeaveCell({ target }) {
+    const {
+      grids,
+      onShowGridAreaHighlight,
+    } = this.props;
+    const id = target.getAttribute("data-grid-id");
+    const color = target.getAttribute("stroke");
+
+    target.setAttribute("fill", "none");
+
+    onShowGridAreaHighlight(grids[id].nodeFront, null, color);
+  },
+
+  onMouseOverCell({ target }) {
+    const {
+      grids,
+      onShowGridAreaHighlight,
+    } = this.props;
+    const name = target.getAttribute("data-grid-area-name");
+    const id = target.getAttribute("data-grid-id");
+    const color = target.getAttribute("stroke");
+
+    target.setAttribute("fill", color);
+
+    onShowGridAreaHighlight(grids[id].nodeFront, name, color);
+  },
+
   render() {
     return this.state.selectedGrids.length ?
       dom.svg(
         {
           className: "grid-outline",
           width: "100%",
           height: 100,
           viewBox: `${TRANSLATE_X} ${TRANSLATE_Y} ${VIEWPORT_WIDTH} ${VIEWPORT_HEIGHT}`,
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -97,16 +97,17 @@ LayoutView.prototype = {
        * Retrieve the shared SwatchColorPicker instance.
        */
       getSwatchColorPickerTooltip: () => {
         return this.swatchColorPickerTooltip;
       },
 
       /**
        * Set the inspector selection.
+       *
        * @param {NodeFront} nodeFront
        *        The NodeFront corresponding to the new selection.
        */
       setSelectedNode: (nodeFront) => {
         this.inspector.selection.setNodeFront(nodeFront, "layout-panel");
       },
 
       /**
@@ -145,30 +146,52 @@ LayoutView.prototype = {
           }
         }
       },
 
       onShowBoxModelEditor,
       onShowBoxModelHighlighter,
 
      /**
-       * Shows the box-model highlighter on the element corresponding to the provided
-       * NodeFront.
-       *
-       * @param  {NodeFront} nodeFront
-       *         The node to highlight.
-       * @param  {Object} options
-       *         Options passed to the highlighter actor.
-       */
+      * Shows the box-model highlighter on the element corresponding to the provided
+      * NodeFront.
+      *
+      * @param  {NodeFront} nodeFront
+      *         The node to highlight.
+      * @param  {Object} options
+      *         Options passed to the highlighter actor.
+      */
       onShowBoxModelHighlighterForNode: (nodeFront, options) => {
         let toolbox = this.inspector.toolbox;
         toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
       },
 
       /**
+       * Highlights the grid area in the CSS Grid Highlighter for the given grid.
+       *
+       * @param  {NodeFront} node
+       *         The NodeFront of the grid container element for which the grid
+       *         highlighter is highlighted for.
+       * @param  {String} gridAreaName
+       *         The name of the grid area for which the grid highlighter
+       *         is highlighted for.
+       * @param  {String} color
+       *         The color of the grid area for which the grid highlighter
+       *         is highlighted for.
+       */
+      onShowGridAreaHighlight: (node, gridAreaName, color) => {
+        let { highlighterSettings } = this.store.getState();
+
+        highlighterSettings.showGridArea = gridAreaName;
+        highlighterSettings.color = color;
+
+        this.highlighters.showGridHighlighter(node, highlighterSettings);
+      },
+
+      /**
        * Handler for a change in the input checkboxes in the GridList component.
        * Toggles on/off the grid highlighter for the provided grid container element.
        *
        * @param  {NodeFront} node
        *         The NodeFront of the grid container element for which the grid
        *         highlighter is toggled on/off for.
        */
       onToggleGridHighlighter: node => {
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -62,21 +62,24 @@
 }
 
 .grid-outline-border {
   stroke-width: 0.75;
   fill: none;
 }
 
 .grid-outline-cell {
-  fill: none;
   pointer-events: all;
   stroke-dasharray: 0.5, 2;
 }
 
+.grid-outline-cell:hover {
+  opacity: 0.45;
+}
+
 /**
  * Container when no grids are present
  */
 
 .layout-no-grids {
   font-style: italic;
   text-align: center;
   padding: 0.5em;
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -320,16 +320,19 @@ CssGridHighlighter.prototype = extend(Au
     if (Services.prefs.getBoolPref(CSS_GRID_ENABLED_PREF) && !this.isGrid()) {
       this.hide();
       return false;
     }
 
     // The grid pattern cache should be cleared in case the color changed.
     this._clearCache();
 
+    // Hide the canvas, grid element highlights and infobar.
+    this._hide();
+
     return this._update();
   },
 
   _clearCache() {
     gCachedGridPattern.delete(ROW_KEY);
     gCachedGridPattern.delete(COLUMN_KEY);
   },