/* Copyright (c) 2011, Geert Bergman (geert@scrivo.nl) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of "Scrivo" nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * $Id: HSVSelector.js 616 2013-04-22 23:48:38Z geert $ */ "use strict"; SUI.control.HSVSelector = SUI.defineClass( /** @lends SUI.control.HSVSelector.prototype */{ /** @ignore */ baseClass: SUI.Box, /** * @class * SUI.control.HSVSelector is a hue, saturation, value color selection * control as can be found in image manipulation programs as the GIMP. * The user can make a color section by choosing the hue value from a bar * and the saturation and value from a cartesian system. The hue bar is * representing the perimeter of a color wheel cycling through the red, * green and blue colors while blending into each other). The saturation * and value are selected by selecting a point in an area that is set up * by these two axes. * * @augments SUI.Box * * @description * Create an HSV color selection control. * * @constructs * @param see base class * @param {String} arg.color Initial color selection of the control. * @param {Function} arg.onChange Listener function that is executed each * time the control's color selection changes. */ initializer: function(arg) { SUI.control.HSVSelector.initializeBase(this, arg); // set the width and height of the control. this is a fixed size // which can't be altered. this.width(2 * this.PADDING + this.AXIS_LENGTH + this.SPLIT_WIDTH + this.HUE_BAR_WIDTH); this.height(this.AXIS_LENGTH + 2 * this.PADDING); // conversion from length to range can be done nicely by substracting // the crosshair width of the length this._axisRange = this.AXIS_LENGTH - this.CROSSHAIR_LINE; // built the saturation-value pane ... this._buildSatValPane(); // ... and hue bar this._buildHueBar(); // set the onChange handler if (arg.onChange) { this.addListener("onChange", arg.onChange); } // set the control's selected color value this.colorCode(arg.color || "#CCCCCC"); }, /** * Length of the hue, saturation and value axes. */ AXIS_LENGTH: 128, /** * Width of the crosshair line. */ CROSSHAIR_LINE: 1, /** * Width of the hue bar. */ HUE_BAR_WIDTH: 20, /** * Height of the hue crosshair image. */ HUE_CROSSHAIR_HEIGHT: 15, /** * Width of the hue crosshair image. */ HUE_CROSSHAIR_WIDTH: 26, /** * Padding of the control (half of the sat.-value crosshair image size). */ PADDING: 15, /** * Width and height of the saturation-value crosshair image */ SATVAL_CROSSHAIR_SIZE: 31, /** * distance between the saturation-value pane and hue bar */ SPLIT_WIDTH: 17, /** * Set or get the HTML color code selection of the control. * @param {String} c An HTML color code (#FF7700), or none to get * the current color selection from the control. * @return {String} An HTML color code (#FF7700), or null if this * method is used as a setter */ colorCode: function(c) { if (c === undefined) { return SUI.color.hsvToCol( {h: this._hue, s: this._sat, v: this._val}); } // got here? the method is a setter var col = SUI.color.colToHsv(c); // set the new color values this._hue = col.h; this._sat = col.s; this._val = col.v; // set the positions of the crosshairs this._hueTop(); this._satTop(); this._valLeft(); // redisplay the control this.display(); return null; }, /** * Display HSV control. Set the size and position of the * saturation-value pane, hue bar, crosshairs and set the background * color of the saturation-value. */ display: function() { this.setDim(); this._satVal.setDim(); this._hueBar.setDim(); this._chSatVal.setDim(); this._chHue.setDim(); this._satVal.el().style.backgroundColor = SUI.color.hsvToCol({h: this._hue, s: 1, v:1}); }, /** * Set or get the hue of the control's currently selected color and * redisplay the control if the value was set. * @param {int} hue The new hue for the control's currently selected color * (0 <= hue <= 360), or no value to use this method as a getter. * @return {int} the hue of the control's currently selected color, or * null if this method is used as a setter. */ hue: function(hue) { if (hue === undefined) { return this._hue; } // got here? the method is a setter if (hue >= 0 && hue <= 360) { this._hue = hue; } this._hueTop(); this.display(); return null; }, /** * Lay out the HSV control. Calculate the size and position of the * saturation-value pane and hue bar. */ layOut: function() { this._satVal.setRect(this._satVal); this._hueBar.setRect(this._hueBar); }, /** * onChange event handler: is executed when the control's color selection * changes. This happens continuously when the user is dragging the * crosshairs. * @param {String} c The HTML color code of the color that is currently * selected by the control. */ onChange: function(c) { }, /** * Set the saturation of the control's currently selected color and * redisplay the control. * @param {float} sat The new saturation for the control's currently * selected color (0 <= saturation <= 1), or no value to use this * method as a getter. * @return {float} the saturation of the control's currently selected * color, or null if this method is used as a setter. */ saturation: function(sat) { if (sat === undefined) { return this._sat; } // got here? the method is a setter if (sat >= 0 && sat <= 1) { this._sat = sat; } this._satTop(); this.display(); return null; }, /** * Set the value of the control's currently selected color and redisplay * the control. * @param {float} val The new value for the control's currently selected * color (0 <= value <= 1), or no value to use this method as a * getter. * @return {float} the value of the control's currently selected * color, or null if this method is used as a setter. */ value: function(val) { if (val === undefined) { return this._val; } // got here? the method is a setter if (val >= 0 && val <= 1) { this._val = val; } this._valLeft(); this.display(); return null; }, // length vs range: fi length: 128 -> range: 0-127 _axisRange: 0, // dragger box for the hue crosshair _chHue: null, // dragger box for the saturation-value pane crosshair _chSatVal: null, // current hue setting _hue: 0, // box for the hue bar _hueBar: null, // offset of the hue crosshair w.r.t. the top of the hue bar _ofsChHue: 0, // offset of the saturation-value crosshair w.r.t. the top left corner // of the saturation-value pane _ofsChSatVal: 0, // current saturation setting _sat: 1, // box for the saturation-value pane _satVal: null, // current value setting _val: 0, // Create the box for the hue bar and create the draggable crosshair _buildHueBar: function() { // build the hue bar this._hueBar = new SUI.Box({ parent: this, width: this.HUE_BAR_WIDTH, height: this.AXIS_LENGTH, top: this.PADDING, left: this.AXIS_LENGTH + this.PADDING + this.SPLIT_WIDTH }); // set hue background image this._hueBar.el().style.backgroundImage = "url(" + SUI.imgDir + "/" + SUI.resource.hsvHue + ")"; // the crosshair extends over the hue bar's edges, show it this._hueBar.el().style.overflow = "visible"; // create the crosshair, a dragger component this._chHue = new SUI.Dragger({ parent: this._hueBar, width: this.HUE_CROSSHAIR_WIDTH, height: this.HUE_CROSSHAIR_HEIGHT, left: (this.HUE_BAR_WIDTH - this.HUE_CROSSHAIR_WIDTH) / 2 | 0 }); // calculate the top offset of the crosshair image w.r.t. the top of // the hue bar this._ofsChHue = (this.CROSSHAIR_LINE - this.HUE_CROSSHAIR_HEIGHT) / 2 | 0; // set the range for dragging ... this._chHue.yMin(this._ofsChHue); this._chHue.yMax( this._ofsChHue + this.AXIS_LENGTH - this.CROSSHAIR_LINE); // ... and the direction in which we may draw this._chHue.direction(this._chSatVal.VERTICAL); // set the crosshair background image ... this._chHue.el().style.backgroundImage = "url(" + SUI.imgDir + "/" + SUI.resource.hsvChHue + ")"; // ... and an appropriate cursor this._chHue.el().style.cursor = "pointer"; // start dragging on the onmousedown of the crosshair var that = this; SUI.browser.addEventListener(this._chHue.el(), "mousedown", function(e) { if (!that._startHueDrag(new SUI.Event(this, e))) { SUI.browser.noPropagation(e); } } ); }, // Create the box for the saturation-value pane and create the draggable // crosshair _buildSatValPane: function() { // build the saturation-value pane this._satVal = new SUI.Box({ parent: this, width: this.AXIS_LENGTH, height: this.AXIS_LENGTH, top: this.PADDING, left: this.PADDING }); // set saturation-value background image this._satVal.el().style.backgroundImage = "url(" + SUI.imgDir + "/" + SUI.resource.hsvSatVal + ")"; // the crosshair extends over the pane's edges, show it this._satVal.el().style.overflow = "visible"; // create the crosshair, a dragger component this._chSatVal = new SUI.Dragger({ parent: this._satVal, width: this.SATVAL_CROSSHAIR_SIZE, height: this.SATVAL_CROSSHAIR_SIZE }); // calculate the top offset of the crosshair image w.r.t. the top left // of the saturation-value pane this._ofsChSatVal = (this.CROSSHAIR_LINE - this.SATVAL_CROSSHAIR_SIZE) / 2 | 0; // set the range for dragging ... var max = this._ofsChSatVal + this.AXIS_LENGTH - this.CROSSHAIR_LINE; this._chSatVal.xMin(this._ofsChSatVal); this._chSatVal.xMax(max); this._chSatVal.yMin(this._ofsChSatVal); this._chSatVal.yMax(max); // ... and the direction in which we may draw this._chSatVal.direction( this._chSatVal.HORIZONTAL + this._chSatVal.VERTICAL); // set the crosshair background image ... this._chSatVal.el().style.backgroundImage = "url(" + SUI.imgDir + "/" + SUI.resource.hsvChSatVal + ")"; // ... and an appropriate cursor this._chSatVal.el().style.cursor = "pointer"; // start dragging on the onmousedown of the crosshair var that = this; SUI.browser.addEventListener(this._chSatVal.el(), "mousedown", function(e) { if (!that._startSatValDrag(new SUI.Event(this, e))) { SUI.browser.noPropagation(e); } } ); }, // While dragging the hue crosshair change the hue setting and update // the hue setting of the saturation-value pane and call the onChange // listener _hueDrag: function() { // calculate hue from top (hue = distance / range * 360) this._hue = (this._axisRange - (this._chHue.top() - this._ofsChHue)) * 360 / this._axisRange; // set the background color of the saturation-value pane this._satVal.el().style.backgroundColor = SUI.color.hsvToCol({h: this._hue, s:1, v:1}); // call the listener this.callListener("onChange", SUI.color.hsvToCol({h: this._hue, s: this._sat, v: this._val})); }, // Set the top of the hue crosshair to reflect the current hue setting. _hueTop: function() { // calculate top from hue (distance = (hue / 360) * range) this._chHue.top( ((1 - this._hue / 360) * this._axisRange | 0) + this._ofsChHue); }, // Set the top of the saturation-value crosshair to reflect the current // saturation setting. _satTop: function() { // calculate top from saturation (distance = saturation * range) this._chSatVal.top( ((1 - this._sat) * this._axisRange | 0) + this._ofsChSatVal); }, // While dragging the saturation crosshair change the saturation and // value settings and call the onChange listener _satValDrag: function() { // calculate saturation from top (saturation = distance / range) this._sat = (this._axisRange - (this._chSatVal.top() - this._ofsChSatVal)) / this._axisRange; // calculate value from left (value = distance / range) this._val = (this._chSatVal.left() - this._ofsChSatVal) / this._axisRange; // call the listener this.callListener("onChange", SUI.color.hsvToCol({h: this._hue, s: this._sat, v: this._val})); }, // On start dragging the hue crosshair initialize the onDrag handler of // the dragger and start dragging _startHueDrag: function(e) { var that = this; this._chHue.addListener("onDrag", function() { that._hueDrag(); }); this._chHue.start(e, this); }, // On start dragging the saturation-value crosshair initialize the // onDrag handler of the dragger and start dragging _startSatValDrag: function(e) { var that = this; this._chSatVal.addListener( "onDrag", function() { that._satValDrag(); }); this._chSatVal.start(e, this); }, // Set the left of the saturation-value crosshair to reflect the current // value setting. _valLeft: function() { // calculate left from value (distance = value * range) this._chSatVal.left( (this._val * this._axisRange | 0) + this._ofsChSatVal); } });