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