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