Bug 1431573 - Part 5: Implement playback rate chooser. r?gl
MozReview-Commit-ID: KK5C6TBhA5X
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -33,16 +33,17 @@ class AnimationInspector {
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.setAnimationsPlaybackRate = this.setAnimationsPlaybackRate.bind(this);
this.setAnimationsPlayState = this.setAnimationsPlayState.bind(this);
this.setDetailVisibility = this.setDetailVisibility.bind(this);
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);
@@ -70,16 +71,17 @@ class AnimationInspector {
emit: emitEventForTest,
getAnimatedPropertyMap,
getComputedStyle,
getNodeFromActor,
isAnimationsRunning,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
selectAnimation,
+ setAnimationsPlaybackRate,
setAnimationsPlayState,
setDetailVisibility,
simulateAnimation,
toggleElementPicker,
} = this;
const target = this.inspector.target;
this.animationsFront = new AnimationsFront(target.client, target.form);
@@ -100,16 +102,17 @@ class AnimationInspector {
getComputedStyle,
getNodeFromActor,
isAnimationsRunning,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
selectAnimation,
+ setAnimationsPlaybackRate,
setAnimationsPlayState,
setDetailVisibility,
setSelectedNode,
simulateAnimation,
toggleElementPicker,
}
)
);
@@ -261,16 +264,22 @@ class AnimationInspector {
await this.updateAnimations(animations);
this.onAnimationsCurrentTimeUpdated(0);
}
selectAnimation(animation) {
this.inspector.store.dispatch(updateSelectedAnimation(animation));
}
+ async setAnimationsPlaybackRate(playbackRate) {
+ const animations = this.state.animations;
+ await this.animationsFront.setPlaybackRates(animations, playbackRate);
+ await this.updateAnimations(animations);
+ }
+
async setAnimationsPlayState(doPlay) {
if (doPlay) {
await this.animationsFront.playAll();
} else {
await this.animationsFront.pauseAll();
}
this.updateAnimations(this.state.animations);
--- a/devtools/client/inspector/animation/components/AnimationToolbar.js
+++ b/devtools/client/inspector/animation/components/AnimationToolbar.js
@@ -5,35 +5,38 @@
"use strict";
const { createFactory, 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");
const CurrentTimeLabel = createFactory(require("./CurrentTimeLabel"));
const PauseResumeButton = createFactory(require("./PauseResumeButton"));
+const PlaybackRateSelector = createFactory(require("./PlaybackRateSelector"));
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,
+ setAnimationsPlaybackRate: PropTypes.func.isRequired,
setAnimationsPlayState: PropTypes.func.isRequired,
};
}
render() {
const {
addAnimationsCurrentTimeListener,
animations,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
+ setAnimationsPlaybackRate,
setAnimationsPlayState,
} = this.props;
return dom.div(
{
className: "animation-toolbar devtools-toolbar",
},
RewindButton(
@@ -42,16 +45,22 @@ class AnimationToolbar extends PureCompo
}
),
PauseResumeButton(
{
animations,
setAnimationsPlayState,
}
),
+ PlaybackRateSelector(
+ {
+ animations,
+ setAnimationsPlaybackRate,
+ }
+ ),
CurrentTimeLabel(
{
addAnimationsCurrentTimeListener,
removeAnimationsCurrentTimeListener,
}
)
);
}
--- a/devtools/client/inspector/animation/components/App.js
+++ b/devtools/client/inspector/animation/components/App.js
@@ -25,16 +25,17 @@ class App extends PureComponent {
getAnimatedPropertyMap: PropTypes.func.isRequired,
getComputedStyle: PropTypes.func.isRequired,
getNodeFromActor: PropTypes.func.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
rewindAnimationsCurrentTime: PropTypes.func.isRequired,
selectAnimation: PropTypes.func.isRequired,
+ setAnimationsPlaybackRate: 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,
};
}
@@ -52,16 +53,17 @@ class App extends PureComponent {
getAnimatedPropertyMap,
getComputedStyle,
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
selectAnimation,
+ setAnimationsPlaybackRate,
setAnimationsPlayState,
setDetailVisibility,
setSelectedNode,
simulateAnimation,
timeScale,
toggleElementPicker,
} = this.props;
@@ -73,16 +75,17 @@ class App extends PureComponent {
animations.length ?
[
AnimationToolbar(
{
addAnimationsCurrentTimeListener,
animations,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
+ setAnimationsPlaybackRate,
setAnimationsPlayState,
}
),
SplitBox({
className: "animation-container-splitter",
endPanel: AnimationDetailContainer(
{
emitEventForTest,
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/PlaybackRateSelector.js
@@ -0,0 +1,103 @@
+/* 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");
+
+const { getFormatStr } = require("../utils/l10n");
+
+const PLAYBACK_RATES = [.1, .25, .5, 1, 2, 5, 10];
+
+class PlaybackRateSelector extends PureComponent {
+ static get propTypes() {
+ return {
+ animations: PropTypes.arrayOf(PropTypes.object).isRequired,
+ setAnimationsPlaybackRate: PropTypes.func.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ options: [],
+ selected: 1,
+ };
+ }
+
+ componentWillMount() {
+ this.updateState(this.props);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.updateState(nextProps);
+ }
+
+ getPlaybackRates(animations) {
+ return sortAndUnique(animations.map(a => a.state.playbackRate));
+ }
+
+ getSelectablePlaybackRates(animationsRates) {
+ return sortAndUnique(PLAYBACK_RATES.concat(animationsRates));
+ }
+
+ onChange(e) {
+ const { setAnimationsPlaybackRate } = this.props;
+
+ if (!e.target.value) {
+ return;
+ }
+
+ setAnimationsPlaybackRate(e.target.value);
+ }
+
+ updateState(props) {
+ const { animations } = props;
+
+ let options;
+ let selected;
+ const rates = this.getPlaybackRates(animations);
+
+ if (rates.length === 1) {
+ options = this.getSelectablePlaybackRates(rates);
+ selected = rates[0];
+ } else {
+ // When the animations displayed have mixed playback rates, we can't
+ // select any of the predefined ones.
+ options = ["", ...PLAYBACK_RATES];
+ selected = "";
+ }
+
+ this.setState({ options, selected });
+ }
+
+ render() {
+ const { options, selected } = this.state;
+
+ return dom.select(
+ {
+ className: "playback-rate-selector devtools-button",
+ onChange: this.onChange.bind(this),
+ },
+ options.map(rate => {
+ return dom.option(
+ {
+ selected: rate === selected ? "true" : null,
+ value: rate,
+ },
+ rate ? getFormatStr("player.playbackRateLabel", rate) : "-"
+ );
+ })
+ );
+ }
+}
+
+function sortAndUnique(array) {
+ return [...new Set(array)].sort((a, b) => a > b);
+}
+
+module.exports = PlaybackRateSelector;
--- a/devtools/client/inspector/animation/components/moz.build
+++ b/devtools/client/inspector/animation/components/moz.build
@@ -24,10 +24,11 @@ DevToolsModules(
'AnimationTimelineTickList.js',
'AnimationToolbar.js',
'App.js',
'CurrentTimeLabel.js',
'KeyframesProgressTickItem.js',
'KeyframesProgressTickList.js',
'NoAnimationPanel.js',
'PauseResumeButton.js',
+ 'PlaybackRateSelector.js',
'RewindButton.js',
)
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -62,16 +62,33 @@
.pause-resume-button::before {
background-image: var(--pause-image);
}
.pause-resume-button.paused::before {
background-image: var(--resume-image);
}
+select.playback-rate-selector.devtools-button {
+ background-image: url("chrome://devtools/skin/images/dropmarker.svg");
+ background-position: calc(100% - 4px) center;
+ background-repeat: no-repeat;
+ padding-right: 1em;
+ text-align: center;
+}
+
+select.playback-rate-selector.devtools-button:not(:empty):not(:disabled):not(.checked):hover {
+ background: none;
+ background-color: var(--toolbarbutton-background);
+ background-image: url("chrome://devtools/skin/images/dropmarker.svg");
+ background-position: calc(100% - 4px) center;
+ background-repeat: no-repeat;
+ border-color: var(--toolbarbutton-hover-border-color);
+}
+
.rewind-button::before {
background-image: var(--rewind-image);
}
/* Animation List Container */
.animation-list-container {
display: flex;
flex-direction: column;