Bug 1416106 - Part 10: Implement easing hit. r?gl
MozReview-Commit-ID: 5d6f1dysdxm
--- a/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js
@@ -62,16 +62,58 @@ class ColorPath extends ComputedStylePat
const { baseValue, maxDistance } = this.state;
const value = getRGBA(computedStyle);
return getRGBADistance(baseValue, value) / maxDistance;
}
/**
* Overide parent's method.
*/
+ renderEasingHint() {
+ const {
+ easingHintStrokeWidth,
+ graphHeight,
+ totalDuration,
+ values,
+ } = this.props;
+
+ const hints = [];
+
+ for (let i = 0; i < values.length - 1; i++) {
+ const startKeyframe = values[i];
+ const endKeyframe = values[i + 1];
+ const startTime = startKeyframe.offset * totalDuration;
+ const endTime = endKeyframe.offset * totalDuration;
+
+ const g = dom.g(
+ {
+ className: "hint"
+ },
+ dom.title({}, startKeyframe.easing),
+ dom.rect(
+ {
+ x: startTime,
+ y: -graphHeight,
+ height: graphHeight,
+ width: endTime - startTime,
+ style: {
+ "stroke-width": easingHintStrokeWidth,
+ },
+ }
+ )
+ );
+ hints.push(g);
+ }
+
+ return hints;
+ }
+
+ /**
+ * Overide parent's method.
+ */
renderPathSegments(segments) {
for (const segment of segments) {
segment.y = 1;
}
const lastSegment = segments[segments.length - 1];
const id = `color-property-${ LINEAR_GRADIENT_ID_COUNT++ }`;
const path = super.renderPathSegments(segments, { fill: `url(#${ id })` });
--- a/devtools/client/inspector/animation/components/keyframes-graph/ComputedStylePath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/ComputedStylePath.js
@@ -36,16 +36,17 @@ const {
* e.g. 0
* @return {Number}
* e.g. 0 (should be 0 - 1.0)
*/
class ComputedStylePath extends PureComponent {
static get propTypes() {
return {
componentWidth: PropTypes.number.isRequired,
+ easingHintStrokeWidth: PropTypes.number.isRequired,
graphHeight: PropTypes.number.isRequired,
simulateAnimation: PropTypes.func.isRequired,
totalDuration: PropTypes.number.isRequired,
values: PropTypes.array.isRequired,
};
}
/**
@@ -106,32 +107,92 @@ class ComputedStylePath extends PureComp
for (const segment of segments) {
segment.x += offset;
}
return segments;
}
/**
+ * Render easing hint from given path segments.
+ *
+ * @param {Array} segments
+ * Path segments.
+ * @return {Element}
+ * Element which represents easing hint.
+ */
+ renderEasingHint(segments) {
+ const {
+ easingHintStrokeWidth,
+ totalDuration,
+ values,
+ } = this.props;
+
+ const hints = [];
+
+ for (let i = 0, indexOfSegments = 0; i < values.length - 1; i++) {
+ const startKeyframe = values[i];
+ const endKeyframe = values[i + 1];
+ const endTime = endKeyframe.offset * totalDuration;
+ const hintSegments = [];
+
+ for (; indexOfSegments < segments.length; indexOfSegments++) {
+ const segment = segments[indexOfSegments];
+ hintSegments.push(segment);
+
+ if (startKeyframe.offset === endKeyframe.offset) {
+ hintSegments.push(segments[++indexOfSegments]);
+ break;
+ } else if (segment.x === endTime) {
+ break;
+ }
+ }
+
+ const g = dom.g(
+ {
+ className: "hint"
+ },
+ dom.title({}, startKeyframe.easing),
+ dom.path(
+ {
+ d: `M${ hintSegments[0].x },${ hintSegments[0].y } ` +
+ toPathString(hintSegments),
+ style: {
+ "stroke-width": easingHintStrokeWidth,
+ }
+ }
+ )
+ );
+
+ hints.push(g);
+ }
+
+ return hints;
+ }
+
+ /**
* Render graph. This method returns React dom.
*
* @return {Element}
*/
renderGraph() {
const { values } = this.props;
const segments = [];
for (let i = 0; i < values.length - 1; i++) {
const startValue = values[i];
const endValue = values[i + 1];
segments.push(...this.getPathSegments(startValue, endValue));
}
- return this.renderPathSegments(segments);
+ return [
+ this.renderPathSegments(segments),
+ this.renderEasingHint(segments)
+ ];
}
/**
* Return react dom fron given path segments.
*
* @param {Array} segments
* @param {Object} style
* @return {Element}
--- a/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraphPath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraphPath.js
@@ -9,16 +9,17 @@ const dom = require("devtools/client/sha
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const ColorPath = createFactory(require("./ColorPath"));
const DiscretePath = createFactory(require("./DiscretePath"));
const DistancePath = createFactory(require("./DistancePath"));
const {
+ DEFAULT_EASING_HINT_STROKE_WIDTH,
DEFAULT_GRAPH_HEIGHT,
DEFAULT_KEYFRAMES_GRAPH_DURATION,
} = require("../../utils/graph-helper");
class KeyframesGraphPath extends PureComponent {
static get propTypes() {
return {
getComputedStyle: PropTypes.func.isRequired,
@@ -28,16 +29,17 @@ class KeyframesGraphPath extends PureCom
values: PropTypes.array.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
+ componentHeight: 0,
componentWidth: 0,
};
}
componentDidMount() {
this.updateState();
}
@@ -49,45 +51,55 @@ class KeyframesGraphPath extends PureCom
return DiscretePath;
default :
return DistancePath;
}
}
updateState() {
const thisEl = ReactDOM.findDOMNode(this);
- this.setState({ componentWidth: thisEl.parentNode.clientWidth });
+ this.setState({
+ componentHeight: thisEl.parentNode.clientHeight,
+ componentWidth: thisEl.parentNode.clientWidth,
+ });
}
render() {
const {
getComputedStyle,
property,
simulateAnimation,
type,
values,
} = this.props;
- const { componentWidth } = this.state;
+ const {
+ componentHeight,
+ componentWidth,
+ } = this.state;
if (!componentWidth) {
return dom.svg();
}
const pathComponent = this.getPathComponent(type);
+ const strokeWidthInViewBox =
+ DEFAULT_EASING_HINT_STROKE_WIDTH / 2 / componentHeight * DEFAULT_GRAPH_HEIGHT;
return dom.svg(
{
className: "keyframes-graph-path",
preserveAspectRatio: "none",
- viewBox: `0 -${ DEFAULT_GRAPH_HEIGHT } `
- + `${ DEFAULT_KEYFRAMES_GRAPH_DURATION } ${ DEFAULT_GRAPH_HEIGHT }`,
+ viewBox: `0 -${ DEFAULT_GRAPH_HEIGHT + strokeWidthInViewBox } ` +
+ `${ DEFAULT_KEYFRAMES_GRAPH_DURATION } ` +
+ `${ DEFAULT_GRAPH_HEIGHT + strokeWidthInViewBox * 2 }`,
},
pathComponent(
{
componentWidth,
+ easingHintStrokeWidth: DEFAULT_EASING_HINT_STROKE_WIDTH,
getComputedStyle,
graphHeight: DEFAULT_GRAPH_HEIGHT,
property,
simulateAnimation,
totalDuration: DEFAULT_KEYFRAMES_GRAPH_DURATION,
values,
}
)
--- a/devtools/client/inspector/animation/utils/graph-helper.js
+++ b/devtools/client/inspector/animation/utils/graph-helper.js
@@ -18,16 +18,18 @@ const DEFAULT_MIN_PROGRESS_THRESHOLD = 0
// DEFAULT_DURATION_RESOLUTION in order to draw the way the animation progresses.
// But depending on the timing-function, we may be not able to make the graph
// smoothly progress if this resolution is not high enough.
// So, if the difference of animation progress between 2 divisions is more than
// DEFAULT_MIN_PROGRESS_THRESHOLD * DEFAULT_GRAPH_HEIGHT, then createPathSegments
// re-divides by DEFAULT_DURATION_RESOLUTION.
// DEFAULT_DURATION_RESOLUTION shoud be integer and more than 2.
const DEFAULT_DURATION_RESOLUTION = 4;
+// Stroke width for easing hint.
+const DEFAULT_EASING_HINT_STROKE_WIDTH = 5;
/**
* The helper class for creating summary graph.
*/
class SummaryGraphHelper {
/**
* Constructor.
*
@@ -255,14 +257,15 @@ function toPathString(segments) {
segments.forEach(segment => {
pathString += `L${ segment.x },${ segment.y } `;
});
return pathString;
}
exports.createPathSegments = createPathSegments;
exports.DEFAULT_DURATION_RESOLUTION = DEFAULT_DURATION_RESOLUTION;
+exports.DEFAULT_EASING_HINT_STROKE_WIDTH = DEFAULT_EASING_HINT_STROKE_WIDTH;
exports.DEFAULT_GRAPH_HEIGHT = DEFAULT_GRAPH_HEIGHT;
exports.DEFAULT_KEYFRAMES_GRAPH_DURATION = DEFAULT_KEYFRAMES_GRAPH_DURATION;
exports.getPreferredProgressThresholdByKeyframes =
getPreferredProgressThresholdByKeyframes;
exports.SummaryGraphHelper = SummaryGraphHelper;
exports.toPathString = toPathString;
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -355,17 +355,17 @@
.animated-property-name.warning span {
text-decoration: underline dotted;
}
/* Keyframes Graph */
.keyframes-graph {
height: 100%;
- padding-top: 5px;
+ padding-top: 3px;
width: calc(100% - var(--sidebar-width) - var(--graph-right-offset));
}
.keyframes-graph-path {
height: 100%;
width: 100%;
}
@@ -385,16 +385,37 @@
fill: #ea800088;
stroke: #ea8000;
}
.keyframes-graph-path .color-path path {
stroke: none;
}
+.keyframes-graph .keyframes-graph-path .hint path {
+ fill: none;
+ stroke-linecap: round;
+ stroke-opacity: 0;
+}
+
+.keyframes-graph-path .hint path:hover {
+ stroke-opacity: 1;
+}
+
+.keyframes-graph-path .hint rect {
+ fill-opacity: 0.1;
+ stroke: #00b0bd;
+ stroke-opacity: 0;
+ vector-effect: non-scaling-stroke;
+}
+
+.keyframes-graph-path .hint rect:hover {
+ stroke-opacity: 1;
+}
+
/* No Animation Panel */
.animation-error-message {
overflow: auto;
}
.animation-error-message > p {
white-space: pre;
}