monaca-lib
Version:
Monaca cloud API bindings for JavaScript
611 lines (540 loc) • 22.5 kB
JavaScript
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @constructor
* @extends {WebInspector.VBox}
* @param {!WebInspector.StylesSidebarPane} stylesPane
*/
WebInspector.AnimationTimeline = function(stylesPane)
{
WebInspector.VBox.call(this, true);
this._stylesPane = stylesPane;
this.registerRequiredCSS("elements/animationTimeline.css");
this.element.classList.add("animations-timeline");
this._timeOverlay = this.contentElement.createChild("div", "animation-time-overlay");
this.contentElement.appendChild(this._createHeader());
this._animationsContainer = this.contentElement.createChild("div");
this._animations = [];
this._uiAnimations = [];
this._duration = this._defaultDuration();
WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this);
}
WebInspector.AnimationTimeline.prototype = {
_createHeader: function()
{
/**
* @param {!Event} event
* @this {WebInspector.AnimationTimeline}
*/
function playbackSliderInputHandler(event)
{
this._animationsPlaybackRate = WebInspector.AnimationsSidebarPane.GlobalPlaybackRates[event.target.value];
var target = WebInspector.targetManager.mainTarget();
if (target)
target.pageAgent().setAnimationsPlaybackRate(this._animationsPaused ? 0 : this._animationsPlaybackRate);
this._playbackLabel.textContent = this._animationsPlaybackRate + "x";
WebInspector.userMetrics.AnimationsPlaybackRateChanged.record();
}
var container = createElementWithClass("div", "animation-timeline-header");
var controls = container.createChild("div", "animation-controls");
this._gridMarkers = container.createChild("div", "animation-timeline-markers");
var replayButton = controls.createSVGChild("svg", "animation-control-replay");
replayButton.setAttribute("height", 24);
replayButton.setAttribute("width", 24);
var g = replayButton.createSVGChild("g")
var circle = g.createSVGChild("circle");
circle.setAttribute("cx", 12);
circle.setAttribute("cy", 12);
circle.setAttribute("r", 9);
var triangle = g.createSVGChild("path");
triangle.setAttribute("d", "M 10 8 L 10 16 L 16 12 z");
replayButton.addEventListener("click", this._replay.bind(this));
this._playbackLabel = controls.createChild("div", "source-code animation-playback-label");
this._playbackLabel.createTextChild("1x");
this._playbackSlider = controls.createChild("input", "animation-playback-slider");
this._playbackSlider.type = "range";
this._playbackSlider.min = 0;
this._playbackSlider.max = WebInspector.AnimationsSidebarPane.GlobalPlaybackRates.length - 1;
this._playbackSlider.value = this._playbackSlider.max;
this._playbackSlider.addEventListener("input", playbackSliderInputHandler.bind(this));
return container;
},
_updateAnimationsPlaybackRate: function()
{
/**
* @param {?Protocol.Error} error
* @param {number} playbackRate
* @this {WebInspector.AnimationTimeline}
*/
function setPlaybackRate(error, playbackRate)
{
this._playbackSlider.value = WebInspector.AnimationsSidebarPane.GlobalPlaybackRates.indexOf(playbackRate);
this._playbackLabel.textContent = playbackRate + "x";
}
var target = WebInspector.targetManager.mainTarget();
if (target)
target.pageAgent().getAnimationsPlaybackRate(setPlaybackRate.bind(this));
},
_replay: function()
{
if (this.startTime() === undefined)
return;
var targets = WebInspector.targetManager.targets();
for (var target of targets)
target.pageAgent().setCurrentTime(/** @type {number} */(this.startTime()));
this._animateTime(this.startTime());
},
/**
* @return {number}
*/
_defaultDuration: function ()
{
return 300;
},
/**
* @return {number}
*/
duration: function()
{
return this._duration;
},
/**
* @param {number} duration
*/
setDuration: function(duration)
{
this._duration = duration;
this.redraw();
},
/**
* @return {number|undefined}
*/
startTime: function()
{
return this._startTime;
},
_reset: function()
{
if (!this._animations.length)
return;
this._animations = [];
this._uiAnimations = [];
this._animationsContainer.removeChildren();
this._duration = this._defaultDuration();
delete this._startTime;
},
/**
* @param {!WebInspector.Event} event
*/
_mainFrameNavigated: function(event)
{
this._reset();
},
/**
* @param {!WebInspector.AnimationModel.AnimationPlayer} animation
* @param {boolean} resetTimeline
*/
addAnimation: function(animation, resetTimeline)
{
/**
* @param {!Element} description
* @param {?WebInspector.DOMNode} node
*/
function nodeResolved(description, node)
{
description.appendChild(WebInspector.DOMPresentationUtils.linkifyNodeReference(node));
uiAnimation.setNode(node);
}
if (resetTimeline)
this._reset();
// Ignore Web Animations custom effects & groups
if (animation.type() === "WebAnimation" && animation.source().keyframesRule().keyframes().length === 0)
return;
var row = this._animationsContainer.createChild("div", "animation-row");
var description = row.createChild("div", "animation-node-description");
animation.source().getNode(nodeResolved.bind(null, description));
var container = row.createChild("div", "animation-timeline-row");
this._resizeWindow(animation);
this._animations.push(animation);
var uiAnimation = new WebInspector.AnimationUI(this._stylesPane, animation, this, container);
this._uiAnimations.push(uiAnimation);
this.redraw();
},
redraw: function()
{
for (var i = 0; i < this._uiAnimations.length; i++)
this._uiAnimations[i].redraw();
},
onResize: function()
{
this.redraw();
},
/**
* @param {!WebInspector.AnimationModel.AnimationPlayer} animation
*/
_resizeWindow: function(animation)
{
if (!this._startTime)
this._startTime = animation.startTime();
// This shows at most 2 iterations
var iterations = animation.source().iterations() || 1;
var duration = animation.source().duration() * Math.min(2, iterations);
var requiredDuration = animation.startTime() + duration + animation.source().delay() - this.startTime();
if (requiredDuration > this._duration * 0.8)
this._duration = requiredDuration * 1.5;
this._animateTime(animation.startTime());
},
/**
* @param {number|undefined} startTime
*/
_animateTime: function(startTime)
{
if (!startTime)
return;
if (this._timeOverlayPlayer)
this._timeOverlayPlayer.cancel();
var width = parseInt(window.getComputedStyle(this._animationsContainer).width, 10) - 200 || 0;
this._timeOverlayPlayer = this._timeOverlay.animate([
{ transform: "translateX(0px)" },
{ transform: "translateX(" + width + "px)" }
], { duration: this.duration(), fill: 'forwards' });
this._timeOverlayPlayer.currentTime = startTime - this._startTime;
},
__proto__: WebInspector.VBox.prototype
}
/**
* @constructor
* @param {!WebInspector.StylesSidebarPane} stylesPane
* @param {!WebInspector.AnimationModel.AnimationPlayer} animation
* @param {!WebInspector.AnimationTimeline} timeline
* @param {!Element} parentElement
*/
WebInspector.AnimationUI = function(stylesPane, animation, timeline, parentElement) {
this._stylesPane = stylesPane;
this._animation = animation;
this._timeline = timeline;
this._parentElement = parentElement;
this._grid = parentElement.createChild("canvas", "animation-timeline-grid-row");
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");
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._svgGroup = this._svg.createSVGChild("g");
this._movementInMs = 0;
this.redraw();
}
/**
* @enum {string}
*/
WebInspector.AnimationUI.MouseEvents = {
AnimationDrag: "AnimationDrag",
KeyframeMove: "KeyframeMove",
StartEndpointMove: "StartEndpointMove",
FinishEndpointMove: "FinishEndpointMove"
}
WebInspector.AnimationUI.prototype = {
/**
* @param {?WebInspector.DOMNode} node
*/
setNode: function(node)
{
this._node = node;
},
_renderGrid: function()
{
var width = parseInt(window.getComputedStyle(this._parentElement).width, 10);
const height = WebInspector.AnimationUI.Options.GridCanvasHeight;
const minorMs = 100;
const majorMs = minorMs * 5;
this._grid.width = width * window.devicePixelRatio;
this._grid.height = height * window.devicePixelRatio;
this._grid.style.width = width + "px";
this._grid.style.height = height + "px";
var ctx = this._grid.getContext("2d");
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
// Draw minor lines
ctx.beginPath();
var minorIncrement = width * minorMs / this._timeline.duration();
for (var x = minorIncrement; x <= width; x += minorIncrement) {
var xr = Math.round(x);
ctx.moveTo(xr + 0.5, 0);
ctx.lineTo(xr + 0.5, height);
}
ctx.strokeStyle = "rgba(0,0,0,0.07)";
ctx.lineWidth = 1;
ctx.stroke();
// Draw major lines
ctx.beginPath();
var majorIncrement = width * majorMs / this._timeline.duration();
for (var x = majorIncrement; x < width; x += majorIncrement) {
var xr = Math.round(x);
ctx.moveTo(xr + 0.5, 0);
ctx.lineTo(xr + 0.5, height);
}
ctx.strokeStyle = "rgba(0,0,0,0.15)";
ctx.lineWidth = 1;
ctx.stroke();
},
_drawAnimationLine: function()
{
var line = this._svgGroup.createSVGChild("line", "animation-line");
line.setAttribute("x1", WebInspector.AnimationUI.Options.AnimationMargin);
line.setAttribute("y1", WebInspector.AnimationUI.Options.AnimationHeight);
line.setAttribute("x2", this._duration() * this._pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin);
line.setAttribute("y2", WebInspector.AnimationUI.Options.AnimationHeight);
line.style.stroke = this._color().asString(WebInspector.Color.Format.RGB);
},
/**
* @param {number} x
* @param {number} keyframeIndex
*/
_drawPoint: function(x, keyframeIndex)
{
var circle = this._svgGroup.createSVGChild("circle", keyframeIndex <= 0 ? "animation-endpoint" : "animation-keyframe-point");
circle.setAttribute("cx", x);
circle.setAttribute("cy", WebInspector.AnimationUI.Options.AnimationHeight);
circle.style.stroke = this._color().asString(WebInspector.Color.Format.RGB);
circle.setAttribute("r", WebInspector.AnimationUI.Options.AnimationMargin / 2);
if (keyframeIndex <= 0)
circle.style.fill = this._color().asString(WebInspector.Color.Format.RGB);
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} leftDistance
* @param {number} width
* @param {!WebInspector.Geometry.CubicBezier} bezier
*/
_renderBezierKeyframe: function(leftDistance, width, bezier)
{
var path = this._svgGroup.createSVGChild("path", "animation-keyframe");
path.style.transform = "translateX(" + leftDistance + "px)";
path.style.fill = this._color().asString(WebInspector.Color.Format.RGB);
WebInspector.BezierUI.drawVelocityChart(bezier, path, width);
},
redraw: function()
{
this._renderGrid();
var animationWidth = this._duration() * this._pixelMsRatio() + 2 * WebInspector.AnimationUI.Options.AnimationMargin;
var leftMargin = (this._animation.startTime() - this._timeline.startTime() + this._delay()) * this._pixelMsRatio();
this._svg.setAttribute("width", animationWidth);
this._svg.style.transform = "translateX(" + leftMargin + "px)";
this._nameElement.style.transform = "translateX(" + leftMargin + "px)";
this._nameElement.style.width = animationWidth + "px";
this._svgGroup.removeChildren();
this._drawAnimationLine();
if (this._animation.type() == "CSSTransition") {
var bezier = WebInspector.Geometry.CubicBezier.parse(this._animation.source().easing());
// FIXME: add support for step functions
if (bezier)
this._renderBezierKeyframe(WebInspector.AnimationUI.Options.AnimationMargin, this._duration() * this._pixelMsRatio(), bezier);
this._drawPoint(WebInspector.AnimationUI.Options.AnimationMargin, 0);
} else {
console.assert(this._keyframes.length > 1);
for (var i = 0; i < this._keyframes.length - 1; i++) {
var leftDistance = this._offset(i) * this._duration() * this._pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin;
var width = this._duration() * (this._offset(i + 1) - this._offset(i)) * this._pixelMsRatio();
var bezier = WebInspector.Geometry.CubicBezier.parse(this._keyframes[i].easing());
// FIXME: add support for step functions
if (bezier)
this._renderBezierKeyframe(leftDistance, width, bezier);
this._drawPoint(leftDistance, i);
}
}
this._drawPoint(this._duration() * this._pixelMsRatio() + WebInspector.AnimationUI.Options.AnimationMargin, -1);
},
/**
* @return {number}
*/
_pixelMsRatio: function()
{
return parseInt(window.getComputedStyle(this._parentElement).width, 10) / this._timeline.duration() || 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 -= this._movementInMs;
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)
{
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._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._pixelMsRatio();
// Commit changes
if (this._mouseEventType === WebInspector.AnimationUI.MouseEvents.KeyframeMove) {
this._keyframes[this._keyframeMoved].setOffset(this._offset(this._keyframeMoved));
} else {
this._setDelay(this._delay());
this._setDuration(this._duration());
}
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; // FIXME: support web animations
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; // FIXME: support web animations
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 + ";");
},
/**
* @return {!WebInspector.Color}
*/
_color: function()
{
/**
* @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);
}
if (!this._selectedColor) {
var names = Object.keys(WebInspector.AnimationUI.Colors);
this._selectedColor = WebInspector.AnimationUI.Colors[names[hash(this._animation.name()) % names.length]];
}
return this._selectedColor;
}
}
WebInspector.AnimationUI.Options = {
AnimationHeight: 32,
AnimationSVGHeight: 80,
AnimationMargin: 8,
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")
}