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.VBox}
33 WebInspector.Spectrum = function()
36 * @param {!Element} parentElement
38 function appendSwitcherIcon(parentElement)
40 var icon = parentElement.createSVGChild("svg");
41 icon.setAttribute("height", 16);
42 icon.setAttribute("width", 16);
43 var path = icon.createSVGChild("path");
44 path.setAttribute("d", "M5,6 L11,6 L8,2 Z M5,10 L11,10 L8,14 Z");
48 WebInspector.VBox.call(this, true);
49 this.registerRequiredCSS("elements/spectrum.css");
50 this.contentElement.tabIndex = 0;
52 this._colorElement = this.contentElement.createChild("div", "spectrum-color");
53 this._colorDragElement = this._colorElement.createChild("div", "spectrum-sat fill").createChild("div", "spectrum-val fill").createChild("div", "spectrum-dragger");
54 var contrastRatioSVG = this._colorElement.createSVGChild("svg", "spectrum-contrast-container fill");
55 this._contrastRatioLine = contrastRatioSVG.createSVGChild("path", "spectrum-contrast-line");
57 var toolbar = new WebInspector.Toolbar(this.contentElement);
58 toolbar.element.classList.add("spectrum-eye-dropper");
59 this._colorPickerButton = new WebInspector.ToolbarButton(WebInspector.UIString("Toggle color picker"), "eyedropper-toolbar-item");
60 this._colorPickerButton.setToggled(true);
61 this._colorPickerButton.addEventListener("click", this._toggleColorPicker.bind(this, undefined));
62 toolbar.appendToolbarItem(this._colorPickerButton);
64 var swatchElement = this.contentElement.createChild("span", "swatch");
65 this._swatchInnerElement = swatchElement.createChild("span", "swatch-inner");
67 this._hueElement = this.contentElement.createChild("div", "spectrum-hue");
68 this._hueSlider = this._hueElement.createChild("div", "spectrum-slider");
69 this._alphaElement = this.contentElement.createChild("div", "spectrum-alpha");
70 this._alphaElementBackground = this._alphaElement.createChild("div", "spectrum-alpha-background");
71 this._alphaSlider = this._alphaElement.createChild("div", "spectrum-slider");
73 var displaySwitcher = this.contentElement.createChild("div", "spectrum-display-switcher spectrum-switcher");
74 appendSwitcherIcon(displaySwitcher);
75 displaySwitcher.addEventListener("click", this._formatViewSwitch.bind(this));
78 this._displayContainer = this.contentElement.createChild("div", "spectrum-text source-code");
79 this._textValues = [];
80 for (var i = 0; i < 4; ++i) {
81 var inputValue = this._displayContainer.createChild("input", "spectrum-text-value");
82 inputValue.maxLength = 4;
83 this._textValues.push(inputValue);
84 inputValue.addEventListener("keydown", this._inputChanged.bind(this), false);
85 inputValue.addEventListener("input", this._inputChanged.bind(this), false);
86 inputValue.addEventListener("mousewheel", this._inputChanged.bind(this), false);
89 this._textLabels = this._displayContainer.createChild("div", "spectrum-text-label");
92 this._hexContainer = this.contentElement.createChild("div", "spectrum-text spectrum-text-hex source-code");
93 this._hexValue = this._hexContainer.createChild("input", "spectrum-text-value");
94 this._hexValue.maxLength = 7;
95 this._hexValue.addEventListener("keydown", this._inputChanged.bind(this), false);
96 this._hexValue.addEventListener("input", this._inputChanged.bind(this), false);
97 this._hexValue.addEventListener("mousewheel", this._inputChanged.bind(this), false);
99 var label = this._hexContainer.createChild("div", "spectrum-text-label");
100 label.textContent = "HEX";
102 WebInspector.installDragHandle(this._hueElement, dragStart.bind(this, positionHue.bind(this)), positionHue.bind(this), null, "default");
103 WebInspector.installDragHandle(this._alphaElement, dragStart.bind(this, positionAlpha.bind(this)), positionAlpha.bind(this), null, "default");
104 WebInspector.installDragHandle(this._colorElement, dragStart.bind(this, positionColor.bind(this)), positionColor.bind(this), null, "default");
106 this.element.classList.add("palettes-enabled");
107 /** @type {!Map.<string, !WebInspector.Spectrum.Palette>} */
108 this._palettes = new Map();
109 this._palettePanel = this.contentElement.createChild("div", "palette-panel");
110 this._palettePanelShowing = false;
111 this._paletteContainer = this.contentElement.createChild("div", "spectrum-palette");
112 this._paletteContainer.addEventListener("contextmenu", this._showPaletteColorContextMenu.bind(this, -1));
113 WebInspector.installDragHandle(this._paletteContainer, this._paletteDragStart.bind(this), this._paletteDrag.bind(this), this._paletteDragEnd.bind(this), "default");
114 var paletteSwitcher = this.contentElement.createChild("div", "spectrum-palette-switcher spectrum-switcher");
115 appendSwitcherIcon(paletteSwitcher);
116 paletteSwitcher.addEventListener("click", this._togglePalettePanel.bind(this, true));
118 this._deleteIconToolbar = new WebInspector.Toolbar();
119 this._deleteIconToolbar.element.classList.add("delete-color-toolbar");
120 this._deleteButton = new WebInspector.ToolbarButton("", "garbage-collect-toolbar-item");
121 this._deleteIconToolbar.appendToolbarItem(this._deleteButton);
123 var overlay = this.contentElement.createChild("div", "spectrum-overlay fill");
124 overlay.addEventListener("click", this._togglePalettePanel.bind(this, false));
126 this._addColorToolbar = new WebInspector.Toolbar();
127 this._addColorToolbar.element.classList.add("add-color-toolbar");
128 var addColorButton = new WebInspector.ToolbarButton(WebInspector.UIString("Add to palette"), "add-toolbar-item");
129 addColorButton.addEventListener("click", this._addColorToCustomPalette.bind(this));
130 this._addColorToolbar.appendToolbarItem(addColorButton);
132 new WebInspector.Spectrum.PaletteGenerator(this._generatedPaletteLoaded.bind(this));
135 * @param {function(!Event)} callback
136 * @param {!Event} event
138 * @this {WebInspector.Spectrum}
140 function dragStart(callback, event)
142 this._hueAlphaLeft = this._hueElement.totalOffsetLeft();
143 this._colorOffset = this._colorElement.totalOffset();
149 * @param {!Event} event
150 * @this {WebInspector.Spectrum}
152 function positionHue(event)
154 var hsva = this._hsv.slice();
155 hsva[0] = Number.constrain(1 - (event.x - this._hueAlphaLeft) / this._hueAlphaWidth, 0, 1);
156 this._innerSetColor(hsva, "", undefined, WebInspector.Spectrum._ChangeSource.Other);
160 * @param {!Event} event
161 * @this {WebInspector.Spectrum}
163 function positionAlpha(event)
165 var newAlpha = Math.round((event.x - this._hueAlphaLeft) / this._hueAlphaWidth * 100) / 100;
166 var hsva = this._hsv.slice();
167 hsva[3] = Number.constrain(newAlpha, 0, 1);
168 var colorFormat = undefined;
169 if (hsva[3] !== 1 && (this._colorFormat === WebInspector.Color.Format.ShortHEX || this._colorFormat === WebInspector.Color.Format.HEX || this._colorFormat === WebInspector.Color.Format.Nickname))
170 colorFormat = WebInspector.Color.Format.RGB;
171 this._innerSetColor(hsva, "", colorFormat, WebInspector.Spectrum._ChangeSource.Other);
175 * @param {!Event} event
176 * @this {WebInspector.Spectrum}
178 function positionColor(event)
180 var hsva = this._hsv.slice();
181 hsva[1] = Number.constrain((event.x - this._colorOffset.left) / this.dragWidth, 0, 1);
182 hsva[2] = Number.constrain(1 - (event.y - this._colorOffset.top) / this.dragHeight, 0, 1);
183 this._innerSetColor(hsva, "", undefined, WebInspector.Spectrum._ChangeSource.Other);
187 WebInspector.Spectrum._ChangeSource = {
193 WebInspector.Spectrum.Events = {
194 ColorChanged: "ColorChanged",
195 SizeChanged: "SizeChanged"
198 WebInspector.Spectrum._colorChipSize = 24;
199 WebInspector.Spectrum._itemsPerPaletteRow = 8;
201 WebInspector.Spectrum.prototype = {
202 _updatePalettePanel: function()
204 this._palettePanel.removeChildren();
205 var title = this._palettePanel.createChild("div", "palette-title");
206 title.textContent = WebInspector.UIString("Color Palettes");
207 var toolbar = new WebInspector.Toolbar(this._palettePanel);
208 var closeButton = new WebInspector.ToolbarButton("Return to color picker", "delete-toolbar-item");
209 closeButton.addEventListener("click", this._togglePalettePanel.bind(this, false));
210 toolbar.appendToolbarItem(closeButton);
211 for (var palette of this._palettes.values())
212 this._palettePanel.appendChild(this._createPreviewPaletteElement(palette));
216 * @param {boolean} show
218 _togglePalettePanel: function(show)
220 if (this._palettePanelShowing === show)
223 this._updatePalettePanel();
224 this._palettePanelShowing = show;
225 this.contentElement.classList.toggle("palette-panel-showing", show);
229 * @param {string} colorText
230 * @param {number=} animationDelay
233 _createPaletteColor: function(colorText, animationDelay)
235 var element = createElementWithClass("div", "spectrum-palette-color");
236 element.style.background = String.sprintf("linear-gradient(%s, %s), url(Images/checker.png)", colorText, colorText);
238 element.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 100, delay: animationDelay, fill: "backwards" });
239 element.title = colorText;
244 * @param {!WebInspector.Spectrum.Palette} palette
245 * @param {boolean} animate
246 * @param {!Event=} event
248 _showPalette: function(palette, animate, event)
250 this._paletteContainer.removeChildren();
251 for (var i = 0; i < palette.colors.length; i++) {
252 var animationDelay = animate ? i * 100 / palette.colors.length : 0;
253 var colorElement = this._createPaletteColor(palette.colors[i], animationDelay);
254 colorElement.addEventListener("mousedown", this._paletteColorSelected.bind(this, palette.colors[i], palette.matchUserFormat));
255 if (palette.mutable) {
256 colorElement.__mutable = true;
257 colorElement.__color = palette.colors[i];
258 colorElement.addEventListener("contextmenu", this._showPaletteColorContextMenu.bind(this, i));
260 this._paletteContainer.appendChild(colorElement);
262 this._paletteContainerMutable = palette.mutable;
264 var numItems = palette.colors.length;
267 var rowsNeeded = Math.max(1, Math.ceil(numItems / WebInspector.Spectrum._itemsPerPaletteRow));
268 if (palette.mutable) {
269 this._paletteContainer.appendChild(this._addColorToolbar.element);
270 this._paletteContainer.appendChild(this._deleteIconToolbar.element);
272 this._addColorToolbar.element.remove();
273 this._deleteIconToolbar.element.remove();
276 this._togglePalettePanel(false);
277 var paletteColorHeight = 12;
278 var paletteMargin = 12;
279 this.element.style.height = (this._paletteContainer.offsetTop + paletteMargin + (paletteColorHeight + paletteMargin) * rowsNeeded) + "px";
280 this.dispatchEventToListeners(WebInspector.Spectrum.Events.SizeChanged);
287 _slotIndexForEvent: function(e)
289 var localX = e.pageX - this._paletteContainer.totalOffsetLeft();
290 var localY = e.pageY - this._paletteContainer.totalOffsetTop();
291 var col = Math.min(localX / WebInspector.Spectrum._colorChipSize | 0, WebInspector.Spectrum._itemsPerPaletteRow - 1);
292 var row = (localY / WebInspector.Spectrum._colorChipSize) | 0;
293 return Math.min(row * WebInspector.Spectrum._itemsPerPaletteRow + col, this._customPaletteSetting.get().colors.length - 1);
300 _isDraggingToBin: function(e)
302 return e.pageX > this._deleteIconToolbar.element.totalOffsetLeft();
309 _paletteDragStart: function(e)
311 var element = e.deepElementFromPoint();
312 if (!element || !element.__mutable)
315 var index = this._slotIndexForEvent(e);
316 this._dragElement = element;
317 this._dragHotSpotX = e.pageX - (index % WebInspector.Spectrum._itemsPerPaletteRow) * WebInspector.Spectrum._colorChipSize;
318 this._dragHotSpotY = e.pageY - (index / WebInspector.Spectrum._itemsPerPaletteRow | 0) * WebInspector.Spectrum._colorChipSize;
320 this._deleteIconToolbar.element.classList.add("dragging");
327 _paletteDrag: function(e)
329 if (e.pageX < this._paletteContainer.totalOffsetLeft() || e.pageY < this._paletteContainer.totalOffsetTop())
331 var newIndex = this._slotIndexForEvent(e);
332 var offsetX = e.pageX - (newIndex % WebInspector.Spectrum._itemsPerPaletteRow) * WebInspector.Spectrum._colorChipSize;
333 var offsetY = e.pageY - (newIndex / WebInspector.Spectrum._itemsPerPaletteRow | 0) * WebInspector.Spectrum._colorChipSize;
335 var isDeleting = this._isDraggingToBin(e);
336 this._deleteIconToolbar.element.classList.toggle("delete-color-toolbar-active", isDeleting);
337 var dragElementTransform = "translateX(" + (offsetX - this._dragHotSpotX) + "px) translateY(" + (offsetY - this._dragHotSpotY) + "px)";
338 this._dragElement.style.transform = isDeleting ? dragElementTransform + " scale(0.8)" : dragElementTransform;
339 var children = Array.prototype.slice.call(this._paletteContainer.children);
340 var index = children.indexOf(this._dragElement);
341 /** @type {!Map.<!Element, {left: number, top: number}>} */
342 var swatchOffsets = new Map();
343 for (var swatch of children)
344 swatchOffsets.set(swatch, swatch.totalOffset());
346 if (index !== newIndex)
347 this._paletteContainer.insertBefore(this._dragElement, children[newIndex > index ? newIndex + 1 : newIndex]);
349 for (var swatch of children) {
350 if (swatch === this._dragElement)
352 var before = swatchOffsets.get(swatch);
353 var after = swatch.totalOffset();
354 if (before.left !== after.left || before.top !== after.top) {
356 { transform: "translateX(" + (before.left - after.left) + "px) translateY(" + (before.top - after.top) + "px)" },
357 { transform: "none" }], { duration: 100, easing: "cubic-bezier(0, 0, 0.2, 1)" });
365 _paletteDragEnd: function(e)
367 if (this._isDraggingToBin(e))
368 this._dragElement.remove();
369 this._dragElement.style.removeProperty("transform");
370 var children = this._paletteContainer.children;
372 for (var i = 0; i < children.length; ++i) {
373 if (children[i].__color)
374 colors.push(children[i].__color);
376 var palette = this._customPaletteSetting.get();
377 palette.colors = colors;
378 this._customPaletteSetting.set(palette);
379 this._showPalette(this._customPaletteSetting.get(), false);
381 this._deleteIconToolbar.element.classList.remove("dragging");
382 this._deleteIconToolbar.element.classList.remove("delete-color-toolbar-active");
383 this._deleteButton.setToggled(false);
387 * @param {!WebInspector.Spectrum.Palette} generatedPalette
389 _generatedPaletteLoaded: function(generatedPalette)
391 if (generatedPalette.colors.length)
392 this._palettes.set(generatedPalette.title, generatedPalette);
393 this._palettes.set(WebInspector.Spectrum.MaterialPalette.title, WebInspector.Spectrum.MaterialPalette);
394 /** @type {!WebInspector.Spectrum.Palette} */
395 var defaultCustomPalette = { title: "Custom", colors: [], mutable: true };
396 this._customPaletteSetting = WebInspector.settings.createSetting("customColorPalette", defaultCustomPalette);
397 this._palettes.set(this._customPaletteSetting.get().title, this._customPaletteSetting.get());
399 this._selectedColorPalette = WebInspector.settings.createSetting("selectedColorPalette", WebInspector.Spectrum.GeneratedPaletteTitle);
400 var paletteToShow = this._palettes.get(this._selectedColorPalette.get() || WebInspector.Spectrum.GeneratedPaletteTitle)
401 || this._palettes.get("Material");
403 this._showPalette(paletteToShow, true);
407 * @param {!WebInspector.Spectrum.Palette} palette
410 _createPreviewPaletteElement: function(palette)
412 var colorsPerPreviewRow = 5;
413 var previewElement = createElementWithClass("div", "palette-preview");
414 var titleElement = previewElement.createChild("div", "palette-preview-title");
415 titleElement.textContent = palette.title;
416 for (var i = 0; i < colorsPerPreviewRow && i < palette.colors.length; i++)
417 previewElement.appendChild(this._createPaletteColor(palette.colors[i]));
418 for (; i < colorsPerPreviewRow; i++)
419 previewElement.createChild("div", "spectrum-palette-color empty-color");
420 previewElement.addEventListener("click", this._paletteSelected.bind(this, palette));
421 return previewElement;
425 * @param {!WebInspector.Spectrum.Palette} palette
427 _paletteSelected: function(palette)
429 this._selectedColorPalette.set(palette.title);
430 this._showPalette(palette, true);
434 * @param {string} colorText
435 * @param {boolean} matchUserFormat
437 _paletteColorSelected: function(colorText, matchUserFormat)
439 var color = WebInspector.Color.parse(colorText);
442 this._innerSetColor(color.hsva(), colorText, matchUserFormat ? this._colorFormat : color.format(), WebInspector.Spectrum._ChangeSource.Other);
445 _addColorToCustomPalette: function()
447 var palette = this._customPaletteSetting.get();
448 palette.colors.push(this.colorString());
449 this._customPaletteSetting.set(palette);
450 this._showPalette(this._customPaletteSetting.get(), false);
454 * @param {number} colorIndex
455 * @param {!Event} event
457 _showPaletteColorContextMenu: function(colorIndex, event)
459 if (!this._paletteContainerMutable)
461 var contextMenu = new WebInspector.ContextMenu(event);
462 if (colorIndex !== -1) {
463 contextMenu.appendItem(WebInspector.UIString("Remove color"), this._deletePaletteColors.bind(this, colorIndex, false));
464 contextMenu.appendItem(WebInspector.UIString("Remove all to the right"), this._deletePaletteColors.bind(this, colorIndex, true));
466 contextMenu.appendItem(WebInspector.UIString("Clear palette"), this._deletePaletteColors.bind(this, -1, true));
471 * @param {number} colorIndex
472 * @param {boolean} toRight
474 _deletePaletteColors: function(colorIndex, toRight)
476 var palette = this._customPaletteSetting.get();
478 palette.colors.splice(colorIndex + 1, palette.colors.length - colorIndex - 1);
480 palette.colors.splice(colorIndex, 1);
481 this._customPaletteSetting.set(palette);
482 this._showPalette(this._customPaletteSetting.get(), false);
486 * @param {!WebInspector.Color} color
487 * @param {string} colorFormat
489 setColor: function(color, colorFormat)
491 this._originalFormat = colorFormat;
492 this._innerSetColor(color.hsva(), "", colorFormat, WebInspector.Spectrum._ChangeSource.Model);
496 * @param {!Array<number>|undefined} hsva
497 * @param {string|undefined} colorString
498 * @param {string|undefined} colorFormat
499 * @param {string} changeSource
501 _innerSetColor: function(hsva, colorString, colorFormat, changeSource)
503 if (hsva !== undefined)
505 if (colorString !== undefined)
506 this._colorString = colorString;
507 if (colorFormat !== undefined) {
508 console.assert(colorFormat !== WebInspector.Color.Format.Original, "Spectrum's color format cannot be Original");
509 if (colorFormat === WebInspector.Color.Format.RGBA)
510 colorFormat = WebInspector.Color.Format.RGB;
511 else if (colorFormat === WebInspector.Color.Format.HSLA)
512 colorFormat = WebInspector.Color.Format.HSL;
513 this._colorFormat = colorFormat;
516 this._updateHelperLocations();
519 if (changeSource !== WebInspector.Spectrum._ChangeSource.Input)
521 if (changeSource !== WebInspector.Spectrum._ChangeSource.Model)
522 this.dispatchEventToListeners(WebInspector.Spectrum.Events.ColorChanged, this.colorString());
526 * @param {!WebInspector.Color} color
528 setContrastColor: function(color)
530 this._contrastColor = color;
535 * @return {!WebInspector.Color}
539 return WebInspector.Color.fromHSVA(this._hsv);
545 colorString: function()
547 if (this._colorString)
548 return this._colorString;
549 var cf = WebInspector.Color.Format;
550 var color = this._color();
551 var colorString = color.asString(this._colorFormat);
555 if (this._colorFormat === cf.Nickname || this._colorFormat === cf.ShortHEX) {
556 colorString = color.asString(cf.HEX);
561 console.assert(color.hasAlpha());
562 return this._colorFormat === cf.HSL ? /** @type {string} */(color.asString(cf.HSLA)) : /** @type {string} */(color.asString(cf.RGBA));
565 _updateHelperLocations: function()
567 var h = this._hsv[0];
568 var s = this._hsv[1];
569 var v = this._hsv[2];
570 var alpha = this._hsv[3];
572 // Where to show the little circle that displays your current selected color.
573 var dragX = s * this.dragWidth;
574 var dragY = this.dragHeight - (v * this.dragHeight);
576 dragX = Math.max(-this._colorDragElementHeight,
577 Math.min(this.dragWidth - this._colorDragElementHeight, dragX - this._colorDragElementHeight));
578 dragY = Math.max(-this._colorDragElementHeight,
579 Math.min(this.dragHeight - this._colorDragElementHeight, dragY - this._colorDragElementHeight));
581 this._colorDragElement.positionAt(dragX, dragY);
583 // Where to show the bar that displays your current selected hue.
584 var hueSlideX = (1 - h) * this._hueAlphaWidth - this.slideHelperWidth;
585 this._hueSlider.style.left = hueSlideX + "px";
586 var alphaSlideX = alpha * this._hueAlphaWidth - this.slideHelperWidth;
587 this._alphaSlider.style.left = alphaSlideX + "px";
590 _updateInput: function()
592 var cf = WebInspector.Color.Format;
593 if (this._colorFormat === cf.HEX || this._colorFormat === cf.ShortHEX || this._colorFormat === cf.Nickname) {
594 this._hexContainer.hidden = false;
595 this._displayContainer.hidden = true;
596 if (this._colorFormat === cf.ShortHEX && this._color().canBeShortHex())
597 this._hexValue.value = this._color().asString(cf.ShortHEX);
599 this._hexValue.value = this._color().asString(cf.HEX);
601 // RGBA, HSLA display.
602 this._hexContainer.hidden = true;
603 this._displayContainer.hidden = false;
604 var isRgb = this._colorFormat === cf.RGB;
605 this._textLabels.textContent = isRgb ? "RGBA" : "HSLA";
606 var colorValues = isRgb ? this._color().canonicalRGBA() : this._color().canonicalHSLA();
607 for (var i = 0; i < 3; ++i) {
608 this._textValues[i].value = colorValues[i];
609 if (!isRgb && (i === 1 || i === 2))
610 this._textValues[i].value += "%";
612 this._textValues[3].value= Math.round(colorValues[3] * 100) / 100;
617 * @param {number} requiredContrast
619 _drawContrastRatioLine: function(requiredContrast)
621 if (!this._contrastColor || !this.dragWidth || !this.dragHeight)
624 /** const */ var width = this.dragWidth;
625 /** const */ var height = this.dragHeight;
626 /** const */ var dS = 0.02;
627 /** const */ var epsilon = 0.01;
630 WebInspector.Color.hsva2rgba(this._hsv, fgRGBA);
631 var fgLuminance = WebInspector.Color.luminance(fgRGBA);
632 var bgRGBA = this._contrastColor.rgba();
633 var bgLuminance = WebInspector.Color.luminance(bgRGBA);
634 var delta = fgLuminance < bgLuminance ? 1 : -1;
636 var lastV = this._hsv[2];
637 var currentSlope = 0;
638 var candidateHSVA = [this._hsv[0], 0, 0, this._hsv[3]];
639 var pathBuilder = [];
640 var candidateRGBA = [];
641 WebInspector.Color.hsva2rgba(candidateHSVA, candidateRGBA);
642 var flattenedRGBA = [];
643 WebInspector.Color.flattenColors(candidateRGBA, bgRGBA, flattenedRGBA);
646 * Approach the desired contrast ratio by modifying the given component
647 * from the given starting value.
648 * @param {number} index
652 function approach(index, x)
654 while (0 <= x && x <= 1) {
655 candidateHSVA[index] = x;
656 WebInspector.Color.hsva2rgba(candidateHSVA, candidateRGBA);
657 WebInspector.Color.flattenColors(candidateRGBA, bgRGBA, flattenedRGBA);
658 var contrast = WebInspector.Color.calculateContrastRatio(flattenedRGBA, bgRGBA);
659 var dContrast = contrast - requiredContrast;
660 if (Math.abs(dContrast) < epsilon) {
663 // 21 is the maximum possible value for contrast ratio:
664 // http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html#contrast-ratiodef
665 x += delta * (dContrast / 21);
671 for (var s = 0; s < 1 + dS; s += dS) {
673 candidateHSVA[1] = s;
675 v = lastV + currentSlope * dS;
681 currentSlope = (v - lastV) / dS;
683 pathBuilder.push(pathBuilder.length ? "L" : "M");
684 pathBuilder.push(s * width);
685 pathBuilder.push((1 - v) * height);
691 candidateHSVA[2] = 1;
694 pathBuilder = pathBuilder.concat(["L", s * width, 0])
697 this._contrastRatioLine.setAttribute("d", pathBuilder.join(" "));
700 _updateUI: function()
702 var h = WebInspector.Color.fromHSVA([this._hsv[0], 1, 1, 1]);
703 this._colorElement.style.backgroundColor = /** @type {string} */ (h.asString(WebInspector.Color.Format.RGB));
704 if (Runtime.experiments.isEnabled("colorContrastRatio")) {
705 // TODO(samli): Determine size of text and switch between AA/AAA ratings.
706 this._drawContrastRatioLine(4.5);
708 this._swatchInnerElement.style.backgroundColor = /** @type {string} */ (this._color().asString(WebInspector.Color.Format.RGBA));
709 // Show border if the swatch is white.
710 this._swatchInnerElement.classList.toggle("swatch-inner-white", this._color().hsla()[2] > 0.9);
711 this._colorDragElement.style.backgroundColor = /** @type {string} */ (this._color().asString(WebInspector.Color.Format.RGBA));
712 var noAlpha = WebInspector.Color.fromHSVA(this._hsv.slice(0,3).concat(1));
713 this._alphaElementBackground.style.backgroundImage = String.sprintf("linear-gradient(to right, rgba(0,0,0,0), %s)", noAlpha.asString(WebInspector.Color.Format.RGB));
716 _formatViewSwitch: function()
718 var cf = WebInspector.Color.Format;
720 if (this._colorFormat === cf.RGB)
722 else if (this._colorFormat === cf.HSL && !this._color().hasAlpha())
723 format = this._originalFormat === cf.ShortHEX ? cf.ShortHEX : cf.HEX;
724 this._innerSetColor(undefined, "", format, WebInspector.Spectrum._ChangeSource.Other);
728 * @param {!Event} event
730 _inputChanged: function(event)
733 * @param {!Element} element
736 function elementValue(element)
738 return element.value;
741 var inputElement = /** @type {!Element} */(event.currentTarget);
742 var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
743 var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
744 if (arrowKeyOrMouseWheelEvent || pageKeyPressed) {
745 var newValue = WebInspector.createReplacementString(inputElement.value, event);
747 inputElement.value = newValue;
748 inputElement.selectionStart = 0;
749 inputElement.selectionEnd = newValue.length;
754 const cf = WebInspector.Color.Format;
756 if (this._colorFormat === cf.HEX || this._colorFormat === cf.ShortHEX) {
757 colorString = this._hexValue.value;
759 var format = this._colorFormat === cf.RGB ? "rgba" : "hsla";
760 var values = this._textValues.map(elementValue).join(",");
761 colorString = String.sprintf("%s(%s)", format, values);
764 var color = WebInspector.Color.parse(colorString);
767 var hsv = color.hsva();
768 if (this._colorFormat === cf.HEX || this._colorFormat === cf.ShortHEX)
769 this._colorFormat = color.canBeShortHex() ? cf.ShortHEX : cf.HEX;
770 this._innerSetColor(hsv, colorString, undefined, WebInspector.Spectrum._ChangeSource.Input);
775 this._hueAlphaWidth = this._hueElement.offsetWidth;
776 this.slideHelperWidth = this._hueSlider.offsetWidth / 2;
777 this.dragWidth = this._colorElement.offsetWidth;
778 this.dragHeight = this._colorElement.offsetHeight;
779 this._colorDragElementHeight = this._colorDragElement.offsetHeight / 2;
780 this._innerSetColor(undefined, undefined, undefined, WebInspector.Spectrum._ChangeSource.Model);
781 this._toggleColorPicker(true);
782 WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.ColorPicked, this._colorPicked, this);
787 this._toggleColorPicker(false);
788 WebInspector.targetManager.removeModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.ColorPicked, this._colorPicked, this);
792 * @param {boolean=} enabled
793 * @param {!WebInspector.Event=} event
795 _toggleColorPicker: function(enabled, event)
797 if (enabled === undefined)
798 enabled = !this._colorPickerButton.toggled();
799 this._colorPickerButton.setToggled(enabled);
800 for (var target of WebInspector.targetManager.targets())
801 target.pageAgent().setColorPickerEnabled(enabled);
805 * @param {!WebInspector.Event} event
807 _colorPicked: function(event)
809 var rgbColor = /** @type {!DOMAgent.RGBA} */ (event.data);
810 var rgba = [rgbColor.r, rgbColor.g, rgbColor.b, (rgbColor.a / 2.55 | 0) / 100];
811 var color = WebInspector.Color.fromRGBA(rgba);
812 this._innerSetColor(color.hsva(), "", undefined, WebInspector.Spectrum._ChangeSource.Other);
813 InspectorFrontendHost.bringToFront();
817 __proto__: WebInspector.VBox.prototype
820 /** @typedef {{ title: string, colors: !Array.<string>, mutable: boolean }} */
821 WebInspector.Spectrum.Palette;
822 WebInspector.Spectrum.GeneratedPaletteTitle = "Page colors";
826 * @param {function(!WebInspector.Spectrum.Palette)} callback
828 WebInspector.Spectrum.PaletteGenerator = function(callback)
830 this._callback = callback;
831 var target = WebInspector.targetManager.mainTarget();
834 var cssModel = WebInspector.CSSStyleModel.fromTarget(target);
835 /** @type {!Map.<string, number>} */
836 this._frequencyMap = new Map();
837 var stylesheetPromises = [];
838 for (var stylesheet of cssModel.allStyleSheets())
839 stylesheetPromises.push(new Promise(this._processStylesheet.bind(this, stylesheet)));
840 Promise.all(stylesheetPromises)
841 .catchException(null)
842 .then(this._finish.bind(this));
845 WebInspector.Spectrum.PaletteGenerator.prototype = {
851 _frequencyComparator: function(a, b)
853 return this._frequencyMap.get(b) - this._frequencyMap.get(a);
863 function hueComparator(a, b)
865 var hsva = paletteColors.get(a).hsva();
866 var hsvb = paletteColors.get(b).hsva();
868 // First trim the shades of gray
869 if (hsvb[1] < 0.12 && hsva[1] < 0.12)
870 return hsvb[2]*hsvb[3] - hsva[2]*hsva[3];
876 // Equal hue -> sort by sat
877 if (hsvb[0] === hsva[0])
878 return hsvb[1]*hsvb[3] - hsva[1]*hsva[3];
880 return (hsvb[0] + 0.94) % 1 - (hsva[0] + 0.94) % 1;
883 var colors = this._frequencyMap.keysArray();
884 colors = colors.sort(this._frequencyComparator.bind(this));
885 /** @type {!Map.<string, !WebInspector.Color>} */
886 var paletteColors = new Map();
887 var colorsPerRow = 24;
888 while (paletteColors.size < colorsPerRow && colors.length) {
889 var colorText = colors.shift();
890 var color = WebInspector.Color.parse(colorText);
891 if (!color || color.nickname() === "white" || color.nickname() === "black")
893 paletteColors.set(colorText, color);
896 this._callback({ title: WebInspector.Spectrum.GeneratedPaletteTitle, colors: paletteColors.keysArray().sort(hueComparator), mutable: false });
900 * @param {!WebInspector.CSSStyleSheetHeader} stylesheet
901 * @param {function(?)} resolve
902 * @this {WebInspector.Spectrum.PaletteGenerator}
904 _processStylesheet: function(stylesheet, resolve)
907 * @param {?string} text
908 * @this {WebInspector.Spectrum.PaletteGenerator}
910 function parseContent(text)
912 text = text.toLowerCase();
913 var regexResult = text.match(/((?:rgb|hsl)a?\([^)]+\)|#[0-9a-f]{6}|#[0-9a-f]{3})/g) || [];
914 for (var c of regexResult) {
915 var frequency = this._frequencyMap.get(c) || 0;
916 this._frequencyMap.set(c, ++frequency);
921 stylesheet.requestContent(parseContent.bind(this));
925 WebInspector.Spectrum.MaterialPalette = { title: "Material", mutable: false, matchUserFormat: true, colors: [