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 */ 33WebInspector.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 var topElement = this.element.createChild("div", "spectrum-top"); 42 topElement.createChild("div", "spectrum-fill"); 43 44 var topInnerElement = topElement.createChild("div", "spectrum-top-inner fill"); 45 this._draggerElement = topInnerElement.createChild("div", "spectrum-color"); 46 this._dragHelperElement = this._draggerElement.createChild("div", "spectrum-sat fill").createChild("div", "spectrum-val fill").createChild("div", "spectrum-dragger"); 47 48 this._sliderElement = topInnerElement.createChild("div", "spectrum-hue"); 49 this.slideHelper = this._sliderElement.createChild("div", "spectrum-slider"); 50 51 var rangeContainer = this.element.createChild("div", "spectrum-range-container"); 52 var alphaLabel = rangeContainer.createChild("label"); 53 alphaLabel.textContent = WebInspector.UIString("\u03B1:"); 54 55 this._alphaElement = rangeContainer.createChild("input", "spectrum-range"); 56 this._alphaElement.setAttribute("type", "range"); 57 this._alphaElement.setAttribute("min", "0"); 58 this._alphaElement.setAttribute("max", "100"); 59 this._alphaElement.addEventListener("input", alphaDrag.bind(this), false); 60 this._alphaElement.addEventListener("change", alphaDrag.bind(this), false); 61 62 var swatchElement = document.createElement("span"); 63 swatchElement.className = "swatch"; 64 this._swatchInnerElement = swatchElement.createChild("span", "swatch-inner"); 65 66 var displayContainer = this.element.createChild("div"); 67 displayContainer.appendChild(swatchElement); 68 this._displayElement = displayContainer.createChild("span", "source-code spectrum-display-value"); 69 70 WebInspector.Spectrum.draggable(this._sliderElement, hueDrag.bind(this)); 71 WebInspector.Spectrum.draggable(this._draggerElement, colorDrag.bind(this), colorDragStart.bind(this)); 72 73 /** 74 * @param {!Element} element 75 * @param {number} dragX 76 * @param {number} dragY 77 * @this {WebInspector.Spectrum} 78 */ 79 function hueDrag(element, dragX, dragY) 80 { 81 this._hsv[0] = (this.slideHeight - dragY) / this.slideHeight; 82 83 this._onchange(); 84 } 85 86 var initialHelperOffset; 87 88 /** 89 * @this {WebInspector.Spectrum} 90 */ 91 function colorDragStart() 92 { 93 initialHelperOffset = { x: this._dragHelperElement.offsetLeft, y: this._dragHelperElement.offsetTop }; 94 } 95 96 /** 97 * @param {!Element} element 98 * @param {number} dragX 99 * @param {number} dragY 100 * @param {!MouseEvent} event 101 * @this {WebInspector.Spectrum} 102 */ 103 function colorDrag(element, dragX, dragY, event) 104 { 105 if (event.shiftKey) { 106 if (Math.abs(dragX - initialHelperOffset.x) >= Math.abs(dragY - initialHelperOffset.y)) 107 dragY = initialHelperOffset.y; 108 else 109 dragX = initialHelperOffset.x; 110 } 111 112 this._hsv[1] = dragX / this.dragWidth; 113 this._hsv[2] = (this.dragHeight - dragY) / this.dragHeight; 114 115 this._onchange(); 116 } 117 118 /** 119 * @this {WebInspector.Spectrum} 120 */ 121 function alphaDrag() 122 { 123 this._hsv[3] = this._alphaElement.value / 100; 124 125 this._onchange(); 126 } 127}; 128 129WebInspector.Spectrum.Events = { 130 ColorChanged: "ColorChanged" 131}; 132 133/** 134 * @param {!Element} element 135 * @param {function(!Element, number, number, !MouseEvent)=} onmove 136 * @param {function(!Element, !MouseEvent)=} onstart 137 * @param {function(!Element, !MouseEvent)=} onstop 138 */ 139WebInspector.Spectrum.draggable = function(element, onmove, onstart, onstop) { 140 141 var doc = document; 142 var dragging; 143 var offset; 144 var scrollOffset; 145 var maxHeight; 146 var maxWidth; 147 148 /** 149 * @param {?Event} e 150 */ 151 function consume(e) 152 { 153 e.consume(true); 154 } 155 156 /** 157 * @param {?Event} e 158 */ 159 function move(e) 160 { 161 if (dragging) { 162 var dragX = Math.max(0, Math.min(e.pageX - offset.left + scrollOffset.left, maxWidth)); 163 var dragY = Math.max(0, Math.min(e.pageY - offset.top + scrollOffset.top, maxHeight)); 164 165 if (onmove) 166 onmove(element, dragX, dragY, /** @type {!MouseEvent} */ (e)); 167 } 168 } 169 170 /** 171 * @param {?Event} e 172 */ 173 function start(e) 174 { 175 var mouseEvent = /** @type {!MouseEvent} */ (e); 176 var rightClick = mouseEvent.which ? (mouseEvent.which === 3) : (mouseEvent.button === 2); 177 178 if (!rightClick && !dragging) { 179 180 if (onstart) 181 onstart(element, mouseEvent); 182 183 dragging = true; 184 maxHeight = element.clientHeight; 185 maxWidth = element.clientWidth; 186 187 scrollOffset = element.scrollOffset(); 188 offset = element.totalOffset(); 189 190 doc.addEventListener("selectstart", consume, false); 191 doc.addEventListener("dragstart", consume, false); 192 doc.addEventListener("mousemove", move, false); 193 doc.addEventListener("mouseup", stop, false); 194 195 move(mouseEvent); 196 consume(mouseEvent); 197 } 198 } 199 200 /** 201 * @param {?Event} e 202 */ 203 function stop(e) 204 { 205 if (dragging) { 206 doc.removeEventListener("selectstart", consume, false); 207 doc.removeEventListener("dragstart", consume, false); 208 doc.removeEventListener("mousemove", move, false); 209 doc.removeEventListener("mouseup", stop, false); 210 211 if (onstop) 212 onstop(element, /** @type {!MouseEvent} */ (e)); 213 } 214 215 dragging = false; 216 } 217 218 element.addEventListener("mousedown", start, false); 219}; 220 221WebInspector.Spectrum.prototype = { 222 /** 223 * @param {!WebInspector.Color} color 224 */ 225 setColor: function(color) 226 { 227 this._hsv = color.hsva(); 228 }, 229 230 /** 231 * @return {!WebInspector.Color} 232 */ 233 color: function() 234 { 235 return WebInspector.Color.fromHSVA(this._hsv); 236 }, 237 238 _colorString: function() 239 { 240 var cf = WebInspector.Color.Format; 241 var format = this._originalFormat; 242 var color = this.color(); 243 var originalFormatString = color.toString(this._originalFormat); 244 if (originalFormatString) 245 return originalFormatString; 246 247 if (color.hasAlpha()) { 248 // Everything except HSL(A) should be returned as RGBA if transparency is involved. 249 if (format === cf.HSLA || format === cf.HSL) 250 return color.toString(cf.HSLA); 251 else 252 return color.toString(cf.RGBA); 253 } 254 255 if (format === cf.ShortHEX) 256 return color.toString(cf.HEX); 257 console.assert(format === cf.Nickname); 258 return color.toString(cf.RGB); 259 }, 260 261 262 set displayText(text) 263 { 264 this._displayElement.textContent = text; 265 }, 266 267 _onchange: function() 268 { 269 this._updateUI(); 270 this.dispatchEventToListeners(WebInspector.Spectrum.Events.ColorChanged, this._colorString()); 271 }, 272 273 _updateHelperLocations: function() 274 { 275 var h = this._hsv[0]; 276 var s = this._hsv[1]; 277 var v = this._hsv[2]; 278 279 // Where to show the little circle that displays your current selected color. 280 var dragX = s * this.dragWidth; 281 var dragY = this.dragHeight - (v * this.dragHeight); 282 283 dragX = Math.max(-this._dragHelperElementHeight, 284 Math.min(this.dragWidth - this._dragHelperElementHeight, dragX - this._dragHelperElementHeight)); 285 dragY = Math.max(-this._dragHelperElementHeight, 286 Math.min(this.dragHeight - this._dragHelperElementHeight, dragY - this._dragHelperElementHeight)); 287 288 this._dragHelperElement.positionAt(dragX, dragY); 289 290 // Where to show the bar that displays your current selected hue. 291 var slideY = this.slideHeight - ((h * this.slideHeight) + this.slideHelperHeight); 292 this.slideHelper.style.top = slideY + "px"; 293 294 this._alphaElement.value = this._hsv[3] * 100; 295 }, 296 297 _updateUI: function() 298 { 299 this._updateHelperLocations(); 300 301 this._draggerElement.style.backgroundColor = /** @type {string} */ (WebInspector.Color.fromHSVA([this._hsv[0], 1, 1, 1]).toString(WebInspector.Color.Format.RGB)); 302 this._swatchInnerElement.style.backgroundColor = /** @type {string} */ (this.color().toString(WebInspector.Color.Format.RGBA)); 303 304 this._alphaElement.value = this._hsv[3] * 100; 305 }, 306 307 wasShown: function() 308 { 309 this.slideHeight = this._sliderElement.offsetHeight; 310 this.dragWidth = this._draggerElement.offsetWidth; 311 this.dragHeight = this._draggerElement.offsetHeight; 312 this._dragHelperElementHeight = this._dragHelperElement.offsetHeight / 2; 313 this.slideHelperHeight = this.slideHelper.offsetHeight / 2; 314 this._updateUI(); 315 }, 316 317 __proto__: WebInspector.VBox.prototype 318} 319 320/** 321 * @constructor 322 * @extends {WebInspector.Object} 323 */ 324WebInspector.SpectrumPopupHelper = function() 325{ 326 this._spectrum = new WebInspector.Spectrum(); 327 this._spectrum.element.addEventListener("keydown", this._onKeyDown.bind(this), false); 328 329 this._popover = new WebInspector.Popover(); 330 this._popover.setCanShrink(false); 331 this._popover.element.addEventListener("mousedown", consumeEvent, false); 332 333 this._hideProxy = this.hide.bind(this, true); 334} 335 336WebInspector.SpectrumPopupHelper.Events = { 337 Hidden: "Hidden" 338}; 339 340WebInspector.SpectrumPopupHelper.prototype = { 341 /** 342 * @return {!WebInspector.Spectrum} 343 */ 344 spectrum: function() 345 { 346 return this._spectrum; 347 }, 348 349 /** 350 * @return {boolean} 351 */ 352 toggle: function(element, color, format) 353 { 354 if (this._popover.isShowing()) 355 this.hide(true); 356 else 357 this.show(element, color, format); 358 359 return this._popover.isShowing(); 360 }, 361 362 /** 363 * @return {boolean} 364 */ 365 show: function(element, color, format) 366 { 367 if (this._popover.isShowing()) { 368 if (this._anchorElement === element) 369 return false; 370 371 // Reopen the picker for another anchor element. 372 this.hide(true); 373 } 374 375 this._anchorElement = element; 376 377 this._spectrum.setColor(color); 378 this._spectrum._originalFormat = format !== WebInspector.Color.Format.Original ? format : color.format(); 379 this.reposition(element); 380 381 document.addEventListener("mousedown", this._hideProxy, false); 382 window.addEventListener("blur", this._hideProxy, false); 383 return true; 384 }, 385 386 reposition: function(element) 387 { 388 if (!this._previousFocusElement) 389 this._previousFocusElement = WebInspector.currentFocusElement(); 390 this._popover.showView(this._spectrum, element); 391 WebInspector.setCurrentFocusElement(this._spectrum.element); 392 }, 393 394 /** 395 * @param {boolean=} commitEdit 396 */ 397 hide: function(commitEdit) 398 { 399 if (!this._popover.isShowing()) 400 return; 401 this._popover.hide(); 402 403 document.removeEventListener("mousedown", this._hideProxy, false); 404 window.removeEventListener("blur", this._hideProxy, false); 405 406 this.dispatchEventToListeners(WebInspector.SpectrumPopupHelper.Events.Hidden, !!commitEdit); 407 408 WebInspector.setCurrentFocusElement(this._previousFocusElement); 409 delete this._previousFocusElement; 410 411 delete this._anchorElement; 412 }, 413 414 _onKeyDown: function(event) 415 { 416 if (event.keyIdentifier === "Enter") { 417 this.hide(true); 418 event.consume(true); 419 return; 420 } 421 if (event.keyIdentifier === "U+001B") { // Escape key 422 this.hide(false); 423 event.consume(true); 424 } 425 }, 426 427 __proto__: WebInspector.Object.prototype 428} 429 430/** 431 * @constructor 432 * @param {boolean=} readOnly 433 */ 434WebInspector.ColorSwatch = function(readOnly) 435{ 436 this.element = document.createElement("span"); 437 this._swatchInnerElement = this.element.createChild("span", "swatch-inner"); 438 var shiftClickMessage = WebInspector.UIString("Shift-click to change color format."); 439 this.element.title = readOnly ? shiftClickMessage : String.sprintf("%s\n%s", WebInspector.UIString("Click to open a colorpicker."), shiftClickMessage); 440 this.element.className = "swatch"; 441 this.element.addEventListener("mousedown", consumeEvent, false); 442 this.element.addEventListener("dblclick", consumeEvent, false); 443} 444 445WebInspector.ColorSwatch.prototype = { 446 /** 447 * @param {string} colorString 448 */ 449 setColorString: function(colorString) 450 { 451 this._swatchInnerElement.style.backgroundColor = colorString; 452 } 453} 454