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