1 /* 2 * Copyright (C) 2011 Brian Grinstead 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 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 /** 30 * @constructor 31 * @extends {WebInspector.VBox} 32 */ 33 WebInspector.Spectrum = function() 34 { 35 WebInspector.VBox.call(this); 36 this.registerRequiredCSS("spectrum.css"); 37 38 this.element.classList.add("spectrum-container"); 39 this.element.tabIndex = 0; 40 41 this._draggerElement = this.element.createChild("div", "spectrum-color"); 42 this._dragHelperElement = this._draggerElement.createChild("div", "spectrum-sat fill").createChild("div", "spectrum-val fill").createChild("div", "spectrum-dragger"); 43 44 this._sliderElement = this.element.createChild("div", "spectrum-hue"); 45 this.slideHelper = this._sliderElement.createChild("div", "spectrum-slider"); 46 47 var rangeContainer = this.element.createChild("div", "spectrum-range-container"); 48 var alphaLabel = rangeContainer.createChild("label"); 49 alphaLabel.textContent = WebInspector.UIString("\u03B1:"); 50 51 this._alphaElement = rangeContainer.createChild("input", "spectrum-range"); 52 this._alphaElement.setAttribute("type", "range"); 53 this._alphaElement.setAttribute("min", "0"); 54 this._alphaElement.setAttribute("max", "100"); 55 this._alphaElement.addEventListener("input", alphaDrag.bind(this), false); 56 this._alphaElement.addEventListener("change", alphaDrag.bind(this), false); 57 58 var displayContainer = this.element.createChild("div", "spectrum-text"); 59 var swatchElement = displayContainer.createChild("span", "swatch"); 60 this._swatchInnerElement = swatchElement.createChild("span", "swatch-inner"); 61 this._displayElement = displayContainer.createChild("span", "source-code spectrum-display-value"); 62 63 WebInspector.Spectrum.draggable(this._sliderElement, hueDrag.bind(this)); 64 WebInspector.Spectrum.draggable(this._draggerElement, colorDrag.bind(this), colorDragStart.bind(this)); 65 66 /** 67 * @param {!Element} element 68 * @param {number} dragX 69 * @param {number} dragY 70 * @this {WebInspector.Spectrum} 71 */ 72 function hueDrag(element, dragX, dragY) 73 { 74 this._hsv[0] = (this.slideHeight - dragY) / this.slideHeight; 75 76 this._onchange(); 77 } 78 79 var initialHelperOffset; 80 81 /** 82 * @this {WebInspector.Spectrum} 83 */ 84 function colorDragStart() 85 { 86 initialHelperOffset = { x: this._dragHelperElement.offsetLeft, y: this._dragHelperElement.offsetTop }; 87 } 88 89 /** 90 * @param {!Element} element 91 * @param {number} dragX 92 * @param {number} dragY 93 * @param {!MouseEvent} event 94 * @this {WebInspector.Spectrum} 95 */ 96 function colorDrag(element, dragX, dragY, event) 97 { 98 if (event.shiftKey) { 99 if (Math.abs(dragX - initialHelperOffset.x) >= Math.abs(dragY - initialHelperOffset.y)) 100 dragY = initialHelperOffset.y; 101 else 102 dragX = initialHelperOffset.x; 103 } 104 105 this._hsv[1] = dragX / this.dragWidth; 106 this._hsv[2] = (this.dragHeight - dragY) / this.dragHeight; 107 108 this._onchange(); 109 } 110 111 /** 112 * @this {WebInspector.Spectrum} 113 */ 114 function alphaDrag() 115 { 116 this._hsv[3] = this._alphaElement.value / 100; 117 118 this._onchange(); 119 } 120 }; 121 122 WebInspector.Spectrum.Events = { 123 ColorChanged: "ColorChanged" 124 }; 125 126 /** 127 * @param {!Element} element 128 * @param {function(!Element, number, number, !MouseEvent)=} onmove 129 * @param {function(!Element, !MouseEvent)=} onstart 130 * @param {function(!Element, !MouseEvent)=} onstop 131 */ 132 WebInspector.Spectrum.draggable = function(element, onmove, onstart, onstop) { 133 134 var doc = document; 135 var dragging; 136 var offset; 137 var scrollOffset; 138 var maxHeight; 139 var maxWidth; 140 141 /** 142 * @param {!Event} e 143 */ 144 function consume(e) 145 { 146 e.consume(true); 147 } 148 149 /** 150 * @param {!Event} e 151 */ 152 function move(e) 153 { 154 if (dragging) { 155 var dragX = Math.max(0, Math.min(e.pageX - offset.left + scrollOffset.left, maxWidth)); 156 var dragY = Math.max(0, Math.min(e.pageY - offset.top + scrollOffset.top, maxHeight)); 157 158 if (onmove) 159 onmove(element, dragX, dragY, /** @type {!MouseEvent} */ (e)); 160 } 161 } 162 163 /** 164 * @param {!Event} e 165 */ 166 function start(e) 167 { 168 var mouseEvent = /** @type {!MouseEvent} */ (e); 169 var rightClick = mouseEvent.which ? (mouseEvent.which === 3) : (mouseEvent.button === 2); 170 171 if (!rightClick && !dragging) { 172 173 if (onstart) 174 onstart(element, mouseEvent); 175 176 dragging = true; 177 maxHeight = element.clientHeight; 178 maxWidth = element.clientWidth; 179 180 scrollOffset = element.scrollOffset(); 181 offset = element.totalOffset(); 182 183 doc.addEventListener("selectstart", consume, false); 184 doc.addEventListener("dragstart", consume, false); 185 doc.addEventListener("mousemove", move, false); 186 doc.addEventListener("mouseup", stop, false); 187 188 move(mouseEvent); 189 consume(mouseEvent); 190 } 191 } 192 193 /** 194 * @param {!Event} e 195 */ 196 function stop(e) 197 { 198 if (dragging) { 199 doc.removeEventListener("selectstart", consume, false); 200 doc.removeEventListener("dragstart", consume, false); 201 doc.removeEventListener("mousemove", move, false); 202 doc.removeEventListener("mouseup", stop, false); 203 204 if (onstop) 205 onstop(element, /** @type {!MouseEvent} */ (e)); 206 } 207 208 dragging = false; 209 } 210 211 element.addEventListener("mousedown", start, false); 212 }; 213 214 WebInspector.Spectrum.prototype = { 215 /** 216 * @param {!WebInspector.Color} color 217 */ 218 setColor: function(color) 219 { 220 this._hsv = color.hsva(); 221 }, 222 223 /** 224 * @return {!WebInspector.Color} 225 */ 226 color: function() 227 { 228 return WebInspector.Color.fromHSVA(this._hsv); 229 }, 230 231 _colorString: function() 232 { 233 var cf = WebInspector.Color.Format; 234 var format = this._originalFormat; 235 var color = this.color(); 236 var originalFormatString = color.toString(this._originalFormat); 237 if (originalFormatString) 238 return originalFormatString; 239 240 if (color.hasAlpha()) { 241 // Everything except HSL(A) should be returned as RGBA if transparency is involved. 242 if (format === cf.HSLA || format === cf.HSL) 243 return color.toString(cf.HSLA); 244 else 245 return color.toString(cf.RGBA); 246 } 247 248 if (format === cf.ShortHEX) 249 return color.toString(cf.HEX); 250 console.assert(format === cf.Nickname); 251 return color.toString(cf.RGB); 252 }, 253 254 255 set displayText(text) 256 { 257 this._displayElement.textContent = text; 258 }, 259 260 _onchange: function() 261 { 262 this._updateUI(); 263 this.dispatchEventToListeners(WebInspector.Spectrum.Events.ColorChanged, this._colorString()); 264 }, 265 266 _updateHelperLocations: function() 267 { 268 var h = this._hsv[0]; 269 var s = this._hsv[1]; 270 var v = this._hsv[2]; 271 272 // Where to show the little circle that displays your current selected color. 273 var dragX = s * this.dragWidth; 274 var dragY = this.dragHeight - (v * this.dragHeight); 275 276 dragX = Math.max(-this._dragHelperElementHeight, 277 Math.min(this.dragWidth - this._dragHelperElementHeight, dragX - this._dragHelperElementHeight)); 278 dragY = Math.max(-this._dragHelperElementHeight, 279 Math.min(this.dragHeight - this._dragHelperElementHeight, dragY - this._dragHelperElementHeight)); 280 281 this._dragHelperElement.positionAt(dragX, dragY); 282 283 // Where to show the bar that displays your current selected hue. 284 var slideY = this.slideHeight - ((h * this.slideHeight) + this.slideHelperHeight); 285 this.slideHelper.style.top = slideY + "px"; 286 287 this._alphaElement.value = this._hsv[3] * 100; 288 }, 289 290 _updateUI: function() 291 { 292 this._updateHelperLocations(); 293 294 this._draggerElement.style.backgroundColor = /** @type {string} */ (WebInspector.Color.fromHSVA([this._hsv[0], 1, 1, 1]).toString(WebInspector.Color.Format.RGB)); 295 this._swatchInnerElement.style.backgroundColor = /** @type {string} */ (this.color().toString(WebInspector.Color.Format.RGBA)); 296 297 this._alphaElement.value = this._hsv[3] * 100; 298 }, 299 300 wasShown: function() 301 { 302 this.slideHeight = this._sliderElement.offsetHeight; 303 this.dragWidth = this._draggerElement.offsetWidth; 304 this.dragHeight = this._draggerElement.offsetHeight; 305 this._dragHelperElementHeight = this._dragHelperElement.offsetHeight / 2; 306 this.slideHelperHeight = this.slideHelper.offsetHeight / 2; 307 this._updateUI(); 308 }, 309 310 __proto__: WebInspector.VBox.prototype 311 } 312 313 /** 314 * @constructor 315 * @extends {WebInspector.Object} 316 */ 317 WebInspector.SpectrumPopupHelper = function() 318 { 319 this._spectrum = new WebInspector.Spectrum(); 320 this._spectrum.element.addEventListener("keydown", this._onKeyDown.bind(this), false); 321 322 this._popover = new WebInspector.Popover(); 323 this._popover.setCanShrink(false); 324 this._popover.element.addEventListener("mousedown", consumeEvent, false); 325 326 this._hideProxy = this.hide.bind(this, true); 327 } 328 329 WebInspector.SpectrumPopupHelper.Events = { 330 Hidden: "Hidden" 331 }; 332 333 WebInspector.SpectrumPopupHelper.prototype = { 334 /** 335 * @return {!WebInspector.Spectrum} 336 */ 337 spectrum: function() 338 { 339 return this._spectrum; 340 }, 341 342 /** 343 * @return {boolean} 344 */ 345 toggle: function(element, color, format) 346 { 347 if (this._popover.isShowing()) 348 this.hide(true); 349 else 350 this.show(element, color, format); 351 352 return this._popover.isShowing(); 353 }, 354 355 /** 356 * @return {boolean} 357 */ 358 show: function(element, color, format) 359 { 360 if (this._popover.isShowing()) { 361 if (this._anchorElement === element) 362 return false; 363 364 // Reopen the picker for another anchor element. 365 this.hide(true); 366 } 367 368 delete this._isHidden; 369 this._anchorElement = element; 370 371 this._spectrum.setColor(color); 372 this._spectrum._originalFormat = format !== WebInspector.Color.Format.Original ? format : color.format(); 373 this.reposition(element); 374 375 document.addEventListener("mousedown", this._hideProxy, false); 376 window.addEventListener("resize", this._hideProxy, false); 377 378 WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.ColorPicked, this._colorPicked, this); 379 PageAgent.setColorPickerEnabled(true); 380 return true; 381 }, 382 383 reposition: function(element) 384 { 385 if (!this._previousFocusElement) 386 this._previousFocusElement = WebInspector.currentFocusElement(); 387 this._popover.showView(this._spectrum, element); 388 WebInspector.setCurrentFocusElement(this._spectrum.element); 389 }, 390 391 /** 392 * @param {boolean=} commitEdit 393 */ 394 hide: function(commitEdit) 395 { 396 if (this._isHidden) 397 return; 398 this._isHidden = true; 399 this._popover.hide(); 400 401 document.removeEventListener("mousedown", this._hideProxy, false); 402 window.removeEventListener("resize", this._hideProxy, false); 403 404 PageAgent.setColorPickerEnabled(false); 405 WebInspector.targetManager.removeModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.ColorPicked, this._colorPicked, this); 406 407 this.dispatchEventToListeners(WebInspector.SpectrumPopupHelper.Events.Hidden, !!commitEdit); 408 409 WebInspector.setCurrentFocusElement(this._previousFocusElement); 410 delete this._previousFocusElement; 411 412 delete this._anchorElement; 413 }, 414 415 _onKeyDown: function(event) 416 { 417 if (event.keyIdentifier === "Enter") { 418 this.hide(true); 419 event.consume(true); 420 return; 421 } 422 if (event.keyIdentifier === "U+001B") { // Escape key 423 this.hide(false); 424 event.consume(true); 425 } 426 }, 427 428 /** 429 * @param {!WebInspector.Event} event 430 */ 431 _colorPicked: function(event) 432 { 433 var color = /** @type {!DOMAgent.RGBA} */ (event.data); 434 var rgba = [color.r, color.g, color.b, (color.a / 2.55 | 0) / 100]; 435 this._spectrum.setColor(WebInspector.Color.fromRGBA(rgba)); 436 this._spectrum._onchange(); 437 InspectorFrontendHost.bringToFront(); 438 }, 439 440 __proto__: WebInspector.Object.prototype 441 } 442 443 /** 444 * @constructor 445 * @param {boolean=} readOnly 446 */ 447 WebInspector.ColorSwatch = function(readOnly) 448 { 449 this.element = document.createElementWithClass("span", "swatch"); 450 this._swatchInnerElement = this.element.createChild("span", "swatch-inner"); 451 var shiftClickMessage = WebInspector.UIString("Shift-click to change color format."); 452 this.element.title = readOnly ? shiftClickMessage : String.sprintf("%s\n%s", WebInspector.UIString("Click to open a colorpicker."), shiftClickMessage); 453 this.element.addEventListener("mousedown", consumeEvent, false); 454 this.element.addEventListener("dblclick", consumeEvent, false); 455 } 456 457 WebInspector.ColorSwatch.prototype = { 458 /** 459 * @param {string} colorString 460 */ 461 setColorString: function(colorString) 462 { 463 this._swatchInnerElement.style.backgroundColor = colorString; 464 } 465 } 466