From 9f2d2b30d60e7a58041e4e0bb2bb6b138f886038 Mon Sep 17 00:00:00 2001 From: Daisuke Akatsuka Date: Thu, 18 Jan 2018 12:46:38 +0900 Subject: [PATCH] Bug 1406285 - Part 7: Implement effect timing graph. r=gl MozReview-Commit-ID: DIrt9PdY2Nd --HG-- extra : rebase_source : d3c2829abefda76a8099be746cfd1bb38b236995 --- .../components/graph/EffectTimingPath.js | 72 +++++++++++++++++++ .../components/graph/SummaryGraphPath.js | 17 ++++- .../animation/components/graph/moz.build | 1 + .../inspector/animation/utils/graph-helper.js | 13 ++-- devtools/client/themes/animation.css | 15 ++++ 5 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 devtools/client/inspector/animation/components/graph/EffectTimingPath.js diff --git a/devtools/client/inspector/animation/components/graph/EffectTimingPath.js b/devtools/client/inspector/animation/components/graph/EffectTimingPath.js new file mode 100644 index 000000000000..b2c820b4b6c0 --- /dev/null +++ b/devtools/client/inspector/animation/components/graph/EffectTimingPath.js @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); +const dom = require("devtools/client/shared/vendor/react-dom-factories"); + +const { SummaryGraphHelper, toPathString } = require("../../utils/graph-helper"); +const TimingPath = require("./TimingPath"); + +class EffectTimingPath extends TimingPath { + static get propTypes() { + return { + animation: PropTypes.object.isRequired, + durationPerPixel: PropTypes.number.isRequired, + simulateAnimation: PropTypes.func.isRequired, + totalDuration: PropTypes.number.isRequired, + }; + } + + render() { + const { + animation, + durationPerPixel, + simulateAnimation, + totalDuration, + } = this.props; + + const { state } = animation; + const effectTiming = Object.assign({}, state, { + iterations: state.iterationCount ? state.iterationCount : Infinity + }); + + const simulatedAnimation = simulateAnimation(null, effectTiming, false); + const endTime = simulatedAnimation.effect.getComputedTiming().endTime; + + const getValueFunc = time => { + if (time < 0) { + return { x: time, y: 0 }; + } + + simulatedAnimation.currentTime = time < endTime ? time : endTime; + return Math.max(simulatedAnimation.effect.getComputedTiming().progress, 0); + }; + + const toPathStringFunc = segments => { + const firstSegment = segments[0]; + let pathString = `M${ firstSegment.x },0 `; + pathString += toPathString(segments); + const lastSegment = segments[segments.length - 1]; + pathString += `L${ lastSegment.x },0`; + return pathString; + }; + + const helper = new SummaryGraphHelper(state, null, + totalDuration, durationPerPixel, + getValueFunc, toPathStringFunc); + const offset = state.previousStartTime ? state.previousStartTime : 0; + + return dom.g( + { + className: "animation-effect-timing-path", + transform: `translate(${ offset })` + }, + super.renderGraph(state, helper) + ); + } +} + +module.exports = EffectTimingPath; diff --git a/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js b/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js index 32e0d9afd507..891e3def90c0 100644 --- a/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js +++ b/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js @@ -10,6 +10,9 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories"); const ReactDOM = require("devtools/client/shared/vendor/react-dom"); const ComputedTimingPath = createFactory(require("./ComputedTimingPath")); +const EffectTimingPath = createFactory(require("./EffectTimingPath")); +const { DEFAULT_GRAPH_HEIGHT } = require("../../utils/graph-helper"); + // Minimum opacity for semitransparent fill color for keyframes's easing graph. const MIN_KEYFRAMES_EASING_OPACITY = 0.5; @@ -163,7 +166,8 @@ class SummaryGraphPath extends PureComponent { { className: "animation-summary-graph-path", preserveAspectRatio: "none", - viewBox: `${ startTime } -1 ${ totalDuration } 1` + viewBox: `${ startTime } -${ DEFAULT_GRAPH_HEIGHT } ` + + `${ totalDuration } ${ DEFAULT_GRAPH_HEIGHT }`, }, keyframesList.map(keyframes => ComputedTimingPath( @@ -176,7 +180,18 @@ class SummaryGraphPath extends PureComponent { totalDuration, } ) + ), + animation.state.easing !== "linear" ? + EffectTimingPath( + { + animation, + durationPerPixel, + simulateAnimation, + totalDuration, + } ) + : + null ); } } diff --git a/devtools/client/inspector/animation/components/graph/moz.build b/devtools/client/inspector/animation/components/graph/moz.build index 0a017d5d3d22..cb77d90df99b 100644 --- a/devtools/client/inspector/animation/components/graph/moz.build +++ b/devtools/client/inspector/animation/components/graph/moz.build @@ -4,6 +4,7 @@ DevToolsModules( 'ComputedTimingPath.js', + 'EffectTimingPath.js', 'SummaryGraph.js', 'SummaryGraphPath.js', 'TimingPath.js' diff --git a/devtools/client/inspector/animation/utils/graph-helper.js b/devtools/client/inspector/animation/utils/graph-helper.js index f16da58e0988..21a676434393 100644 --- a/devtools/client/inspector/animation/utils/graph-helper.js +++ b/devtools/client/inspector/animation/utils/graph-helper.js @@ -7,6 +7,9 @@ // BOUND_EXCLUDING_TIME should be less than 1ms and is used to exclude start // and end bounds when dividing duration in createPathSegments. const BOUND_EXCLUDING_TIME = 0.001; +// We define default graph height since if the height of viewport in SVG is +// too small (e.g. 1), vector-effect may not be able to calculate correctly. +const DEFAULT_GRAPH_HEIGHT = 100; // DEFAULT_MIN_PROGRESS_THRESHOLD shoud be between more than 0 to 1. const DEFAULT_MIN_PROGRESS_THRESHOLD = 0.1; // In the createPathSegments function, an animation duration is divided by @@ -14,8 +17,8 @@ const DEFAULT_MIN_PROGRESS_THRESHOLD = 0.1; // 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, then createPathSegments re-divides -// by DURATION_RESOLUTION. +// DEFAULT_MIN_PROGRESS_THRESHOLD * DEFAULT_GRAPH_HEIGHT, then createPathSegments +// re-divides by DURATION_RESOLUTION. // DURATION_RESOLUTION shoud be integer and more than 2. const DURATION_RESOLUTION = 4; @@ -45,7 +48,8 @@ class SummaryGraphHelper { getValueFunc, toPathStringFunc) { this.totalDuration = totalDuration; this.minSegmentDuration = minSegmentDuration; - this.minProgressThreshold = getPreferredProgressThreshold(state, keyframes); + this.minProgressThreshold = + getPreferredProgressThreshold(state, keyframes) * DEFAULT_GRAPH_HEIGHT; this.durationResolution = getPreferredDurationResolution(keyframes); this.getValue = getValueFunc; this.toPathString = toPathStringFunc; @@ -79,7 +83,7 @@ class SummaryGraphHelper { */ getSegment(time) { const value = this.getValue(time); - return { x: time, y: value }; + return { x: time, y: value * DEFAULT_GRAPH_HEIGHT }; } } @@ -235,5 +239,6 @@ function toPathString(segments) { return pathString; } +module.exports.DEFAULT_GRAPH_HEIGHT = DEFAULT_GRAPH_HEIGHT; exports.SummaryGraphHelper = SummaryGraphHelper; exports.toPathString = toPathString; diff --git a/devtools/client/themes/animation.css b/devtools/client/themes/animation.css index 92f1b0bd92f9..a8ce83d4d047 100644 --- a/devtools/client/themes/animation.css +++ b/devtools/client/themes/animation.css @@ -58,14 +58,17 @@ .animation-item.cssanimation { --computed-timing-graph-color: var(--theme-contrast-background); + --effect-timing-graph-color: var(--theme-highlight-lightorange); } .animation-item.csstransition { --computed-timing-graph-color: var(--theme-highlight-blue); + --effect-timing-graph-color: var(--theme-highlight-bluegrey); } .animation-item.scriptanimation { --computed-timing-graph-color: var(--theme-graphs-green); + --effect-timing-graph-color: var(--theme-highlight-green); } /* Animation Target */ @@ -103,6 +106,18 @@ opacity: 0.3; } +.animation-effect-timing-path path { + fill: none; + stroke: var(--effect-timing-graph-color); + stroke-dasharray: 2px 2px; + transform: scale(1, -1); + vector-effect: non-scaling-stroke; +} + +.animation-effect-timing-path path.infinity:nth-child(n+2) { + opacity: 0.3; +} + /* No Animation Panel */ .animation-error-message { overflow: auto;