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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: Source/devtools/front_end/animation/AnimationUI.js
diff --git a/Source/devtools/front_end/animation/AnimationUI.js b/Source/devtools/front_end/animation/AnimationUI.js
new file mode 100644
index 0000000000000000000000000000000000000000..fa3cd59347d759b9efa78cb6ecaffae5d52d50f1
--- /dev/null
+++ b/Source/devtools/front_end/animation/AnimationUI.js
@@ -0,0 +1,455 @@
+// 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
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @constructor
+ * @param {!WebInspector.AnimationModel.Animation} animation
+ * @param {!WebInspector.AnimationTimeline} timeline
+ * @param {!Element} parentElement
+ */
+WebInspector.AnimationUI = function(animation, timeline, parentElement) {
+ this._animation = animation;
+ this._timeline = timeline;
+ this._parentElement = parentElement;
+
+ if (this._animation.source().keyframesRule())
+ this._keyframes = this._animation.source().keyframesRule().keyframes();
+
+ this._nameElement = parentElement.createChild("div", "animation-name");
+ this._nameElement.textContent = this._animation.name();
+
+ this._svg = parentElement.createSVGChild("svg", "animation-ui");
+ this._svg.setAttribute("height", WebInspector.AnimationUI.Options.AnimationSVGHeight);
+ this._svg.style.marginLeft = "-" + WebInspector.AnimationUI.Options.AnimationMargin + "px";
+ this._svg.addEventListener("mousedown", this._mouseDown.bind(this, WebInspector.AnimationUI.MouseEvents.AnimationDrag, null));
+ this._activeIntervalGroup = this._svg.createSVGChild("g");
+
+ /** @type {!Array.<{group: ?Element, animationLine: ?Element, keyframePoints: !Object.<number, !Element>, keyframeRender: !Object.<number, !Element>}>} */
+ this._cachedElements = [];
+
+ this._movementInMs = 0;
+ this._color = WebInspector.AnimationUI.Color(this._animation);
+}
+
+/**
+ * @enum {string}
+ */
+WebInspector.AnimationUI.MouseEvents = {
+ AnimationDrag: "AnimationDrag",
+ KeyframeMove: "KeyframeMove",
+ StartEndpointMove: "StartEndpointMove",
+ FinishEndpointMove: "FinishEndpointMove"
+}
+
+WebInspector.AnimationUI.prototype = {
+ /**
+ * @return {!WebInspector.AnimationModel.Animation}
+ */
+ animation: function()
+ {
+ return this._animation;
+ },
+
+ /**
+ * @param {?WebInspector.DOMNode} node
+ */
+ setNode: function(node)
+ {
+ this._node = node;
+ },
+
+ /**
+ * @param {!Element} parentElement
+ * @param {string} className
+ */
+ _createLine: function(parentElement, className)
+ {
+ var line = parentElement.createSVGChild("line", className);
+ line.setAttribute("x1", WebInspector.AnimationUI.Options.AnimationMargin);
+ line.setAttribute("y1", WebInspector.AnimationUI.Options.AnimationHeight);
+ line.setAttribute("y2", WebInspector.AnimationUI.Options.AnimationHeight);
+ line.style.stroke = this._color;
+ return line;
+ },
+
+ /**
+ * @param {number} iteration
+ * @param {!Element} parentElement
+ */
+ _drawAnimationLine: function(iteration, parentElement)
+ {
+ var cache = this._cachedElements[iteration];
+ if (!cache.animationLine)
+ cache.animationLine = this._createLine(parentElement, "animation-line");
+ cache.animationLine.setAttribute("x2", (this._duration() * this._timeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin).toFixed(2));
+ },
+
+ /**
+ * @param {!Element} parentElement
+ */
+ _drawDelayLine: function(parentElement)
+ {
+ if (!this._delayLine) {
+ this._delayLine = this._createLine(parentElement, "animation-delay-line");
+ this._endDelayLine = this._createLine(parentElement, "animation-delay-line");
+ }
+ this._delayLine.setAttribute("x1", WebInspector.AnimationUI.Options.AnimationMargin);
+ this._delayLine.setAttribute("x2", (this._delay() * this._timeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin).toFixed(2));
+ var leftMargin = (this._delay() + this._duration() * this._animation.source().iterations()) * this._timeline.pixelMsRatio();
+ this._endDelayLine.style.transform = "translateX(" + Math.min(leftMargin, this._timeline.width()).toFixed(2) + "px)";
+ this._endDelayLine.setAttribute("x1", WebInspector.AnimationUI.Options.AnimationMargin);
+ this._endDelayLine.setAttribute("x2", (this._animation.source().endDelay() * this._timeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin).toFixed(2));
+ },
+
+ /**
+ * @param {number} iteration
+ * @param {!Element} parentElement
+ * @param {number} x
+ * @param {number} keyframeIndex
+ * @param {boolean} attachEvents
+ */
+ _drawPoint: function(iteration, parentElement, x, keyframeIndex, attachEvents)
+ {
+ if (this._cachedElements[iteration].keyframePoints[keyframeIndex]) {
+ this._cachedElements[iteration].keyframePoints[keyframeIndex].setAttribute("cx", x.toFixed(2));
+ return;
+ }
+
+ var circle = parentElement.createSVGChild("circle", keyframeIndex <= 0 ? "animation-endpoint" : "animation-keyframe-point");
+ circle.setAttribute("cx", x.toFixed(2));
+ circle.setAttribute("cy", WebInspector.AnimationUI.Options.AnimationHeight);
+ circle.style.stroke = this._color;
+ circle.setAttribute("r", WebInspector.AnimationUI.Options.AnimationMargin / 2);
+
+ if (keyframeIndex <= 0)
+ circle.style.fill = this._color;
+
+ this._cachedElements[iteration].keyframePoints[keyframeIndex] = circle;
+
+ if (!attachEvents)
+ return;
+
+ if (keyframeIndex === 0) {
+ circle.addEventListener("mousedown", this._mouseDown.bind(this, WebInspector.AnimationUI.MouseEvents.StartEndpointMove, keyframeIndex));
+ } else if (keyframeIndex === -1) {
+ circle.addEventListener("mousedown", this._mouseDown.bind(this, WebInspector.AnimationUI.MouseEvents.FinishEndpointMove, keyframeIndex));
+ } else {
+ circle.addEventListener("mousedown", this._mouseDown.bind(this, WebInspector.AnimationUI.MouseEvents.KeyframeMove, keyframeIndex));
+ }
+ },
+
+ /**
+ * @param {number} iteration
+ * @param {number} keyframeIndex
+ * @param {!Element} parentElement
+ * @param {number} leftDistance
+ * @param {number} width
+ * @param {string} easing
+ */
+ _renderKeyframe: function(iteration, keyframeIndex, parentElement, leftDistance, width, easing)
+ {
+ /**
+ * @param {!Element} parentElement
+ * @param {number} x
+ * @param {string} strokeColor
+ */
+ function createStepLine(parentElement, x, strokeColor)
+ {
+ var line = parentElement.createSVGChild("line");
+ line.setAttribute("x1", x);
+ line.setAttribute("x2", x);
+ line.setAttribute("y1", WebInspector.AnimationUI.Options.AnimationMargin);
+ line.setAttribute("y2", WebInspector.AnimationUI.Options.AnimationHeight);
+ line.style.stroke = strokeColor;
+ }
+
+ var bezier = WebInspector.Geometry.CubicBezier.parse(easing);
+ var cache = this._cachedElements[iteration].keyframeRender;
+ if (!cache[keyframeIndex])
+ cache[keyframeIndex] = bezier ? parentElement.createSVGChild("path", "animation-keyframe") : parentElement.createSVGChild("g", "animation-keyframe-step");
+ var group = cache[keyframeIndex];
+ group.style.transform = "translateX(" + leftDistance.toFixed(2) + "px)";
+
+ if (bezier) {
+ group.style.fill = this._color;
+ WebInspector.BezierUI.drawVelocityChart(bezier, group, width);
+ } else {
+ var stepFunction = WebInspector.AnimationTimeline.StepTimingFunction.parse(easing);
+ group.removeChildren();
+ const offsetMap = {"start": 0, "middle": 0.5, "end": 1};
+ const offsetWeight = offsetMap[stepFunction.stepAtPosition];
+ for (var i = 0; i < stepFunction.steps; i++)
+ createStepLine(group, (i + offsetWeight) * width / stepFunction.steps, this._color);
+ }
+ },
+
+ redraw: function()
+ {
+ var durationWithDelay = this._delay() + this._duration() * this._animation.source().iterations() + this._animation.source().endDelay();
+ var leftMargin = ((this._animation.startTime() - this._timeline.startTime()) * this._timeline.pixelMsRatio());
+ var maxWidth = this._timeline.width() - WebInspector.AnimationUI.Options.AnimationMargin - leftMargin;
+ var svgWidth = Math.min(maxWidth, durationWithDelay * this._timeline.pixelMsRatio());
+
+ this._svg.classList.toggle("animation-ui-canceled", this._animation.playState() === "idle");
+ this._svg.setAttribute("width", (svgWidth + 2 * WebInspector.AnimationUI.Options.AnimationMargin).toFixed(2));
+ this._svg.style.transform = "translateX(" + leftMargin.toFixed(2) + "px)";
+ this._activeIntervalGroup.style.transform = "translateX(" + (this._delay() * this._timeline.pixelMsRatio()).toFixed(2) + "px)";
+
+ this._nameElement.style.transform = "translateX(" + (leftMargin + this._delay() * this._timeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin).toFixed(2) + "px)";
+ this._nameElement.style.width = (this._duration() * this._timeline.pixelMsRatio().toFixed(2)) + "px";
+ this._drawDelayLine(this._svg);
+
+ if (this._animation.type() === "CSSTransition") {
+ this._renderTransition();
+ return;
+ }
+
+ this._renderIteration(this._activeIntervalGroup, 0);
+ if (!this._tailGroup)
+ this._tailGroup = this._activeIntervalGroup.createSVGChild("g", "animation-tail-iterations");
+ var iterationWidth = this._duration() * this._timeline.pixelMsRatio();
+ for (var iteration = 1; iteration < this._animation.source().iterations() && iterationWidth * (iteration - 1) < this._timeline.width(); iteration++)
+ this._renderIteration(this._tailGroup, iteration);
+ while (iteration < this._cachedElements.length)
+ this._cachedElements.pop().group.remove();
+ },
+
+
+ _renderTransition: function()
+ {
+ if (!this._cachedElements[0])
+ this._cachedElements[0] = { animationLine: null, keyframePoints: {}, keyframeRender: {}, group: null };
+ this._drawAnimationLine(0, this._activeIntervalGroup);
+ this._renderKeyframe(0, 0, this._activeIntervalGroup, WebInspector.AnimationUI.Options.AnimationMargin, this._duration() * this._timeline.pixelMsRatio(), this._animation.source().easing());
+ this._drawPoint(0, this._activeIntervalGroup, WebInspector.AnimationUI.Options.AnimationMargin, 0, true);
+ this._drawPoint(0, this._activeIntervalGroup, this._duration() * this._timeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin, -1, true);
+ },
+
+ /**
+ * @param {!Element} parentElement
+ * @param {number} iteration
+ */
+ _renderIteration: function(parentElement, iteration)
+ {
+ if (!this._cachedElements[iteration])
+ this._cachedElements[iteration] = { animationLine: null, keyframePoints: {}, keyframeRender: {}, group: parentElement.createSVGChild("g") };
+ var group = this._cachedElements[iteration].group;
+ group.style.transform = "translateX(" + (iteration * this._duration() * this._timeline.pixelMsRatio()).toFixed(2) + "px)";
+ this._drawAnimationLine(iteration, group);
+ console.assert(this._keyframes.length > 1);
+ for (var i = 0; i < this._keyframes.length - 1; i++) {
+ var leftDistance = this._offset(i) * this._duration() * this._timeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin;
+ var width = this._duration() * (this._offset(i + 1) - this._offset(i)) * this._timeline.pixelMsRatio();
+ this._renderKeyframe(iteration, i, group, leftDistance, width, this._keyframes[i].easing());
+ if (i || (!i && iteration === 0))
+ this._drawPoint(iteration, group, leftDistance, i, iteration === 0);
+ }
+ this._drawPoint(iteration, group, this._duration() * this._timeline.pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin, -1, iteration === 0);
+ },
+
+ /**
+ * @return {number}
+ */
+ _delay: function()
+ {
+ var delay = this._animation.source().delay();
+ if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.AnimationDrag || this._mouseEventType === WebInspector.AnimationUI.MouseEvents.StartEndpointMove)
+ delay += this._movementInMs;
+ // FIXME: add support for negative start delay
+ return Math.max(0, delay);
+ },
+
+ /**
+ * @return {number}
+ */
+ _duration: function()
+ {
+ var duration = this._animation.source().duration();
+ if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.FinishEndpointMove)
+ duration += this._movementInMs;
+ else if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.StartEndpointMove)
+ duration -= Math.max(this._movementInMs, -this._animation.source().delay()); // Cannot have negative delay
+ return Math.max(0, duration);
+ },
+
+ /**
+ * @param {number} i
+ * @return {number} offset
+ */
+ _offset: function(i)
+ {
+ var offset = this._keyframes[i].offsetAsNumber();
+ if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.KeyframeMove && i === this._keyframeMoved) {
+ console.assert(i > 0 && i < this._keyframes.length - 1, "First and last keyframe cannot be moved");
+ offset += this._movementInMs / this._animation.source().duration();
+ offset = Math.max(offset, this._keyframes[i - 1].offsetAsNumber());
+ offset = Math.min(offset, this._keyframes[i + 1].offsetAsNumber());
+ }
+ return offset;
+ },
+
+ /**
+ * @param {!WebInspector.AnimationUI.MouseEvents} mouseEventType
+ * @param {?number} keyframeIndex
+ * @param {!Event} event
+ */
+ _mouseDown: function(mouseEventType, keyframeIndex, event)
+ {
+ if (this._animation.playState() === "idle")
+ return;
+ this._mouseEventType = mouseEventType;
+ this._keyframeMoved = keyframeIndex;
+ this._downMouseX = event.clientX;
+ this._mouseMoveHandler = this._mouseMove.bind(this);
+ this._mouseUpHandler = this._mouseUp.bind(this);
+ this._parentElement.ownerDocument.addEventListener("mousemove", this._mouseMoveHandler);
+ this._parentElement.ownerDocument.addEventListener("mouseup", this._mouseUpHandler);
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (this._node)
+ WebInspector.Revealer.reveal(this._node);
+ },
+
+ /**
+ * @param {!Event} event
+ */
+ _mouseMove: function (event)
+ {
+ this._movementInMs = (event.clientX - this._downMouseX) / this._timeline.pixelMsRatio();
+ if (this._animation.startTime() + this._delay() + this._duration() - this._timeline.startTime() > this._timeline.duration() * 0.8)
+ this._timeline.setDuration(this._timeline.duration() * 1.2);
+ this.redraw();
+ },
+
+ /**
+ * @param {!Event} event
+ */
+ _mouseUp: function(event)
+ {
+ this._movementInMs = (event.clientX - this._downMouseX) / this._timeline.pixelMsRatio();
+
+ // Commit changes
+ if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.KeyframeMove) {
+ this._keyframes[this._keyframeMoved].setOffset(this._offset(this._keyframeMoved));
+ } else {
+ var delay = this._delay();
+ var duration = this._duration();
+ this._setDelay(delay);
+ this._setDuration(duration);
+ if (this._animation.type() !== "CSSAnimation") {
+ var target = WebInspector.targetManager.mainTarget();
+ if (target)
+ target.animationAgent().setTiming(this._animation.id(), duration, delay);
+ }
+ }
+
+ this._movementInMs = 0;
+ this.redraw();
+
+ this._parentElement.ownerDocument.removeEventListener("mousemove", this._mouseMoveHandler);
+ this._parentElement.ownerDocument.removeEventListener("mouseup", this._mouseUpHandler);
+ delete this._mouseMoveHandler;
+ delete this._mouseUpHandler;
+ delete this._mouseEventType;
+ delete this._downMouseX;
+ delete this._keyframeMoved;
+ },
+
+ /**
+ * @param {number} value
+ */
+ _setDelay: function(value)
+ {
+ if (!this._node || this._animation.source().delay() == this._delay())
+ return;
+
+ this._animation.source().setDelay(this._delay());
+ var propertyName;
+ if (this._animation.type() == "CSSTransition")
+ propertyName = "transition-delay";
+ else if (this._animation.type() == "CSSAnimation")
+ propertyName = "animation-delay";
+ else
+ return;
+ this._setNodeStyle(propertyName, Math.round(value) + "ms");
+ },
+
+ /**
+ * @param {number} value
+ */
+ _setDuration: function(value)
+ {
+ if (!this._node || this._animation.source().duration() == value)
+ return;
+
+ this._animation.source().setDuration(value);
+ var propertyName;
+ if (this._animation.type() == "CSSTransition")
+ propertyName = "transition-duration";
+ else if (this._animation.type() == "CSSAnimation")
+ propertyName = "animation-duration";
+ else
+ return;
+ this._setNodeStyle(propertyName, Math.round(value) + "ms");
+ },
+
+ /**
+ * @param {string} name
+ * @param {string} value
+ */
+ _setNodeStyle: function(name, value)
+ {
+ var style = this._node.getAttribute("style") || "";
+ if (style)
+ style = style.replace(new RegExp("\\s*(-webkit-)?" + name + ":[^;]*;?\\s*", "g"), "");
+ var valueString = name + ": " + value;
+ this._node.setAttributeValue("style", style + " " + valueString + "; -webkit-" + valueString + ";");
+ }
+}
+
+WebInspector.AnimationUI.Options = {
+ AnimationHeight: 32,
+ AnimationSVGHeight: 80,
+ AnimationMargin: 7,
+ EndpointsClickRegionSize: 10,
+ GridCanvasHeight: 40
+}
+
+WebInspector.AnimationUI.Colors = {
+ "Purple": WebInspector.Color.parse("#9C27B0"),
+ "Light Blue": WebInspector.Color.parse("#03A9F4"),
+ "Deep Orange": WebInspector.Color.parse("#FF5722"),
+ "Blue": WebInspector.Color.parse("#5677FC"),
+ "Lime": WebInspector.Color.parse("#CDDC39"),
+ "Blue Grey": WebInspector.Color.parse("#607D8B"),
+ "Pink": WebInspector.Color.parse("#E91E63"),
+ "Green": WebInspector.Color.parse("#0F9D58"),
+ "Brown": WebInspector.Color.parse("#795548"),
+ "Cyan": WebInspector.Color.parse("#00BCD4")
+}
+
+
+/**
+ * @param {!WebInspector.AnimationModel.Animation} animation
+ * @return {string}
+ */
+WebInspector.AnimationUI.Color = function(animation)
+{
+ /**
+ * @param {string} string
+ * @return {number}
+ */
+ function hash(string)
+ {
+ var hash = 0;
+ for (var i = 0; i < string.length; i++)
+ hash = (hash << 5) + hash + string.charCodeAt(i);
+ return Math.abs(hash);
+ }
+
+ var names = Object.keys(WebInspector.AnimationUI.Colors);
+ var color = WebInspector.AnimationUI.Colors[names[hash(animation.name() || animation.id()) % names.length]];
+ return color.asString(WebInspector.Color.Format.RGB);
+}
« 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