blob: 32aefb736af42dd01ad87827b6175cc175d6e1c0 [file] [log] [blame]
/*
* Copyright (C) 2014-2015 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.
* 3. Neither the name of Apple Inc. ("Apple") 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 APPLE 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 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.GradientSlider = class GradientSlider extends WI.Object
{
constructor(delegate)
{
super();
this.delegate = delegate;
this._element = null;
this._stops = [];
this._knobs = [];
this._selectedKnob = null;
this._canvas = document.createElement("canvas");
this._keyboardShortcutEsc = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Escape);
}
// Public
get element()
{
if (!this._element) {
this._element = document.createElement("div");
this._element.className = "gradient-slider";
this._element.appendChild(this._canvas);
this._addArea = this._element.appendChild(document.createElement("div"));
this._addArea.addEventListener("mouseover", this);
this._addArea.addEventListener("mousemove", this);
this._addArea.addEventListener("mouseout", this);
this._addArea.addEventListener("click", this);
this._addArea.className = WI.GradientSlider.AddAreaClassName;
}
return this._element;
}
get stops()
{
return this._stops;
}
set stops(stops)
{
this._stops = stops;
this._updateStops();
}
get selectedStop()
{
return this._selectedKnob ? this._selectedKnob.stop : null;
}
// Protected
handleEvent(event)
{
switch (event.type) {
case "mouseover":
this._handleMouseover(event);
break;
case "mousemove":
this._handleMousemove(event);
break;
case "mouseout":
this._handleMouseout(event);
break;
case "click":
this._handleClick(event);
break;
}
}
handleKeydownEvent(event)
{
if (!this._keyboardShortcutEsc.matchesEvent(event) || !this._selectedKnob || !this._selectedKnob.selected)
return false;
this._selectedKnob.selected = false;
return true;
}
knobXDidChange(knob)
{
knob.stop.offset = knob.x / WI.GradientSlider.Width;
this._sortStops();
this._updateCanvas();
}
knobCanDetach(knob)
{
return this._knobs.length > 2;
}
knobWillDetach(knob)
{
knob.element.classList.add(WI.GradientSlider.DetachingClassName);
this._stops.remove(knob.stop);
this._knobs.remove(knob);
this._sortStops();
this._updateCanvas();
}
knobSelectionChanged(knob)
{
if (this._selectedKnob && this._selectedKnob !== knob && knob.selected)
this._selectedKnob.selected = false;
this._selectedKnob = knob.selected ? knob : null;
if (this.delegate && typeof this.delegate.gradientSliderStopWasSelected === "function")
this.delegate.gradientSliderStopWasSelected(this, knob.stop);
if (this._selectedKnob)
WI.addWindowKeydownListener(this);
else
WI.removeWindowKeydownListener(this);
}
// Private
_handleMouseover(event)
{
this._updateShadowKnob(event);
}
_handleMousemove(event)
{
this._updateShadowKnob(event);
}
_handleMouseout(event)
{
if (!this._shadowKnob)
return;
this._shadowKnob.element.remove();
delete this._shadowKnob;
}
_handleClick(event)
{
this._updateShadowKnob(event);
this._knobs.push(this._shadowKnob);
this._shadowKnob.element.classList.remove(WI.GradientSlider.ShadowClassName);
var stop = {offset: this._shadowKnob.x / WI.GradientSlider.Width, color: this._shadowKnob.wellColor};
this._stops.push(stop);
this._sortStops();
this._updateStops();
this._knobs[this._stops.indexOf(stop)].selected = true;
delete this._shadowKnob;
}
_updateShadowKnob(event)
{
if (!this._shadowKnob) {
this._shadowKnob = new WI.GradientSliderKnob(this);
this._shadowKnob.element.classList.add(WI.GradientSlider.ShadowClassName);
this.element.appendChild(this._shadowKnob.element);
}
this._shadowKnob.x = window.webkitConvertPointFromPageToNode(this.element, new WebKitPoint(event.pageX, event.pageY)).x;
var colorData = this._canvas.getContext("2d").getImageData(this._shadowKnob.x - 1, 0, 1, 1).data;
this._shadowKnob.wellColor = new WI.Color(WI.Color.Format.RGB, [colorData[0], colorData[1], colorData[2], colorData[3] / 255]);
}
_sortStops()
{
this._stops.sort(function(a, b) {
return a.offset - b.offset;
});
}
_updateStops()
{
this._updateCanvas();
this._updateKnobs();
}
_updateCanvas()
{
var w = WI.GradientSlider.Width;
var h = WI.GradientSlider.Height;
this._canvas.width = w;
this._canvas.height = h;
var ctx = this._canvas.getContext("2d");
var gradient = ctx.createLinearGradient(0, 0, w, 0);
for (var stop of this._stops)
gradient.addColorStop(stop.offset, stop.color);
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
if (this.delegate && typeof this.delegate.gradientSliderStopsDidChange === "function")
this.delegate.gradientSliderStopsDidChange(this);
}
_updateKnobs()
{
var selectedStop = this._selectedKnob ? this._selectedKnob.stop : null;
while (this._knobs.length > this._stops.length)
this._knobs.pop().element.remove();
while (this._knobs.length < this._stops.length) {
var knob = new WI.GradientSliderKnob(this);
this.element.appendChild(knob.element);
this._knobs.push(knob);
}
for (var i = 0; i < this._stops.length; ++i) {
var stop = this._stops[i];
var knob = this._knobs[i];
knob.stop = stop;
knob.x = Math.round(stop.offset * WI.GradientSlider.Width);
knob.selected = stop === selectedStop;
}
}
};
WI.GradientSlider.Width = 238;
WI.GradientSlider.Height = 19;
WI.GradientSlider.AddAreaClassName = "add-area";
WI.GradientSlider.DetachingClassName = "detaching";
WI.GradientSlider.ShadowClassName = "shadow";
WI.GradientSliderKnob = class GradientSliderKnob extends WI.Object
{
constructor(delegate)
{
super();
this._x = 0;
this._y = 0;
this._stop = null;
this.delegate = delegate;
this._element = document.createElement("div");
this._element.className = "gradient-slider-knob";
// Checkers pattern.
this._element.appendChild(document.createElement("img"));
this._well = this._element.appendChild(document.createElement("div"));
this._element.addEventListener("mousedown", this);
}
// Public
get element()
{
return this._element;
}
get stop()
{
return this._stop;
}
set stop(stop)
{
this.wellColor = stop.color;
this._stop = stop;
}
get x()
{
return this._x;
}
set x(x) {
this._x = x;
this._updateTransform();
}
get y()
{
return this._x;
}
set y(y) {
this._y = y;
this._updateTransform();
}
get wellColor()
{
return this._wellColor;
}
set wellColor(color)
{
this._wellColor = color;
this._well.style.backgroundColor = color;
}
get selected()
{
return this._element.classList.contains(WI.GradientSliderKnob.SelectedClassName);
}
set selected(selected)
{
if (this.selected === selected)
return;
this._element.classList.toggle(WI.GradientSliderKnob.SelectedClassName, selected);
if (this.delegate && typeof this.delegate.knobSelectionChanged === "function")
this.delegate.knobSelectionChanged(this);
}
// Protected
handleEvent(event)
{
event.preventDefault();
event.stopPropagation();
switch (event.type) {
case "mousedown":
this._handleMousedown(event);
break;
case "mousemove":
this._handleMousemove(event);
break;
case "mouseup":
this._handleMouseup(event);
break;
case "transitionend":
this._handleTransitionEnd(event);
break;
}
}
// Private
_handleMousedown(event)
{
this._moved = false;
this._detaching = false;
window.addEventListener("mousemove", this, true);
window.addEventListener("mouseup", this, true);
this._startX = this.x;
this._startMouseX = event.pageX;
this._startMouseY = event.pageY;
}
_handleMousemove(event)
{
var w = WI.GradientSlider.Width;
this._moved = true;
if (!this._detaching && Math.abs(event.pageY - this._startMouseY) > 50) {
this._detaching = this.delegate && typeof this.delegate.knobCanDetach === "function" && this.delegate.knobCanDetach(this);
if (this._detaching && this.delegate && typeof this.delegate.knobWillDetach === "function") {
var translationFromParentToBody = window.webkitConvertPointFromNodeToPage(this.element.parentNode, new WebKitPoint(0, 0));
this._startMouseX -= translationFromParentToBody.x;
this._startMouseY -= translationFromParentToBody.y;
document.body.appendChild(this.element);
this.delegate.knobWillDetach(this);
}
}
var x = this._startX + event.pageX - this._startMouseX;
if (!this._detaching)
x = Math.min(Math.max(0, x), w);
this.x = x;
if (this._detaching)
this.y = event.pageY - this._startMouseY;
else if (this.delegate && typeof this.delegate.knobXDidChange === "function")
this.delegate.knobXDidChange(this);
}
_handleMouseup(event)
{
window.removeEventListener("mousemove", this, true);
window.removeEventListener("mouseup", this, true);
if (this._detaching) {
this.element.addEventListener("transitionend", this);
this.element.classList.add(WI.GradientSliderKnob.FadeOutClassName);
this.selected = false;
} else if (!this._moved)
this.selected = !this.selected;
}
_handleTransitionEnd(event)
{
this.element.removeEventListener("transitionend", this);
this.element.classList.remove(WI.GradientSliderKnob.FadeOutClassName);
this.element.remove();
}
_updateTransform()
{
this.element.style.webkitTransform = "translate3d(" + this._x + "px, " + this._y + "px, 0)";
}
};
WI.GradientSliderKnob.SelectedClassName = "selected";
WI.GradientSliderKnob.FadeOutClassName = "fade-out";