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