Skip to content

Commit

Permalink
Merge pull request #38457 from code-dot-org/jan7-spritelab-pause
Browse files Browse the repository at this point in the history
[Spritelab] Implement Pause button behind experiment
  • Loading branch information
ajpal committed Jan 8, 2021
2 parents 82fbe4f + 361c287 commit 310a226
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 16 deletions.
10 changes: 9 additions & 1 deletion apps/src/p5lab/P5Lab.js
Expand Up @@ -391,8 +391,10 @@ P5Lab.prototype.init = function(config) {
(!config.hideSource &&
!config.level.debuggerDisabled &&
!config.level.iframeEmbedAppAndCode);
var showPauseButton = experiments.isEnabled(experiments.SPRITELAB_PAUSE);
var showDebugConsole = config.level.editCode && !config.hideSource;
this.debuggerEnabled = showDebugButtons || showDebugConsole;
this.debuggerEnabled =
showDebugButtons || showPauseButton || showDebugConsole;

if (this.debuggerEnabled) {
getStore().dispatch(
Expand Down Expand Up @@ -492,6 +494,7 @@ P5Lab.prototype.init = function(config) {
<P5LabView
showFinishButton={finishButtonFirstLine && showFinishButton}
onMount={onMount}
pauseHandler={this.onPause}
/>
</Provider>,
document.getElementById(config.containerId)
Expand Down Expand Up @@ -647,6 +650,11 @@ P5Lab.prototype.setupReduxSubscribers = function(store) {
});
};

/**
* Override to change pause behavior.
*/
P5Lab.prototype.onPause = function() {};

P5Lab.prototype.onIsRunningChange = function() {
this.setCrosshairCursorForPlaySpace();
};
Expand Down
8 changes: 6 additions & 2 deletions apps/src/p5lab/P5LabView.jsx
Expand Up @@ -40,7 +40,8 @@ class P5LabView extends React.Component {
isIframeEmbed: PropTypes.bool.isRequired,
isRunning: PropTypes.bool.isRequired,
spriteLab: PropTypes.bool.isRequired,
isBackground: PropTypes.bool
isBackground: PropTypes.bool,
pauseHandler: PropTypes.func.isRequired
};

state = {
Expand Down Expand Up @@ -119,7 +120,10 @@ class P5LabView extends React.Component {
style={visualizationColumnStyle}
>
{this.props.showVisualizationHeader && <P5LabVisualizationHeader />}
<P5LabVisualizationColumn finishButton={showFinishButton} />
<P5LabVisualizationColumn
finishButton={showFinishButton}
pauseHandler={this.props.pauseHandler}
/>
{this.getChannelId() && (
<AnimationPicker
channelId={this.getChannelId()}
Expand Down
21 changes: 17 additions & 4 deletions apps/src/p5lab/P5LabVisualizationColumn.jsx
Expand Up @@ -5,6 +5,7 @@ import {connect} from 'react-redux';
import classNames from 'classnames';
import GameButtons from '@cdo/apps/templates/GameButtons';
import ArrowButtons from '@cdo/apps/templates/ArrowButtons';
import PauseButton from '@cdo/apps/templates/PauseButton';
import BelowVisualization from '@cdo/apps/templates/BelowVisualization';
import experiments from '@cdo/apps/util/experiments';
import {APP_HEIGHT, APP_WIDTH} from './constants';
Expand Down Expand Up @@ -56,9 +57,20 @@ class P5LabVisualizationColumn extends React.Component {
cancelPicker: PropTypes.func.isRequired,
selectPicker: PropTypes.func.isRequired,
updatePicker: PropTypes.func.isRequired,
consoleMessages: PropTypes.array.isRequired
consoleMessages: PropTypes.array.isRequired,
pauseHandler: PropTypes.func.isRequired
};

constructor(props) {
super(props);
this.spritelabPauseExperiment = experiments.isEnabled(
experiments.SPRITELAB_PAUSE
);
this.spritelabInputExperiment = experiments.isEnabled(
experiments.SPRITELAB_INPUT
);
}

// Cache app-space mouse coordinates, which we get from the
// VisualizationOverlay when they change.
state = {
Expand Down Expand Up @@ -186,12 +198,13 @@ class P5LabVisualizationColumn extends React.Component {
</VisualizationOverlay>
</ProtectedVisualizationDiv>
<TextConsole consoleMessages={this.props.consoleMessages} />
{experiments.isEnabled(experiments.SPRITELAB_INPUT) && (
<SpritelabInput />
)}
{this.spritelabInputExperiment && <SpritelabInput />}
</div>

<GameButtons>
{this.spritelabPauseExperiment && (
<PauseButton pauseHandler={this.props.pauseHandler} />
)}
<ArrowButtons />

<CompletionButton />
Expand Down
9 changes: 9 additions & 0 deletions apps/src/p5lab/spritelab/SpriteLab.js
Expand Up @@ -50,3 +50,12 @@ SpriteLab.prototype.reset = function() {
getStore().dispatch(clearPrompts());
this.preview();
};

SpriteLab.prototype.onPause = function(isPaused) {
const current = new Date().getTime();
if (isPaused) {
coreLibrary.endPause(current);
} else {
coreLibrary.startPause(current);
}
};
2 changes: 1 addition & 1 deletion apps/src/p5lab/spritelab/commands/worldCommands.js
Expand Up @@ -10,7 +10,7 @@ export const commands = {

getTime(unit) {
if (unit === 'seconds') {
return this.World.seconds || 0;
return coreLibrary.getAdjustedWorldTime(this) || 0;
} else if (unit === 'frames') {
return this.World.frameCount || 0;
}
Expand Down
35 changes: 27 additions & 8 deletions apps/src/p5lab/spritelab/coreLibrary.js
Expand Up @@ -4,6 +4,8 @@ var inputEvents = [];
var behaviors = [];
var userInputEventCallbacks = {};
var newSprites = {};
var totalPauseTime = 0;
var currentPauseStartTime = 0;

export var background;
export var title = '';
Expand All @@ -19,6 +21,24 @@ export function reset() {
title = subtitle = '';
userInputEventCallbacks = {};
defaultSpriteSize = 100;
totalPauseTime = 0;
}

export function startPause(time) {
currentPauseStartTime = time;
}

export function endPause(time) {
totalPauseTime += time - currentPauseStartTime;
currentPauseStartTime = 0;
}

/**
* Returns World.seconds adjusted to exclude time during which the app was paused
*/
export function getAdjustedWorldTime(p5Inst) {
const current = new Date().getTime();
return Math.round((current - p5Inst._startTime - totalPauseTime) / 1000);
}

/**
Expand Down Expand Up @@ -205,13 +225,11 @@ export function clearCollectDataEvents() {
function atTimeEvent(inputEvent, p5Inst) {
if (inputEvent.args.unit === 'seconds') {
const previousTime = inputEvent.previousTime || 0;
inputEvent.previousTime = p5Inst.World.seconds;
const worldTime = getAdjustedWorldTime(p5Inst);
inputEvent.previousTime = worldTime;
// There are many ticks per second, but we only want to fire the event once (on the first tick where
// World.seconds matches the event argument)
if (
p5Inst.World.seconds === inputEvent.args.n &&
previousTime !== inputEvent.args.n
) {
// the time matches the event argument)
if (worldTime === inputEvent.args.n && previousTime !== inputEvent.args.n) {
// Call callback with no extra args
return [{}];
}
Expand All @@ -227,10 +245,11 @@ function atTimeEvent(inputEvent, p5Inst) {

function collectDataEvent(inputEvent, p5Inst) {
const previous = inputEvent.previous || 0;
inputEvent.previous = p5Inst.World.seconds;
const worldTime = getAdjustedWorldTime(p5Inst);
inputEvent.previous = worldTime;

// Only log data once per second
if (p5Inst.World.seconds !== previous) {
if (worldTime !== previous) {
// Call callback with no extra args
return [{}];
} else {
Expand Down
57 changes: 57 additions & 0 deletions apps/src/templates/PauseButton.jsx
@@ -0,0 +1,57 @@
import React from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import color from '@cdo/apps/util/color';
import {actions, selectors} from '@cdo/apps/lib/tools/jsdebugger/redux';

const styles = {
button: {
minWidth: 0,
padding: '10px 13px',
backgroundColor: color.orange,
borderColor: color.orange,
color: color.white
}
};

class PauseButton extends React.Component {
static propTypes = {
pauseHandler: PropTypes.func.isRequired,
// from redux
togglePause: PropTypes.func.isRequired,
isPaused: PropTypes.bool.isRequired,
isAttached: PropTypes.bool.isRequired
};

state = {
pauseStart: 0
};

togglePause = () => {
this.props.pauseHandler(this.props.isPaused);
this.props.togglePause();
};

render() {
return (
<button
type="button"
onClick={this.togglePause}
style={styles.button}
disabled={!this.props.isAttached}
>
<i className={this.props.isPaused ? 'fa fa-play' : 'fa fa-pause'} />
</button>
);
}
}

export default connect(
state => ({
isAttached: selectors.isAttached(state),
isPaused: selectors.isPaused(state)
}),
{
togglePause: actions.togglePause
}
)(PauseButton);
1 change: 1 addition & 0 deletions apps/src/util/experiments.js
Expand Up @@ -33,6 +33,7 @@ experiments.SPRITELAB_INPUT = 'spritelabInput';
experiments.TIME_SPENT = 'time-spent';
experiments.APPLAB_ML = 'applab-ml';
experiments.BYPASS_DIALOG_POPUP = 'bypass-dialog-popup';
experiments.SPRITELAB_PAUSE = 'spritelabPause';

/**
* Get our query string. Provided as a method so that tests can mock this.
Expand Down

0 comments on commit 310a226

Please sign in to comment.