diff options
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/BezierEditor.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/BezierEditor.js | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/BezierEditor.js b/Source/WebInspectorUI/UserInterface/Views/BezierEditor.js new file mode 100644 index 000000000..a07c19d6f --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/BezierEditor.js @@ -0,0 +1,390 @@ +/* + * Copyright (C) 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. + * + * 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. + */ + +WebInspector.BezierEditor = class BezierEditor extends WebInspector.Object +{ + constructor() + { + super(); + + this._element = document.createElement("div"); + this._element.classList.add("bezier-editor"); + + var editorWidth = 184; + var editorHeight = 200; + this._padding = 25; + this._controlHandleRadius = 7; + this._bezierWidth = editorWidth - (this._controlHandleRadius * 2); + this._bezierHeight = editorHeight - (this._controlHandleRadius * 2) - (this._padding * 2); + + this._bezierPreviewContainer = this._element.createChild("div", "bezier-preview"); + this._bezierPreviewContainer.title = WebInspector.UIString("Restart animation"); + this._bezierPreviewContainer.addEventListener("mousedown", this._resetPreviewAnimation.bind(this)); + + this._bezierPreview = this._bezierPreviewContainer.createChild("div"); + + this._bezierPreviewTiming = this._element.createChild("div", "bezier-preview-timing"); + + this._bezierContainer = this._element.appendChild(createSVGElement("svg")); + this._bezierContainer.setAttribute("width", editorWidth); + this._bezierContainer.setAttribute("height", editorHeight); + this._bezierContainer.classList.add("bezier-container"); + + let svgGroup = this._bezierContainer.appendChild(createSVGElement("g")); + svgGroup.setAttribute("transform", "translate(0, " + this._padding + ")"); + + let linearCurve = svgGroup.appendChild(createSVGElement("line")); + linearCurve.classList.add("linear-curve"); + linearCurve.setAttribute("x1", this._controlHandleRadius); + linearCurve.setAttribute("y1", this._bezierHeight + this._controlHandleRadius); + linearCurve.setAttribute("x2", this._bezierWidth + this._controlHandleRadius); + linearCurve.setAttribute("y2", this._controlHandleRadius); + + this._bezierCurve = svgGroup.appendChild(createSVGElement("path")); + this._bezierCurve.classList.add("bezier-curve"); + + function createControl(x1, y1) + { + x1 += this._controlHandleRadius; + y1 += this._controlHandleRadius; + + let line = svgGroup.appendChild(createSVGElement("line")); + line.classList.add("control-line"); + line.setAttribute("x1", x1); + line.setAttribute("y1", y1); + line.setAttribute("x2", x1); + line.setAttribute("y2", y1); + + let handle = svgGroup.appendChild(createSVGElement("circle")); + handle.classList.add("control-handle"); + + return {point: null, line, handle}; + } + + this._inControl = createControl.call(this, 0, this._bezierHeight); + this._outControl = createControl.call(this, this._bezierWidth, 0); + + this._numberInputContainer = this._element.createChild("div", "number-input-container"); + + function createBezierInput(id, {min, max} = {}) + { + let key = "_bezier" + id + "Input"; + this[key] = this._numberInputContainer.createChild("input"); + this[key].type = "number"; + this[key].step = 0.01; + + if (!isNaN(min)) + this[key].min = min; + + if (!isNaN(max)) + this[key].max = max; + + this[key].addEventListener("input", this.debounce(250)._handleNumberInputInput); + this[key].addEventListener("keydown", this._handleNumberInputKeydown.bind(this)); + } + + createBezierInput.call(this, "InX", {min: 0, max: 1}); + createBezierInput.call(this, "InY"); + createBezierInput.call(this, "OutX", {min: 0, max: 1}); + createBezierInput.call(this, "OutY"); + + this._selectedControl = null; + this._mouseDownPosition = null; + this._bezierContainer.addEventListener("mousedown", this); + + WebInspector.addWindowKeydownListener(this); + } + + // Public + + get element() + { + return this._element; + } + + set bezier(bezier) + { + if (!bezier) + return; + + var isCubicBezier = bezier instanceof WebInspector.CubicBezier; + console.assert(isCubicBezier); + if (!isCubicBezier) + return; + + this._bezier = bezier; + this._updateBezierPreview(); + } + + get bezier() + { + return this._bezier; + } + + removeListeners() + { + WebInspector.removeWindowKeydownListener(this); + } + + // Protected + + handleEvent(event) + { + switch (event.type) { + case "mousedown": + this._handleMousedown(event); + break; + case "mousemove": + this._handleMousemove(event); + break; + case "mouseup": + this._handleMouseup(event); + break; + } + } + + handleKeydownEvent(event) + { + if (!this._selectedControl || !this._element.parentNode) + return false; + + let horizontal = 0; + let vertical = 0; + switch (event.keyCode) { + case WebInspector.KeyboardShortcut.Key.Up.keyCode: + vertical = -1; + break; + case WebInspector.KeyboardShortcut.Key.Right.keyCode: + horizontal = 1; + break; + case WebInspector.KeyboardShortcut.Key.Down.keyCode: + vertical = 1; + break; + case WebInspector.KeyboardShortcut.Key.Left.keyCode: + horizontal = -1; + break; + default: + return false; + } + + if (event.shiftKey) { + horizontal *= 10; + vertical *= 10; + } + + vertical *= this._bezierWidth / 100; + horizontal *= this._bezierHeight / 100; + + this._selectedControl.point.x = Number.constrain(this._selectedControl.point.x + horizontal, 0, this._bezierWidth); + this._selectedControl.point.y += vertical; + this._updateControl(this._selectedControl); + this._updateValue(); + + return true; + } + + // Private + + _handleMousedown(event) + { + if (event.button !== 0) + return; + + window.addEventListener("mousemove", this, true); + window.addEventListener("mouseup", this, true); + + this._bezierPreviewContainer.classList.remove("animate"); + this._bezierPreviewTiming.classList.remove("animate"); + + this._updateControlPointsForMouseEvent(event, true); + } + + _handleMousemove(event) + { + this._updateControlPointsForMouseEvent(event); + } + + _handleMouseup(event) + { + this._selectedControl.handle.classList.remove("selected"); + this._mouseDownPosition = null; + this._triggerPreviewAnimation(); + + window.removeEventListener("mousemove", this, true); + window.removeEventListener("mouseup", this, true); + } + + _updateControlPointsForMouseEvent(event, calculateSelectedControlPoint) + { + var point = WebInspector.Point.fromEventInElement(event, this._bezierContainer); + point.x = Number.constrain(point.x - this._controlHandleRadius, 0, this._bezierWidth); + point.y -= this._controlHandleRadius + this._padding; + + if (calculateSelectedControlPoint) { + this._mouseDownPosition = point; + + if (this._inControl.point.distance(point) < this._outControl.point.distance(point)) + this._selectedControl = this._inControl; + else + this._selectedControl = this._outControl; + } + + if (event.shiftKey && this._mouseDownPosition) { + if (Math.abs(this._mouseDownPosition.x - point.x) > Math.abs(this._mouseDownPosition.y - point.y)) + point.y = this._mouseDownPosition.y; + else + point.x = this._mouseDownPosition.x; + } + + this._selectedControl.point = point; + this._selectedControl.handle.classList.add("selected"); + this._updateValue(); + } + + _updateValue() + { + function round(num) + { + return Math.round(num * 100) / 100; + } + + var inValueX = round(this._inControl.point.x / this._bezierWidth); + var inValueY = round(1 - (this._inControl.point.y / this._bezierHeight)); + + var outValueX = round(this._outControl.point.x / this._bezierWidth); + var outValueY = round(1 - (this._outControl.point.y / this._bezierHeight)); + + this._bezier = new WebInspector.CubicBezier(inValueX, inValueY, outValueX, outValueY); + this._updateBezier(); + + this.dispatchEventToListeners(WebInspector.BezierEditor.Event.BezierChanged, {bezier: this._bezier}); + } + + _updateBezier() + { + var r = this._controlHandleRadius; + var inControlX = this._inControl.point.x + r; + var inControlY = this._inControl.point.y + r; + var outControlX = this._outControl.point.x + r; + var outControlY = this._outControl.point.y + r; + var path = `M ${r} ${this._bezierHeight + r} C ${inControlX} ${inControlY} ${outControlX} ${outControlY} ${this._bezierWidth + r} ${r}`; + this._bezierCurve.setAttribute("d", path); + this._updateControl(this._inControl); + this._updateControl(this._outControl); + + this._bezierInXInput.value = this._bezier.inPoint.x; + this._bezierInYInput.value = this._bezier.inPoint.y; + this._bezierOutXInput.value = this._bezier.outPoint.x; + this._bezierOutYInput.value = this._bezier.outPoint.y; + } + + _updateControl(control) + { + control.handle.setAttribute("cx", control.point.x + this._controlHandleRadius); + control.handle.setAttribute("cy", control.point.y + this._controlHandleRadius); + + control.line.setAttribute("x2", control.point.x + this._controlHandleRadius); + control.line.setAttribute("y2", control.point.y + this._controlHandleRadius); + } + + _updateBezierPreview() + { + this._inControl.point = new WebInspector.Point(this._bezier.inPoint.x * this._bezierWidth, (1 - this._bezier.inPoint.y) * this._bezierHeight); + this._outControl.point = new WebInspector.Point(this._bezier.outPoint.x * this._bezierWidth, (1 - this._bezier.outPoint.y) * this._bezierHeight); + + this._updateBezier(); + this._triggerPreviewAnimation(); + } + + _triggerPreviewAnimation() + { + this._bezierPreview.style.animationTimingFunction = this._bezier.toString(); + this._bezierPreviewContainer.classList.add("animate"); + this._bezierPreviewTiming.classList.add("animate"); + } + + _resetPreviewAnimation() + { + var parent = this._bezierPreview.parentNode; + parent.removeChild(this._bezierPreview); + parent.appendChild(this._bezierPreview); + + this._element.removeChild(this._bezierPreviewTiming); + this._element.appendChild(this._bezierPreviewTiming); + } + + _handleNumberInputInput(event) + { + this._changeBezierForInput(event.target, event.target.value); + } + + _handleNumberInputKeydown(event) + { + let shift = 0; + if (event.keyIdentifier === "Up") + shift = 0.01; + else if (event.keyIdentifier === "Down") + shift = -0.01; + + if (!shift) + return; + + if (event.shiftKey) + shift *= 10; + + event.preventDefault(); + this._changeBezierForInput(event.target, parseFloat(event.target.value) + shift); + } + + _changeBezierForInput(target, value) + { + value = Math.round(value * 100) / 100; + + switch (target) { + case this._bezierInXInput: + this._bezier.inPoint.x = Number.constrain(value, 0, 1); + break; + case this._bezierInYInput: + this._bezier.inPoint.y = value; + break; + case this._bezierOutXInput: + this._bezier.outPoint.x = Number.constrain(value, 0, 1); + break; + case this._bezierOutYInput: + this._bezier.outPoint.y = value; + break; + default: + return; + } + + this._updateBezierPreview(); + + this.dispatchEventToListeners(WebInspector.BezierEditor.Event.BezierChanged, {bezier: this._bezier}); + } +}; + +WebInspector.BezierEditor.Event = { + BezierChanged: "bezier-editor-bezier-changed" +}; |