OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. | |
pfeldman
2015/09/19 00:17:40
Could you split this into the move and refactoring
| |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 /** | |
6 * @constructor | |
7 * @param {!WebInspector.AnimationModel.Animation} animation | |
8 * @param {!WebInspector.AnimationTimeline} timeline | |
9 * @param {!Element} parentElement | |
10 */ | |
11 WebInspector.AnimationUI = function(animation, timeline, parentElement) { | |
12 this._animation = animation; | |
13 this._timeline = timeline; | |
14 this._parentElement = parentElement; | |
15 | |
16 if (this._animation.source().keyframesRule()) | |
17 this._keyframes = this._animation.source().keyframesRule().keyframes(); | |
18 | |
19 this._nameElement = parentElement.createChild("div", "animation-name"); | |
20 this._nameElement.textContent = this._animation.name(); | |
21 | |
22 this._svg = parentElement.createSVGChild("svg", "animation-ui"); | |
23 this._svg.setAttribute("height", WebInspector.AnimationUI.Options.AnimationS VGHeight); | |
24 this._svg.style.marginLeft = "-" + WebInspector.AnimationUI.Options.Animatio nMargin + "px"; | |
25 this._svg.addEventListener("mousedown", this._mouseDown.bind(this, WebInspec tor.AnimationUI.MouseEvents.AnimationDrag, null)); | |
26 this._activeIntervalGroup = this._svg.createSVGChild("g"); | |
27 | |
28 /** @type {!Array.<{group: ?Element, animationLine: ?Element, keyframePoints : !Object.<number, !Element>, keyframeRender: !Object.<number, !Element>}>} */ | |
29 this._cachedElements = []; | |
30 | |
31 this._movementInMs = 0; | |
32 this._color = WebInspector.AnimationUI.Color(this._animation); | |
33 } | |
34 | |
35 /** | |
36 * @enum {string} | |
37 */ | |
38 WebInspector.AnimationUI.MouseEvents = { | |
39 AnimationDrag: "AnimationDrag", | |
40 KeyframeMove: "KeyframeMove", | |
41 StartEndpointMove: "StartEndpointMove", | |
42 FinishEndpointMove: "FinishEndpointMove" | |
43 } | |
44 | |
45 WebInspector.AnimationUI.prototype = { | |
46 /** | |
47 * @return {!WebInspector.AnimationModel.Animation} | |
48 */ | |
49 animation: function() | |
50 { | |
51 return this._animation; | |
52 }, | |
53 | |
54 /** | |
55 * @param {?WebInspector.DOMNode} node | |
56 */ | |
57 setNode: function(node) | |
58 { | |
59 this._node = node; | |
60 }, | |
61 | |
62 /** | |
63 * @param {!Element} parentElement | |
64 * @param {string} className | |
65 */ | |
66 _createLine: function(parentElement, className) | |
67 { | |
68 var line = parentElement.createSVGChild("line", className); | |
69 line.setAttribute("x1", WebInspector.AnimationUI.Options.AnimationMargin ); | |
70 line.setAttribute("y1", WebInspector.AnimationUI.Options.AnimationHeight ); | |
71 line.setAttribute("y2", WebInspector.AnimationUI.Options.AnimationHeight ); | |
72 line.style.stroke = this._color; | |
73 return line; | |
74 }, | |
75 | |
76 /** | |
77 * @param {number} iteration | |
78 * @param {!Element} parentElement | |
79 */ | |
80 _drawAnimationLine: function(iteration, parentElement) | |
81 { | |
82 var cache = this._cachedElements[iteration]; | |
83 if (!cache.animationLine) | |
84 cache.animationLine = this._createLine(parentElement, "animation-lin e"); | |
85 cache.animationLine.setAttribute("x2", (this._duration() * this._timelin e.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin).toFixed(2)) ; | |
86 }, | |
87 | |
88 /** | |
89 * @param {!Element} parentElement | |
90 */ | |
91 _drawDelayLine: function(parentElement) | |
92 { | |
93 if (!this._delayLine) { | |
94 this._delayLine = this._createLine(parentElement, "animation-delay-l ine"); | |
95 this._endDelayLine = this._createLine(parentElement, "animation-dela y-line"); | |
96 } | |
97 this._delayLine.setAttribute("x1", WebInspector.AnimationUI.Options.Anim ationMargin); | |
98 this._delayLine.setAttribute("x2", (this._delay() * this._timeline.pixel MsRatio() + WebInspector.AnimationUI.Options.AnimationMargin).toFixed(2)); | |
99 var leftMargin = (this._delay() + this._duration() * this._animation.sou rce().iterations()) * this._timeline.pixelMsRatio(); | |
100 this._endDelayLine.style.transform = "translateX(" + Math.min(leftMargin , this._timeline.width()).toFixed(2) + "px)"; | |
101 this._endDelayLine.setAttribute("x1", WebInspector.AnimationUI.Options.A nimationMargin); | |
102 this._endDelayLine.setAttribute("x2", (this._animation.source().endDelay () * this._timeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationM argin).toFixed(2)); | |
103 }, | |
104 | |
105 /** | |
106 * @param {number} iteration | |
107 * @param {!Element} parentElement | |
108 * @param {number} x | |
109 * @param {number} keyframeIndex | |
110 * @param {boolean} attachEvents | |
111 */ | |
112 _drawPoint: function(iteration, parentElement, x, keyframeIndex, attachEvent s) | |
113 { | |
114 if (this._cachedElements[iteration].keyframePoints[keyframeIndex]) { | |
115 this._cachedElements[iteration].keyframePoints[keyframeIndex].setAtt ribute("cx", x.toFixed(2)); | |
116 return; | |
117 } | |
118 | |
119 var circle = parentElement.createSVGChild("circle", keyframeIndex <= 0 ? "animation-endpoint" : "animation-keyframe-point"); | |
120 circle.setAttribute("cx", x.toFixed(2)); | |
121 circle.setAttribute("cy", WebInspector.AnimationUI.Options.AnimationHeig ht); | |
122 circle.style.stroke = this._color; | |
123 circle.setAttribute("r", WebInspector.AnimationUI.Options.AnimationMargi n / 2); | |
124 | |
125 if (keyframeIndex <= 0) | |
126 circle.style.fill = this._color; | |
127 | |
128 this._cachedElements[iteration].keyframePoints[keyframeIndex] = circle; | |
129 | |
130 if (!attachEvents) | |
131 return; | |
132 | |
133 if (keyframeIndex === 0) { | |
134 circle.addEventListener("mousedown", this._mouseDown.bind(this, WebI nspector.AnimationUI.MouseEvents.StartEndpointMove, keyframeIndex)); | |
135 } else if (keyframeIndex === -1) { | |
136 circle.addEventListener("mousedown", this._mouseDown.bind(this, WebI nspector.AnimationUI.MouseEvents.FinishEndpointMove, keyframeIndex)); | |
137 } else { | |
138 circle.addEventListener("mousedown", this._mouseDown.bind(this, WebI nspector.AnimationUI.MouseEvents.KeyframeMove, keyframeIndex)); | |
139 } | |
140 }, | |
141 | |
142 /** | |
143 * @param {number} iteration | |
144 * @param {number} keyframeIndex | |
145 * @param {!Element} parentElement | |
146 * @param {number} leftDistance | |
147 * @param {number} width | |
148 * @param {string} easing | |
149 */ | |
150 _renderKeyframe: function(iteration, keyframeIndex, parentElement, leftDista nce, width, easing) | |
151 { | |
152 /** | |
153 * @param {!Element} parentElement | |
154 * @param {number} x | |
155 * @param {string} strokeColor | |
156 */ | |
157 function createStepLine(parentElement, x, strokeColor) | |
158 { | |
159 var line = parentElement.createSVGChild("line"); | |
160 line.setAttribute("x1", x); | |
161 line.setAttribute("x2", x); | |
162 line.setAttribute("y1", WebInspector.AnimationUI.Options.AnimationMa rgin); | |
163 line.setAttribute("y2", WebInspector.AnimationUI.Options.AnimationHe ight); | |
164 line.style.stroke = strokeColor; | |
165 } | |
166 | |
167 var bezier = WebInspector.Geometry.CubicBezier.parse(easing); | |
168 var cache = this._cachedElements[iteration].keyframeRender; | |
169 if (!cache[keyframeIndex]) | |
170 cache[keyframeIndex] = bezier ? parentElement.createSVGChild("path", "animation-keyframe") : parentElement.createSVGChild("g", "animation-keyframe-s tep"); | |
171 var group = cache[keyframeIndex]; | |
172 group.style.transform = "translateX(" + leftDistance.toFixed(2) + "px)"; | |
173 | |
174 if (bezier) { | |
175 group.style.fill = this._color; | |
176 WebInspector.BezierUI.drawVelocityChart(bezier, group, width); | |
177 } else { | |
178 var stepFunction = WebInspector.AnimationTimeline.StepTimingFunction .parse(easing); | |
179 group.removeChildren(); | |
180 const offsetMap = {"start": 0, "middle": 0.5, "end": 1}; | |
181 const offsetWeight = offsetMap[stepFunction.stepAtPosition]; | |
182 for (var i = 0; i < stepFunction.steps; i++) | |
183 createStepLine(group, (i + offsetWeight) * width / stepFunction. steps, this._color); | |
184 } | |
185 }, | |
186 | |
187 redraw: function() | |
188 { | |
189 var durationWithDelay = this._delay() + this._duration() * this._animati on.source().iterations() + this._animation.source().endDelay(); | |
190 var leftMargin = ((this._animation.startTime() - this._timeline.startTim e()) * this._timeline.pixelMsRatio()); | |
191 var maxWidth = this._timeline.width() - WebInspector.AnimationUI.Options .AnimationMargin - leftMargin; | |
192 var svgWidth = Math.min(maxWidth, durationWithDelay * this._timeline.pix elMsRatio()); | |
193 | |
194 this._svg.classList.toggle("animation-ui-canceled", this._animation.play State() === "idle"); | |
195 this._svg.setAttribute("width", (svgWidth + 2 * WebInspector.AnimationUI .Options.AnimationMargin).toFixed(2)); | |
196 this._svg.style.transform = "translateX(" + leftMargin.toFixed(2) + "px )"; | |
197 this._activeIntervalGroup.style.transform = "translateX(" + (this._delay () * this._timeline.pixelMsRatio()).toFixed(2) + "px)"; | |
198 | |
199 this._nameElement.style.transform = "translateX(" + (leftMargin + this._ delay() * this._timeline.pixelMsRatio() + WebInspector.AnimationUI.Options.Anima tionMargin).toFixed(2) + "px)"; | |
200 this._nameElement.style.width = (this._duration() * this._timeline.pixel MsRatio().toFixed(2)) + "px"; | |
201 this._drawDelayLine(this._svg); | |
202 | |
203 if (this._animation.type() === "CSSTransition") { | |
204 this._renderTransition(); | |
205 return; | |
206 } | |
207 | |
208 this._renderIteration(this._activeIntervalGroup, 0); | |
209 if (!this._tailGroup) | |
210 this._tailGroup = this._activeIntervalGroup.createSVGChild("g", "ani mation-tail-iterations"); | |
211 var iterationWidth = this._duration() * this._timeline.pixelMsRatio(); | |
212 for (var iteration = 1; iteration < this._animation.source().iterations( ) && iterationWidth * (iteration - 1) < this._timeline.width(); iteration++) | |
213 this._renderIteration(this._tailGroup, iteration); | |
214 while (iteration < this._cachedElements.length) | |
215 this._cachedElements.pop().group.remove(); | |
216 }, | |
217 | |
218 | |
219 _renderTransition: function() | |
220 { | |
221 if (!this._cachedElements[0]) | |
222 this._cachedElements[0] = { animationLine: null, keyframePoints: {}, keyframeRender: {}, group: null }; | |
223 this._drawAnimationLine(0, this._activeIntervalGroup); | |
224 this._renderKeyframe(0, 0, this._activeIntervalGroup, WebInspector.Anima tionUI.Options.AnimationMargin, this._duration() * this._timeline.pixelMsRatio() , this._animation.source().easing()); | |
225 this._drawPoint(0, this._activeIntervalGroup, WebInspector.AnimationUI.O ptions.AnimationMargin, 0, true); | |
226 this._drawPoint(0, this._activeIntervalGroup, this._duration() * this._t imeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin, -1, t rue); | |
227 }, | |
228 | |
229 /** | |
230 * @param {!Element} parentElement | |
231 * @param {number} iteration | |
232 */ | |
233 _renderIteration: function(parentElement, iteration) | |
234 { | |
235 if (!this._cachedElements[iteration]) | |
236 this._cachedElements[iteration] = { animationLine: null, keyframePoi nts: {}, keyframeRender: {}, group: parentElement.createSVGChild("g") }; | |
237 var group = this._cachedElements[iteration].group; | |
238 group.style.transform = "translateX(" + (iteration * this._duration() * this._timeline.pixelMsRatio()).toFixed(2) + "px)"; | |
239 this._drawAnimationLine(iteration, group); | |
240 console.assert(this._keyframes.length > 1); | |
241 for (var i = 0; i < this._keyframes.length - 1; i++) { | |
242 var leftDistance = this._offset(i) * this._duration() * this._timeli ne.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin; | |
243 var width = this._duration() * (this._offset(i + 1) - this._offset(i )) * this._timeline.pixelMsRatio(); | |
244 this._renderKeyframe(iteration, i, group, leftDistance, width, this. _keyframes[i].easing()); | |
245 if (i || (!i && iteration === 0)) | |
246 this._drawPoint(iteration, group, leftDistance, i, iteration === 0); | |
247 } | |
248 this._drawPoint(iteration, group, this._duration() * this._timeline.pixe lMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin, -1, iteration === 0); | |
249 }, | |
250 | |
251 /** | |
252 * @return {number} | |
253 */ | |
254 _delay: function() | |
255 { | |
256 var delay = this._animation.source().delay(); | |
257 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.Animat ionDrag || this._mouseEventType === WebInspector.AnimationUI.MouseEvents.StartEn dpointMove) | |
258 delay += this._movementInMs; | |
259 // FIXME: add support for negative start delay | |
260 return Math.max(0, delay); | |
261 }, | |
262 | |
263 /** | |
264 * @return {number} | |
265 */ | |
266 _duration: function() | |
267 { | |
268 var duration = this._animation.source().duration(); | |
269 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.Finish EndpointMove) | |
270 duration += this._movementInMs; | |
271 else if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.S tartEndpointMove) | |
272 duration -= Math.max(this._movementInMs, -this._animation.source().d elay()); // Cannot have negative delay | |
273 return Math.max(0, duration); | |
274 }, | |
275 | |
276 /** | |
277 * @param {number} i | |
278 * @return {number} offset | |
279 */ | |
280 _offset: function(i) | |
281 { | |
282 var offset = this._keyframes[i].offsetAsNumber(); | |
283 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.Keyfra meMove && i === this._keyframeMoved) { | |
284 console.assert(i > 0 && i < this._keyframes.length - 1, "First and l ast keyframe cannot be moved"); | |
285 offset += this._movementInMs / this._animation.source().duration(); | |
286 offset = Math.max(offset, this._keyframes[i - 1].offsetAsNumber()); | |
287 offset = Math.min(offset, this._keyframes[i + 1].offsetAsNumber()); | |
288 } | |
289 return offset; | |
290 }, | |
291 | |
292 /** | |
293 * @param {!WebInspector.AnimationUI.MouseEvents} mouseEventType | |
294 * @param {?number} keyframeIndex | |
295 * @param {!Event} event | |
296 */ | |
297 _mouseDown: function(mouseEventType, keyframeIndex, event) | |
298 { | |
299 if (this._animation.playState() === "idle") | |
300 return; | |
301 this._mouseEventType = mouseEventType; | |
302 this._keyframeMoved = keyframeIndex; | |
303 this._downMouseX = event.clientX; | |
304 this._mouseMoveHandler = this._mouseMove.bind(this); | |
305 this._mouseUpHandler = this._mouseUp.bind(this); | |
306 this._parentElement.ownerDocument.addEventListener("mousemove", this._mo useMoveHandler); | |
307 this._parentElement.ownerDocument.addEventListener("mouseup", this._mous eUpHandler); | |
308 event.preventDefault(); | |
309 event.stopPropagation(); | |
310 | |
311 if (this._node) | |
312 WebInspector.Revealer.reveal(this._node); | |
313 }, | |
314 | |
315 /** | |
316 * @param {!Event} event | |
317 */ | |
318 _mouseMove: function (event) | |
319 { | |
320 this._movementInMs = (event.clientX - this._downMouseX) / this._timeline .pixelMsRatio(); | |
321 if (this._animation.startTime() + this._delay() + this._duration() - thi s._timeline.startTime() > this._timeline.duration() * 0.8) | |
322 this._timeline.setDuration(this._timeline.duration() * 1.2); | |
323 this.redraw(); | |
324 }, | |
325 | |
326 /** | |
327 * @param {!Event} event | |
328 */ | |
329 _mouseUp: function(event) | |
330 { | |
331 this._movementInMs = (event.clientX - this._downMouseX) / this._timeline .pixelMsRatio(); | |
332 | |
333 // Commit changes | |
334 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.Keyfra meMove) { | |
335 this._keyframes[this._keyframeMoved].setOffset(this._offset(this._ke yframeMoved)); | |
336 } else { | |
337 var delay = this._delay(); | |
338 var duration = this._duration(); | |
339 this._setDelay(delay); | |
340 this._setDuration(duration); | |
341 if (this._animation.type() !== "CSSAnimation") { | |
342 var target = WebInspector.targetManager.mainTarget(); | |
343 if (target) | |
344 target.animationAgent().setTiming(this._animation.id(), dura tion, delay); | |
345 } | |
346 } | |
347 | |
348 this._movementInMs = 0; | |
349 this.redraw(); | |
350 | |
351 this._parentElement.ownerDocument.removeEventListener("mousemove", this. _mouseMoveHandler); | |
352 this._parentElement.ownerDocument.removeEventListener("mouseup", this._m ouseUpHandler); | |
353 delete this._mouseMoveHandler; | |
354 delete this._mouseUpHandler; | |
355 delete this._mouseEventType; | |
356 delete this._downMouseX; | |
357 delete this._keyframeMoved; | |
358 }, | |
359 | |
360 /** | |
361 * @param {number} value | |
362 */ | |
363 _setDelay: function(value) | |
364 { | |
365 if (!this._node || this._animation.source().delay() == this._delay()) | |
366 return; | |
367 | |
368 this._animation.source().setDelay(this._delay()); | |
369 var propertyName; | |
370 if (this._animation.type() == "CSSTransition") | |
371 propertyName = "transition-delay"; | |
372 else if (this._animation.type() == "CSSAnimation") | |
373 propertyName = "animation-delay"; | |
374 else | |
375 return; | |
376 this._setNodeStyle(propertyName, Math.round(value) + "ms"); | |
377 }, | |
378 | |
379 /** | |
380 * @param {number} value | |
381 */ | |
382 _setDuration: function(value) | |
383 { | |
384 if (!this._node || this._animation.source().duration() == value) | |
385 return; | |
386 | |
387 this._animation.source().setDuration(value); | |
388 var propertyName; | |
389 if (this._animation.type() == "CSSTransition") | |
390 propertyName = "transition-duration"; | |
391 else if (this._animation.type() == "CSSAnimation") | |
392 propertyName = "animation-duration"; | |
393 else | |
394 return; | |
395 this._setNodeStyle(propertyName, Math.round(value) + "ms"); | |
396 }, | |
397 | |
398 /** | |
399 * @param {string} name | |
400 * @param {string} value | |
401 */ | |
402 _setNodeStyle: function(name, value) | |
403 { | |
404 var style = this._node.getAttribute("style") || ""; | |
405 if (style) | |
406 style = style.replace(new RegExp("\\s*(-webkit-)?" + name + ":[^;]*; ?\\s*", "g"), ""); | |
407 var valueString = name + ": " + value; | |
408 this._node.setAttributeValue("style", style + " " + valueString + "; -we bkit-" + valueString + ";"); | |
409 } | |
410 } | |
411 | |
412 WebInspector.AnimationUI.Options = { | |
413 AnimationHeight: 32, | |
414 AnimationSVGHeight: 80, | |
415 AnimationMargin: 7, | |
416 EndpointsClickRegionSize: 10, | |
417 GridCanvasHeight: 40 | |
418 } | |
419 | |
420 WebInspector.AnimationUI.Colors = { | |
421 "Purple": WebInspector.Color.parse("#9C27B0"), | |
422 "Light Blue": WebInspector.Color.parse("#03A9F4"), | |
423 "Deep Orange": WebInspector.Color.parse("#FF5722"), | |
424 "Blue": WebInspector.Color.parse("#5677FC"), | |
425 "Lime": WebInspector.Color.parse("#CDDC39"), | |
426 "Blue Grey": WebInspector.Color.parse("#607D8B"), | |
427 "Pink": WebInspector.Color.parse("#E91E63"), | |
428 "Green": WebInspector.Color.parse("#0F9D58"), | |
429 "Brown": WebInspector.Color.parse("#795548"), | |
430 "Cyan": WebInspector.Color.parse("#00BCD4") | |
431 } | |
432 | |
433 | |
434 /** | |
435 * @param {!WebInspector.AnimationModel.Animation} animation | |
436 * @return {string} | |
437 */ | |
438 WebInspector.AnimationUI.Color = function(animation) | |
439 { | |
440 /** | |
441 * @param {string} string | |
442 * @return {number} | |
443 */ | |
444 function hash(string) | |
445 { | |
446 var hash = 0; | |
447 for (var i = 0; i < string.length; i++) | |
448 hash = (hash << 5) + hash + string.charCodeAt(i); | |
449 return Math.abs(hash); | |
450 } | |
451 | |
452 var names = Object.keys(WebInspector.AnimationUI.Colors); | |
453 var color = WebInspector.AnimationUI.Colors[names[hash(animation.name() || a nimation.id()) % names.length]]; | |
454 return color.asString(WebInspector.Color.Format.RGB); | |
455 } | |
OLD | NEW |