OLD | NEW |
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | |
5 /** | 4 /** |
6 * @constructor | 5 * @unrestricted |
7 * @param {!WebInspector.AnimationModel.Animation} animation | |
8 * @param {!WebInspector.AnimationTimeline} timeline | |
9 * @param {!Element} parentElement | |
10 */ | 6 */ |
11 WebInspector.AnimationUI = function(animation, timeline, parentElement) { | 7 WebInspector.AnimationUI = class { |
| 8 /** |
| 9 * @param {!WebInspector.AnimationModel.Animation} animation |
| 10 * @param {!WebInspector.AnimationTimeline} timeline |
| 11 * @param {!Element} parentElement |
| 12 */ |
| 13 constructor(animation, timeline, parentElement) { |
12 this._animation = animation; | 14 this._animation = animation; |
13 this._timeline = timeline; | 15 this._timeline = timeline; |
14 this._parentElement = parentElement; | 16 this._parentElement = parentElement; |
15 | 17 |
16 if (this._animation.source().keyframesRule()) | 18 if (this._animation.source().keyframesRule()) |
17 this._keyframes = this._animation.source().keyframesRule().keyframes(); | 19 this._keyframes = this._animation.source().keyframesRule().keyframes(); |
18 | 20 |
19 this._nameElement = parentElement.createChild("div", "animation-name"); | 21 this._nameElement = parentElement.createChild('div', 'animation-name'); |
20 this._nameElement.textContent = this._animation.name(); | 22 this._nameElement.textContent = this._animation.name(); |
21 | 23 |
22 this._svg = parentElement.createSVGChild("svg", "animation-ui"); | 24 this._svg = parentElement.createSVGChild('svg', 'animation-ui'); |
23 this._svg.setAttribute("height", WebInspector.AnimationUI.Options.AnimationS
VGHeight); | 25 this._svg.setAttribute('height', WebInspector.AnimationUI.Options.AnimationS
VGHeight); |
24 this._svg.style.marginLeft = "-" + WebInspector.AnimationUI.Options.Animatio
nMargin + "px"; | 26 this._svg.style.marginLeft = '-' + WebInspector.AnimationUI.Options.Animatio
nMargin + 'px'; |
25 this._svg.addEventListener("contextmenu", this._onContextMenu.bind(this)); | 27 this._svg.addEventListener('contextmenu', this._onContextMenu.bind(this)); |
26 this._activeIntervalGroup = this._svg.createSVGChild("g"); | 28 this._activeIntervalGroup = this._svg.createSVGChild('g'); |
27 WebInspector.installDragHandle(this._activeIntervalGroup, this._mouseDown.bi
nd(this, WebInspector.AnimationUI.MouseEvents.AnimationDrag, null), this._mouse
Move.bind(this), this._mouseUp.bind(this), "-webkit-grabbing", "-webkit-grab"); | 29 WebInspector.installDragHandle( |
| 30 this._activeIntervalGroup, this._mouseDown.bind(this, WebInspector.Anima
tionUI.MouseEvents.AnimationDrag, null), |
| 31 this._mouseMove.bind(this), this._mouseUp.bind(this), '-webkit-grabbing'
, '-webkit-grab'); |
28 | 32 |
29 /** @type {!Array.<{group: ?Element, animationLine: ?Element, keyframePoints
: !Object.<number, !Element>, keyframeRender: !Object.<number, !Element>}>} */ | 33 /** @type {!Array.<{group: ?Element, animationLine: ?Element, keyframePoints
: !Object.<number, !Element>, keyframeRender: !Object.<number, !Element>}>} */ |
30 this._cachedElements = []; | 34 this._cachedElements = []; |
31 | 35 |
32 this._movementInMs = 0; | 36 this._movementInMs = 0; |
33 this._color = WebInspector.AnimationUI.Color(this._animation); | 37 this._color = WebInspector.AnimationUI.Color(this._animation); |
| 38 } |
| 39 |
| 40 /** |
| 41 * @param {!WebInspector.AnimationModel.Animation} animation |
| 42 * @return {string} |
| 43 */ |
| 44 static Color(animation) { |
| 45 var names = Object.keys(WebInspector.AnimationUI.Colors); |
| 46 var color = |
| 47 WebInspector.AnimationUI.Colors[names[String.hashCode(animation.name() |
| animation.id()) % names.length]]; |
| 48 return color.asString(WebInspector.Color.Format.RGB); |
| 49 } |
| 50 |
| 51 /** |
| 52 * @return {!WebInspector.AnimationModel.Animation} |
| 53 */ |
| 54 animation() { |
| 55 return this._animation; |
| 56 } |
| 57 |
| 58 /** |
| 59 * @param {?WebInspector.DOMNode} node |
| 60 */ |
| 61 setNode(node) { |
| 62 this._node = node; |
| 63 } |
| 64 |
| 65 /** |
| 66 * @param {!Element} parentElement |
| 67 * @param {string} className |
| 68 */ |
| 69 _createLine(parentElement, className) { |
| 70 var line = parentElement.createSVGChild('line', className); |
| 71 line.setAttribute('x1', WebInspector.AnimationUI.Options.AnimationMargin); |
| 72 line.setAttribute('y1', WebInspector.AnimationUI.Options.AnimationHeight); |
| 73 line.setAttribute('y2', WebInspector.AnimationUI.Options.AnimationHeight); |
| 74 line.style.stroke = this._color; |
| 75 return line; |
| 76 } |
| 77 |
| 78 /** |
| 79 * @param {number} iteration |
| 80 * @param {!Element} parentElement |
| 81 */ |
| 82 _drawAnimationLine(iteration, parentElement) { |
| 83 var cache = this._cachedElements[iteration]; |
| 84 if (!cache.animationLine) |
| 85 cache.animationLine = this._createLine(parentElement, 'animation-line'); |
| 86 cache.animationLine.setAttribute( |
| 87 'x2', (this._duration() * this._timeline.pixelMsRatio() + WebInspector.A
nimationUI.Options.AnimationMargin) |
| 88 .toFixed(2)); |
| 89 } |
| 90 |
| 91 /** |
| 92 * @param {!Element} parentElement |
| 93 */ |
| 94 _drawDelayLine(parentElement) { |
| 95 if (!this._delayLine) { |
| 96 this._delayLine = this._createLine(parentElement, 'animation-delay-line'); |
| 97 this._endDelayLine = this._createLine(parentElement, 'animation-delay-line
'); |
| 98 } |
| 99 var fill = this._animation.source().fill(); |
| 100 this._delayLine.classList.toggle('animation-fill', fill === 'backwards' || f
ill === 'both'); |
| 101 var margin = WebInspector.AnimationUI.Options.AnimationMargin; |
| 102 this._delayLine.setAttribute('x1', margin); |
| 103 this._delayLine.setAttribute('x2', (this._delay() * this._timeline.pixelMsRa
tio() + margin).toFixed(2)); |
| 104 var forwardsFill = fill === 'forwards' || fill === 'both'; |
| 105 this._endDelayLine.classList.toggle('animation-fill', forwardsFill); |
| 106 var leftMargin = Math.min( |
| 107 this._timeline.width(), |
| 108 (this._delay() + this._duration() * this._animation.source().iterations(
)) * this._timeline.pixelMsRatio()); |
| 109 this._endDelayLine.style.transform = 'translateX(' + leftMargin.toFixed(2) +
'px)'; |
| 110 this._endDelayLine.setAttribute('x1', margin); |
| 111 this._endDelayLine.setAttribute( |
| 112 'x2', forwardsFill ? (this._timeline.width() - leftMargin + margin).toFi
xed(2) : |
| 113 (this._animation.source().endDelay() * this._timeli
ne.pixelMsRatio() + margin).toFixed(2)); |
| 114 } |
| 115 |
| 116 /** |
| 117 * @param {number} iteration |
| 118 * @param {!Element} parentElement |
| 119 * @param {number} x |
| 120 * @param {number} keyframeIndex |
| 121 * @param {boolean} attachEvents |
| 122 */ |
| 123 _drawPoint(iteration, parentElement, x, keyframeIndex, attachEvents) { |
| 124 if (this._cachedElements[iteration].keyframePoints[keyframeIndex]) { |
| 125 this._cachedElements[iteration].keyframePoints[keyframeIndex].setAttribute
('cx', x.toFixed(2)); |
| 126 return; |
| 127 } |
| 128 |
| 129 var circle = |
| 130 parentElement.createSVGChild('circle', keyframeIndex <= 0 ? 'animation-e
ndpoint' : 'animation-keyframe-point'); |
| 131 circle.setAttribute('cx', x.toFixed(2)); |
| 132 circle.setAttribute('cy', WebInspector.AnimationUI.Options.AnimationHeight); |
| 133 circle.style.stroke = this._color; |
| 134 circle.setAttribute('r', WebInspector.AnimationUI.Options.AnimationMargin /
2); |
| 135 |
| 136 if (keyframeIndex <= 0) |
| 137 circle.style.fill = this._color; |
| 138 |
| 139 this._cachedElements[iteration].keyframePoints[keyframeIndex] = circle; |
| 140 |
| 141 if (!attachEvents) |
| 142 return; |
| 143 |
| 144 var eventType; |
| 145 if (keyframeIndex === 0) |
| 146 eventType = WebInspector.AnimationUI.MouseEvents.StartEndpointMove; |
| 147 else if (keyframeIndex === -1) |
| 148 eventType = WebInspector.AnimationUI.MouseEvents.FinishEndpointMove; |
| 149 else |
| 150 eventType = WebInspector.AnimationUI.MouseEvents.KeyframeMove; |
| 151 WebInspector.installDragHandle( |
| 152 circle, this._mouseDown.bind(this, eventType, keyframeIndex), this._mous
eMove.bind(this), |
| 153 this._mouseUp.bind(this), 'ew-resize'); |
| 154 } |
| 155 |
| 156 /** |
| 157 * @param {number} iteration |
| 158 * @param {number} keyframeIndex |
| 159 * @param {!Element} parentElement |
| 160 * @param {number} leftDistance |
| 161 * @param {number} width |
| 162 * @param {string} easing |
| 163 */ |
| 164 _renderKeyframe(iteration, keyframeIndex, parentElement, leftDistance, width,
easing) { |
| 165 /** |
| 166 * @param {!Element} parentElement |
| 167 * @param {number} x |
| 168 * @param {string} strokeColor |
| 169 */ |
| 170 function createStepLine(parentElement, x, strokeColor) { |
| 171 var line = parentElement.createSVGChild('line'); |
| 172 line.setAttribute('x1', x); |
| 173 line.setAttribute('x2', x); |
| 174 line.setAttribute('y1', WebInspector.AnimationUI.Options.AnimationMargin); |
| 175 line.setAttribute('y2', WebInspector.AnimationUI.Options.AnimationHeight); |
| 176 line.style.stroke = strokeColor; |
| 177 } |
| 178 |
| 179 var bezier = WebInspector.Geometry.CubicBezier.parse(easing); |
| 180 var cache = this._cachedElements[iteration].keyframeRender; |
| 181 if (!cache[keyframeIndex]) |
| 182 cache[keyframeIndex] = bezier ? parentElement.createSVGChild('path', 'anim
ation-keyframe') : |
| 183 parentElement.createSVGChild('g', 'animati
on-keyframe-step'); |
| 184 var group = cache[keyframeIndex]; |
| 185 group.style.transform = 'translateX(' + leftDistance.toFixed(2) + 'px)'; |
| 186 |
| 187 if (easing === 'linear') { |
| 188 group.style.fill = this._color; |
| 189 var height = WebInspector.BezierUI.Height; |
| 190 group.setAttribute( |
| 191 'd', ['M', 0, height, 'L', 0, 5, 'L', width.toFixed(2), 5, 'L', width.
toFixed(2), height, 'Z'].join(' ')); |
| 192 } else if (bezier) { |
| 193 group.style.fill = this._color; |
| 194 WebInspector.BezierUI.drawVelocityChart(bezier, group, width); |
| 195 } else { |
| 196 var stepFunction = WebInspector.AnimationTimeline.StepTimingFunction.parse
(easing); |
| 197 group.removeChildren(); |
| 198 /** @const */ var offsetMap = {'start': 0, 'middle': 0.5, 'end': 1}; |
| 199 /** @const */ var offsetWeight = offsetMap[stepFunction.stepAtPosition]; |
| 200 for (var i = 0; i < stepFunction.steps; i++) |
| 201 createStepLine(group, (i + offsetWeight) * width / stepFunction.steps, t
his._color); |
| 202 } |
| 203 } |
| 204 |
| 205 redraw() { |
| 206 var durationWithDelay = |
| 207 this._delay() + this._duration() * this._animation.source().iterations()
+ this._animation.source().endDelay(); |
| 208 var maxWidth = this._timeline.width() - WebInspector.AnimationUI.Options.Ani
mationMargin; |
| 209 |
| 210 this._svg.setAttribute('width', (maxWidth + 2 * WebInspector.AnimationUI.Opt
ions.AnimationMargin).toFixed(2)); |
| 211 this._activeIntervalGroup.style.transform = |
| 212 'translateX(' + (this._delay() * this._timeline.pixelMsRatio()).toFixed(
2) + 'px)'; |
| 213 |
| 214 this._nameElement.style.transform = 'translateX(' + |
| 215 (this._delay() * this._timeline.pixelMsRatio() + WebInspector.AnimationU
I.Options.AnimationMargin).toFixed(2) + |
| 216 'px)'; |
| 217 this._nameElement.style.width = (this._duration() * this._timeline.pixelMsRa
tio()).toFixed(2) + 'px'; |
| 218 this._drawDelayLine(this._svg); |
| 219 |
| 220 if (this._animation.type() === 'CSSTransition') { |
| 221 this._renderTransition(); |
| 222 return; |
| 223 } |
| 224 |
| 225 this._renderIteration(this._activeIntervalGroup, 0); |
| 226 if (!this._tailGroup) |
| 227 this._tailGroup = this._activeIntervalGroup.createSVGChild('g', 'animation
-tail-iterations'); |
| 228 var iterationWidth = this._duration() * this._timeline.pixelMsRatio(); |
| 229 for (var iteration = 1; |
| 230 iteration < this._animation.source().iterations() && iterationWidth * (
iteration - 1) < this._timeline.width(); |
| 231 iteration++) |
| 232 this._renderIteration(this._tailGroup, iteration); |
| 233 while (iteration < this._cachedElements.length) |
| 234 this._cachedElements.pop().group.remove(); |
| 235 } |
| 236 |
| 237 _renderTransition() { |
| 238 if (!this._cachedElements[0]) |
| 239 this._cachedElements[0] = {animationLine: null, keyframePoints: {}, keyfra
meRender: {}, group: null}; |
| 240 this._drawAnimationLine(0, this._activeIntervalGroup); |
| 241 this._renderKeyframe( |
| 242 0, 0, this._activeIntervalGroup, WebInspector.AnimationUI.Options.Animat
ionMargin, |
| 243 this._duration() * this._timeline.pixelMsRatio(), this._animation.source
().easing()); |
| 244 this._drawPoint(0, this._activeIntervalGroup, WebInspector.AnimationUI.Optio
ns.AnimationMargin, 0, true); |
| 245 this._drawPoint( |
| 246 0, this._activeIntervalGroup, |
| 247 this._duration() * this._timeline.pixelMsRatio() + WebInspector.Animatio
nUI.Options.AnimationMargin, -1, true); |
| 248 } |
| 249 |
| 250 /** |
| 251 * @param {!Element} parentElement |
| 252 * @param {number} iteration |
| 253 */ |
| 254 _renderIteration(parentElement, iteration) { |
| 255 if (!this._cachedElements[iteration]) |
| 256 this._cachedElements[iteration] = |
| 257 {animationLine: null, keyframePoints: {}, keyframeRender: {}, group: p
arentElement.createSVGChild('g')}; |
| 258 var group = this._cachedElements[iteration].group; |
| 259 group.style.transform = |
| 260 'translateX(' + (iteration * this._duration() * this._timeline.pixelMsRa
tio()).toFixed(2) + 'px)'; |
| 261 this._drawAnimationLine(iteration, group); |
| 262 console.assert(this._keyframes.length > 1); |
| 263 for (var i = 0; i < this._keyframes.length - 1; i++) { |
| 264 var leftDistance = this._offset(i) * this._duration() * this._timeline.pix
elMsRatio() + |
| 265 WebInspector.AnimationUI.Options.AnimationMargin; |
| 266 var width = this._duration() * (this._offset(i + 1) - this._offset(i)) * t
his._timeline.pixelMsRatio(); |
| 267 this._renderKeyframe(iteration, i, group, leftDistance, width, this._keyfr
ames[i].easing()); |
| 268 if (i || (!i && iteration === 0)) |
| 269 this._drawPoint(iteration, group, leftDistance, i, iteration === 0); |
| 270 } |
| 271 this._drawPoint( |
| 272 iteration, group, |
| 273 this._duration() * this._timeline.pixelMsRatio() + WebInspector.Animatio
nUI.Options.AnimationMargin, -1, |
| 274 iteration === 0); |
| 275 } |
| 276 |
| 277 /** |
| 278 * @return {number} |
| 279 */ |
| 280 _delay() { |
| 281 var delay = this._animation.source().delay(); |
| 282 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.AnimationD
rag || |
| 283 this._mouseEventType === WebInspector.AnimationUI.MouseEvents.StartEndpo
intMove) |
| 284 delay += this._movementInMs; |
| 285 // FIXME: add support for negative start delay |
| 286 return Math.max(0, delay); |
| 287 } |
| 288 |
| 289 /** |
| 290 * @return {number} |
| 291 */ |
| 292 _duration() { |
| 293 var duration = this._animation.source().duration(); |
| 294 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.FinishEndp
ointMove) |
| 295 duration += this._movementInMs; |
| 296 else if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.Start
EndpointMove) |
| 297 duration -= Math.max(this._movementInMs, -this._animation.source().delay()
); // Cannot have negative delay |
| 298 return Math.max(0, duration); |
| 299 } |
| 300 |
| 301 /** |
| 302 * @param {number} i |
| 303 * @return {number} offset |
| 304 */ |
| 305 _offset(i) { |
| 306 var offset = this._keyframes[i].offsetAsNumber(); |
| 307 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.KeyframeMo
ve && i === this._keyframeMoved) { |
| 308 console.assert(i > 0 && i < this._keyframes.length - 1, 'First and last ke
yframe cannot be moved'); |
| 309 offset += this._movementInMs / this._animation.source().duration(); |
| 310 offset = Math.max(offset, this._keyframes[i - 1].offsetAsNumber()); |
| 311 offset = Math.min(offset, this._keyframes[i + 1].offsetAsNumber()); |
| 312 } |
| 313 return offset; |
| 314 } |
| 315 |
| 316 /** |
| 317 * @param {!WebInspector.AnimationUI.MouseEvents} mouseEventType |
| 318 * @param {?number} keyframeIndex |
| 319 * @param {!Event} event |
| 320 */ |
| 321 _mouseDown(mouseEventType, keyframeIndex, event) { |
| 322 if (event.buttons === 2) |
| 323 return false; |
| 324 if (this._svg.enclosingNodeOrSelfWithClass('animation-node-removed')) |
| 325 return false; |
| 326 this._mouseEventType = mouseEventType; |
| 327 this._keyframeMoved = keyframeIndex; |
| 328 this._downMouseX = event.clientX; |
| 329 event.consume(true); |
| 330 if (this._node) |
| 331 WebInspector.Revealer.reveal(this._node); |
| 332 return true; |
| 333 } |
| 334 |
| 335 /** |
| 336 * @param {!Event} event |
| 337 */ |
| 338 _mouseMove(event) { |
| 339 this._movementInMs = (event.clientX - this._downMouseX) / this._timeline.pix
elMsRatio(); |
| 340 if (this._delay() + this._duration() > this._timeline.duration() * 0.8) |
| 341 this._timeline.setDuration(this._timeline.duration() * 1.2); |
| 342 this.redraw(); |
| 343 } |
| 344 |
| 345 /** |
| 346 * @param {!Event} event |
| 347 */ |
| 348 _mouseUp(event) { |
| 349 this._movementInMs = (event.clientX - this._downMouseX) / this._timeline.pix
elMsRatio(); |
| 350 |
| 351 // Commit changes |
| 352 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.KeyframeMo
ve) |
| 353 this._keyframes[this._keyframeMoved].setOffset(this._offset(this._keyframe
Moved)); |
| 354 else |
| 355 this._animation.setTiming(this._duration(), this._delay()); |
| 356 |
| 357 this._movementInMs = 0; |
| 358 this.redraw(); |
| 359 |
| 360 delete this._mouseEventType; |
| 361 delete this._downMouseX; |
| 362 delete this._keyframeMoved; |
| 363 } |
| 364 |
| 365 /** |
| 366 * @param {!Event} event |
| 367 */ |
| 368 _onContextMenu(event) { |
| 369 /** |
| 370 * @param {?WebInspector.RemoteObject} remoteObject |
| 371 */ |
| 372 function showContextMenu(remoteObject) { |
| 373 if (!remoteObject) |
| 374 return; |
| 375 var contextMenu = new WebInspector.ContextMenu(event); |
| 376 contextMenu.appendApplicableItems(remoteObject); |
| 377 contextMenu.show(); |
| 378 } |
| 379 |
| 380 this._animation.remoteObjectPromise().then(showContextMenu); |
| 381 event.consume(true); |
| 382 } |
34 }; | 383 }; |
35 | 384 |
36 /** | 385 /** |
37 * @enum {string} | 386 * @enum {string} |
38 */ | 387 */ |
39 WebInspector.AnimationUI.MouseEvents = { | 388 WebInspector.AnimationUI.MouseEvents = { |
40 AnimationDrag: "AnimationDrag", | 389 AnimationDrag: 'AnimationDrag', |
41 KeyframeMove: "KeyframeMove", | 390 KeyframeMove: 'KeyframeMove', |
42 StartEndpointMove: "StartEndpointMove", | 391 StartEndpointMove: 'StartEndpointMove', |
43 FinishEndpointMove: "FinishEndpointMove" | 392 FinishEndpointMove: 'FinishEndpointMove' |
44 }; | 393 }; |
45 | 394 |
46 WebInspector.AnimationUI.prototype = { | 395 WebInspector.AnimationUI.Options = { |
47 /** | 396 AnimationHeight: 26, |
48 * @return {!WebInspector.AnimationModel.Animation} | 397 AnimationSVGHeight: 50, |
49 */ | 398 AnimationMargin: 7, |
50 animation: function() | 399 EndpointsClickRegionSize: 10, |
51 { | 400 GridCanvasHeight: 40 |
52 return this._animation; | |
53 }, | |
54 | |
55 /** | |
56 * @param {?WebInspector.DOMNode} node | |
57 */ | |
58 setNode: function(node) | |
59 { | |
60 this._node = node; | |
61 }, | |
62 | |
63 /** | |
64 * @param {!Element} parentElement | |
65 * @param {string} className | |
66 */ | |
67 _createLine: function(parentElement, className) | |
68 { | |
69 var line = parentElement.createSVGChild("line", className); | |
70 line.setAttribute("x1", WebInspector.AnimationUI.Options.AnimationMargin
); | |
71 line.setAttribute("y1", WebInspector.AnimationUI.Options.AnimationHeight
); | |
72 line.setAttribute("y2", WebInspector.AnimationUI.Options.AnimationHeight
); | |
73 line.style.stroke = this._color; | |
74 return line; | |
75 }, | |
76 | |
77 /** | |
78 * @param {number} iteration | |
79 * @param {!Element} parentElement | |
80 */ | |
81 _drawAnimationLine: function(iteration, parentElement) | |
82 { | |
83 var cache = this._cachedElements[iteration]; | |
84 if (!cache.animationLine) | |
85 cache.animationLine = this._createLine(parentElement, "animation-lin
e"); | |
86 cache.animationLine.setAttribute("x2", (this._duration() * this._timelin
e.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin).toFixed(2))
; | |
87 }, | |
88 | |
89 /** | |
90 * @param {!Element} parentElement | |
91 */ | |
92 _drawDelayLine: function(parentElement) | |
93 { | |
94 if (!this._delayLine) { | |
95 this._delayLine = this._createLine(parentElement, "animation-delay-l
ine"); | |
96 this._endDelayLine = this._createLine(parentElement, "animation-dela
y-line"); | |
97 } | |
98 var fill = this._animation.source().fill(); | |
99 this._delayLine.classList.toggle("animation-fill", fill === "backwards"
|| fill === "both"); | |
100 var margin = WebInspector.AnimationUI.Options.AnimationMargin; | |
101 this._delayLine.setAttribute("x1", margin); | |
102 this._delayLine.setAttribute("x2", (this._delay() * this._timeline.pixel
MsRatio() + margin).toFixed(2)); | |
103 var forwardsFill = fill === "forwards" || fill === "both"; | |
104 this._endDelayLine.classList.toggle("animation-fill", forwardsFill); | |
105 var leftMargin = Math.min(this._timeline.width(), (this._delay() + this.
_duration() * this._animation.source().iterations()) * this._timeline.pixelMsRat
io()); | |
106 this._endDelayLine.style.transform = "translateX(" + leftMargin.toFixed(
2) + "px)"; | |
107 this._endDelayLine.setAttribute("x1", margin); | |
108 this._endDelayLine.setAttribute("x2", forwardsFill | |
109 ? (this._timeline.width() - leftMargin + margin).toFixed(2) | |
110 : (this._animation.source().endDelay() * this._timeline.pixelMsRatio
() + margin).toFixed(2)); | |
111 }, | |
112 | |
113 /** | |
114 * @param {number} iteration | |
115 * @param {!Element} parentElement | |
116 * @param {number} x | |
117 * @param {number} keyframeIndex | |
118 * @param {boolean} attachEvents | |
119 */ | |
120 _drawPoint: function(iteration, parentElement, x, keyframeIndex, attachEvent
s) | |
121 { | |
122 if (this._cachedElements[iteration].keyframePoints[keyframeIndex]) { | |
123 this._cachedElements[iteration].keyframePoints[keyframeIndex].setAtt
ribute("cx", x.toFixed(2)); | |
124 return; | |
125 } | |
126 | |
127 var circle = parentElement.createSVGChild("circle", keyframeIndex <= 0 ?
"animation-endpoint" : "animation-keyframe-point"); | |
128 circle.setAttribute("cx", x.toFixed(2)); | |
129 circle.setAttribute("cy", WebInspector.AnimationUI.Options.AnimationHeig
ht); | |
130 circle.style.stroke = this._color; | |
131 circle.setAttribute("r", WebInspector.AnimationUI.Options.AnimationMargi
n / 2); | |
132 | |
133 if (keyframeIndex <= 0) | |
134 circle.style.fill = this._color; | |
135 | |
136 this._cachedElements[iteration].keyframePoints[keyframeIndex] = circle; | |
137 | |
138 if (!attachEvents) | |
139 return; | |
140 | |
141 var eventType; | |
142 if (keyframeIndex === 0) | |
143 eventType = WebInspector.AnimationUI.MouseEvents.StartEndpointMove; | |
144 else if (keyframeIndex === -1) | |
145 eventType = WebInspector.AnimationUI.MouseEvents.FinishEndpointMove; | |
146 else | |
147 eventType = WebInspector.AnimationUI.MouseEvents.KeyframeMove; | |
148 WebInspector.installDragHandle(circle, this._mouseDown.bind(this, eventT
ype, keyframeIndex), this._mouseMove.bind(this), this._mouseUp.bind(this), "ew-r
esize"); | |
149 }, | |
150 | |
151 /** | |
152 * @param {number} iteration | |
153 * @param {number} keyframeIndex | |
154 * @param {!Element} parentElement | |
155 * @param {number} leftDistance | |
156 * @param {number} width | |
157 * @param {string} easing | |
158 */ | |
159 _renderKeyframe: function(iteration, keyframeIndex, parentElement, leftDista
nce, width, easing) | |
160 { | |
161 /** | |
162 * @param {!Element} parentElement | |
163 * @param {number} x | |
164 * @param {string} strokeColor | |
165 */ | |
166 function createStepLine(parentElement, x, strokeColor) | |
167 { | |
168 var line = parentElement.createSVGChild("line"); | |
169 line.setAttribute("x1", x); | |
170 line.setAttribute("x2", x); | |
171 line.setAttribute("y1", WebInspector.AnimationUI.Options.AnimationMa
rgin); | |
172 line.setAttribute("y2", WebInspector.AnimationUI.Options.AnimationHe
ight); | |
173 line.style.stroke = strokeColor; | |
174 } | |
175 | |
176 var bezier = WebInspector.Geometry.CubicBezier.parse(easing); | |
177 var cache = this._cachedElements[iteration].keyframeRender; | |
178 if (!cache[keyframeIndex]) | |
179 cache[keyframeIndex] = bezier ? parentElement.createSVGChild("path",
"animation-keyframe") : parentElement.createSVGChild("g", "animation-keyframe-s
tep"); | |
180 var group = cache[keyframeIndex]; | |
181 group.style.transform = "translateX(" + leftDistance.toFixed(2) + "px)"; | |
182 | |
183 if (easing === "linear") { | |
184 group.style.fill = this._color; | |
185 var height = WebInspector.BezierUI.Height; | |
186 group.setAttribute("d", ["M", 0, height, "L", 0, 5, "L", width.toFix
ed(2), 5, "L", width.toFixed(2), height, "Z"].join(" ")); | |
187 } else if (bezier) { | |
188 group.style.fill = this._color; | |
189 WebInspector.BezierUI.drawVelocityChart(bezier, group, width); | |
190 } else { | |
191 var stepFunction = WebInspector.AnimationTimeline.StepTimingFunction
.parse(easing); | |
192 group.removeChildren(); | |
193 /** @const */ var offsetMap = {"start": 0, "middle": 0.5, "end": 1}; | |
194 /** @const */ var offsetWeight = offsetMap[stepFunction.stepAtPositi
on]; | |
195 for (var i = 0; i < stepFunction.steps; i++) | |
196 createStepLine(group, (i + offsetWeight) * width / stepFunction.
steps, this._color); | |
197 } | |
198 }, | |
199 | |
200 redraw: function() | |
201 { | |
202 var durationWithDelay = this._delay() + this._duration() * this._animati
on.source().iterations() + this._animation.source().endDelay(); | |
203 var maxWidth = this._timeline.width() - WebInspector.AnimationUI.Options
.AnimationMargin; | |
204 | |
205 this._svg.setAttribute("width", (maxWidth + 2 * WebInspector.AnimationUI
.Options.AnimationMargin).toFixed(2)); | |
206 this._activeIntervalGroup.style.transform = "translateX(" + (this._delay
() * this._timeline.pixelMsRatio()).toFixed(2) + "px)"; | |
207 | |
208 this._nameElement.style.transform = "translateX(" + (this._delay() * thi
s._timeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin).t
oFixed(2) + "px)"; | |
209 this._nameElement.style.width = (this._duration() * this._timeline.pixel
MsRatio()).toFixed(2) + "px"; | |
210 this._drawDelayLine(this._svg); | |
211 | |
212 if (this._animation.type() === "CSSTransition") { | |
213 this._renderTransition(); | |
214 return; | |
215 } | |
216 | |
217 this._renderIteration(this._activeIntervalGroup, 0); | |
218 if (!this._tailGroup) | |
219 this._tailGroup = this._activeIntervalGroup.createSVGChild("g", "ani
mation-tail-iterations"); | |
220 var iterationWidth = this._duration() * this._timeline.pixelMsRatio(); | |
221 for (var iteration = 1; iteration < this._animation.source().iterations(
) && iterationWidth * (iteration - 1) < this._timeline.width(); iteration++) | |
222 this._renderIteration(this._tailGroup, iteration); | |
223 while (iteration < this._cachedElements.length) | |
224 this._cachedElements.pop().group.remove(); | |
225 }, | |
226 | |
227 | |
228 _renderTransition: function() | |
229 { | |
230 if (!this._cachedElements[0]) | |
231 this._cachedElements[0] = { animationLine: null, keyframePoints: {},
keyframeRender: {}, group: null }; | |
232 this._drawAnimationLine(0, this._activeIntervalGroup); | |
233 this._renderKeyframe(0, 0, this._activeIntervalGroup, WebInspector.Anima
tionUI.Options.AnimationMargin, this._duration() * this._timeline.pixelMsRatio()
, this._animation.source().easing()); | |
234 this._drawPoint(0, this._activeIntervalGroup, WebInspector.AnimationUI.O
ptions.AnimationMargin, 0, true); | |
235 this._drawPoint(0, this._activeIntervalGroup, this._duration() * this._t
imeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin, -1, t
rue); | |
236 }, | |
237 | |
238 /** | |
239 * @param {!Element} parentElement | |
240 * @param {number} iteration | |
241 */ | |
242 _renderIteration: function(parentElement, iteration) | |
243 { | |
244 if (!this._cachedElements[iteration]) | |
245 this._cachedElements[iteration] = { animationLine: null, keyframePoi
nts: {}, keyframeRender: {}, group: parentElement.createSVGChild("g") }; | |
246 var group = this._cachedElements[iteration].group; | |
247 group.style.transform = "translateX(" + (iteration * this._duration() *
this._timeline.pixelMsRatio()).toFixed(2) + "px)"; | |
248 this._drawAnimationLine(iteration, group); | |
249 console.assert(this._keyframes.length > 1); | |
250 for (var i = 0; i < this._keyframes.length - 1; i++) { | |
251 var leftDistance = this._offset(i) * this._duration() * this._timeli
ne.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin; | |
252 var width = this._duration() * (this._offset(i + 1) - this._offset(i
)) * this._timeline.pixelMsRatio(); | |
253 this._renderKeyframe(iteration, i, group, leftDistance, width, this.
_keyframes[i].easing()); | |
254 if (i || (!i && iteration === 0)) | |
255 this._drawPoint(iteration, group, leftDistance, i, iteration ===
0); | |
256 } | |
257 this._drawPoint(iteration, group, this._duration() * this._timeline.pixe
lMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin, -1, iteration ===
0); | |
258 }, | |
259 | |
260 /** | |
261 * @return {number} | |
262 */ | |
263 _delay: function() | |
264 { | |
265 var delay = this._animation.source().delay(); | |
266 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.Animat
ionDrag || this._mouseEventType === WebInspector.AnimationUI.MouseEvents.StartEn
dpointMove) | |
267 delay += this._movementInMs; | |
268 // FIXME: add support for negative start delay | |
269 return Math.max(0, delay); | |
270 }, | |
271 | |
272 /** | |
273 * @return {number} | |
274 */ | |
275 _duration: function() | |
276 { | |
277 var duration = this._animation.source().duration(); | |
278 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.Finish
EndpointMove) | |
279 duration += this._movementInMs; | |
280 else if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.S
tartEndpointMove) | |
281 duration -= Math.max(this._movementInMs, -this._animation.source().d
elay()); // Cannot have negative delay | |
282 return Math.max(0, duration); | |
283 }, | |
284 | |
285 /** | |
286 * @param {number} i | |
287 * @return {number} offset | |
288 */ | |
289 _offset: function(i) | |
290 { | |
291 var offset = this._keyframes[i].offsetAsNumber(); | |
292 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.Keyfra
meMove && i === this._keyframeMoved) { | |
293 console.assert(i > 0 && i < this._keyframes.length - 1, "First and l
ast keyframe cannot be moved"); | |
294 offset += this._movementInMs / this._animation.source().duration(); | |
295 offset = Math.max(offset, this._keyframes[i - 1].offsetAsNumber()); | |
296 offset = Math.min(offset, this._keyframes[i + 1].offsetAsNumber()); | |
297 } | |
298 return offset; | |
299 }, | |
300 | |
301 /** | |
302 * @param {!WebInspector.AnimationUI.MouseEvents} mouseEventType | |
303 * @param {?number} keyframeIndex | |
304 * @param {!Event} event | |
305 */ | |
306 _mouseDown: function(mouseEventType, keyframeIndex, event) | |
307 { | |
308 if (event.buttons === 2) | |
309 return false; | |
310 if (this._svg.enclosingNodeOrSelfWithClass("animation-node-removed")) | |
311 return false; | |
312 this._mouseEventType = mouseEventType; | |
313 this._keyframeMoved = keyframeIndex; | |
314 this._downMouseX = event.clientX; | |
315 event.consume(true); | |
316 if (this._node) | |
317 WebInspector.Revealer.reveal(this._node); | |
318 return true; | |
319 }, | |
320 | |
321 /** | |
322 * @param {!Event} event | |
323 */ | |
324 _mouseMove: function(event) | |
325 { | |
326 this._movementInMs = (event.clientX - this._downMouseX) / this._timeline
.pixelMsRatio(); | |
327 if (this._delay() + this._duration() > this._timeline.duration() * 0.8) | |
328 this._timeline.setDuration(this._timeline.duration() * 1.2); | |
329 this.redraw(); | |
330 }, | |
331 | |
332 /** | |
333 * @param {!Event} event | |
334 */ | |
335 _mouseUp: function(event) | |
336 { | |
337 this._movementInMs = (event.clientX - this._downMouseX) / this._timeline
.pixelMsRatio(); | |
338 | |
339 // Commit changes | |
340 if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.Keyfra
meMove) | |
341 this._keyframes[this._keyframeMoved].setOffset(this._offset(this._ke
yframeMoved)); | |
342 else | |
343 this._animation.setTiming(this._duration(), this._delay()); | |
344 | |
345 this._movementInMs = 0; | |
346 this.redraw(); | |
347 | |
348 delete this._mouseEventType; | |
349 delete this._downMouseX; | |
350 delete this._keyframeMoved; | |
351 }, | |
352 | |
353 /** | |
354 * @param {!Event} event | |
355 */ | |
356 _onContextMenu: function(event) | |
357 { | |
358 /** | |
359 * @param {?WebInspector.RemoteObject} remoteObject | |
360 */ | |
361 function showContextMenu(remoteObject) | |
362 { | |
363 if (!remoteObject) | |
364 return; | |
365 var contextMenu = new WebInspector.ContextMenu(event); | |
366 contextMenu.appendApplicableItems(remoteObject); | |
367 contextMenu.show(); | |
368 } | |
369 | |
370 this._animation.remoteObjectPromise().then(showContextMenu); | |
371 event.consume(true); | |
372 } | |
373 }; | 401 }; |
374 | 402 |
375 WebInspector.AnimationUI.Options = { | 403 WebInspector.AnimationUI.Colors = { |
376 AnimationHeight: 26, | 404 'Purple': WebInspector.Color.parse('#9C27B0'), |
377 AnimationSVGHeight: 50, | 405 'Light Blue': WebInspector.Color.parse('#03A9F4'), |
378 AnimationMargin: 7, | 406 'Deep Orange': WebInspector.Color.parse('#FF5722'), |
379 EndpointsClickRegionSize: 10, | 407 'Blue': WebInspector.Color.parse('#5677FC'), |
380 GridCanvasHeight: 40 | 408 'Lime': WebInspector.Color.parse('#CDDC39'), |
| 409 'Blue Grey': WebInspector.Color.parse('#607D8B'), |
| 410 'Pink': WebInspector.Color.parse('#E91E63'), |
| 411 'Green': WebInspector.Color.parse('#0F9D58'), |
| 412 'Brown': WebInspector.Color.parse('#795548'), |
| 413 'Cyan': WebInspector.Color.parse('#00BCD4') |
381 }; | 414 }; |
382 | 415 |
383 WebInspector.AnimationUI.Colors = { | 416 |
384 "Purple": WebInspector.Color.parse("#9C27B0"), | |
385 "Light Blue": WebInspector.Color.parse("#03A9F4"), | |
386 "Deep Orange": WebInspector.Color.parse("#FF5722"), | |
387 "Blue": WebInspector.Color.parse("#5677FC"), | |
388 "Lime": WebInspector.Color.parse("#CDDC39"), | |
389 "Blue Grey": WebInspector.Color.parse("#607D8B"), | |
390 "Pink": WebInspector.Color.parse("#E91E63"), | |
391 "Green": WebInspector.Color.parse("#0F9D58"), | |
392 "Brown": WebInspector.Color.parse("#795548"), | |
393 "Cyan": WebInspector.Color.parse("#00BCD4") | |
394 }; | |
395 | |
396 /** | |
397 * @param {!WebInspector.AnimationModel.Animation} animation | |
398 * @return {string} | |
399 */ | |
400 WebInspector.AnimationUI.Color = function(animation) | |
401 { | |
402 var names = Object.keys(WebInspector.AnimationUI.Colors); | |
403 var color = WebInspector.AnimationUI.Colors[names[String.hashCode(animation.
name() || animation.id()) % names.length]]; | |
404 return color.asString(WebInspector.Color.Format.RGB); | |
405 }; | |
OLD | NEW |