Bug 1383974 - Part 1: Display easing in keyframes. r=pbro

MozReview-Commit-ID: 8pIUMAurfS3

--HG--
extra : rebase_source : d7acb339c012bd67cb1a68975e6934afa995eb5c
This commit is contained in:
Daisuke Akatsuka 2017-09-25 08:44:05 +09:00
Родитель f23bf7a153
Коммит 054392da3b
4 изменённых файлов: 220 добавлений и 56 удалений

Просмотреть файл

@ -462,7 +462,7 @@ function renderGraph(parentEl, state, totalDisplayedDuration, className, graphHe
function renderDelay(parentEl, state, graphHelper) {
const startSegment = graphHelper.getSegment(0);
const endSegment = { x: state.delay, y: startSegment.y };
graphHelper.appendPathElement(parentEl, [startSegment, endSegment], "delay-path");
graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "delay-path");
}
/**
@ -478,7 +478,7 @@ function renderFirstIteration(parentEl, state, mainIterationStartTime,
const startTime = mainIterationStartTime;
const endTime = startTime + firstSectionCount * state.duration;
const segments = graphHelper.createPathSegments(startTime, endTime);
graphHelper.appendPathElement(parentEl, segments, "iteration-path");
graphHelper.appendShapePath(parentEl, segments, "iteration-path");
}
/**
@ -499,7 +499,7 @@ function renderMiddleIterations(parentEl, state, mainIterationStartTime,
const startTime = offset + i * state.duration;
const endTime = startTime + state.duration;
const segments = graphHelper.createPathSegments(startTime, endTime);
graphHelper.appendPathElement(parentEl, segments, "iteration-path");
graphHelper.appendShapePath(parentEl, segments, "iteration-path");
}
}
@ -520,7 +520,7 @@ function renderLastIteration(parentEl, state, mainIterationStartTime,
(firstSectionCount + middleSectionCount) * state.duration;
const endTime = startTime + lastSectionCount * state.duration;
const segments = graphHelper.createPathSegments(startTime, endTime);
graphHelper.appendPathElement(parentEl, segments, "iteration-path");
graphHelper.appendShapePath(parentEl, segments, "iteration-path");
}
/**
@ -552,7 +552,7 @@ function renderInfinity(parentEl, state, mainIterationStartTime,
const firstEndTime = firstStartTime + state.duration;
const firstSegments =
graphHelper.createPathSegments(firstStartTime, firstEndTime);
graphHelper.appendPathElement(parentEl, firstSegments, "iteration-path infinity");
graphHelper.appendShapePath(parentEl, firstSegments, "iteration-path infinity");
// Append other iterations. We can copy first segments.
const isAlternate = state.direction.match(/alternate/);
@ -570,7 +570,7 @@ function renderInfinity(parentEl, state, mainIterationStartTime,
return { x: segment.x - firstStartTime + startTime, y: segment.y };
});
}
graphHelper.appendPathElement(parentEl, segments, "iteration-path infinity copied");
graphHelper.appendShapePath(parentEl, segments, "iteration-path infinity copied");
}
}
@ -587,7 +587,7 @@ function renderEndDelay(parentEl, state,
const startTime = mainIterationStartTime + iterationCount * state.duration;
const startSegment = graphHelper.getSegment(startTime);
const endSegment = { x: startTime + state.endDelay, y: startSegment.y };
graphHelper.appendPathElement(parentEl, [startSegment, endSegment], "enddelay-path");
graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "enddelay-path");
}
/**
@ -605,8 +605,7 @@ function renderForwardsFill(parentEl, state, mainIterationStartTime,
(state.endDelay > 0 ? state.endDelay : 0);
const startSegment = graphHelper.getSegment(startTime);
const endSegment = { x: totalDuration, y: startSegment.y };
graphHelper.appendPathElement(parentEl, [startSegment, endSegment],
"fill-forwards-path");
graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "fill-forwards-path");
}
/**
@ -620,7 +619,7 @@ function renderNegativeDelayHiddenProgress(parentEl, state, graphHelper) {
const endTime = 0;
const segments =
graphHelper.createPathSegments(startTime, endTime);
graphHelper.appendPathElement(parentEl, segments, "delay-path negative");
graphHelper.appendShapePath(parentEl, segments, "delay-path negative");
}
/**
@ -633,7 +632,7 @@ function renderNegativeEndDelayHiddenProgress(parentEl, state, graphHelper) {
const endTime = state.delay + state.iterationCount * state.duration;
const startTime = endTime + state.endDelay;
const segments = graphHelper.createPathSegments(startTime, endTime);
graphHelper.appendPathElement(parentEl, segments, "enddelay-path negative");
graphHelper.appendShapePath(parentEl, segments, "enddelay-path negative");
}
/**

Просмотреть файл

@ -9,7 +9,7 @@
const {createNode, createSVGNode} =
require("devtools/client/animationinspector/utils");
const {ProgressGraphHelper, getPreferredKeyframesProgressThreshold} =
require("devtools/client/animationinspector/graph-helper.js");
require("devtools/client/animationinspector/graph-helper.js");
// Counter for linearGradient ID.
let LINEAR_GRADIENT_ID_COUNTER = 0;
@ -55,22 +55,14 @@ Keyframes.prototype = {
// so we use animation.state.duration as total duration.
const totalDuration = animation.state.duration;
// Calculate stroke height in viewBox to display stroke of path.
const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight;
// Minimum segment duration is the duration of one pixel.
const minSegmentDuration =
totalDuration / this.containerEl.clientWidth;
// Set viewBox.
graphEl.setAttribute("viewBox",
`0 -${ 1 + strokeHeightForViewBox }
${ totalDuration }
${ 1 + strokeHeightForViewBox * 2 }`);
// Create graph helper to render the animation property graph.
const win = this.containerEl.ownerGlobal;
const graphHelper =
new ProgressGraphHelper(this.containerEl.ownerDocument.defaultView,
propertyName, animationType, keyframes, totalDuration);
new ProgressGraphHelper(win, propertyName, animationType, keyframes, totalDuration);
renderPropertyGraph(graphEl, totalDuration, minSegmentDuration,
getPreferredKeyframesProgressThreshold(keyframes), graphHelper);
@ -78,6 +70,19 @@ Keyframes.prototype = {
// Destroy ProgressGraphHelper resources.
graphHelper.destroy();
// Set viewBox which includes invisible stroke width.
// At first, calculate invisible stroke width from maximum width.
// The reason why divide by 2 is that half of stroke width will be invisible
// if we use 0 or 1 for y axis.
const maxStrokeWidth =
win.getComputedStyle(graphEl.querySelector(".keyframes svg .hint")).strokeWidth;
const invisibleStrokeWidthInViewBox =
maxStrokeWidth / 2 / this.containerEl.clientHeight;
graphEl.setAttribute("viewBox",
`0 -${ 1 + invisibleStrokeWidthInViewBox }
${ totalDuration }
${ 1 + invisibleStrokeWidthInViewBox * 2 }`);
// Append elements to display keyframe values.
this.keyframesEl.classList.add(animation.state.type);
for (let frame of this.keyframes) {
@ -110,7 +115,8 @@ function renderPropertyGraph(parentEl, duration, minSegmentDuration,
const graphType = graphHelper.getGraphType();
if (graphType !== "color") {
graphHelper.appendPathElement(parentEl, segments, graphType);
graphHelper.appendShapePath(parentEl, segments, graphType);
renderEasingHint(parentEl, segments, graphHelper);
return;
}
@ -118,7 +124,7 @@ function renderPropertyGraph(parentEl, duration, minSegmentDuration,
segments.forEach(segment => {
segment.y = 1;
});
const path = graphHelper.appendPathElement(parentEl, segments, graphType);
const path = graphHelper.appendShapePath(parentEl, segments, graphType);
const defEl = createSVGNode({
parent: parentEl,
nodeType: "def"
@ -142,4 +148,105 @@ function renderPropertyGraph(parentEl, duration, minSegmentDuration,
});
});
path.style.fill = `url(#${ id })`;
renderEasingHintForColor(parentEl, graphHelper);
}
/**
* Renders the easing hint.
* This method renders an emphasized path over the easing path for a keyframe.
* It appears when hovering over the easing.
* It also renders a tooltip that appears when hovering.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Array} path segments - [{x: {Number} time, y: {Number} progress}, ...]
* @param {ProgressGraphHelper} graphHelper - The object of ProgressGraphHelper.
*/
function renderEasingHint(parentEl, segments, helper) {
const keyframes = helper.getKeyframes();
const duration = helper.getDuration();
// Split segments for each keyframe.
for (let i = 0, indexOfSegments = 0; i < keyframes.length - 1; i++) {
const startKeyframe = keyframes[i];
const startTime = startKeyframe.offset * duration;
const endKeyframe = keyframes[i + 1];
const endTime = endKeyframe.offset * duration;
const keyframeSegments = [];
for (; indexOfSegments < segments.length; indexOfSegments++) {
const segment = segments[indexOfSegments];
if (segment.x < startTime) {
// If previous easings were linear, we need to increment the indexOfSegments.
continue;
}
if (segment.x > endTime) {
indexOfSegments -= 1;
break;
}
keyframeSegments.push(segment);
}
// If keyframeSegments does not have segment which is at startTime,
// get and set the segment.
if (keyframeSegments[0].x !== startTime) {
keyframeSegments.unshift(helper.getSegment(startTime));
}
// Also, endTime.
if (keyframeSegments[keyframeSegments.length - 1].x !== endTime) {
keyframeSegments.push(helper.getSegment(endTime));
}
// Append easing hint as text and emphasis path.
const gEl = createSVGNode({
parent: parentEl,
nodeType: "g"
});
createSVGNode({
parent: gEl,
nodeType: "title",
textContent: startKeyframe.easing
});
helper.appendLinePath(gEl, keyframeSegments, `${helper.getGraphType()} hint`);
}
}
/**
* Render easing hint for properties that are represented by color.
* This method render as text only.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {ProgressGraphHelper} graphHelper - The object of ProgressGraphHalper.
*/
function renderEasingHintForColor(parentEl, helper) {
const keyframes = helper.getKeyframes();
const duration = helper.getDuration();
// Split segments for each keyframe.
for (let i = 0; i < keyframes.length - 1; i++) {
const startKeyframe = keyframes[i];
const startTime = startKeyframe.offset * duration;
const endKeyframe = keyframes[i + 1];
const endTime = endKeyframe.offset * duration;
// Append easing hint.
const gEl = createSVGNode({
parent: parentEl,
nodeType: "g"
});
createSVGNode({
parent: gEl,
nodeType: "title",
textContent: startKeyframe.easing
});
createSVGNode({
parent: gEl,
nodeType: "rect",
attributes: {
x: startTime,
y: -1,
width: endTime - startTime,
height: 1,
class: "hint",
}
});
}
}

Просмотреть файл

@ -85,6 +85,22 @@ ProgressGraphHelper.prototype = {
this.win = null;
},
/**
* Return animation duration.
* @return {Number} duration
*/
getDuration: function () {
return this.animation.effect.timing.duration;
},
/**
* Return animation's keyframe.
* @return {Object} keyframe
*/
getKeyframes: function () {
return this.keyframes;
},
/**
* Return graph type.
* @return {String} if property is 'opacity' or 'transform', return that value.
@ -248,14 +264,27 @@ ProgressGraphHelper.prototype = {
},
/**
* Append path element.
* Append path element as shape. Also, this method appends two segment
* that are {start x, 0} and {end x, 0} to make shape.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
* @param {String} cls - Class name.
* @return {Element} path element.
*/
appendPathElement: function (parentEl, pathSegments, cls) {
return appendPathElement(parentEl, pathSegments, cls);
appendShapePath: function (parentEl, pathSegments, cls) {
return appendShapePath(parentEl, pathSegments, cls);
},
/**
* Append path element as line.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
* @param {String} cls - Class name.
* @return {Element} path element.
*/
appendLinePath: function (parentEl, pathSegments, cls) {
const isClosePathNeeded = false;
return appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded);
},
};
@ -271,7 +300,7 @@ exports.ProgressGraphHelper = ProgressGraphHelper;
* setFillMode:
* Animation fill-mode (e.g. "none", "backwards", "forwards" or "both")
* setClosePathNeeded:
* If true, appendPathElement make the last segment of <path> element to
* If true, appendShapePath make the last segment of <path> element to
* "close" segment("Z").
* Therefore, if don't need under-line of graph, please set false.
* setOriginalBehavior:
@ -360,7 +389,7 @@ SummaryGraphHelper.prototype = {
},
/**
* Set true if need to close path in appendPathElement.
* Set true if need to close path in appendShapePath.
* @param {bool} isClosePathNeeded - true: close, false: open.
*/
setClosePathNeeded: function (isClosePathNeeded) {
@ -411,14 +440,15 @@ SummaryGraphHelper.prototype = {
},
/**
* Append path element.
* Append path element as shape. Also, this method appends two segment
* that are {start x, 0} and {end x, 0} to make shape.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
* @param {String} cls - Class name.
* @return {Element} path element.
*/
appendPathElement: function (parentEl, pathSegments, cls) {
return appendPathElement(parentEl, pathSegments, cls, this.isClosePathNeeded);
appendShapePath: function (parentEl, pathSegments, cls) {
return appendShapePath(parentEl, pathSegments, cls, this.isClosePathNeeded);
},
/**
@ -498,40 +528,50 @@ function createPathSegments(startTime, endTime, minSegmentDuration,
}
/**
* Append path element.
* Append path element as shape. Also, this method appends two segment
* that are {start x, 0} and {end x, 0} to make shape.
* But does not affect given pathSegments.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
* @param {String} cls - Class name.
* @param {bool} isClosePathNeeded - Set true if need to close the path. (default true)
* @return {Element} path element.
*/
function appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded = true) {
function appendShapePath(parentEl, pathSegments, cls, isClosePathNeeded = true) {
const segments = [
{ x: pathSegments[0].x, y: 0 },
...pathSegments,
{ x: pathSegments[pathSegments.length - 1].x, y: 0 }
];
return appendPathElement(parentEl, segments, cls, isClosePathNeeded);
}
/**
* Append path element.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
* @param {String} cls - Class name.
* @param {bool} isClosePathNeeded - Set true if need to close the path.
* @return {Element} path element.
*/
function appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded) {
// Create path string.
let path = `M${ pathSegments[0].x },0`;
for (let i = 0; i < pathSegments.length; i++) {
const pathSegment = pathSegments[i];
if (!pathSegment.easing || pathSegment.easing === "linear") {
path += createLinePathString(pathSegment);
continue;
}
if (i + 1 === pathSegments.length) {
// We already create steps or cubic-bezier path string in previous.
break;
}
const nextPathSegment = pathSegments[i + 1];
let createPathFunction;
if (pathSegment.easing.startsWith("steps")) {
createPathFunction = createStepsPathString;
} else if (pathSegment.easing.startsWith("frames")) {
createPathFunction = createFramesPathString;
let currentSegment = pathSegments[0];
let path = `M${ currentSegment.x },${ currentSegment.y }`;
for (let i = 1; i < pathSegments.length; i++) {
const currentEasing = currentSegment.easing ? currentSegment.easing : "linear";
const nextSegment = pathSegments[i];
if (currentEasing === "linear") {
path += createLinePathString(nextSegment);
} else if (currentEasing.startsWith("steps")) {
path += createStepsPathString(currentSegment, nextSegment);
} else if (currentEasing.startsWith("frames")) {
path += createFramesPathString(currentSegment, nextSegment);
} else {
createPathFunction = createCubicBezierPathString;
path += createCubicBezierPathString(currentSegment, nextSegment);
}
path += createPathFunction(pathSegment, nextPathSegment);
currentSegment = nextSegment;
}
path += ` L${ pathSegments[pathSegments.length - 1].x },0`;
if (isClosePathNeeded) {
path += " Z";
}

Просмотреть файл

@ -697,6 +697,24 @@ body {
height: 100%;
}
.keyframes svg .hint {
stroke-opacity: 0;
stroke-linecap: round;
stroke-width: 5;
}
.keyframes svg path.hint {
fill: none;
}
.keyframes svg path.hint:hover {
stroke-opacity: 1;
}
.keyframes svg rect.hint {
fill-opacity: .1;
}
.animation-detail {
position: relative;
width: 100%;