Bug 1431573 - Part 4: Implement time label. r=gl

MozReview-Commit-ID: Cg6A4hNLXnO
This commit is contained in:
Daisuke Akatsuka 2018-03-13 16:45:19 +09:00
Родитель 49b1360194
Коммит 162722dd52
10 изменённых файлов: 219 добавлений и 6 удалений

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

@ -19,16 +19,23 @@ const {
updateSelectedAnimation,
updateSidebarSize
} = require("./actions/animations");
const { isAllAnimationEqual } = require("./utils/utils");
const {
isAllAnimationEqual,
hasPlayingAnimation,
} = require("./utils/utils");
class AnimationInspector {
constructor(inspector, win) {
this.inspector = inspector;
this.win = win;
this.addAnimationsCurrentTimeListener =
this.addAnimationsCurrentTimeListener.bind(this);
this.getAnimatedPropertyMap = this.getAnimatedPropertyMap.bind(this);
this.getComputedStyle = this.getComputedStyle.bind(this);
this.getNodeFromActor = this.getNodeFromActor.bind(this);
this.removeAnimationsCurrentTimeListener =
this.removeAnimationsCurrentTimeListener.bind(this);
this.rewindAnimationsCurrentTime = this.rewindAnimationsCurrentTime.bind(this);
this.selectAnimation = this.selectAnimation.bind(this);
this.setAnimationsPlayState = this.setAnimationsPlayState.bind(this);
@ -36,6 +43,7 @@ class AnimationInspector {
this.simulateAnimation = this.simulateAnimation.bind(this);
this.toggleElementPicker = this.toggleElementPicker.bind(this);
this.update = this.update.bind(this);
this.onAnimationsCurrentTimeUpdated = this.onAnimationsCurrentTimeUpdated.bind(this);
this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
this.onElementPickerStopped = this.onElementPickerStopped.bind(this);
this.onSidebarResized = this.onSidebarResized.bind(this);
@ -58,10 +66,13 @@ class AnimationInspector {
} = this.inspector.getPanel("boxmodel").getComponentProps();
const {
addAnimationsCurrentTimeListener,
emit: emitEventForTest,
getAnimatedPropertyMap,
getComputedStyle,
getNodeFromActor,
isAnimationsRunning,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
selectAnimation,
setAnimationsPlayState,
@ -73,6 +84,8 @@ class AnimationInspector {
const target = this.inspector.target;
this.animationsFront = new AnimationsFront(target.client, target.form);
this.animationsCurrentTimeListeners = [];
const provider = createElement(Provider,
{
id: "newanimationinspector",
@ -81,12 +94,15 @@ class AnimationInspector {
},
App(
{
addAnimationsCurrentTimeListener,
emitEventForTest,
getAnimatedPropertyMap,
getComputedStyle,
getNodeFromActor,
isAnimationsRunning,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
selectAnimation,
setAnimationsPlayState,
@ -123,6 +139,8 @@ class AnimationInspector {
this.simulatedElement = null;
}
this.stopAnimationsCurrentTimeTimer();
this.inspector = null;
this.win = null;
}
@ -131,6 +149,10 @@ class AnimationInspector {
return this.inspector.store.getState().animations;
}
addAnimationsCurrentTimeListener(listener) {
this.animationsCurrentTimeListeners.push(listener);
}
/**
* Return a map of animated property from given animation actor.
*
@ -201,6 +223,12 @@ class AnimationInspector {
this.inspector.sidebar.getCurrentTabID() === "newanimationinspector";
}
onAnimationsCurrentTimeUpdated(currentTime) {
for (const listener of this.animationsCurrentTimeListeners) {
listener(currentTime);
}
}
onElementPickerStarted() {
this.inspector.store.dispatch(updateElementPickerEnabled(true));
}
@ -222,10 +250,16 @@ class AnimationInspector {
this.inspector.store.dispatch(updateSidebarSize(size));
}
removeAnimationsCurrentTimeListener(listener) {
this.animationsCurrentTimeListeners =
this.animationsCurrentTimeListeners.filter(l => l !== listener);
}
async rewindAnimationsCurrentTime() {
const animations = this.state.animations;
await this.animationsFront.setCurrentTimes(animations, 0, true);
this.updateAnimations(animations);
await this.updateAnimations(animations);
this.onAnimationsCurrentTimeUpdated(0);
}
selectAnimation(animation) {
@ -288,6 +322,19 @@ class AnimationInspector {
return this.simulatedAnimation;
}
stopAnimationsCurrentTimeTimer() {
if (this.currentTimeTimer) {
this.currentTimeTimer.destroy();
this.currentTimeTimer = null;
}
}
startAnimationsCurrentTimeTimer() {
const currentTimeTimer = new CurrentTimeTimer(this);
currentTimeTimer.start();
this.currentTimeTimer = currentTimeTimer;
}
toggleElementPicker() {
this.inspector.toolbox.highlighterUtils.togglePicker();
}
@ -325,6 +372,8 @@ class AnimationInspector {
}
updateState(animations) {
this.stopAnimationsCurrentTimeTimer();
this.inspector.store.dispatch(updateAnimations(animations));
// If number of displayed animations is one, we select the animation automatically.
// But if selected animation is in given animations, ignores.
@ -334,6 +383,45 @@ class AnimationInspector {
!animations.find(animation => animation.actorID === selectedAnimation.actorID)) {
this.selectAnimation(animations.length === 1 ? animations[0] : null);
}
if (hasPlayingAnimation(animations)) {
this.startAnimationsCurrentTimeTimer();
}
}
}
class CurrentTimeTimer {
constructor(animationInspector) {
const timeScale = animationInspector.state.timeScale;
this.baseCurrentTime = timeScale.documentCurrentTime - timeScale.minStartTime;
this.startTime = animationInspector.win.performance.now();
this.animationInspector = animationInspector;
this.next = this.next.bind(this);
}
destroy() {
this.stop();
this.animationInspector = null;
}
next() {
if (this.doStop) {
return;
}
const { onAnimationsCurrentTimeUpdated, win } = this.animationInspector;
const currentTime = this.baseCurrentTime + win.performance.now() - this.startTime;
onAnimationsCurrentTimeUpdated(currentTime);
win.requestAnimationFrame(this.next);
}
start() {
this.next();
}
stop() {
this.doStop = true;
}
}

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

@ -12,8 +12,6 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
const AnimationList = createFactory(require("./AnimationList"));
const AnimationListHeader = createFactory(require("./AnimationListHeader"));
const TimeScale = require("../utils/timescale");
class AnimationListContainer extends PureComponent {
static get propTypes() {
return {
@ -26,6 +24,7 @@ class AnimationListContainer extends PureComponent {
selectAnimation: PropTypes.func.isRequired,
setSelectedNode: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired,
timeScale: PropTypes.object.isRequired,
};
}
@ -40,8 +39,8 @@ class AnimationListContainer extends PureComponent {
selectAnimation,
setSelectedNode,
simulateAnimation,
timeScale,
} = this.props;
const timeScale = new TimeScale(animations);
return dom.div(
{

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

@ -8,13 +8,16 @@ const { createFactory, PureComponent } = require("devtools/client/shared/vendor/
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const CurrentTimeLabel = createFactory(require("./CurrentTimeLabel"));
const PauseResumeButton = createFactory(require("./PauseResumeButton"));
const RewindButton = createFactory(require("./RewindButton"));
class AnimationToolbar extends PureComponent {
static get propTypes() {
return {
addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
rewindAnimationsCurrentTime: PropTypes.func.isRequired,
setAnimationsPlayState: PropTypes.func.isRequired,
};
@ -22,7 +25,9 @@ class AnimationToolbar extends PureComponent {
render() {
const {
addAnimationsCurrentTimeListener,
animations,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
setAnimationsPlayState,
} = this.props;
@ -41,6 +46,12 @@ class AnimationToolbar extends PureComponent {
animations,
setAnimationsPlayState,
}
),
CurrentTimeLabel(
{
addAnimationsCurrentTimeListener,
removeAnimationsCurrentTimeListener,
}
)
);
}

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

@ -18,6 +18,7 @@ const SplitBox = createFactory(require("devtools/client/shared/components/splitt
class App extends PureComponent {
static get propTypes() {
return {
addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
detailVisibility: PropTypes.bool.isRequired,
emitEventForTest: PropTypes.func.isRequired,
@ -26,12 +27,14 @@ class App extends PureComponent {
getNodeFromActor: PropTypes.func.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
rewindAnimationsCurrentTime: PropTypes.func.isRequired,
selectAnimation: PropTypes.func.isRequired,
setAnimationsPlayState: PropTypes.func.isRequired,
setDetailVisibility: PropTypes.func.isRequired,
setSelectedNode: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired,
timeScale: PropTypes.object.isRequired,
toggleElementPicker: PropTypes.func.isRequired,
};
}
@ -42,6 +45,7 @@ class App extends PureComponent {
render() {
const {
addAnimationsCurrentTimeListener,
animations,
detailVisibility,
emitEventForTest,
@ -50,12 +54,14 @@ class App extends PureComponent {
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
selectAnimation,
setAnimationsPlayState,
setDetailVisibility,
setSelectedNode,
simulateAnimation,
timeScale,
toggleElementPicker,
} = this.props;
@ -68,7 +74,9 @@ class App extends PureComponent {
[
AnimationToolbar(
{
addAnimationsCurrentTimeListener,
animations,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
setAnimationsPlayState,
}
@ -98,6 +106,7 @@ class App extends PureComponent {
selectAnimation,
setSelectedNode,
simulateAnimation,
timeScale,
}
),
vert: false,
@ -117,6 +126,7 @@ const mapStateToProps = state => {
return {
animations: state.animations.animations,
detailVisibility: state.animations.detailVisibility,
timeScale: state.animations.timeScale,
};
};

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

@ -0,0 +1,83 @@
/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
class CurrentTimeLabel extends PureComponent {
static get propTypes() {
return {
addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
const { addAnimationsCurrentTimeListener } = props;
this.onCurrentTimeUpdated = this.onCurrentTimeUpdated.bind(this);
this.state = {
currentTime: 0,
};
addAnimationsCurrentTimeListener(this.onCurrentTimeUpdated);
}
componentWillUnmount() {
const { removeAnimationsCurrentTimeListener } = this.props;
removeAnimationsCurrentTimeListener(this.onCurrentTimeUpdated);
}
onCurrentTimeUpdated(currentTime) {
this.setState({ currentTime });
}
render() {
const { currentTime } = this.state;
return dom.label(
{
className: "current-time-label",
},
formatStopwatchTime(currentTime)
);
}
}
/**
* Format a timestamp (in ms) as a mm:ss.mmm string.
*
* @param {Number} time
* @return {String}
*/
function formatStopwatchTime(time) {
// Format falsy values as 0
if (!time) {
return "00:00.000";
}
let milliseconds = parseInt(time % 1000, 10);
let seconds = parseInt((time / 1000) % 60, 10);
let minutes = parseInt((time / (1000 * 60)), 10);
let pad = (nb, max) => {
if (nb < max) {
return new Array((max + "").length - (nb + "").length + 1).join("0") + nb;
}
return nb;
};
minutes = pad(minutes, 10);
seconds = pad(seconds, 10);
milliseconds = pad(milliseconds, 100);
return `${minutes}:${seconds}.${milliseconds}`;
}
module.exports = CurrentTimeLabel;

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

@ -9,6 +9,7 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { getStr } = require("../utils/l10n");
const { hasPlayingAnimation } = require("../utils/utils");
class PauseResumeButton extends PureComponent {
static get propTypes() {
@ -43,7 +44,7 @@ class PauseResumeButton extends PureComponent {
updateState() {
const { animations } = this.props;
const isPlaying = animations.some(({state}) => state.playState === "running");
const isPlaying = hasPlayingAnimation(animations);
this.setState({ isPlaying });
}

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

@ -24,6 +24,7 @@ DevToolsModules(
'AnimationTimelineTickList.js',
'AnimationToolbar.js',
'App.js',
'CurrentTimeLabel.js',
'KeyframesProgressTickItem.js',
'KeyframesProgressTickList.js',
'NoAnimationPanel.js',

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

@ -12,6 +12,8 @@ const {
UPDATE_SIDEBAR_SIZE,
} = require("../actions/index");
const TimeScale = require("../utils/timescale");
const INITIAL_STATE = {
animations: [],
detailVisibility: false,
@ -21,12 +23,14 @@ const INITIAL_STATE = {
height: 0,
width: 0,
},
timeScale: null,
};
const reducers = {
[UPDATE_ANIMATIONS](state, { animations }) {
return Object.assign({}, state, {
animations,
timeScale: new TimeScale(animations),
});
},

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

@ -24,6 +24,8 @@ class TimeScale {
constructor(animations) {
this.minStartTime = Infinity;
this.maxEndTime = 0;
this.documentCurrentTime = 0;
for (const animation of animations) {
this.addAnimation(animation.state);
}
@ -38,6 +40,7 @@ class TimeScale {
addAnimation(state) {
let {
delay,
documentCurrentTime,
duration,
endDelay = 0,
iterationCount,
@ -67,6 +70,8 @@ class TimeScale {
const length = toRate(delay) + rateRelativeDuration + toRate(minZero(endDelay));
const endTime = previousStartTime + length;
this.maxEndTime = Math.max(this.maxEndTime, endTime);
this.documentCurrentTime = Math.max(this.documentCurrentTime, documentCurrentTime);
}
/**

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

@ -69,6 +69,16 @@ function isAllAnimationEqual(animationsA, animationsB) {
return true;
}
/**
* Check wether the animations are running at least one.
*
* @param {Array} animations.
* @return {Boolean} true: playing
*/
function hasPlayingAnimation(animations) {
return animations.some(({state}) => state.playState === "running");
}
/**
* Check the equality given states as effect timing.
*
@ -88,5 +98,6 @@ function isTimingEffectEqual(stateA, stateB) {
}
exports.findOptimalTimeInterval = findOptimalTimeInterval;
exports.hasPlayingAnimation = hasPlayingAnimation;
exports.isAllAnimationEqual = isAllAnimationEqual;
exports.isTimingEffectEqual = isTimingEffectEqual;