Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(813)

Side by Side Diff: Source/devtools/front_end/animation/AnimationUI.js

Issue 1218433007: Devtools Animations: Add buffer and effect selection to animation timeline (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « Source/devtools/front_end/animation/AnimationTimeline.js ('k') | Source/devtools/front_end/animation/animationTimeline.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698