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