2 * Copyright (C) 2011 Brian Grinstead All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
31 * @extends {WebInspector.View}
33 WebInspector.Spectrum = function()
35 WebInspector.View.call(this);
36 this.registerRequiredCSS("spectrum.css");
38 this.element.className = "spectrum-container";
39 this.element.tabIndex = 0;
41 var topElement = this.element.createChild("div", "spectrum-top");
42 topElement.createChild("div", "spectrum-fill");
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");
48 this._sliderElement = topInnerElement.createChild("div", "spectrum-hue");
49 this.slideHelper = this._sliderElement.createChild("div", "spectrum-slider");
51 var rangeContainer = this.element.createChild("div", "spectrum-range-container");
52 var alphaLabel = rangeContainer.createChild("label");
53 alphaLabel.textContent = WebInspector.UIString("\u03B1:");
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);
61 var swatchElement = document.createElement("span");
62 swatchElement.className = "swatch";
63 this._swatchInnerElement = swatchElement.createChild("span", "swatch-inner");
65 var displayContainer = this.element.createChild("div");
66 displayContainer.appendChild(swatchElement);
67 this._displayElement = displayContainer.createChild("span", "source-code spectrum-display-value");
69 WebInspector.Spectrum.draggable(this._sliderElement, hueDrag.bind(this));
70 WebInspector.Spectrum.draggable(this._draggerElement, colorDrag.bind(this), colorDragStart.bind(this));
72 function hueDrag(element, dragX, dragY)
74 this.hsv[0] = (dragY / this.slideHeight);
79 var initialHelperOffset;
81 function colorDragStart(element, dragX, dragY)
83 initialHelperOffset = { x: this._dragHelperElement.offsetLeft, y: this._dragHelperElement.offsetTop };
86 function colorDrag(element, dragX, dragY, event)
89 if (Math.abs(dragX - initialHelperOffset.x) >= Math.abs(dragY - initialHelperOffset.y))
90 dragY = initialHelperOffset.y;
92 dragX = initialHelperOffset.x;
95 this.hsv[1] = dragX / this.dragWidth;
96 this.hsv[2] = (this.dragHeight - dragY) / this.dragHeight;
103 this.hsv[3] = this._alphaElement.value / 100;
109 WebInspector.Spectrum.Events = {
110 ColorChanged: "ColorChanged"
113 WebInspector.Spectrum.hsvaToRGBA = function(h, s, v, a)
117 var i = Math.floor(h * 6);
120 var q = v * (1 - f * s);
121 var t = v * (1 - (1 - f) * s);
144 return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a];
147 WebInspector.Spectrum.rgbaToHSVA = function(r, g, b, a)
153 var max = Math.max(r, g, b);
154 var min = Math.min(r, g, b);
160 s = max ? d / max : 0;
168 h = (g - b) / d + (g < b ? 6 : 0);
182 //FIXME: migrate to WebInspector.installDragHandle
184 * @param {Function=} onmove
185 * @param {Function=} onstart
186 * @param {Function=} onstop
188 WebInspector.Spectrum.draggable = function(element, onmove, onstart, onstop) {
205 var dragX = Math.max(0, Math.min(e.pageX - offset.left + scrollOffset.left, maxWidth));
206 var dragY = Math.max(0, Math.min(e.pageY - offset.top + scrollOffset.top, maxHeight));
209 onmove(element, dragX, dragY, e);
215 var rightClick = e.which ? (e.which === 3) : (e.button === 2);
217 if (!rightClick && !dragging) {
223 maxHeight = element.clientHeight;
224 maxWidth = element.clientWidth;
226 scrollOffset = element.scrollOffset();
227 offset = element.totalOffset();
229 doc.addEventListener("selectstart", consume, false);
230 doc.addEventListener("dragstart", consume, false);
231 doc.addEventListener("mousemove", move, false);
232 doc.addEventListener("mouseup", stop, false);
242 doc.removeEventListener("selectstart", consume, false);
243 doc.removeEventListener("dragstart", consume, false);
244 doc.removeEventListener("mousemove", move, false);
245 doc.removeEventListener("mouseup", stop, false);
254 element.addEventListener("mousedown", start, false);
257 WebInspector.Spectrum.prototype = {
259 * @type {WebInspector.Color}
263 var rgba = (color.rgba || color.rgb).slice(0);
265 if (rgba.length === 3)
268 this.hsv = WebInspector.Spectrum.rgbaToHSVA(rgba[0], rgba[1], rgba[2], rgba[3]);
273 var rgba = WebInspector.Spectrum.hsvaToRGBA(this.hsv[0], this.hsv[1], this.hsv[2], this.hsv[3]);
277 color = WebInspector.Color.fromRGB(rgba[0], rgba[1], rgba[2]);
279 color = WebInspector.Color.fromRGBA(rgba[0], rgba[1], rgba[2], rgba[3]);
281 var colorValue = color.toString(this.outputColorFormat);
283 colorValue = color.toString(); // this.outputColorFormat can be invalid for current color (e.g. "nickname").
284 return WebInspector.Color.parse(colorValue);
287 get outputColorFormat()
289 var cf = WebInspector.Color.Format;
290 var format = this._originalFormat;
292 if (this.hsv[3] === 1) {
293 // Simplify transparent formats.
294 if (format === cf.RGBA)
296 else if (format === cf.HSLA)
299 // Everything except HSL(A) should be returned as RGBA if transparency is involved.
300 if (format === cf.HSL || format === cf.HSLA)
311 var rgba = WebInspector.Spectrum.hsvaToRGBA(this.hsv[0], 1, 1, 1);
312 return WebInspector.Color.fromRGBA(rgba[0], rgba[1], rgba[2], rgba[3]);
315 set displayText(text)
317 this._displayElement.textContent = text;
320 _onchange: function()
323 this.dispatchEventToListeners(WebInspector.Spectrum.Events.ColorChanged, this.color);
326 _updateHelperLocations: function()
332 // Where to show the little circle that displays your current selected color.
333 var dragX = s * this.dragWidth;
334 var dragY = this.dragHeight - (v * this.dragHeight);
336 dragX = Math.max(-this._dragHelperElementHeight,
337 Math.min(this.dragWidth - this._dragHelperElementHeight, dragX - this._dragHelperElementHeight));
338 dragY = Math.max(-this._dragHelperElementHeight,
339 Math.min(this.dragHeight - this._dragHelperElementHeight, dragY - this._dragHelperElementHeight));
341 this._dragHelperElement.positionAt(dragX, dragY);
343 // Where to show the bar that displays your current selected hue.
344 var slideY = (h * this.slideHeight) - this.slideHelperHeight;
345 this.slideHelper.style.top = slideY + "px";
347 this._alphaElement.value = this.hsv[3] * 100;
350 _updateUI: function()
352 this._updateHelperLocations();
354 var rgb = (this.color.rgba || this.color.rgb).slice(0);
356 if (rgb.length === 3)
359 var rgbHueOnly = this.colorHueOnly.rgb;
361 var flatColor = "rgb(" + rgbHueOnly[0] + ", " + rgbHueOnly[1] + ", " + rgbHueOnly[2] + ")";
362 var fullColor = "rgba(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ", " + rgb[3] + ")";
364 this._draggerElement.style.backgroundColor = flatColor;
365 this._swatchInnerElement.style.backgroundColor = fullColor;
367 this._alphaElement.value = this.hsv[3] * 100;
372 this.slideHeight = this._sliderElement.offsetHeight;
373 this.dragWidth = this._draggerElement.offsetWidth;
374 this.dragHeight = this._draggerElement.offsetHeight;
375 this._dragHelperElementHeight = this._dragHelperElement.offsetHeight / 2;
376 this.slideHelperHeight = this.slideHelper.offsetHeight / 2;
380 __proto__: WebInspector.View.prototype
385 * @extends {WebInspector.Object}
387 WebInspector.SpectrumPopupHelper = function()
389 this._spectrum = new WebInspector.Spectrum();
390 this._spectrum.element.addEventListener("keydown", this._onKeyDown.bind(this), false);
392 this._popover = new WebInspector.Popover();
393 this._popover.setCanShrink(false);
394 this._popover.element.addEventListener("mousedown", consumeEvent, false);
396 this._hideProxy = this.hide.bind(this, true);
399 WebInspector.SpectrumPopupHelper.Events = {
403 WebInspector.SpectrumPopupHelper.prototype = {
405 * @return {WebInspector.Spectrum}
409 return this._spectrum;
412 toggle: function(element, color, format)
414 if (this._popover.isShowing())
417 this.show(element, color, format);
419 return this._popover.isShowing();
422 show: function(element, color, format)
424 if (this._popover.isShowing()) {
425 if (this._anchorElement === element)
428 // Reopen the picker for another anchor element.
432 this._anchorElement = element;
434 this._spectrum.color = color;
435 this._spectrum._originalFormat = format || color.format;
436 this.reposition(element);
438 document.addEventListener("mousedown", this._hideProxy, false);
439 window.addEventListener("blur", this._hideProxy, false);
443 reposition: function(element)
445 if (!this._previousFocusElement)
446 this._previousFocusElement = WebInspector.currentFocusElement();
447 this._popover.showView(this._spectrum, element);
448 WebInspector.setCurrentFocusElement(this._spectrum.element);
452 * @param {boolean=} commitEdit
454 hide: function(commitEdit)
456 if (!this._popover.isShowing())
458 this._popover.hide();
460 document.removeEventListener("mousedown", this._hideProxy, false);
461 window.removeEventListener("blur", this._hideProxy, false);
463 this.dispatchEventToListeners(WebInspector.SpectrumPopupHelper.Events.Hidden, !!commitEdit);
465 WebInspector.setCurrentFocusElement(this._previousFocusElement);
466 delete this._previousFocusElement;
468 delete this._anchorElement;
471 _onKeyDown: function(event)
473 if (event.keyIdentifier === "Enter") {
478 if (event.keyIdentifier === "U+001B") { // Escape key
484 __proto__: WebInspector.Object.prototype
490 WebInspector.ColorSwatch = function()
492 this.element = document.createElement("span");
493 this._swatchInnerElement = this.element.createChild("span", "swatch-inner");
494 this.element.title = WebInspector.UIString("Click to open a colorpicker. Shift-click to change color format");
495 this.element.className = "swatch";
496 this.element.addEventListener("mousedown", consumeEvent, false);
497 this.element.addEventListener("dblclick", consumeEvent, false);
500 WebInspector.ColorSwatch.prototype = {
502 * @param {string} colorString
504 setColorString: function(colorString)
506 this._swatchInnerElement.style.backgroundColor = colorString;