Bug 1432029 - Highlight the justify-content area in the flexbox highlighter. r=gl draft
authorZhenghong Qian <zhenghong.rico.qian@gmail.com>
Sun, 04 Feb 2018 15:13:27 -0500
changeset 751037 9d39f5c9476dbf43ecda3f5162a25af05e519010
parent 751031 6cc4949cad7781cb78c7c38fbc2b680b0412d3d6
push id97825
push userbmo:zhenghong.qian@mail.utoronto.ca
push dateSun, 04 Feb 2018 20:15:39 +0000
reviewersgl
bugs1432029
milestone60.0a1
Bug 1432029 - Highlight the justify-content area in the flexbox highlighter. r=gl MozReview-Commit-ID: 4VCCLDL9Apk
devtools/server/actors/highlighters/flexbox.js
--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -31,25 +31,28 @@ const FLEXBOX_LINES_PROPERTIES = {
   },
   "item": {
     lineDash: [0, 0]
   }
 };
 
 const FLEXBOX_CONTAINER_PATTERN_WIDTH = 14; // px
 const FLEXBOX_CONTAINER_PATTERN_HEIGHT = 14; // px
+const FLEXBOX_JUSTIFYCONTENT_PATTERN_WIDTH = 7; // px
+const FLEXBOX_JUSTIFYCONTENT_PATTERN_HEIGHT = 7; // px
 const FLEXBOX_CONTAINER_PATTERN_LINE_DISH = [5, 3]; // px
 const BASIS_FILL_COLOR = "rgb(109, 184, 255, 0.4)";
 
 /**
  * Cached used by `FlexboxHighlighter.getFlexContainerPattern`.
  */
 const gCachedFlexboxPattern = new Map();
 
 const FLEXBOX = "flexbox";
+const JUSTIFY_CONTENT = "justify-content";
 
 class FlexboxHighlighter extends AutoRefreshHighlighter {
   constructor(highlighterEnv) {
     super(highlighterEnv);
 
     this.ID_CLASS_PREFIX = "flexbox-";
 
     this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
@@ -125,16 +128,26 @@ class FlexboxHighlighter extends AutoRef
 
     this.markup.destroy();
 
     // Clear the pattern cache to avoid dead object exceptions (Bug 1342051).
     this.clearCache();
     AutoRefreshHighlighter.prototype.destroy.call(this);
   }
 
+  /**
+   * Draw the justify content for a given flex item (left, top, right, bottom) position.
+   */
+  drawJustifyContent(left, top, right, bottom) {
+    let { devicePixelRatio } = this.win;
+    this.ctx.fillStyle = this.getJustifyContentPattern(devicePixelRatio);
+    drawRect(this.ctx, left, top, right, bottom, this.currentMatrix);
+    this.ctx.fill();
+  }
+
   get canvas() {
     return this.getElement("canvas");
   }
 
   get ctx() {
     return this.canvas.getCanvasContext("2d");
   }
 
@@ -183,16 +196,62 @@ class FlexboxHighlighter extends AutoRef
     let pattern = ctx.createPattern(canvas, "repeat");
     flexboxPatternMap.set(FLEXBOX, pattern);
     gCachedFlexboxPattern.set(devicePixelRatio, flexboxPatternMap);
 
     return pattern;
   }
 
   /**
+  * Gets the flexbox justify content pattern used to render the justify content regions.
+  *
+  * @param  {Number} devicePixelRatio
+  *         The device pixel ratio we want the pattern for.
+  * @return {CanvasPattern} flex justify content pattern.
+  */
+  getJustifyContentPattern(devicePixelRatio) {
+    let flexboxPatternMap = null;
+
+    if (gCachedFlexboxPattern.has(devicePixelRatio)) {
+      flexboxPatternMap = gCachedFlexboxPattern.get(devicePixelRatio);
+    } else {
+      flexboxPatternMap = new Map();
+    }
+
+    if (gCachedFlexboxPattern.has(JUSTIFY_CONTENT)) {
+      return gCachedFlexboxPattern.get(JUSTIFY_CONTENT);
+    }
+
+    // Create the inversed diagonal lines pattern
+    // for the rendering the justify content gaps.
+    let canvas = createNode(this.win, { nodeType: "canvas" });
+    let width = canvas.width = FLEXBOX_JUSTIFYCONTENT_PATTERN_WIDTH * devicePixelRatio;
+    let height = canvas.height = FLEXBOX_JUSTIFYCONTENT_PATTERN_HEIGHT * devicePixelRatio;
+
+    let ctx = canvas.getContext("2d");
+    ctx.save();
+    ctx.setLineDash(FLEXBOX_CONTAINER_PATTERN_LINE_DISH);
+    ctx.beginPath();
+    ctx.translate(.5, .5);
+
+    ctx.moveTo(0, height);
+    ctx.lineTo(width, 0);
+
+    ctx.strokeStyle = DEFAULT_COLOR;
+    ctx.stroke();
+    ctx.restore();
+
+    let pattern = ctx.createPattern(canvas, "repeat");
+    flexboxPatternMap.set(JUSTIFY_CONTENT, pattern);
+    gCachedFlexboxPattern.set(devicePixelRatio, flexboxPatternMap);
+
+    return pattern;
+  }
+
+  /**
    * The AutoRefreshHighlighter's _hasMoved method returns true only if the
    * element's quads have changed. Override it so it also returns true if the
    * element and its flex items have changed.
    */
   _hasMoved() {
     let hasMoved = AutoRefreshHighlighter.prototype._hasMoved.call(this);
 
     // TODO: Implement a check of old and new flex container and flex items to react
@@ -310,20 +369,20 @@ class FlexboxHighlighter extends AutoRef
   /**
    * Renders the flex basis for a given flex item.
    */
   renderFlexItemBasis(flexItem, left, top, right, bottom, boundsWidth) {
     let computedStyle = getComputedStyle(flexItem);
     let basis = computedStyle.getPropertyValue("flex-basis");
 
     if (basis.endsWith("px")) {
-      right = left + parseFloat(basis);
+      right = Math.round(left + parseFloat(basis));
     } else if (basis.endsWith("%")) {
       basis = parseFloat(basis) / 100 * boundsWidth;
-      right = left + basis;
+      right = Math.round(left + basis);
     }
 
     this.ctx.fillStyle = BASIS_FILL_COLOR;
     drawRect(this.ctx, left, top, right, bottom, this.currentMatrix);
     this.ctx.fill();
   }
 
   renderFlexItems() {
@@ -351,28 +410,27 @@ class FlexboxHighlighter extends AutoRef
     for (let flexItem of flexItems) {
       let quads = getAdjustedQuads(this.win, flexItem, "border");
       if (!quads.length) {
         continue;
       }
 
       // Adjust the flex item bounds relative to the current quads.
       let { bounds: flexItemBounds } = quads[0];
-      let left = flexItemBounds.left - bounds.left;
-      let top = flexItemBounds.top - bounds.top;
-      let right = flexItemBounds.right - bounds.left;
-      let bottom = flexItemBounds.bottom - bounds.top;
+      let left = Math.round(flexItemBounds.left - bounds.left);
+      let top = Math.round(flexItemBounds.top - bounds.top);
+      let right = Math.round(flexItemBounds.right - bounds.left);
+      let bottom = Math.round(flexItemBounds.bottom - bounds.top);
 
       clearRect(this.ctx, left, top, right, bottom, this.currentMatrix);
       drawRect(this.ctx, left, top, right, bottom, this.currentMatrix);
       this.ctx.stroke();
 
       this.renderFlexItemBasis(flexItem, left, top, right, bottom, bounds.width);
     }
-
     this.ctx.restore();
   }
 
   renderFlexLines() {
     if (!this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
@@ -412,16 +470,86 @@ class FlexboxHighlighter extends AutoRef
           crossStart + crossSize, this.currentMatrix);
         this.ctx.stroke();
       }
     }
 
     this.ctx.restore();
   }
 
+  renderJudtifyContent() {
+    if (!this.currentQuads.content || !this.currentQuads.content[0]) {
+      return;
+    }
+
+    let { bounds } = this.currentQuads.content[0];
+    let flexItems = this.currentNode.children;
+    let flexLines = this.currentNode.getAsFlexContainer().getLines();
+    let computedStyle = getComputedStyle(this.currentNode);
+    let direction = computedStyle.getPropertyValue("flex-direction");
+
+    // The way to render justify-content is that highlighting all the contents first, and
+    // clearing the ocupied and margin areas of flexItem.
+    // highlight all the contents first:
+    for (let flexLine of flexLines) {
+      let { crossStart, crossSize } = flexLine;
+
+      if (direction === "column" || direction === "column-reverse") {
+        this.drawJustifyContent(crossStart, 0, crossStart + crossSize, bounds.height);
+      } else {
+        this.drawJustifyContent(0, crossStart, bounds.width, crossStart + crossSize);
+      }
+    }
+
+    for (let flexItem of flexItems) {
+      let quads = getAdjustedQuads(this.win, flexItem, "border");
+      if (!quads.length) {
+        continue;
+      }
+
+      // Adjust the flex item bounds relative to the current quads.
+      let { bounds: flexItemBounds } = quads[0];
+      let left = Math.round(flexItemBounds.left - bounds.left);
+      let top = Math.round(flexItemBounds.top - bounds.top);
+      let right = Math.round(flexItemBounds.right - bounds.left);
+      let bottom = Math.round(flexItemBounds.bottom - bounds.top);
+      let flexItemComputedStyle = getComputedStyle(flexItem);
+
+      // clearing the ocupied and margin areas of flexItem:
+      for (let flexLine of flexLines) {
+        let { crossStart, crossSize } = flexLine;
+        crossSize = Math.round(crossSize);
+        crossStart = Math.round(crossStart);
+
+        if ((direction === "column" || direction === "column-reverse") &&
+          (crossStart <= left) && (left <= right) &&
+          (right <= crossSize + crossStart)) {
+          // Remove the margin area for justify-content
+          let marginTop = Math.round(parseFloat(
+            flexItemComputedStyle.getPropertyValue("margin-top")));
+          let marginBottom = Math.round(parseFloat(
+            flexItemComputedStyle.getPropertyValue("margin-bottom")));
+          clearRect(this.ctx, crossStart, top - marginTop, crossSize + crossStart,
+            bottom + marginBottom, this.currentMatrix);
+          break;
+        } else if ((crossStart <= top) && (top <= bottom) &&
+          (bottom <= crossSize + crossStart)) {
+          let marginLeft = Math.round(parseFloat(
+            flexItemComputedStyle.getPropertyValue("margin-left")));
+          let marginRight = Math.round(parseFloat(
+            flexItemComputedStyle.getPropertyValue("margin-right")));
+          clearRect(this.ctx, left - marginLeft, crossStart, right + marginRight,
+            crossSize + crossStart, this.currentMatrix);
+          break;
+        }
+      }
+    }
+    this.ctx.restore();
+  }
+
   _update() {
     setIgnoreLayoutChanges(true);
 
     let root = this.getElement("root");
 
     // Hide the root element and force the reflow in order to get the proper window's
     // dimensions without increasing them.
     root.setAttribute("style", "display: none");
@@ -436,16 +564,17 @@ class FlexboxHighlighter extends AutoRef
     // Update the current matrix used in our canvas' rendering
     let { currentMatrix, hasNodeTransformations } = getCurrentMatrix(this.currentNode,
       this.win);
     this.currentMatrix = currentMatrix;
     this.hasNodeTransformations = hasNodeTransformations;
 
     this.renderFlexContainerFill();
     this.renderFlexLines();
+    this.renderJudtifyContent();
     this.renderFlexItems();
     this.renderFlexContainerBorder();
 
     this._showFlexbox();
 
     root.setAttribute("style",
       `position: absolute; width: ${width}px; height: ${height}px; overflow: hidden`);