blob: cbc46fbd8f56bb569b50c410f0749a4b1abefcc1 [file] [log] [blame]
/*
* Copyright (C) 2019 Apple Inc. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
*/
WI.ColorSquare = class ColorSquare
{
constructor(delegate, dimension)
{
this._delegate = delegate;
this._hue = 0;
this._x = 0;
this._y = 0;
this._gamut = null;
this._crosshairPosition = null;
this._element = document.createElement("div");
this._element.className = "color-square";
let saturationGradientElement = this._element.appendChild(document.createElement("div"));
saturationGradientElement.className = "saturation-gradient fill";
let lightnessGradientElement = this._element.appendChild(document.createElement("div"));
lightnessGradientElement.className = "lightness-gradient fill";
this._element.addEventListener("mousedown", this);
this._crosshairElement = this._element.appendChild(document.createElement("div"));
this._crosshairElement.className = "crosshair";
this.dimension = dimension;
}
// Public
get element() { return this._element; }
set dimension(dimension)
{
console.assert(!isNaN(dimension));
if (dimension === this._dimension)
return;
this._dimension = dimension;
this._element.style.width = this.element.style.height = `${this._dimension}px`;
this._updateBaseColor();
}
get hue()
{
return this._hue;
}
set hue(hue)
{
this._hue = hue;
this._updateBaseColor();
}
get tintedColor()
{
if (this._crosshairPosition) {
if (this._gamut === WI.Color.Gamut.DisplayP3) {
let rgb = WI.Color.hsv2rgb(this._hue, this._saturation, this._brightness);
rgb = rgb.map(((x) => Math.roundTo(x, 0.001)));
return new WI.Color(WI.Color.Format.ColorFunction, rgb, this._gamut);
}
let hsl = WI.Color.hsv2hsl(this._hue, this._saturation, this._brightness);
return new WI.Color(WI.Color.Format.HSL, hsl);
}
return new WI.Color(WI.Color.Format.HSLA, [0, 0, 0, 0]);
}
set tintedColor(tintedColor)
{
console.assert(tintedColor instanceof WI.Color);
this._gamut = tintedColor.gamut;
if (tintedColor.format === WI.Color.Format.ColorFunction) {
// CSS color function only supports RGB. It doesn't support HSL.
let hsv = WI.Color.rgb2hsv(...tintedColor.rgb);
let x = hsv[1] / 100 * this._dimension;
let y = (1 - (hsv[2] / 100)) * this._dimension;
this._setCrosshairPosition(new WI.Point(x, y));
} else {
let hsl = tintedColor.hsl;
let saturation = Number.constrain(hsl[1], 0, 100);
let x = saturation / 100 * this._dimension;
let lightness = hsl[2];
// The color picker is HSV-based. (HSV is also known as HSB.)
// Derive lightness by using HSV to HSL equation.
let y = 2 * lightness / (200 - saturation);
y = -1 * (y - 1) * this._dimension;
this._setCrosshairPosition(new WI.Point(x, y));
}
this._updateBaseColor();
}
// Protected
handleEvent(event)
{
switch (event.type) {
case "mousedown":
this._handleMousedown(event);
break;
case "mousemove":
this._handleMousemove(event);
break;
case "mouseup":
this._handleMouseup(event);
break;
}
}
// Private
get _saturation()
{
let saturation = this._x / this._dimension;
return Number.constrain(saturation, 0, 1) * 100;
}
get _brightness()
{
let brightness = 1 - (this._y / this._dimension);
return Number.constrain(brightness, 0, 1) * 100;
}
_handleMousedown(event)
{
if (event.button !== 0 || event.ctrlKey)
return;
window.addEventListener("mousemove", this, true);
window.addEventListener("mouseup", this, true);
this._updateColorForMouseEvent(event);
// Prevent text selection.
event.stop();
}
_handleMousemove(event)
{
this._updateColorForMouseEvent(event);
}
_handleMouseup(event)
{
window.removeEventListener("mousemove", this, true);
window.removeEventListener("mouseup", this, true);
}
_updateColorForMouseEvent(event)
{
let point = window.webkitConvertPointFromPageToNode(this._element, new WebKitPoint(event.pageX, event.pageY));
this._setCrosshairPosition(point);
if (this._delegate && this._delegate.colorSquareColorDidChange)
this._delegate.colorSquareColorDidChange(this);
}
_setCrosshairPosition(point)
{
this._crosshairPosition = point;
this._x = Number.constrain(Math.round(point.x), 0, this._dimension);
this._y = Number.constrain(Math.round(point.y), 0, this._dimension);
this._crosshairElement.style.setProperty("transform", `translate(${this._x}px, ${this._y}px)`);
this._updateCrosshairBackground();
}
_updateBaseColor()
{
if (this._gamut === WI.Color.Gamut.DisplayP3) {
let [r, g, b] = WI.Color.hsl2rgb(this._hue, 100, 50);
this._element.style.backgroundColor = `color(display-p3 ${r / 255} ${g / 255} ${b / 255})`;
} else
this._element.style.backgroundColor = `hsl(${this._hue}, 100%, 50%)`;
this._updateCrosshairBackground();
}
_updateCrosshairBackground()
{
this._crosshairElement.style.backgroundColor = this.tintedColor.toString();
}
};