1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.HoverMenu = function(delegate) 27{ 28 WebInspector.Object.call(this); 29 30 this.delegate = delegate; 31 32 this._element = document.createElement("div"); 33 this._element.className = WebInspector.HoverMenu.StyleClassName; 34 this._element.addEventListener("transitionend", this, true); 35 36 this._outlineElement = this._element.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); 37 38 this._button = this._element.appendChild(document.createElement("img")); 39 this._button.addEventListener("click", this); 40} 41 42WebInspector.HoverMenu.StyleClassName = "hover-menu"; 43WebInspector.HoverMenu.VisibleClassName = "visible"; 44 45WebInspector.HoverMenu.prototype = { 46 constructor: WebInspector.HoverMenu, 47 __proto__: WebInspector.Object.prototype, 48 49 // Public 50 51 get element() 52 { 53 return this._element; 54 }, 55 56 present: function(rects) 57 { 58 this._outlineElement.textContent = ""; 59 60 document.body.appendChild(this._element); 61 this._drawOutline(rects); 62 this._element.classList.add(WebInspector.HoverMenu.VisibleClassName); 63 64 window.addEventListener("scroll", this, true); 65 }, 66 67 dismiss: function(discrete) 68 { 69 if (this._element.parentNode !== document.body) 70 return; 71 72 if (discrete) 73 this._element.remove(); 74 75 this._element.classList.remove(WebInspector.HoverMenu.VisibleClassName); 76 77 window.removeEventListener("scroll", this, true); 78 }, 79 80 // Protected 81 82 handleEvent: function(event) 83 { 84 switch (event.type) { 85 case "scroll": 86 if (!this._element.contains(event.target)) 87 this.dismiss(true); 88 break; 89 case "click": 90 this._handleClickEvent(event); 91 break; 92 case "transitionend": 93 if (!this._element.classList.contains(WebInspector.HoverMenu.VisibleClassName)) 94 this._element.remove(); 95 break; 96 } 97 }, 98 99 // Private 100 101 _handleClickEvent: function(event) 102 { 103 if (this.delegate && typeof this.delegate.hoverMenuButtonWasPressed === "function") 104 this.delegate.hoverMenuButtonWasPressed(this); 105 }, 106 107 _drawOutline: function(rects) 108 { 109 var buttonWidth = this._button.width; 110 var buttonHeight = this._button.height; 111 112 // Add room for the button on the last line. 113 var lastRect = rects.pop(); 114 lastRect.size.width += buttonWidth; 115 rects.push(lastRect); 116 117 if (rects.length === 1) 118 this._drawSingleLine(rects[0]); 119 else if (rects.length === 2 && rects[0].minX() >= rects[1].maxX()) 120 this._drawTwoNonOverlappingLines(rects); 121 else 122 this._drawOverlappingLines(rects); 123 124 var bounds = WebInspector.Rect.unionOfRects(rects).pad(3); // padding + 1/2 stroke-width 125 126 var style = this._element.style; 127 style.left = bounds.minX() + "px"; 128 style.top = bounds.minY() + "px"; 129 style.width = bounds.size.width + "px"; 130 style.height = bounds.size.height + "px"; 131 132 this._outlineElement.style.width = bounds.size.width + "px"; 133 this._outlineElement.style.height = bounds.size.height + "px"; 134 135 this._button.style.left = (lastRect.maxX() - bounds.minX() - buttonWidth) + "px"; 136 this._button.style.top = (lastRect.maxY() - bounds.minY() - buttonHeight) + "px"; 137 }, 138 139 _addRect: function(rect) 140 { 141 const r = 4; 142 143 var svgRect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 144 svgRect.setAttribute("x", 1); 145 svgRect.setAttribute("y", 1); 146 svgRect.setAttribute("width", rect.size.width); 147 svgRect.setAttribute("height", rect.size.height); 148 svgRect.setAttribute("rx", r); 149 svgRect.setAttribute("ry", r); 150 return this._outlineElement.appendChild(svgRect); 151 }, 152 153 _addPath: function(commands, tx, ty) 154 { 155 var path = document.createElementNS("http://www.w3.org/2000/svg", "path"); 156 path.setAttribute("d", commands.join(" ")); 157 path.setAttribute("transform", "translate(" + (tx + 1) + "," + (ty + 1) + ")"); 158 return this._outlineElement.appendChild(path); 159 }, 160 161 _drawSingleLine: function(rect) 162 { 163 this._addRect(rect.pad(2)); 164 }, 165 166 _drawTwoNonOverlappingLines: function(rects) 167 { 168 const r = 4; 169 170 var firstRect = rects[0].pad(2); 171 var secondRect = rects[1].pad(2); 172 173 var tx = -secondRect.minX(); 174 var ty = -firstRect.minY(); 175 176 var rect = firstRect; 177 this._addPath([ 178 "M", rect.maxX(), rect.minY(), 179 "H", rect.minX() + r, 180 "q", -r, 0, -r, r, 181 "V", rect.maxY() - r, 182 "q", 0, r, r, r, 183 "H", rect.maxX() 184 ], tx, ty); 185 186 rect = secondRect; 187 this._addPath([ 188 "M", rect.minX(), rect.minY(), 189 "H", rect.maxX() - r, 190 "q", r, 0, r, r, 191 "V", rect.maxY() - r, 192 "q", 0, r, -r, r, 193 "H", rect.minX() 194 ], tx, ty); 195 }, 196 197 _drawOverlappingLines: function(rects) 198 { 199 const PADDING = 2; 200 const r = 4; 201 202 var minX = Number.MAX_VALUE; 203 var maxX = -Number.MAX_VALUE; 204 for (var rect of rects) { 205 var minX = Math.min(rect.minX(), minX); 206 var maxX = Math.max(rect.maxX(), maxX); 207 } 208 209 minX -= PADDING; 210 maxX += PADDING; 211 212 var minY = rects[0].minY() - PADDING; 213 var maxY = rects.lastValue.maxY() + PADDING; 214 var firstLineMinX = rects[0].minX() - PADDING; 215 var lastLineMaxX = rects.lastValue.maxX() + PADDING; 216 217 if (firstLineMinX === minX && lastLineMaxX === maxX) 218 return this._addRect(new WebInspector.Rect(minX, minY, maxX - minX, maxY - minY)); 219 220 var lastLineMinY = rects.lastValue.minY() + PADDING; 221 if (rects[0].minX() === minX + PADDING) 222 return this._addPath([ 223 "M", minX + r, minY, 224 "H", maxX - r, 225 "q", r, 0, r, r, 226 "V", lastLineMinY - r, 227 "q", 0, r, -r, r, 228 "H", lastLineMaxX + r, 229 "q", -r, 0, -r, r, 230 "V", maxY - r, 231 "q", 0, r, -r, r, 232 "H", minX + r, 233 "q", -r, 0, -r, -r, 234 "V", minY + r, 235 "q", 0, -r, r, -r 236 ], -minX, -minY); 237 238 var firstLineMaxY = rects[0].maxY() - PADDING; 239 if (rects.lastValue.maxX() === maxX - PADDING) 240 return this._addPath([ 241 "M", firstLineMinX + r, minY, 242 "H", maxX - r, 243 "q", r, 0, r, r, 244 "V", maxY - r, 245 "q", 0, r, -r, r, 246 "H", minX + r, 247 "q", -r, 0, -r, -r, 248 "V", firstLineMaxY + r, 249 "q", 0, -r, r, -r, 250 "H", firstLineMinX - r, 251 "q", r, 0, r, -r, 252 "V", minY + r, 253 "q", 0, -r, r, -r 254 ], -minX, -minY); 255 256 return this._addPath([ 257 "M", firstLineMinX + r, minY, 258 "H", maxX - r, 259 "q", r, 0, r, r, 260 "V", lastLineMinY - r, 261 "q", 0, r, -r, r, 262 "H", lastLineMaxX + r, 263 "q", -r, 0, -r, r, 264 "V", maxY - r, 265 "q", 0, r, -r, r, 266 "H", minX + r, 267 "q", -r, 0, -r, -r, 268 "V", firstLineMaxY + r, 269 "q", 0, -r, r, -r, 270 "H", firstLineMinX - r, 271 "q", r, 0, r, -r, 272 "V", minY + r, 273 "q", 0, -r, r, -r 274 ], -minX, -minY); 275 } 276} 277