| <!-- |
| Copyright (C) 2012 Google Inc. All rights reserved. |
| |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions |
| are met: |
| |
| 1. Redistributions of source code must retain the above copyright |
| notice, this list of conditions and the following disclaimer. |
| 2. Redistributions in binary form must reproduce the above copyright |
| notice, this list of conditions and the following disclaimer in the |
| documentation and/or other materials provided with the distribution. |
| 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| its contributors may be used to endorse or promote products derived |
| from this software without specific prior written permission. |
| |
| THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| --> |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <style> |
| body { |
| margin: 0; |
| padding: 0; |
| } |
| |
| body.platform-mac { |
| font-size: 11px; |
| font-family: Menlo, Monaco; |
| } |
| |
| body.platform-windows { |
| font-size: 12px; |
| font-family: Consolas, Lucida Console; |
| } |
| |
| body.platform-linux { |
| font-size: 11px; |
| font-family: dejavu sans mono; |
| } |
| |
| .fill { |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| } |
| |
| .dimmed { |
| background-color: rgba(0, 0, 0, 0.31); |
| } |
| |
| #canvas, #layout-editor-matched-nodes-canvas { |
| pointer-events: none; |
| } |
| |
| .controls-line { |
| display: flex; |
| justify-content: center; |
| margin: 10px 0; |
| } |
| |
| .message-box { |
| padding: 2px 4px; |
| display: flex; |
| align-items: center; |
| cursor: default; |
| } |
| |
| .controls-line > * { |
| background-color: rgb(255, 255, 194); |
| border: 1px solid rgb(202, 202, 202); |
| height: 22px; |
| box-sizing: border-box; |
| } |
| |
| .controls-line .button { |
| width: 26px; |
| margin-left: -1px; |
| margin-right: 0; |
| padding: 0; |
| } |
| |
| .controls-line .button:hover { |
| cursor: pointer; |
| } |
| |
| .controls-line .button .glyph { |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0, 0, 0, 0.75); |
| opacity: 0.8; |
| -webkit-mask-repeat: no-repeat; |
| -webkit-mask-position: center; |
| position: relative; |
| } |
| |
| .controls-line .button:active .glyph { |
| top: 1px; |
| left: 1px; |
| } |
| |
| #resume-button .glyph { |
| -webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAKCAYAAABv7tTEAAAAAXNSR0IArs4c6QAAAFJJREFUKM+10bEJgGAMBeEPbR3BLRzEVdzEVRzELRzBVohVwEJ+iODBlQfhBeJhsmHU4C0KnFjQV6J0x1SNAhdWDJUoPTB3PvLLeaUhypM3n3sD/qc7lDrdpIEAAAAASUVORK5CYII=); |
| -webkit-mask-size: 13px 10px; |
| background-color: rgb(66, 129, 235); |
| } |
| |
| #step-over-button .glyph { |
| -webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAKCAYAAAC5Sw6hAAAAAXNSR0IArs4c6QAAAOFJREFUKM+N0j8rhXEUB/DPcxW35CqhvIBrtqibkklhV8qkTHe4ZbdblcXgPVhuMdqUTUl5A2KRRCF5LGc4PT1P7qnfcr5/zu/8KdTHLFaxjHnc4RZXKI0QYxjgLQTVd42l/0wmg5iFX3iq5H6w22RS4DyRH7CB8cAXcBTGJT6xUmd0mEwuMdFQcA3fwXvGTAan8BrgPabTL9fRRyfx91PRMwyjGwcJ2EyCfsrfpPw2Pipz24NT/MZciiQYVshzOKnZ5Hturxt3k2MnCpS4SPkeHpPR8Sh3tYgttBoW9II2/AHiaEqvD2Fc0wAAAABJRU5ErkJggg==); |
| -webkit-mask-size: 18px 10px; |
| } |
| |
| .px { |
| color: rgb(128, 128, 128); |
| } |
| |
| #element-title { |
| position: absolute; |
| z-index: 10; |
| } |
| |
| #tag-name { |
| /* Keep this in sync with view-source.css (.html-tag) */ |
| color: rgb(136, 18, 128); |
| } |
| |
| #node-id { |
| /* Keep this in sync with view-source.css (.html-attribute-value) */ |
| color: rgb(26, 26, 166); |
| } |
| |
| #class-name { |
| /* Keep this in sync with view-source.css (.html-attribute-name) */ |
| color: rgb(153, 69, 0); |
| } |
| |
| .wall { |
| position: absolute; |
| z-index: -2; |
| opacity: 0.2; |
| pointer-events: none; |
| } |
| |
| .wall.horizontal { |
| width: 8px; |
| height: 16px; |
| } |
| |
| .wall.vertical { |
| height: 8px; |
| width: 16px; |
| } |
| |
| .wall.padding { |
| background: repeating-linear-gradient(45deg, #FFFFFF 2px, #FFFFFF 4px, rgba(147, 196, 125, 1) 2px, rgba(147, 196, 125, 1) 10px) |
| |
| } |
| |
| .wall.margin { |
| background: repeating-linear-gradient(45deg, #FFFFFF 2px, #FFFFFF 4px, rgba(246, 178, 107, 1) 4px, rgba(246, 178, 107, 1) 10px) |
| } |
| |
| .wall.highlighted { |
| z-index: 1; |
| opacity: 1; |
| } |
| |
| .blur-rect { |
| position: absolute; |
| background-color: rgba(0, 0, 0, 0.1); |
| } |
| |
| .control-lane { |
| position: absolute; |
| } |
| |
| .control-lane.padding { |
| background-color: rgba(147, 196, 125, 0.1); |
| } |
| |
| .control-lane.margin { |
| background-color: rgba(246, 178, 107, 0.1); |
| } |
| |
| .editor-anchor { |
| position: absolute; |
| -webkit-filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.34)); |
| } |
| |
| .editor-anchor.vertical { |
| height: 6px; |
| width: 16px; |
| } |
| |
| .editor-anchor.horizontal { |
| width: 6px; |
| height: 16px; |
| } |
| |
| .editor-anchor.horizontal::before { |
| content: ""; |
| position: absolute; |
| height: 8px; |
| border-right: 2px dotted rgba(255, 255, 255, 0.4); |
| top: 4px; |
| left: 2px; |
| } |
| |
| .editor-anchor.vertical::before { |
| content: ""; |
| position: absolute; |
| width: 8px; |
| border-bottom: 2px dotted rgba(255, 255, 255, 0.4); |
| top: 2px; |
| left: 4px; |
| } |
| |
| .editor-anchor.vertical:hover { |
| cursor: ns-resize; |
| } |
| |
| .editor-anchor.horizontal:hover { |
| cursor: ew-resize; |
| } |
| |
| .editor-anchor.padding { |
| background-color: rgb(107, 213, 0); |
| } |
| |
| .editor-anchor.margin { |
| background-color: rgb(246, 167, 35); |
| } |
| |
| .editor-anchor:hover { |
| z-index: 3; |
| } |
| |
| .editor-anchor.padding.highlighted { |
| background-color: rgba(147, 196, 125, 1); |
| } |
| |
| .editor-anchor.margin.highlighted { |
| background-color: rgba(246, 178, 107, 1); |
| } |
| |
| .guide-line.horizontal { |
| border-top: dashed 1px; |
| } |
| |
| .guide-line.vertical { |
| border-left: dashed 1px; |
| } |
| |
| .guide-line.padding { |
| border-color: rgba(147, 196, 125, 0.56); |
| } |
| |
| .guide-line.margin { |
| border-color: rgba(246, 178, 107, 0.56); |
| } |
| |
| .guide-line.content { |
| border-color: rgba(147, 147, 147, 0.56) |
| } |
| |
| .guide-line { |
| position: absolute; |
| pointer-events: none; |
| } |
| |
| .label { |
| position: absolute; |
| font-size: 10px; |
| font-family: Arial, Roboto; |
| color: white; |
| line-height: 1em; |
| padding: 2px 5px; |
| -webkit-filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.34)); |
| border-radius: 2px; |
| min-width: 30px; |
| z-index: 5; |
| } |
| |
| .label.padding { |
| background-color: rgb(91, 181, 0); |
| } |
| |
| .label.margin { |
| background-color: rgb(246, 167, 35); |
| } |
| |
| .label.disabled { |
| background-color: rgb(159, 188, 191); |
| } |
| |
| .label .dimension { |
| color: rgba(255, 255, 255, 0.7); |
| } |
| |
| .label .name { |
| color: rgba(255, 255, 255, 0.7); |
| display: none; |
| border-radius: 4px; |
| } |
| |
| .label.full .name { |
| display: inline; |
| z-index: 5; |
| } |
| |
| .label::before { |
| content: ''; |
| display: block; |
| position: absolute; |
| width: 0; |
| height: 0; |
| border-top: 6px solid transparent; |
| border-bottom: 6px solid transparent; |
| top: 1px; |
| } |
| |
| .label.right-arrow::before { |
| border-left-width: 6px; |
| border-left-style: solid; |
| border-right: none; |
| left: auto; |
| right: -6px; |
| } |
| |
| .label.left-arrow::before { |
| border-left: none; |
| border-right-width: 6px; |
| border-right-style: solid; |
| left: -6px; |
| right: auto; |
| } |
| |
| .label.padding::before { |
| border-left-color: rgb(91, 181, 0); |
| border-right-color: rgb(91, 181, 0); |
| } |
| |
| .label.margin::before { |
| border-left-color: rgb(246, 167, 35); |
| border-right-color: rgb(246, 167, 35); |
| } |
| |
| /* Material */ |
| .hidden { |
| display: none !important; |
| } |
| |
| .tooltip-content, |
| .material-tooltip-arrow { |
| position: absolute; |
| z-index: 10; |
| -webkit-user-select: none; |
| } |
| |
| .tooltip-content { |
| background-color: #333740; |
| font-size: 11px; |
| line-height: 14px; |
| padding: 5px 8px; |
| border-radius: 3px; |
| color: white; |
| box-sizing: border-box; |
| max-width: calc(100% - 4px); |
| border: 1px solid hsla(0, 0%, 100%, 0.3); |
| z-index: 1; |
| background-clip: padding-box; |
| will-change: transform; |
| text-rendering: optimizeLegibility; |
| pointer-events: none; |
| } |
| |
| .element-info { |
| display: flex; |
| align-content: stretch; |
| } |
| |
| .material-tooltip-arrow { |
| border: solid; |
| border-color: #333740 transparent; |
| border-width: 0 8px 8px 8px; |
| z-index: 2; |
| margin-top: 1px; |
| } |
| |
| .material-tooltip-arrow.tooltip-arrow-top { |
| border-width: 8px 8px 0 8px; |
| margin-top: -1px; |
| } |
| |
| .element-description { |
| flex: 1 1; |
| word-wrap: break-word; |
| word-break: break-all; |
| } |
| |
| .dimensions { |
| border-left: 1px solid hsl(0, 0%, 50%); |
| padding-left: 7px; |
| margin-left: 7px; |
| float: right; |
| flex: 0 0 auto; |
| white-space: nowrap; |
| display: flex; |
| align-items: center; |
| color: hsl(0, 0%, 85%); |
| } |
| |
| .material-node-width { |
| margin-right: 2px; |
| } |
| |
| .material-node-height { |
| margin-left: 2px; |
| } |
| |
| .material-tag-name { |
| color: hsl(304, 77%, 70%); |
| } |
| |
| .material-node-id { |
| color: hsl(27, 100%, 70%); |
| } |
| |
| .material-class-name { |
| color: hsl(202,92%,77%); |
| } |
| |
| .layout-editor-media-tooltip { |
| color: hsl(0, 0%, 85%); |
| } |
| |
| .layout-editor-selector-tooltip { |
| color: hsl(202,92%,77%); |
| } |
| |
| </style> |
| <script> |
| const lightGridColor = "rgba(0,0,0,0.2)"; |
| const darkGridColor = "rgba(0,0,0,0.7)"; |
| const transparentColor = "rgba(0, 0, 0, 0)"; |
| const gridBackgroundColor = "rgba(255, 255, 255, 0.8)"; |
| |
| function drawPausedInDebuggerMessage(message) |
| { |
| window._controlsVisible = true; |
| document.querySelector(".controls-line").style.visibility = "visible"; |
| document.getElementById("paused-in-debugger").textContent = message; |
| document.body.classList.add("dimmed"); |
| } |
| |
| function _drawGrid(context, rulerAtRight, rulerAtBottom) |
| { |
| if (window._gridPainted) |
| return; |
| window._gridPainted = true; |
| |
| context.save(); |
| |
| var pageFactor = pageZoomFactor * pageScaleFactor; |
| var scrollX = window.scrollX * pageScaleFactor; |
| var scrollY = window.scrollY * pageScaleFactor; |
| function zoom(x) |
| { |
| return Math.round(x * pageFactor); |
| } |
| function unzoom(x) |
| { |
| return Math.round(x / pageFactor); |
| } |
| |
| var width = canvasWidth / pageFactor; |
| var height = canvasHeight / pageFactor; |
| |
| const gridSubStep = 5; |
| const gridStep = 50; |
| |
| { |
| // Draw X grid background |
| context.save(); |
| context.fillStyle = gridBackgroundColor; |
| if (rulerAtBottom) |
| context.fillRect(0, zoom(height) - 15, zoom(width), zoom(height)); |
| else |
| context.fillRect(0, 0, zoom(width), 15); |
| |
| // Clip out backgrounds intersection |
| context.globalCompositeOperation = "destination-out"; |
| context.fillStyle = "red"; |
| if (rulerAtRight) |
| context.fillRect(zoom(width) - 15, 0, zoom(width), zoom(height)); |
| else |
| context.fillRect(0, 0, 15, zoom(height)); |
| context.restore(); |
| |
| // Draw Y grid background |
| context.fillStyle = gridBackgroundColor; |
| if (rulerAtRight) |
| context.fillRect(zoom(width) - 15, 0, zoom(width), zoom(height)); |
| else |
| context.fillRect(0, 0, 15, zoom(height)); |
| } |
| |
| context.lineWidth = 1; |
| context.strokeStyle = darkGridColor; |
| context.fillStyle = darkGridColor; |
| { |
| // Draw labels. |
| context.save(); |
| context.translate(-scrollX, 0.5 - scrollY); |
| var maxY = height + unzoom(scrollY); |
| for (var y = 2 * gridStep; y < maxY; y += 2 * gridStep) { |
| context.save(); |
| context.translate(scrollX, zoom(y)); |
| context.rotate(-Math.PI / 2); |
| context.fillText(y, 2, rulerAtRight ? zoom(width) - 7 : 13); |
| context.restore(); |
| } |
| context.translate(0.5, -0.5); |
| var maxX = width + unzoom(scrollX); |
| for (var x = 2 * gridStep; x < maxX; x += 2 * gridStep) { |
| context.save(); |
| context.fillText(x, zoom(x) + 2, rulerAtBottom ? scrollY + zoom(height) - 7 : scrollY + 13); |
| context.restore(); |
| } |
| context.restore(); |
| } |
| |
| { |
| // Draw vertical grid |
| context.save(); |
| if (rulerAtRight) { |
| context.translate(zoom(width), 0); |
| context.scale(-1, 1); |
| } |
| context.translate(-scrollX, 0.5 - scrollY); |
| var maxY = height + unzoom(scrollY); |
| for (var y = gridStep; y < maxY; y += gridStep) { |
| context.beginPath(); |
| context.moveTo(scrollX, zoom(y)); |
| var markLength = (y % (gridStep * 2)) ? 5 : 8; |
| context.lineTo(scrollX + markLength, zoom(y)); |
| context.stroke(); |
| } |
| context.strokeStyle = lightGridColor; |
| for (var y = gridSubStep; y < maxY; y += gridSubStep) { |
| if (!(y % gridStep)) |
| continue; |
| context.beginPath(); |
| context.moveTo(scrollX, zoom(y)); |
| context.lineTo(scrollX + gridSubStep, zoom(y)); |
| context.stroke(); |
| } |
| context.restore(); |
| } |
| |
| { |
| // Draw horizontal grid |
| context.save(); |
| if (rulerAtBottom) { |
| context.translate(0, zoom(height)); |
| context.scale(1, -1); |
| } |
| context.translate(0.5 - scrollX, -scrollY); |
| var maxX = width + unzoom(scrollX); |
| for (var x = gridStep; x < maxX; x += gridStep) { |
| context.beginPath(); |
| context.moveTo(zoom(x), scrollY); |
| var markLength = (x % (gridStep * 2)) ? 5 : 8; |
| context.lineTo(zoom(x), scrollY + markLength); |
| context.stroke(); |
| } |
| context.strokeStyle = lightGridColor; |
| for (var x = gridSubStep; x < maxX; x += gridSubStep) { |
| if (!(x % gridStep)) |
| continue; |
| context.beginPath(); |
| context.moveTo(zoom(x), scrollY); |
| context.lineTo(zoom(x), scrollY + gridSubStep); |
| context.stroke(); |
| } |
| context.restore(); |
| } |
| |
| context.restore(); |
| } |
| |
| function resetCanvas(canvasElement) |
| { |
| canvasElement.width = deviceScaleFactor * viewportSize.width; |
| canvasElement.height = deviceScaleFactor * viewportSize.height; |
| canvasElement.style.width = viewportSize.width + "px"; |
| canvasElement.style.height = viewportSize.height + "px"; |
| var context = canvasElement.getContext("2d"); |
| context.scale(deviceScaleFactor, deviceScaleFactor); |
| } |
| |
| function reset(resetData) |
| { |
| window.viewportSize = resetData.viewportSize; |
| window.deviceScaleFactor = resetData.deviceScaleFactor; |
| window.pageScaleFactor = resetData.pageScaleFactor; |
| window.pageZoomFactor = resetData.pageZoomFactor; |
| window.scrollX = Math.round(resetData.scrollX); |
| window.scrollY = Math.round(resetData.scrollY); |
| |
| window.canvas = document.getElementById("canvas"); |
| window.context = canvas.getContext("2d"); |
| resetCanvas(canvas); |
| |
| window.canvasWidth = viewportSize.width; |
| window.canvasHeight = viewportSize.height; |
| |
| window._controlsVisible = false; |
| document.querySelector(".controls-line").style.visibility = "hidden"; |
| document.getElementById("element-title").style.visibility = "hidden"; |
| document.getElementById("tooltip-container").removeChildren(); |
| |
| document.body.classList.remove("dimmed"); |
| |
| window._gridPainted = false; |
| |
| if (window.layoutEditor) |
| window.layoutEditor.reset(); |
| } |
| |
| function _drawElementTitle(context, elementInfo, bounds) |
| { |
| document.getElementById("tag-name").textContent = elementInfo.tagName; |
| document.getElementById("node-id").textContent = elementInfo.idValue ? "#" + elementInfo.idValue : ""; |
| document.getElementById("class-name").textContent = (elementInfo.className || "").trimEnd(50); |
| document.getElementById("node-width").textContent = elementInfo.nodeWidth; |
| document.getElementById("node-height").textContent = elementInfo.nodeHeight; |
| var elementTitle = document.getElementById("element-title"); |
| |
| var titleWidth = elementTitle.offsetWidth + 6; |
| var titleHeight = elementTitle.offsetHeight + 4; |
| |
| var anchorTop = bounds.minY; |
| var anchorBottom = bounds.maxY; |
| var anchorLeft = bounds.minX; |
| |
| const arrowHeight = 7; |
| var renderArrowUp = false; |
| var renderArrowDown = false; |
| |
| var boxX = Math.max(2, anchorLeft); |
| if (boxX + titleWidth > canvasWidth) |
| boxX = canvasWidth - titleWidth - 2; |
| |
| var boxY; |
| if (anchorTop > canvasHeight) { |
| boxY = canvasHeight - titleHeight - arrowHeight; |
| renderArrowDown = true; |
| } else if (anchorBottom < 0) { |
| boxY = arrowHeight; |
| renderArrowUp = true; |
| } else if (anchorBottom + titleHeight + arrowHeight < canvasHeight) { |
| boxY = anchorBottom + arrowHeight - 4; |
| renderArrowUp = true; |
| } else if (anchorTop - titleHeight - arrowHeight > 0) { |
| boxY = anchorTop - titleHeight - arrowHeight + 3; |
| renderArrowDown = true; |
| } else |
| boxY = arrowHeight; |
| |
| context.save(); |
| context.translate(0.5, 0.5); |
| context.beginPath(); |
| context.moveTo(boxX, boxY); |
| if (renderArrowUp) { |
| context.lineTo(boxX + 2 * arrowHeight, boxY); |
| context.lineTo(boxX + 3 * arrowHeight, boxY - arrowHeight); |
| context.lineTo(boxX + 4 * arrowHeight, boxY); |
| } |
| context.lineTo(boxX + titleWidth, boxY); |
| context.lineTo(boxX + titleWidth, boxY + titleHeight); |
| if (renderArrowDown) { |
| context.lineTo(boxX + 4 * arrowHeight, boxY + titleHeight); |
| context.lineTo(boxX + 3 * arrowHeight, boxY + titleHeight + arrowHeight); |
| context.lineTo(boxX + 2 * arrowHeight, boxY + titleHeight); |
| } |
| context.lineTo(boxX, boxY + titleHeight); |
| context.closePath(); |
| context.fillStyle = "rgb(255, 255, 194)"; |
| context.fill(); |
| context.strokeStyle = "rgb(128, 128, 128)"; |
| context.stroke(); |
| |
| context.restore(); |
| |
| elementTitle.style.visibility = "visible"; |
| elementTitle.style.top = (boxY + 3) + "px"; |
| elementTitle.style.left = (boxX + 3) + "px"; |
| } |
| |
| function _createElementDescription(elementInfo) |
| { |
| var elementInfoElement = createElement("div", "element-info"); |
| var descriptionElement = elementInfoElement.createChild("div", "element-description monospace"); |
| var tagNameElement = descriptionElement.createChild("b").createChild("span", "material-tag-name"); |
| tagNameElement.textContent = elementInfo.tagName; |
| var nodeIdElement = descriptionElement.createChild("span", "material-node-id"); |
| nodeIdElement.textContent = elementInfo.idValue ? "#" + elementInfo.idValue : ""; |
| nodeIdElement.classList.toggle("hidden", !elementInfo.idValue); |
| |
| var classNameElement = descriptionElement.createChild("span", "material-class-name"); |
| classNameElement.textContent = (elementInfo.className || "").trim(50); |
| classNameElement.classList.toggle("hidden", !elementInfo.className); |
| var dimensionsElement = elementInfoElement.createChild("div", "dimensions"); |
| dimensionsElement.createChild("span", "material-node-width").textContent = Math.round(elementInfo.nodeWidth * 100) / 100; |
| dimensionsElement.createTextChild("\u00d7"); |
| dimensionsElement.createChild("span", "material-node-height").textContent = Math.round(elementInfo.nodeHeight * 100) / 100; |
| return elementInfoElement; |
| } |
| |
| function _drawMaterialElementTitle(elementInfo, bounds) |
| { |
| var tooltipContainer = document.getElementById("tooltip-container"); |
| tooltipContainer.removeChildren(); |
| _createMaterialTooltip(tooltipContainer, bounds, _createElementDescription(elementInfo), true); |
| } |
| |
| function _createMaterialTooltip(parentElement, bounds, contentElement, withArrow) |
| { |
| var tooltipContainer = parentElement.createChild("div"); |
| var tooltipContent = tooltipContainer.createChild("div", "tooltip-content"); |
| tooltipContent.appendChild(contentElement); |
| |
| var titleWidth = tooltipContent.offsetWidth; |
| var titleHeight = tooltipContent.offsetHeight; |
| var arrowRadius = 8; |
| var pageMargin = 2; |
| |
| var boxX = Math.min(bounds.minX, canvasWidth - titleWidth - pageMargin); |
| |
| var boxY = bounds.minY - arrowRadius - titleHeight; |
| var onTop = true; |
| if (boxY < 0) { |
| boxY = Math.min(canvasHeight - titleHeight, bounds.maxY + arrowRadius); |
| onTop = false; |
| } else if (bounds.minY > canvasHeight) { |
| boxY = canvasHeight - arrowRadius - titleHeight; |
| } |
| |
| tooltipContent.style.top = boxY + "px"; |
| tooltipContent.style.left = boxX + "px"; |
| if (!withArrow) |
| return; |
| |
| var tooltipArrow = tooltipContainer.createChild("div", "material-tooltip-arrow"); |
| // Left align arrow to the tooltip but ensure it is pointing to the element. |
| var tooltipBorderRadius = 2; |
| var arrowX = boxX + arrowRadius + tooltipBorderRadius; |
| if (arrowX < bounds.minX) |
| arrowX = bounds.minX + arrowRadius; |
| // Hide arrow if element is completely off the sides of the page. |
| var arrowHidden = arrowX < pageMargin + tooltipBorderRadius || arrowX + arrowRadius * 2 > canvasWidth - pageMargin - tooltipBorderRadius; |
| tooltipArrow.classList.toggle("hidden", arrowHidden); |
| if (!arrowHidden) { |
| tooltipArrow.classList.toggle("tooltip-arrow-top", onTop); |
| tooltipArrow.style.top = (onTop ? boxY + titleHeight : boxY - arrowRadius) + "px"; |
| tooltipArrow.style.left = arrowX + "px"; |
| } |
| } |
| |
| function _drawRulers(context, bounds, rulerAtRight, rulerAtBottom) |
| { |
| context.save(); |
| var width = canvasWidth; |
| var height = canvasHeight; |
| context.strokeStyle = "rgba(128, 128, 128, 0.3)"; |
| context.lineWidth = 1; |
| context.translate(0.5, 0.5); |
| |
| if (rulerAtRight) { |
| for (var y in bounds.rightmostXForY) { |
| context.beginPath(); |
| context.moveTo(width, y); |
| context.lineTo(bounds.rightmostXForY[y], y); |
| context.stroke(); |
| } |
| } else { |
| for (var y in bounds.leftmostXForY) { |
| context.beginPath(); |
| context.moveTo(0, y); |
| context.lineTo(bounds.leftmostXForY[y], y); |
| context.stroke(); |
| } |
| } |
| |
| if (rulerAtBottom) { |
| for (var x in bounds.bottommostYForX) { |
| context.beginPath(); |
| context.moveTo(x, height); |
| context.lineTo(x, bounds.topmostYForX[x]); |
| context.stroke(); |
| } |
| } else { |
| for (var x in bounds.topmostYForX) { |
| context.beginPath(); |
| context.moveTo(x, 0); |
| context.lineTo(x, bounds.topmostYForX[x]); |
| context.stroke(); |
| } |
| } |
| |
| context.restore(); |
| } |
| |
| function drawPath(context, commands, fillColor, outlineColor, bounds) |
| { |
| var commandsIndex = 0; |
| |
| function extractPoints(count) |
| { |
| var points = []; |
| |
| for (var i = 0; i < count; ++i) { |
| var x = Math.round(commands[commandsIndex++]); |
| bounds.maxX = Math.max(bounds.maxX, x); |
| bounds.minX = Math.min(bounds.minX, x); |
| |
| var y = Math.round(commands[commandsIndex++]); |
| bounds.maxY = Math.max(bounds.maxY, y); |
| bounds.minY = Math.min(bounds.minY, y); |
| |
| bounds.leftmostXForY[y] = Math.min(bounds.leftmostXForY[y] || Number.MAX_VALUE, x); |
| bounds.rightmostXForY[y] = Math.max(bounds.rightmostXForY[y] || Number.MIN_VALUE, x); |
| bounds.topmostYForX[x] = Math.min(bounds.topmostYForX[x] || Number.MAX_VALUE, y); |
| bounds.bottommostYForX[x] = Math.max(bounds.bottommostYForX[x] || Number.MIN_VALUE, y); |
| points.push(x, y); |
| } |
| return points; |
| } |
| |
| context.save(); |
| var commandsLength = commands.length; |
| var path = new Path2D(); |
| while (commandsIndex < commandsLength) { |
| switch (commands[commandsIndex++]) { |
| case "M": |
| path.moveTo.apply(path, extractPoints(1)); |
| break; |
| case "L": |
| path.lineTo.apply(path, extractPoints(1)); |
| break; |
| case "C": |
| path.bezierCurveTo.apply(path, extractPoints(3)); |
| break; |
| case "Q": |
| path.quadraticCurveTo.apply(path, extractPoints(2)); |
| break; |
| case "Z": |
| path.closePath(); |
| break; |
| } |
| } |
| path.closePath(); |
| context.lineWidth = 0; |
| context.fillStyle = fillColor; |
| context.fill(path); |
| |
| if (outlineColor) { |
| context.lineWidth = 2; |
| context.strokeStyle = outlineColor; |
| context.stroke(path); |
| } |
| |
| context.restore(); |
| return path; |
| } |
| |
| function emptyBounds() |
| { |
| var bounds = { |
| minX: Number.MAX_VALUE, |
| minY: Number.MAX_VALUE, |
| maxX: Number.MIN_VALUE, |
| maxY: Number.MIN_VALUE, |
| leftmostXForY: {}, |
| rightmostXForY: {}, |
| topmostYForX: {}, |
| bottommostYForX: {} |
| }; |
| return bounds; |
| } |
| |
| function drawHighlight(highlight, context) |
| { |
| context = context || window.context; |
| context.save(); |
| |
| var bounds = emptyBounds(); |
| |
| for (var paths = highlight.paths.slice(); paths.length;) { |
| var path = paths.pop(); |
| drawPath(context, path.path, path.fillColor, path.outlineColor, bounds); |
| if (paths.length) { |
| context.save(); |
| context.globalCompositeOperation = "destination-out"; |
| drawPath(context, paths[paths.length - 1].path, "red", null, bounds); |
| context.restore(); |
| } |
| } |
| |
| var rulerAtRight = highlight.paths.length && highlight.showRulers && bounds.minX < 20 && bounds.maxX + 20 < canvasWidth; |
| var rulerAtBottom = highlight.paths.length && highlight.showRulers && bounds.minY < 20 && bounds.maxY + 20 < canvasHeight; |
| |
| if (highlight.showRulers) |
| _drawGrid(context, rulerAtRight, rulerAtBottom); |
| |
| if (highlight.paths.length) { |
| if (highlight.showExtensionLines) |
| _drawRulers(context, bounds, rulerAtRight, rulerAtBottom); |
| |
| if (highlight.elementInfo && highlight.displayAsMaterial) |
| _drawMaterialElementTitle(highlight.elementInfo, bounds); |
| else if (highlight.elementInfo) |
| _drawElementTitle(context, highlight.elementInfo, bounds); |
| } |
| context.restore(); |
| |
| return { bounds: bounds }; |
| } |
| |
| function setPlatform(platform) |
| { |
| window.platform = platform; |
| document.body.classList.add("platform-" + platform); |
| } |
| |
| function dispatch(message) |
| { |
| var functionName = message.shift(); |
| window[functionName].apply(null, message); |
| } |
| |
| function log(text) |
| { |
| document.getElementById("log").createChild("div").textContent = text; |
| } |
| |
| function onResumeClick() |
| { |
| InspectorOverlayHost.resume(); |
| } |
| |
| function onStepOverClick() |
| { |
| InspectorOverlayHost.stepOver(); |
| } |
| |
| function onLoaded() |
| { |
| document.getElementById("resume-button").addEventListener("click", onResumeClick); |
| document.getElementById("step-over-button").addEventListener("click", onStepOverClick); |
| } |
| |
| function eventHasCtrlOrMeta(event) |
| { |
| return window.platform == "mac" ? (event.metaKey && !event.ctrlKey) : (event.ctrlKey && !event.metaKey); |
| } |
| |
| function onDocumentKeyDown(event) |
| { |
| if (!window._controlsVisible) |
| return; |
| if (event.keyIdentifier == "F8" || eventHasCtrlOrMeta(event) && event.keyCode == 220 /* backslash */) |
| InspectorOverlayHost.resume(); |
| else if (event.keyIdentifier == "F10" || eventHasCtrlOrMeta(event) && event.keyCode == 222 /* single quote */) |
| InspectorOverlayHost.stepOver(); |
| } |
| |
| function showLayoutEditor(info) |
| { |
| if (!window.layoutEditor) |
| window.layoutEditor = new LayoutEditor(); |
| |
| window.layoutEditor.setState(info); |
| } |
| |
| function setSelectorInLayoutEditor(selectorInfo) |
| { |
| if (!window.layoutEditor) |
| return; |
| |
| window.layoutEditor.setSelector(selectorInfo); |
| } |
| |
| /** |
| * @constructor |
| */ |
| function LayoutEditor() |
| { |
| this._boundConsumeEvent = this._consumeEvent.bind(this); |
| this._boundStrayClick = this._onStrayClick.bind(this); |
| this._boundKeyDown = this._onKeyDown.bind(this); |
| this._boundMouseWheel = this._onMouseWheel.bind(this); |
| |
| this._paddingBounds = this._defaultBounds(); |
| this._contentBounds = this._defaultBounds(); |
| this._marginBounds = this._defaultBounds(); |
| this._element = document.getElementById("editor"); |
| this._editorElement = this._element.createChild("div"); |
| this._selectorTooltipElement = this._element.createChild("div", "selector-tooltip-element"); |
| this._matchedNodesCanvas = createCanvas("layout-editor-matched-nodes-canvas"); |
| this._editorElement.parentElement.insertBefore(this._matchedNodesCanvas, this._editorElement); |
| |
| this._wallsElements = new Map(); |
| this._labelsElements = new Map(); |
| this._anchorsElements = new Map(); |
| this._anchorsInfo = new Map(); |
| } |
| |
| /** |
| * @typedef {{ |
| * type: string, |
| * propertyName: number, |
| * propertyValue: {value: number, unit: string, growInside: boolean, mutable: boolean} |
| * }} |
| */ |
| LayoutEditor.AnchorInfo; |
| |
| /** |
| * @typedef {{ |
| * left: number, |
| * top: number, |
| * right: number, |
| * bottom: number |
| * }} |
| */ |
| LayoutEditor.Bounds; |
| |
| /** |
| * @typedef {{ |
| * orientation: string, |
| * border1: string, |
| * border2: string, |
| * dimension1: string, |
| * dimension2: string |
| * }} |
| */ |
| LayoutEditor.Bounds; |
| |
| /** |
| * @typedef {{ |
| * p1: !Point, |
| * p2: !Point, |
| * p3: !Point, |
| * p4: !Point |
| * }} |
| */ |
| LayoutEditor.Quad; |
| |
| /** |
| * @typedef {{ |
| * contentQuad: !LayoutEditor.Quad, |
| * marginQuad: !LayoutEditor.Quad, |
| * paddingQuad: !LayoutEditor.Quad, |
| * anchors: !Array.<!LayoutEditor.AnchorInfo> |
| * }} |
| */ |
| LayoutEditor.Info; |
| |
| LayoutEditor._controlLaneWidth = 16; |
| LayoutEditor._wallWidth = 8; |
| LayoutEditor._handleWidth = 8; |
| LayoutEditor._labelOffset = 12; |
| |
| LayoutEditor.prototype = { |
| /** |
| * @return {!LayoutEditor.Bounds} |
| */ |
| _defaultBounds: function () |
| { |
| var bounds = { |
| left: Number.MAX_VALUE, |
| top: Number.MAX_VALUE, |
| right: Number.MIN_VALUE, |
| bottom: Number.MIN_VALUE, |
| }; |
| return bounds; |
| }, |
| |
| reset: function() |
| { |
| this._anchorsInfo.clear(); |
| this._anchorsElements.clear(); |
| this._wallsElements.clear(); |
| this._labelsElements.clear(); |
| this._paddingBounds = this._defaultBounds(); |
| this._contentBounds = this._defaultBounds(); |
| this._marginBounds = this._defaultBounds(); |
| |
| resetCanvas(this._matchedNodesCanvas); |
| document.body.style.cursor = ""; |
| |
| this._editorElement.removeChildren(); |
| this._selectorTooltipElement.removeChildren(); |
| |
| document.removeEventListener("mousedown", this._boundConsumeEvent); |
| document.removeEventListener("mouseup", this._boundConsumeEvent); |
| document.removeEventListener("click", this._boundStrayClick); |
| document.removeEventListener("keydown", this._boundKeyDown); |
| document.removeEventListener("mousewheel", this._boundMouseWheel); |
| document.removeEventListener("mousemove", this._boundConsumeEvent); |
| }, |
| |
| /** |
| * @param {!LayoutEditor.Info} info |
| */ |
| setState: function(info) |
| { |
| this._editorElement.style.visibility = "visible"; |
| |
| function buildBounds(quad) |
| { |
| var bounds = this._defaultBounds(); |
| for (var i = 1; i <= 4; ++i) { |
| var point = quad["p" + i]; |
| bounds.left = Math.min(bounds.left, point.x); |
| bounds.right = Math.max(bounds.right, point.x); |
| bounds.top = Math.min(bounds.top, point.y); |
| bounds.bottom = Math.max(bounds.bottom, point.y); |
| } |
| return bounds; |
| } |
| |
| this._contentBounds = buildBounds.call(this, info.contentQuad); |
| this._paddingBounds = buildBounds.call(this, info.paddingQuad); |
| this._marginBounds = buildBounds.call(this, info.marginQuad); |
| |
| this._anchorsInfo = new Map(); |
| for (var i = 0; i < info.anchors.length; ++i) |
| this._anchorsInfo.set(info.anchors[i].propertyName, info.anchors[i]) |
| |
| document.addEventListener("mousedown", this._boundConsumeEvent); |
| document.addEventListener("mouseup", this._boundConsumeEvent); |
| document.addEventListener("click", this._boundStrayClick); |
| document.addEventListener("keydown", this._boundKeyDown); |
| document.addEventListener("mousewheel", this._boundMouseWheel); |
| |
| this._createBlurWindow(); |
| this._createGuideLines(); |
| this._createControlLanes(info.anchors); |
| document.addEventListener("mousemove", this._boundConsumeEvent); |
| |
| if (this._draggedPropertyName) { |
| document.body.style.cursor = (this._draggedPropertyName.endsWith("left") || this._draggedPropertyName.endsWith("right")) ? "ew-resize" : "ns-resize"; |
| this._toggleHighlightedState(this._draggedPropertyName, true); |
| } |
| }, |
| |
| _createBlurWindow: function() |
| { |
| var left = this._editorElement.createChild("div", "blur-rect"); |
| left.style.height = canvasHeight + "px"; |
| left.style.width = this._marginBounds.left + "px"; |
| var top = this._editorElement.createChild("div", "blur-rect"); |
| top.style.left = this._marginBounds.left + "px"; |
| top.style.width = canvasWidth - this._marginBounds.left + "px"; |
| top.style.height = this._marginBounds.top + "px"; |
| var right = this._editorElement.createChild("div", "blur-rect"); |
| right.style.left = this._marginBounds.right + "px"; |
| right.style.top = this._marginBounds.top + "px"; |
| right.style.width = (canvasWidth - this._marginBounds.right) + "px"; |
| right.style.height = (canvasHeight - this._marginBounds.top) + "px"; |
| var bottom = this._editorElement.createChild("div", "blur-rect"); |
| bottom.style.left = this._marginBounds.left + "px"; |
| bottom.style.top = this._marginBounds.bottom + "px"; |
| bottom.style.width = this._marginBounds.right - this._marginBounds.left + "px"; |
| bottom.style.height = canvasHeight - this._marginBounds.bottom + "px"; |
| top.addEventListener("click", this._boundStrayClick); |
| bottom.addEventListener("click", this._boundStrayClick); |
| left.addEventListener("click", this._boundStrayClick); |
| right.addEventListener("click", this._boundStrayClick); |
| }, |
| |
| _createGuideLines: function() |
| { |
| /** |
| * @param {number} x |
| * @param {string} type |
| * @this {LayoutEditor} |
| */ |
| function verticalLine(x, type) |
| { |
| var verticalElement = this._editorElement.createChild("div", "guide-line vertical"); |
| verticalElement.classList.add(type); |
| verticalElement.style.height = canvasHeight + "px"; |
| verticalElement.style.top = "0px"; |
| verticalElement.style.left = x + "px"; |
| } |
| |
| /** |
| * @param {number} y |
| * @param {string} type |
| * @this {LayoutEditor} |
| */ |
| function horizontalLine(y, type) |
| { |
| var horizontalElement = this._editorElement.createChild("div", "guide-line horizontal"); |
| horizontalElement.classList.add(type); |
| horizontalElement.style.width = canvasWidth + "px"; |
| horizontalElement.style.left = "0px"; |
| horizontalElement.style.top = y + "px"; |
| } |
| |
| /** |
| * @param {!LayoutEditor.Bounds} bounds |
| * @param {string} type |
| * @this {LayoutEditor} |
| */ |
| function guideLines(bounds, type) |
| { |
| verticalLine.call(this, bounds.left, type); |
| verticalLine.call(this, bounds.right, type); |
| horizontalLine.call(this, bounds.top, type); |
| horizontalLine.call(this, bounds.bottom, type); |
| } |
| |
| guideLines.call(this, this._contentBounds, "content"); |
| guideLines.call(this, this._paddingBounds, "padding"); |
| guideLines.call(this, this._marginBounds, "margin"); |
| }, |
| |
| /** |
| * @param {!Element} parent |
| * @param {!Element} anchorElement |
| * @param {!LayoutEditor.AnchorInfo} anchorInfo |
| */ |
| _createLabel: function(parent, anchorElement, anchorInfo) |
| { |
| var label = parent.createChild("div", "label " + anchorInfo.type); |
| var nameElement = label.createChild("span", "name"); |
| var anchorName = anchorInfo.propertyName; |
| nameElement.textContent = anchorName + ": "; |
| var valueElement = label.createChild("span", "value"); |
| valueElement.textContent = String(parseFloat(anchorInfo.propertyValue.value.toFixed(2))); |
| var dimensionElement = label.createChild("span", "dimension"); |
| dimensionElement.textContent = "\u2009" + anchorInfo.propertyValue.unit; |
| |
| var leftX = anchorElement.offsetLeft - LayoutEditor._labelOffset - label.offsetWidth; |
| var fitLeft = leftX > 0; |
| var rightX = anchorElement.offsetLeft + anchorElement.offsetWidth + LayoutEditor._labelOffset; |
| var fitRight = rightX + label.offsetWidth < canvasWidth; |
| var growsInside = anchorInfo.propertyValue.growInside; |
| var toLeft = anchorName.endsWith("right") && growsInside || anchorName.endsWith("left") && !growsInside; |
| var startPosition; |
| if (!fitLeft || (!toLeft && fitRight)) { |
| startPosition = rightX; |
| label.classList.add("left-arrow"); |
| } else { |
| startPosition = leftX; |
| label.classList.add("right-arrow"); |
| } |
| |
| var y = anchorElement.offsetTop + anchorElement.offsetHeight / 2; |
| label.style.left = (startPosition | 0) + "px"; |
| label.style.top = ((y - label.offsetHeight / 2) | 0) + "px"; |
| |
| label.classList.add("hidden"); |
| return label; |
| }, |
| |
| /** |
| * @param {string} anchorName |
| * @param {boolean} highlighted |
| */ |
| _toggleHighlightedState: function(anchorName, highlighted) |
| { |
| this._anchorsElements.get(anchorName).classList.toggle("highlighted", highlighted); |
| this._wallsElements.get(anchorName).classList.toggle("highlighted", highlighted); |
| this._labelsElements.get(anchorName).classList.toggle("hidden", !highlighted); |
| }, |
| |
| _createControlLanes: function() |
| { |
| var descriptionH = {orientation: "horizontal", border1: "left", border2: "top", dimension1: "width", dimension2: "height"}; |
| var descriptionV = {orientation: "vertical", border1: "top", border2: "left", dimension1: "height", dimension2: "width"}; |
| var sidesByOrientation = {horizontal : ["left", "right"], vertical : ["top", "bottom"]}; |
| |
| /** |
| * @param {!Element} parent |
| * @param {!LayoutEditor.Bounds} innerBox |
| * @param {!LayoutEditor.Bounds} outerBox |
| * @param {string} type |
| * @param {string} side |
| * @param {!LayoutEditor.Description} description |
| * @param {number} border2Position |
| * @this {LayoutEditor} |
| */ |
| function createAnchorWithWall(parent, innerBox, outerBox, type, side, description, border2Position) |
| { |
| var anchorName = type + "-" + side; |
| var anchorInfo = this._anchorsInfo.get(anchorName); |
| var growsInside = anchorInfo.propertyValue.growInside; |
| |
| var anchorPosition = (growsInside ? innerBox[side] : outerBox[side]); |
| var anchorElement = parent.createChild("div", "editor-anchor " + description.orientation + " " + type); |
| var handleHalf = LayoutEditor._handleWidth / 2; |
| anchorElement.style[description.border1] = (anchorPosition - handleHalf) + "px"; |
| anchorElement.style[description.border2] = border2Position + "px"; |
| this._anchorsElements.set(anchorName, anchorElement); |
| |
| if (anchorInfo.propertyValue.mutable) |
| anchorElement.addEventListener("mousedown", this._onAnchorMouseDown.bind(this, anchorName)); |
| |
| anchorElement.addEventListener("mouseenter", this._toggleHighlightedState.bind(this, anchorName, true)); |
| anchorElement.addEventListener("mouseleave", this._toggleHighlightedState.bind(this, anchorName, false)); |
| |
| var wallElement = parent.createChild("div", "wall " + description.orientation + " " + type); |
| var wallPosition = (growsInside ? outerBox[side] : innerBox[side]); |
| var minSide = (side === "left" || side === "top"); |
| wallPosition += (minSide === growsInside) ? - handleHalf - LayoutEditor._wallWidth : handleHalf; |
| wallElement.style[description.border1] = wallPosition + "px"; |
| wallElement.style[description.border2] = border2Position + "px"; |
| |
| this._wallsElements.set(anchorName, wallElement); |
| |
| var labelElement = this._createLabel(parent, anchorElement, anchorInfo); |
| this._labelsElements.set(anchorName, labelElement); |
| } |
| |
| /** |
| * @param {!Element} parent |
| * @param {!LayoutEditor.Bounds} innerBox |
| * @param {!LayoutEditor.Bounds} outerBox |
| * @param {string} type |
| * @param {!LayoutEditor.Description} description |
| * @param {number} border2Position |
| * @this {LayoutEditor} |
| */ |
| function createLane(parent, innerBox, outerBox, type, description, border2Position) |
| { |
| var verticalElement = parent.createChild("div", "control-lane " + type); |
| var sides = sidesByOrientation[description.orientation]; |
| verticalElement.style[description.border1] = outerBox[sides[0]] + "px"; |
| verticalElement.style[description.border2] = border2Position + "px"; |
| verticalElement.style[description.dimension1] = (outerBox[sides[1]] - outerBox[sides[0]]) + "px"; |
| verticalElement.style[description.dimension2] = LayoutEditor._controlLaneWidth + "px"; |
| createAnchorWithWall.call(this, parent, innerBox, outerBox, type, sides[0], description, border2Position); |
| createAnchorWithWall.call(this, parent, innerBox, outerBox, type, sides[1], description, border2Position); |
| } |
| |
| var yPosition = 0; |
| var xPosition = 0; |
| if (this._marginBounds.bottom + 2 * LayoutEditor._controlLaneWidth <= canvasHeight) |
| yPosition = this._marginBounds.bottom; |
| else if (this._marginBounds.top - 2 * LayoutEditor._controlLaneWidth >= 0) |
| yPosition = this._marginBounds.top - 2 * LayoutEditor._controlLaneWidth; |
| else |
| yPosition = (this._contentBounds.top + this._contentBounds.bottom) / 2; |
| |
| if (this._marginBounds.left - 2 * LayoutEditor._controlLaneWidth >= 0) |
| xPosition = this._marginBounds.left - 2 * LayoutEditor._controlLaneWidth; |
| else if (this._marginBounds.right + 2 * LayoutEditor._controlLaneWidth <= canvasWidth) |
| xPosition = this._marginBounds.right; |
| else |
| xPosition = (this._contentBounds.right + this._contentBounds.left) / 2; |
| |
| createLane.call(this, this._editorElement, this._contentBounds, this._paddingBounds, "padding", descriptionH, yPosition); |
| createLane.call(this, this._editorElement, this._paddingBounds, this._marginBounds, "margin", descriptionH, yPosition + LayoutEditor._controlLaneWidth); |
| createLane.call(this, this._editorElement, this._contentBounds, this._paddingBounds, "padding", descriptionV, xPosition); |
| createLane.call(this, this._editorElement, this._paddingBounds, this._marginBounds, "margin", descriptionV, xPosition + LayoutEditor._controlLaneWidth); |
| }, |
| |
| setSelector: function(selectorInfo) |
| { |
| this._selectorTooltipElement.removeChildren(); |
| var containerElement = createElement("div"); |
| |
| for (var i = (selectorInfo.medias || []).length - 1; i >= 0; --i) |
| containerElement.createChild("div", "layout-editor-media-tooltip").textContent = ("@media " + selectorInfo.medias[i]).trim(50); |
| |
| var selectorElement = containerElement.createChild("div", "layout-editor-selector-tooltip"); |
| selectorElement.textContent = selectorInfo.selector.trimEnd(50); |
| |
| var margin = 40; |
| var bounds = {minX: this._marginBounds.left, maxX: this._marginBounds.right, |
| minY: this._marginBounds.top - margin, maxY: this._marginBounds.bottom + margin}; |
| _createMaterialTooltip(this._selectorTooltipElement, bounds, containerElement); |
| resetCanvas(this._matchedNodesCanvas); |
| |
| if (!selectorInfo.nodes) |
| return; |
| |
| for (var nodeHighlight of selectorInfo.nodes) |
| drawHighlight(nodeHighlight, this._matchedNodesCanvas.getContext("2d")); |
| }, |
| |
| _calculateDelta: function(deltaVector, moveDelta) |
| { |
| return scalarProduct(deltaVector, moveDelta) / Math.sqrt(scalarProduct(deltaVector, deltaVector)); |
| }, |
| |
| /** |
| * @param {string} anchorName |
| * @return {!Point} |
| */ |
| _defaultDeltaVector: function(anchorName) |
| { |
| if (anchorName.endsWith("right")) |
| return new Point(1, 0); |
| if (anchorName.endsWith("left")) |
| return new Point(-1, 0); |
| if (anchorName.endsWith("top")) |
| return new Point(0, -1); |
| if (anchorName.endsWith("bottom")) |
| return new Point(0, 1); |
| }, |
| |
| /** |
| * @param {string} anchorName |
| * @param {!Event} event |
| */ |
| _onAnchorMouseDown: function(anchorName, event) |
| { |
| // Only drag upon left button. Right will likely cause a context menu. So will ctrl-click on mac. |
| if (event.button || (window.platform == "mac" && event.ctrlKey)) |
| return; |
| |
| event.preventDefault(); |
| var deltaVector = this._defaultDeltaVector(anchorName); |
| var anchorInfo = this._anchorsInfo.get(anchorName); |
| if (anchorInfo.propertyValue.growInside) |
| deltaVector = new Point(-deltaVector.x, -deltaVector.y); |
| |
| this._boundDragMove = this._onDragMove.bind(this, new Point(event.screenX, event.screenY), deltaVector); |
| this._boundDragEnd = this._onDragEnd.bind(this); |
| document.addEventListener("mousemove", this._boundDragMove); |
| document.addEventListener("mouseup", this._boundDragEnd); |
| InspectorOverlayHost.startPropertyChange(anchorName); |
| this._preciseDrag = !!event.shiftKey; |
| this._draggedPropertyName = anchorName; |
| this._toggleHighlightedState(anchorName, true); |
| }, |
| |
| /** |
| * @param {!Point} mouseDownPoint |
| * @param {!Point} deltaVector |
| * @param {!Event} event |
| */ |
| _onDragMove: function(mouseDownPoint, deltaVector, event) |
| { |
| if (event.buttons !== 1) { |
| this._onDragEnd(event); |
| return; |
| } |
| event.preventDefault(); |
| if (this._preciseDrag !== event.shiftKey) { |
| InspectorOverlayHost.endPropertyChange(); |
| document.removeEventListener("mousemove", this._boundDragMove); |
| mouseDownPoint = new Point(event.screenX, event.screenY); |
| this._boundDragMove = this._onDragMove.bind(this, mouseDownPoint, deltaVector); |
| document.addEventListener("mousemove", this._boundDragMove); |
| this._preciseDrag = event.shiftKey; |
| InspectorOverlayHost.startPropertyChange(this._draggedPropertyName); |
| } |
| |
| var preciseFactor = this._preciseDrag ? 5 : 1; |
| InspectorOverlayHost.changeProperty(this._calculateDelta(deltaVector, new Point(event.screenX - mouseDownPoint.x, event.screenY - mouseDownPoint.y)) / preciseFactor); |
| }, |
| |
| /** |
| * @param {!Event} event |
| */ |
| _onDragEnd: function(event) |
| { |
| document.removeEventListener("mousemove", this._boundDragMove); |
| document.removeEventListener("mouseup", this._boundDragEnd); |
| delete this._boundDragMove; |
| delete this._boundDragEnd; |
| this._toggleHighlightedState(this._draggedPropertyName, false); |
| document.body.style.cursor = ""; |
| delete this._draggedPropertyName; |
| delete this._preciseDrag; |
| event.preventDefault(); |
| InspectorOverlayHost.endPropertyChange(); |
| }, |
| |
| /** |
| * @param {!Event} event |
| */ |
| _onStrayClick: function(event) |
| { |
| event.preventDefault(); |
| InspectorOverlayHost.clearSelection(true); |
| }, |
| |
| /** |
| * @param {!Event} event |
| */ |
| _onKeyDown: function(event) |
| { |
| if (this._draggedPropertyName) |
| return; |
| |
| // Clear selection on Esc. |
| if (event.keyIdentifier === "U+001B") { |
| event.preventDefault(); |
| InspectorOverlayHost.clearSelection(false); |
| } |
| }, |
| |
| /** |
| * @param {!Event} event |
| */ |
| _onMouseWheel: function(event) |
| { |
| event.preventDefault(); |
| this._mouseWheelDelta = (this._mouseWheelDelta || 0) + event.wheelDelta; |
| if (this._mouseWheelDelta >= 120) { |
| InspectorOverlayHost.nextSelector(); |
| this._mouseWheelDelta = 0; |
| } else if (this._mouseWheelDelta <= -120) { |
| InspectorOverlayHost.previousSelector(); |
| this._mouseWheelDelta = 0; |
| } |
| }, |
| |
| /** |
| * @param {!Event} event |
| */ |
| _consumeEvent: function(event) |
| { |
| event.preventDefault(); |
| } |
| } |
| |
| /** |
| * @constructor |
| * @param {number} x |
| * @param {number} y |
| */ |
| function Point(x, y) |
| { |
| this.x = x; |
| this.y = y; |
| } |
| |
| function createCanvas(id) |
| { |
| var canvas = createElement("canvas", "fill"); |
| canvas.id = id; |
| resetCanvas(canvas); |
| return canvas; |
| } |
| |
| function scalarProduct(v1, v2) |
| { |
| return v1.x * v2.x + v1.y * v2.y; |
| } |
| |
| Element.prototype.createChild = function(tagName, className) |
| { |
| var element = createElement(tagName, className); |
| element.addEventListener("click", function(e) { e.stopPropagation(); }, false); |
| this.appendChild(element); |
| return element; |
| } |
| |
| Element.prototype.createTextChild = function(text) |
| { |
| var element = document.createTextNode(text); |
| this.appendChild(element); |
| return element; |
| } |
| |
| Element.prototype.removeChildren = function() |
| { |
| if (this.firstChild) |
| this.textContent = ""; |
| } |
| |
| function createElement(tagName, className) |
| { |
| var element = document.createElement(tagName); |
| if (className) |
| element.className = className; |
| return element; |
| } |
| |
| String.prototype.trimEnd = function(maxLength) |
| { |
| if (this.length <= maxLength) |
| return String(this); |
| return this.substr(0, maxLength - 1) + "\u2026"; |
| } |
| |
| window.addEventListener("DOMContentLoaded", onLoaded); |
| document.addEventListener("keydown", onDocumentKeyDown); |
| </script> |
| </head> |
| <body class="fill"> |
| </body> |
| <canvas id="canvas" class="fill"></canvas> |
| <div id="element-title"> |
| <span id="tag-name"></span><span id="node-id"></span><span id="class-name"></span> |
| <span id="node-width"></span><span class="px">px</span><span class="px"> × </span><span id="node-height"></span><span class="px">px</span> |
| </div> |
| <div id="tooltip-container"></div> |
| <div id="editor" class="fill"></div> |
| <div class="controls-line"> |
| <div class="message-box"><div id="paused-in-debugger"></div></div> |
| <div class="button" id="resume-button" title="Resume script execution (F8)."><div class="glyph"></div></div> |
| <div class="button" id="step-over-button" title="Step over next function call (F10)."><div class="glyph"></div></div> |
| </div> |
| <div id="log"></div> |
| </html> |