browser/devtools/shared/widgets/Spectrum.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const EventEmitter = require("devtools/toolkit/event-emitter");
michael@0 8
michael@0 9 /**
michael@0 10 * Spectrum creates a color picker widget in any container you give it.
michael@0 11 *
michael@0 12 * Simple usage example:
michael@0 13 *
michael@0 14 * const {Spectrum} = require("devtools/shared/widgets/Spectrum");
michael@0 15 * let s = new Spectrum(containerElement, [255, 126, 255, 1]);
michael@0 16 * s.on("changed", (event, rgba, color) => {
michael@0 17 * console.log("rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ", " + rgba[3] + ")");
michael@0 18 * });
michael@0 19 * s.show();
michael@0 20 * s.destroy();
michael@0 21 *
michael@0 22 * Note that the color picker is hidden by default and you need to call show to
michael@0 23 * make it appear. This 2 stages initialization helps in cases you are creating
michael@0 24 * the color picker in a parent element that hasn't been appended anywhere yet
michael@0 25 * or that is hidden. Calling show() when the parent element is appended and
michael@0 26 * visible will allow spectrum to correctly initialize its various parts.
michael@0 27 *
michael@0 28 * Fires the following events:
michael@0 29 * - changed : When the user changes the current color
michael@0 30 */
michael@0 31 function Spectrum(parentEl, rgb) {
michael@0 32 EventEmitter.decorate(this);
michael@0 33
michael@0 34 this.element = parentEl.ownerDocument.createElement('div');
michael@0 35 this.parentEl = parentEl;
michael@0 36
michael@0 37 this.element.className = "spectrum-container";
michael@0 38 this.element.innerHTML = [
michael@0 39 "<div class='spectrum-top'>",
michael@0 40 "<div class='spectrum-fill'></div>",
michael@0 41 "<div class='spectrum-top-inner'>",
michael@0 42 "<div class='spectrum-color spectrum-box'>",
michael@0 43 "<div class='spectrum-sat'>",
michael@0 44 "<div class='spectrum-val'>",
michael@0 45 "<div class='spectrum-dragger'></div>",
michael@0 46 "</div>",
michael@0 47 "</div>",
michael@0 48 "</div>",
michael@0 49 "<div class='spectrum-hue spectrum-box'>",
michael@0 50 "<div class='spectrum-slider spectrum-slider-control'></div>",
michael@0 51 "</div>",
michael@0 52 "</div>",
michael@0 53 "</div>",
michael@0 54 "<div class='spectrum-alpha spectrum-checker spectrum-box'>",
michael@0 55 "<div class='spectrum-alpha-inner'>",
michael@0 56 "<div class='spectrum-alpha-handle spectrum-slider-control'></div>",
michael@0 57 "</div>",
michael@0 58 "</div>",
michael@0 59 ].join("");
michael@0 60
michael@0 61 this.onElementClick = this.onElementClick.bind(this);
michael@0 62 this.element.addEventListener("click", this.onElementClick, false);
michael@0 63
michael@0 64 this.parentEl.appendChild(this.element);
michael@0 65
michael@0 66 this.slider = this.element.querySelector(".spectrum-hue");
michael@0 67 this.slideHelper = this.element.querySelector(".spectrum-slider");
michael@0 68 Spectrum.draggable(this.slider, this.onSliderMove.bind(this));
michael@0 69
michael@0 70 this.dragger = this.element.querySelector(".spectrum-color");
michael@0 71 this.dragHelper = this.element.querySelector(".spectrum-dragger");
michael@0 72 Spectrum.draggable(this.dragger, this.onDraggerMove.bind(this));
michael@0 73
michael@0 74 this.alphaSlider = this.element.querySelector(".spectrum-alpha");
michael@0 75 this.alphaSliderInner = this.element.querySelector(".spectrum-alpha-inner");
michael@0 76 this.alphaSliderHelper = this.element.querySelector(".spectrum-alpha-handle");
michael@0 77 Spectrum.draggable(this.alphaSliderInner, this.onAlphaSliderMove.bind(this));
michael@0 78
michael@0 79 if (rgb) {
michael@0 80 this.rgb = rgb;
michael@0 81 this.updateUI();
michael@0 82 }
michael@0 83 }
michael@0 84
michael@0 85 module.exports.Spectrum = Spectrum;
michael@0 86
michael@0 87 Spectrum.hsvToRgb = function(h, s, v, a) {
michael@0 88 let r, g, b;
michael@0 89
michael@0 90 let i = Math.floor(h * 6);
michael@0 91 let f = h * 6 - i;
michael@0 92 let p = v * (1 - s);
michael@0 93 let q = v * (1 - f * s);
michael@0 94 let t = v * (1 - (1 - f) * s);
michael@0 95
michael@0 96 switch(i % 6) {
michael@0 97 case 0: r = v, g = t, b = p; break;
michael@0 98 case 1: r = q, g = v, b = p; break;
michael@0 99 case 2: r = p, g = v, b = t; break;
michael@0 100 case 3: r = p, g = q, b = v; break;
michael@0 101 case 4: r = t, g = p, b = v; break;
michael@0 102 case 5: r = v, g = p, b = q; break;
michael@0 103 }
michael@0 104
michael@0 105 return [r * 255, g * 255, b * 255, a];
michael@0 106 };
michael@0 107
michael@0 108 Spectrum.rgbToHsv = function(r, g, b, a) {
michael@0 109 r = r / 255;
michael@0 110 g = g / 255;
michael@0 111 b = b / 255;
michael@0 112
michael@0 113 let max = Math.max(r, g, b), min = Math.min(r, g, b);
michael@0 114 let h, s, v = max;
michael@0 115
michael@0 116 let d = max - min;
michael@0 117 s = max == 0 ? 0 : d / max;
michael@0 118
michael@0 119 if(max == min) {
michael@0 120 h = 0; // achromatic
michael@0 121 }
michael@0 122 else {
michael@0 123 switch(max) {
michael@0 124 case r: h = (g - b) / d + (g < b ? 6 : 0); break;
michael@0 125 case g: h = (b - r) / d + 2; break;
michael@0 126 case b: h = (r - g) / d + 4; break;
michael@0 127 }
michael@0 128 h /= 6;
michael@0 129 }
michael@0 130 return [h, s, v, a];
michael@0 131 };
michael@0 132
michael@0 133 Spectrum.getOffset = function(el) {
michael@0 134 let curleft = 0, curtop = 0;
michael@0 135 if (el.offsetParent) {
michael@0 136 while (el) {
michael@0 137 curleft += el.offsetLeft;
michael@0 138 curtop += el.offsetTop;
michael@0 139 el = el.offsetParent;
michael@0 140 }
michael@0 141 }
michael@0 142 return {
michael@0 143 left: curleft,
michael@0 144 top: curtop
michael@0 145 };
michael@0 146 };
michael@0 147
michael@0 148 Spectrum.draggable = function(element, onmove, onstart, onstop) {
michael@0 149 onmove = onmove || function() {};
michael@0 150 onstart = onstart || function() {};
michael@0 151 onstop = onstop || function() {};
michael@0 152
michael@0 153 let doc = element.ownerDocument;
michael@0 154 let dragging = false;
michael@0 155 let offset = {};
michael@0 156 let maxHeight = 0;
michael@0 157 let maxWidth = 0;
michael@0 158
michael@0 159 function prevent(e) {
michael@0 160 e.stopPropagation();
michael@0 161 e.preventDefault();
michael@0 162 }
michael@0 163
michael@0 164 function move(e) {
michael@0 165 if (dragging) {
michael@0 166 let pageX = e.pageX;
michael@0 167 let pageY = e.pageY;
michael@0 168
michael@0 169 let dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
michael@0 170 let dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
michael@0 171
michael@0 172 onmove.apply(element, [dragX, dragY]);
michael@0 173 }
michael@0 174 }
michael@0 175
michael@0 176 function start(e) {
michael@0 177 let rightclick = e.which === 3;
michael@0 178
michael@0 179 if (!rightclick && !dragging) {
michael@0 180 if (onstart.apply(element, arguments) !== false) {
michael@0 181 dragging = true;
michael@0 182 maxHeight = element.offsetHeight;
michael@0 183 maxWidth = element.offsetWidth;
michael@0 184
michael@0 185 offset = Spectrum.getOffset(element);
michael@0 186
michael@0 187 move(e);
michael@0 188
michael@0 189 doc.addEventListener("selectstart", prevent, false);
michael@0 190 doc.addEventListener("dragstart", prevent, false);
michael@0 191 doc.addEventListener("mousemove", move, false);
michael@0 192 doc.addEventListener("mouseup", stop, false);
michael@0 193
michael@0 194 prevent(e);
michael@0 195 }
michael@0 196 }
michael@0 197 }
michael@0 198
michael@0 199 function stop() {
michael@0 200 if (dragging) {
michael@0 201 doc.removeEventListener("selectstart", prevent, false);
michael@0 202 doc.removeEventListener("dragstart", prevent, false);
michael@0 203 doc.removeEventListener("mousemove", move, false);
michael@0 204 doc.removeEventListener("mouseup", stop, false);
michael@0 205 onstop.apply(element, arguments);
michael@0 206 }
michael@0 207 dragging = false;
michael@0 208 }
michael@0 209
michael@0 210 element.addEventListener("mousedown", start, false);
michael@0 211 };
michael@0 212
michael@0 213 Spectrum.prototype = {
michael@0 214 set rgb(color) {
michael@0 215 this.hsv = Spectrum.rgbToHsv(color[0], color[1], color[2], color[3]);
michael@0 216 },
michael@0 217
michael@0 218 get rgb() {
michael@0 219 let rgb = Spectrum.hsvToRgb(this.hsv[0], this.hsv[1], this.hsv[2], this.hsv[3]);
michael@0 220 return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2]), Math.round(rgb[3]*100)/100];
michael@0 221 },
michael@0 222
michael@0 223 get rgbNoSatVal() {
michael@0 224 let rgb = Spectrum.hsvToRgb(this.hsv[0], 1, 1);
michael@0 225 return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2]), rgb[3]];
michael@0 226 },
michael@0 227
michael@0 228 get rgbCssString() {
michael@0 229 let rgb = this.rgb;
michael@0 230 return "rgba(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ", " + rgb[3] + ")";
michael@0 231 },
michael@0 232
michael@0 233 show: function() {
michael@0 234 this.element.classList.add('spectrum-show');
michael@0 235
michael@0 236 this.slideHeight = this.slider.offsetHeight;
michael@0 237 this.dragWidth = this.dragger.offsetWidth;
michael@0 238 this.dragHeight = this.dragger.offsetHeight;
michael@0 239 this.dragHelperHeight = this.dragHelper.offsetHeight;
michael@0 240 this.slideHelperHeight = this.slideHelper.offsetHeight;
michael@0 241 this.alphaSliderWidth = this.alphaSliderInner.offsetWidth;
michael@0 242 this.alphaSliderHelperWidth = this.alphaSliderHelper.offsetWidth;
michael@0 243
michael@0 244 this.updateUI();
michael@0 245 },
michael@0 246
michael@0 247 onElementClick: function(e) {
michael@0 248 e.stopPropagation();
michael@0 249 },
michael@0 250
michael@0 251 onSliderMove: function(dragX, dragY) {
michael@0 252 this.hsv[0] = (dragY / this.slideHeight);
michael@0 253 this.updateUI();
michael@0 254 this.onChange();
michael@0 255 },
michael@0 256
michael@0 257 onDraggerMove: function(dragX, dragY) {
michael@0 258 this.hsv[1] = dragX / this.dragWidth;
michael@0 259 this.hsv[2] = (this.dragHeight - dragY) / this.dragHeight;
michael@0 260 this.updateUI();
michael@0 261 this.onChange();
michael@0 262 },
michael@0 263
michael@0 264 onAlphaSliderMove: function(dragX, dragY) {
michael@0 265 this.hsv[3] = dragX / this.alphaSliderWidth;
michael@0 266 this.updateUI();
michael@0 267 this.onChange();
michael@0 268 },
michael@0 269
michael@0 270 onChange: function() {
michael@0 271 this.emit("changed", this.rgb, this.rgbCssString);
michael@0 272 },
michael@0 273
michael@0 274 updateHelperLocations: function() {
michael@0 275 // If the UI hasn't been shown yet then none of the dimensions will be correct
michael@0 276 if (!this.element.classList.contains('spectrum-show'))
michael@0 277 return;
michael@0 278
michael@0 279 let h = this.hsv[0];
michael@0 280 let s = this.hsv[1];
michael@0 281 let v = this.hsv[2];
michael@0 282
michael@0 283 // Placing the color dragger
michael@0 284 let dragX = s * this.dragWidth;
michael@0 285 let dragY = this.dragHeight - (v * this.dragHeight);
michael@0 286 let helperDim = this.dragHelperHeight/2;
michael@0 287
michael@0 288 dragX = Math.max(
michael@0 289 -helperDim,
michael@0 290 Math.min(this.dragWidth - helperDim, dragX - helperDim)
michael@0 291 );
michael@0 292 dragY = Math.max(
michael@0 293 -helperDim,
michael@0 294 Math.min(this.dragHeight - helperDim, dragY - helperDim)
michael@0 295 );
michael@0 296
michael@0 297 this.dragHelper.style.top = dragY + "px";
michael@0 298 this.dragHelper.style.left = dragX + "px";
michael@0 299
michael@0 300 // Placing the hue slider
michael@0 301 let slideY = (h * this.slideHeight) - this.slideHelperHeight/2;
michael@0 302 this.slideHelper.style.top = slideY + "px";
michael@0 303
michael@0 304 // Placing the alpha slider
michael@0 305 let alphaSliderX = (this.hsv[3] * this.alphaSliderWidth) - (this.alphaSliderHelperWidth / 2);
michael@0 306 this.alphaSliderHelper.style.left = alphaSliderX + "px";
michael@0 307 },
michael@0 308
michael@0 309 updateUI: function() {
michael@0 310 this.updateHelperLocations();
michael@0 311
michael@0 312 let rgb = this.rgb;
michael@0 313 let rgbNoSatVal = this.rgbNoSatVal;
michael@0 314
michael@0 315 let flatColor = "rgb(" + rgbNoSatVal[0] + ", " + rgbNoSatVal[1] + ", " + rgbNoSatVal[2] + ")";
michael@0 316 let fullColor = "rgba(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ", " + rgb[3] + ")";
michael@0 317
michael@0 318 this.dragger.style.backgroundColor = flatColor;
michael@0 319
michael@0 320 var rgbNoAlpha = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
michael@0 321 var rgbAlpha0 = "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ", 0)";
michael@0 322 var alphaGradient = "linear-gradient(to right, " + rgbAlpha0 + ", " + rgbNoAlpha + ")";
michael@0 323 this.alphaSliderInner.style.background = alphaGradient;
michael@0 324 },
michael@0 325
michael@0 326 destroy: function() {
michael@0 327 this.element.removeEventListener("click", this.onElementClick, false);
michael@0 328
michael@0 329 this.parentEl.removeChild(this.element);
michael@0 330
michael@0 331 this.slider = null;
michael@0 332 this.dragger = null;
michael@0 333 this.alphaSlider = this.alphaSliderInner = this.alphaSliderHelper = null;
michael@0 334 this.parentEl = null;
michael@0 335 this.element = null;
michael@0 336 }
michael@0 337 };

mercurial