1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 * @extends {WebInspector.VBox}
9 WebInspector
.BezierEditor = function()
11 WebInspector
.VBox
.call(this, true);
12 this.registerRequiredCSS("elements/bezierEditor.css");
13 this.contentElement
.tabIndex
= 0;
16 this._previewElement
= this.contentElement
.createChild("div", "bezier-preview-container");
17 this._previewElement
.createChild("div", "bezier-preview-animation");
18 this._previewElement
.addEventListener("click", this._startPreviewAnimation
.bind(this));
19 this._previewOnion
= this.contentElement
.createChild("div", "bezier-preview-onion");
20 this._previewOnion
.addEventListener("click", this._startPreviewAnimation
.bind(this));
22 this._outerContainer
= this.contentElement
.createChild("div", "bezier-container");
25 this._presetsContainer
= this._outerContainer
.createChild("div", "bezier-presets");
26 this._presetUI
= new WebInspector
.BezierUI(40, 40, 0, 2, false);
27 this._presetCategories
= [];
28 for (var i
= 0; i
< WebInspector
.BezierEditor
.Presets
.length
; i
++) {
29 this._presetCategories
[i
] = this._createCategory(WebInspector
.BezierEditor
.Presets
[i
]);
30 this._presetsContainer
.appendChild(this._presetCategories
[i
].icon
);
34 this._curveUI
= new WebInspector
.BezierUI(150, 250, 50, 7, true);
35 this._curve
= this._outerContainer
.createSVGChild("svg", "bezier-curve");
36 WebInspector
.installDragHandle(this._curve
, this._dragStart
.bind(this), this._dragMove
.bind(this), this._dragEnd
.bind(this), "default");
38 this._header
= this.contentElement
.createChild("div", "bezier-header");
39 var minus
= this._createPresetModifyIcon(this._header
, "bezier-preset-minus", "M 12 6 L 8 10 L 12 14");
40 var plus
= this._createPresetModifyIcon(this._header
, "bezier-preset-plus", "M 8 6 L 12 10 L 8 14");
41 minus
.addEventListener("click", this._presetModifyClicked
.bind(this, false));
42 plus
.addEventListener("click", this._presetModifyClicked
.bind(this, true));
43 this._label
= this._header
.createChild("span", "source-code bezier-display-value");
46 WebInspector
.BezierEditor
.Events
= {
47 BezierChanged
: "BezierChanged"
50 WebInspector
.BezierEditor
.Presets
= [
52 { name
: "ease-in-out", value
: "ease-in-out" },
53 { name
: "In Out · Sine", value
: "cubic-bezier(0.45, 0.05, 0.55, 0.95)" },
54 { name
: "In Out · Quadratic", value
: "cubic-bezier(0.46, 0.03, 0.52, 0.96)" },
55 { name
: "In Out · Cubic", value
: "cubic-bezier(0.65, 0.05, 0.36, 1)" },
56 { name
: "Fast Out, Slow In", value
: "cubic-bezier(0.4, 0, 0.2, 1)" },
57 { name
: "In Out · Back", value
: "cubic-bezier(0.68, -0.55, 0.27, 1.55)" }
60 { name
: "Fast Out, Linear In", value
: "cubic-bezier(0.4, 0, 1, 1)" },
61 { name
: "ease-in", value
: "ease-in" },
62 { name
: "In · Sine", value
: "cubic-bezier(0.47, 0, 0.75, 0.72)" },
63 { name
: "In · Quadratic", value
: "cubic-bezier(0.55, 0.09, 0.68, 0.53)" },
64 { name
: "In · Cubic", value
: "cubic-bezier(0.55, 0.06, 0.68, 0.19)" },
65 { name
: "In · Back", value
: "cubic-bezier(0.6, -0.28, 0.74, 0.05)" }
68 { name
: "ease-out", value
: "ease-out" },
69 { name
: "Out · Sine", value
: "cubic-bezier(0.39, 0.58, 0.57, 1)" },
70 { name
: "Out · Quadratic", value
: "cubic-bezier(0.25, 0.46, 0.45, 0.94)" },
71 { name
: "Out · Cubic", value
: "cubic-bezier(0.22, 0.61, 0.36, 1)" },
72 { name
: "Linear Out, Slow In", value
: "cubic-bezier(0, 0, 0.2, 1)" },
73 { name
: "Out · Back", value
: "cubic-bezier(0.18, 0.89, 0.32, 1.28)" }
77 /** @typedef {{presets: !Array.<{name: string, value: string}>, icon: !Element, presetIndex: number}} */
78 WebInspector
.BezierEditor
.PresetCategory
;
80 WebInspector
.BezierEditor
.prototype = {
82 * @param {?WebInspector.Geometry.CubicBezier} bezier
84 setBezier: function(bezier
)
88 this._bezier
= bezier
;
93 * @return {!WebInspector.Geometry.CubicBezier}
102 this._unselectPresets();
103 // Check if bezier matches a preset
104 for (var category
of this._presetCategories
) {
105 for (var i
= 0; i
< category
.presets
.length
; i
++) {
106 if (this._bezier
.asCSSText() === category
.presets
[i
].value
) {
107 category
.presetIndex
= i
;
108 this._presetCategorySelected(category
);
114 this._startPreviewAnimation();
117 _onchange: function()
120 this.dispatchEventToListeners(WebInspector
.BezierEditor
.Events
.BezierChanged
, this._bezier
.asCSSText());
123 _updateUI: function()
125 var labelText
= this._selectedCategory
? this._selectedCategory
.presets
[this._selectedCategory
.presetIndex
].name
: this._bezier
.asCSSText().replace(/\s(-\d\.\d)/g, "$1");
126 this._label
.textContent
= WebInspector
.UIString(labelText
);
127 this._curveUI
.drawCurve(this._bezier
, this._curve
);
128 this._previewOnion
.removeChildren();
132 * @param {!Event} event
135 _dragStart: function(event
)
137 this._mouseDownPosition
= new WebInspector
.Geometry
.Point(event
.x
, event
.y
);
138 var ui
= this._curveUI
;
139 this._controlPosition
= new WebInspector
.Geometry
.Point(
140 Number
.constrain((event
.offsetX
- ui
.radius
) / ui
.curveWidth(), 0, 1),
141 (ui
.curveHeight() + ui
.marginTop
+ ui
.radius
- event
.offsetY
) / ui
.curveHeight());
143 var firstControlPointIsCloser
= this._controlPosition
.distanceTo(this._bezier
.controlPoints
[0]) < this._controlPosition
.distanceTo(this._bezier
.controlPoints
[1]);
144 this._selectedPoint
= firstControlPointIsCloser
? 0 : 1;
146 this._bezier
.controlPoints
[this._selectedPoint
] = this._controlPosition
;
147 this._unselectPresets();
155 * @param {number} mouseX
156 * @param {number} mouseY
158 _updateControlPosition: function(mouseX
, mouseY
)
160 var deltaX
= (mouseX
- this._mouseDownPosition
.x
) / this._curveUI
.curveWidth();
161 var deltaY
= (mouseY
- this._mouseDownPosition
.y
) / this._curveUI
.curveHeight();
162 var newPosition
= new WebInspector
.Geometry
.Point(Number
.constrain(this._controlPosition
.x
+ deltaX
, 0, 1), this._controlPosition
.y
- deltaY
);
163 this._bezier
.controlPoints
[this._selectedPoint
] = newPosition
;
167 * @param {!Event} event
169 _dragMove: function(event
)
171 this._updateControlPosition(event
.x
, event
.y
);
176 * @param {!Event} event
178 _dragEnd: function(event
)
180 this._updateControlPosition(event
.x
, event
.y
);
182 this._startPreviewAnimation();
186 * @param {!Array<{name: string, value: string}>} presetGroup
187 * @return {!WebInspector.BezierEditor.PresetCategory}
189 _createCategory: function(presetGroup
)
191 var presetElement
= createElementWithClass("div", "bezier-preset-category");
192 var iconElement
= presetElement
.createSVGChild("svg", "bezier-preset monospace");
193 var category
= { presets
: presetGroup
, presetIndex
: 0, icon
: presetElement
};
194 this._presetUI
.drawCurve(WebInspector
.Geometry
.CubicBezier
.parse(category
.presets
[0].value
), iconElement
);
195 iconElement
.addEventListener("click", this._presetCategorySelected
.bind(this, category
));
200 * @param {!Element} parentElement
201 * @param {string} className
202 * @param {string} drawPath
205 _createPresetModifyIcon: function (parentElement
, className
, drawPath
)
207 var icon
= parentElement
.createSVGChild("svg", "bezier-preset-modify " + className
);
208 icon
.setAttribute("width", 20);
209 icon
.setAttribute("height", 20);
210 var path
= icon
.createSVGChild("path");
211 path
.setAttribute("d", drawPath
);
215 _unselectPresets: function()
217 for (var category
of this._presetCategories
)
218 category
.icon
.classList
.remove("bezier-preset-selected");
219 delete this._selectedCategory
;
220 this._header
.classList
.remove("bezier-header-active");
224 * @param {!WebInspector.BezierEditor.PresetCategory} category
225 * @param {!Event=} event
227 _presetCategorySelected: function(category
, event
)
229 if (this._selectedCategory
=== category
)
231 this._unselectPresets();
232 this._header
.classList
.add("bezier-header-active");
233 this._selectedCategory
= category
;
234 this._selectedCategory
.icon
.classList
.add("bezier-preset-selected");
235 this.setBezier(WebInspector
.Geometry
.CubicBezier
.parse(category
.presets
[category
.presetIndex
].value
));
237 this._startPreviewAnimation();
243 * @param {boolean} intensify
244 * @param {!Event} event
246 _presetModifyClicked: function(intensify
, event
)
248 if (!this._selectedCategory
)
251 var length
= this._selectedCategory
.presets
.length
;
252 this._selectedCategory
.presetIndex
= (this._selectedCategory
.presetIndex
+ (intensify
? 1 : -1) + length
) % length
;
253 this.setBezier(WebInspector
.Geometry
.CubicBezier
.parse(this._selectedCategory
.presets
[this._selectedCategory
.presetIndex
].value
));
255 this._startPreviewAnimation();
258 _startPreviewAnimation: function()
260 if (this._previewAnimation
)
261 this._previewAnimation
.cancel();
263 const animationDuration
= 1600;
264 const numberOnionSlices
= 20;
266 var keyframes
= [{ offset
: 0, transform
: "translateX(0px)", easing
: this._bezier
.asCSSText(), opacity
: 1 },
267 { offset
: 0.9, transform
: "translateX(218px)", opacity
: 1 },
268 { offset
: 1, transform
: "translateX(218px)", opacity
: 0 }];
269 this._previewAnimation
= this._previewElement
.animate(keyframes
, animationDuration
);
270 this._previewOnion
.removeChildren();
271 for (var i
= 0; i
<= numberOnionSlices
; i
++) {
272 var slice
= this._previewOnion
.createChild("div", "bezier-preview-animation");
273 var player
= slice
.animate([{ transform
: "translateX(0px)", easing
: this._bezier
.asCSSText() }, { transform
: "translateX(218px)" }],
274 { duration
: animationDuration
, fill
: "forwards" });
276 player
.currentTime
= animationDuration
* i
/ numberOnionSlices
;
280 __proto__
: WebInspector
.VBox
.prototype