Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a85d2466f | ||
|
|
3ece0e1255 | ||
|
|
729395b8d4 | ||
|
|
39f85aa4f1 | ||
|
|
a00f86bf16 | ||
|
|
6fe78429a5 | ||
|
|
9138e1e164 | ||
|
|
c75a91ac29 | ||
|
|
a554acf667 | ||
|
|
782681dcc4 | ||
|
|
3c7193b562 | ||
|
|
0ed7f78abb | ||
|
|
737ce711e5 | ||
|
|
533408409f | ||
|
|
a99b5fcbf9 | ||
|
|
a08bb42658 | ||
|
|
3e5f7e917a | ||
|
|
3014e56e24 | ||
|
|
a8cd6f9c69 | ||
|
|
c04acb513a | ||
|
|
8ce4322051 | ||
|
|
f0637a40c5 | ||
|
|
a6cd49d400 |
No files matched your search
Generated
+26
-26
@@ -4860,27 +4860,27 @@
|
||||
"dev": true
|
||||
},
|
||||
"falafel": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/falafel/-/falafel-2.1.0.tgz",
|
||||
"integrity": "sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw=",
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/falafel/-/falafel-2.2.4.tgz",
|
||||
"integrity": "sha512-0HXjo8XASWRmsS0X1EkhwEMZaD3Qvp7FfURwjLKjG1ghfRm/MGZl2r4cWUTv41KdNghTw4OUMmVtdGQp3+H+uQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^5.0.0",
|
||||
"acorn": "^7.1.1",
|
||||
"foreach": "^2.0.5",
|
||||
"isarray": "0.0.1",
|
||||
"isarray": "^2.0.1",
|
||||
"object-keys": "^1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
|
||||
"integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
|
||||
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
|
||||
"dev": true
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -10649,9 +10649,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -11549,9 +11549,9 @@
|
||||
}
|
||||
},
|
||||
"scratch-blocks": {
|
||||
"version": "0.1.0-prerelease.1585138708",
|
||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.1585138708.tgz",
|
||||
"integrity": "sha512-36UhpFoqVZg79nvZi9lxn0nmZ5hOQqIyIELMmogfwMl5CsRE4HfcjiLizDY/ORm6H5zH/AS1SFmiG78YNCnN+A==",
|
||||
"version": "0.1.0-prerelease.1585323236",
|
||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.1585323236.tgz",
|
||||
"integrity": "sha512-iiadFomOLeKX+AU/cwBKTq0X/82zr/d+TcOorEpE0xiIfhztKV+cE5EiQA+eMfpQYZs8XBjojgAgs8Ob+zPuRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"exports-loader": "0.6.3",
|
||||
@@ -11686,9 +11686,9 @@
|
||||
}
|
||||
},
|
||||
"scratch-render": {
|
||||
"version": "0.1.0-prerelease.20200228152431",
|
||||
"resolved": "https://registry.npmjs.org/scratch-render/-/scratch-render-0.1.0-prerelease.20200228152431.tgz",
|
||||
"integrity": "sha512-rI/IVwzAHVhq6IoFOgsqwwwwl9acizjJBVxOHKyP9F+/vs34uhbxr+mVUdpO82tSWpjDR+xuw3XfF7oH9U6WnQ==",
|
||||
"version": "0.1.0-prerelease.20200324022200",
|
||||
"resolved": "https://registry.npmjs.org/scratch-render/-/scratch-render-0.1.0-prerelease.20200324022200.tgz",
|
||||
"integrity": "sha512-nW77KkYJa6YG9ytTs6v+DJ1cy6fc+E1B5Jm+K4735VTsrz3eej/APS0QuXx/TdcD29gJryOGkfAcAF4UkBq4lw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"grapheme-breaker": "0.3.2",
|
||||
@@ -11773,9 +11773,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"scratch-vm": {
|
||||
"version": "0.2.0-prerelease.20200319175700",
|
||||
"resolved": "https://registry.npmjs.org/scratch-vm/-/scratch-vm-0.2.0-prerelease.20200319175700.tgz",
|
||||
"integrity": "sha512-CSCGU3XJhq68kw+74Nsdj3Bfo4D0jsGumh3DocLQCDbltrpvfEDfAMHwkFwetdCE8/XO+oHY/FdI4Joy7OimwA==",
|
||||
"version": "0.2.0-prerelease.20200327113900",
|
||||
"resolved": "https://registry.npmjs.org/scratch-vm/-/scratch-vm-0.2.0-prerelease.20200327113900.tgz",
|
||||
"integrity": "sha512-CjnzBAT3GoP0g9X4QXkW2GP3AFRIoPTLwsR0luiN3VLYp4NOETsZpF+0uXfGzKp03M8lXH+YRsmlhsls8by1Pw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@vernier/godirect": "1.5.0",
|
||||
@@ -12493,9 +12493,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"static-eval": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.3.tgz",
|
||||
"integrity": "sha512-zsxDGucfAh8T339sSKgpFbvg15Fms2IVaJGC+jqp0bVsxhcpM+iMeAI8weNo8dmf4OblgifTBUoyk1vGVtYw2w==",
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.5.tgz",
|
||||
"integrity": "sha512-nNbV6LbGtMBgv7e9LFkt5JV8RVlRsyJrphfAt9tOtBBW/SfnzZDf2KnS72an8e434A+9e/BmJuTxeGPvrAK7KA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"escodegen": "^1.11.1"
|
||||
|
||||
+3
-3
@@ -108,13 +108,13 @@
|
||||
"redux-throttle": "0.1.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"scratch-audio": "0.1.0-prerelease.20190925183642",
|
||||
"scratch-blocks": "0.1.0-prerelease.1585138708",
|
||||
"scratch-blocks": "0.1.0-prerelease.1585323236",
|
||||
"scratch-l10n": "3.8.20200325112845",
|
||||
"scratch-paint": "0.2.0-prerelease.20200213174123",
|
||||
"scratch-render": "0.1.0-prerelease.20200228152431",
|
||||
"scratch-render": "0.1.0-prerelease.20200324022200",
|
||||
"scratch-storage": "1.3.2",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20200205003400",
|
||||
"scratch-vm": "0.2.0-prerelease.20200319175700",
|
||||
"scratch-vm": "0.2.0-prerelease.20200327113900",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
|
||||
@@ -18,6 +18,7 @@ import StageWrapper from '../../containers/stage-wrapper.jsx';
|
||||
import Loader from '../loader/loader.jsx';
|
||||
import Box from '../box/box.jsx';
|
||||
import MenuBar from '../menu-bar/menu-bar.jsx';
|
||||
import MysteryMode from '../mystery-mode/mystery-mode.jsx';
|
||||
import CostumeLibrary from '../../containers/costume-library.jsx';
|
||||
import BackdropLibrary from '../../containers/backdrop-library.jsx';
|
||||
import Watermark from '../../containers/watermark.jsx';
|
||||
@@ -156,6 +157,7 @@ const GUIComponent = props => {
|
||||
dir={isRtl ? 'rtl' : 'ltr'}
|
||||
{...componentProps}
|
||||
>
|
||||
<MysteryMode />
|
||||
{telemetryModalVisible ? (
|
||||
<TelemetryModal
|
||||
onCancel={onTelemetryModalCancel}
|
||||
|
||||
@@ -32,6 +32,8 @@ import MenuBarHOC from '../../containers/menu-bar-hoc.jsx';
|
||||
|
||||
import {openTipsLibrary} from '../../reducers/modals';
|
||||
import {setPlayer} from '../../reducers/mode';
|
||||
import {toggleMysteryMode} from '../../reducers/mystery-mode';
|
||||
import {toggleWobblyDragging} from '../../reducers/wobbly-dragging';
|
||||
import {
|
||||
autoUpdateProject,
|
||||
getIsUpdating,
|
||||
@@ -469,6 +471,36 @@ class MenuBar extends React.Component {
|
||||
)}
|
||||
</MenuItem>
|
||||
)}</TurboMode>
|
||||
<MenuItem onClick={this.props.onClickToggleMysteryMode}>
|
||||
{this.props.mysteryMode ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="Turn off Mystery Mode"
|
||||
description="Menu bar item for turning off dark mode"
|
||||
id="gui.menuBar.mysteryModeOff"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
defaultMessage="Turn on Mystery Mode"
|
||||
description="Menu bar item for turning on dark mode"
|
||||
id="gui.menuBar.mysteryModeOn"
|
||||
/>
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={this.props.onClickToggleWobblyDragging}>
|
||||
{this.props.wobblyDragging ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="Turn off wobbly sprite dragging"
|
||||
description="Menu bar item for turning off wobbly sprite dragging"
|
||||
id="gui.menuBar.wobblyDraggingOff"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
defaultMessage="Turn on wobbly sprite dragging"
|
||||
description="Menu bar item for turning on wobbly sprite dragging"
|
||||
id="gui.menuBar.wobblyDraggingOn"
|
||||
/>
|
||||
)}
|
||||
</MenuItem>
|
||||
</MenuSection>
|
||||
</MenuBarMenu>
|
||||
</div>
|
||||
@@ -722,6 +754,7 @@ MenuBar.propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
loginMenuOpen: PropTypes.bool,
|
||||
logo: PropTypes.string,
|
||||
mysteryMode: PropTypes.bool,
|
||||
onClickAccount: PropTypes.func,
|
||||
onClickEdit: PropTypes.func,
|
||||
onClickFile: PropTypes.func,
|
||||
@@ -732,6 +765,8 @@ MenuBar.propTypes = {
|
||||
onClickRemix: PropTypes.func,
|
||||
onClickSave: PropTypes.func,
|
||||
onClickSaveAsCopy: PropTypes.func,
|
||||
onClickToggleMysteryMode: PropTypes.func,
|
||||
onClickToggleWobblyDragging: PropTypes.func,
|
||||
onLogOut: PropTypes.func,
|
||||
onOpenRegistration: PropTypes.func,
|
||||
onOpenTipLibrary: PropTypes.func,
|
||||
@@ -751,7 +786,8 @@ MenuBar.propTypes = {
|
||||
showComingSoon: PropTypes.bool,
|
||||
userOwnsProject: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
vm: PropTypes.instanceOf(VM).isRequired,
|
||||
wobblyDragging: PropTypes.bool
|
||||
};
|
||||
|
||||
MenuBar.defaultProps = {
|
||||
@@ -772,12 +808,14 @@ const mapStateToProps = (state, ownProps) => {
|
||||
languageMenuOpen: languageMenuOpen(state),
|
||||
locale: state.locales.locale,
|
||||
loginMenuOpen: loginMenuOpen(state),
|
||||
mysteryMode: state.scratchGui.mysteryMode,
|
||||
projectTitle: state.scratchGui.projectTitle,
|
||||
sessionExists: state.session && typeof state.session.session !== 'undefined',
|
||||
username: user ? user.username : null,
|
||||
userOwnsProject: ownProps.authorUsername && user &&
|
||||
(ownProps.authorUsername === user.username),
|
||||
vm: state.scratchGui.vm
|
||||
vm: state.scratchGui.vm,
|
||||
wobblyDragging: state.scratchGui.wobblyDragging
|
||||
};
|
||||
};
|
||||
|
||||
@@ -798,6 +836,8 @@ const mapDispatchToProps = dispatch => ({
|
||||
onClickRemix: () => dispatch(remixProject()),
|
||||
onClickSave: () => dispatch(manualUpdateProject()),
|
||||
onClickSaveAsCopy: () => dispatch(saveProjectAsCopy()),
|
||||
onClickToggleMysteryMode: () => dispatch(toggleMysteryMode()),
|
||||
onClickToggleWobblyDragging: () => dispatch(toggleWobblyDragging()),
|
||||
onSeeCommunity: () => dispatch(setPlayer(true))
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.mystery-mode {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.9;
|
||||
background: transparent url('./mystery.png') center center;
|
||||
z-index: 50;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import styles from './mystery-mode.css';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
class MysteryMode extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleMouseMove'
|
||||
]);
|
||||
this.state = {
|
||||
x: window.innerWidth / 2,
|
||||
y: window.innerHeight / 2
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
document.addEventListener('mousemove', this.handleMouseMove);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove);
|
||||
}
|
||||
|
||||
handleMouseMove (event) {
|
||||
this.setState({x: event.clientX, y: event.clientY});
|
||||
}
|
||||
|
||||
render () {
|
||||
if (!this.props.show) return null;
|
||||
return (
|
||||
<div
|
||||
className={styles.mysteryMode}
|
||||
style={{
|
||||
backgroundPosition: `${this.state.x + 2000}px ${this.state.y + 2000}px`
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MysteryMode.propTypes = {
|
||||
show: PropTypes.bool
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
// This is the button's mode, as opposed to the actual current state
|
||||
show: state.scratchGui.mysteryMode
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(MysteryMode);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
@@ -108,7 +108,12 @@ to adjust for the border using a different method */
|
||||
left: 0;
|
||||
z-index: $z-index-dragging-sprite;
|
||||
filter: drop-shadow(5px 5px 5px $ui-black-transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.dragging-sprite.wobbly {
|
||||
/* prevent overflow (when canvas crosses the right edge of the page) from causing a scrollbar to appear */
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.stage-bottom-wrapper {
|
||||
position: absolute;
|
||||
|
||||
@@ -29,6 +29,8 @@ const StageComponent = props => {
|
||||
onDeactivateColorPicker,
|
||||
onDoubleClick,
|
||||
onQuestionAnswered,
|
||||
wobblyDragging,
|
||||
wobblyDragRef,
|
||||
...boxProps
|
||||
} = props;
|
||||
|
||||
@@ -118,12 +120,23 @@ const StageComponent = props => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<canvas
|
||||
className={styles.draggingSprite}
|
||||
height={0}
|
||||
ref={dragRef}
|
||||
width={0}
|
||||
/>
|
||||
{
|
||||
// Give the canvases two different keys to prevent React from recycling the same canvas
|
||||
// across two different context types (2D and WebGL)
|
||||
wobblyDragging ? <canvas
|
||||
className={classNames(styles.draggingSprite, styles.wobbly)}
|
||||
height={0}
|
||||
ref={wobblyDragRef}
|
||||
width={0}
|
||||
key="non-wobbly"
|
||||
/> : <canvas
|
||||
className={styles.draggingSprite}
|
||||
height={0}
|
||||
ref={dragRef}
|
||||
width={0}
|
||||
key="wobbly"
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
{isColorPicking ? (
|
||||
<Box
|
||||
@@ -147,7 +160,9 @@ StageComponent.propTypes = {
|
||||
onQuestionAnswered: PropTypes.func,
|
||||
question: PropTypes.string,
|
||||
stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired,
|
||||
useEditorDragStyle: PropTypes.bool
|
||||
useEditorDragStyle: PropTypes.bool,
|
||||
wobblyDragging: PropTypes.bool,
|
||||
wobblyDragRef: PropTypes.func
|
||||
};
|
||||
StageComponent.defaultProps = {
|
||||
dragRef: () => {}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
/**
|
||||
* Turbo Mode component passes toggleTurboMode function to its child.
|
||||
* It also includes `turboMode` in the props passed to the children.
|
||||
* It expects this child to be a function with the signature
|
||||
* function (toggleTurboMode, {turboMode, ...props}) {}
|
||||
* The component can then be used to attach turbo mode setting functionality
|
||||
* to any other component:
|
||||
*
|
||||
* <TurboMode>{(toggleTurboMode, props) => (
|
||||
* <MyCoolComponent
|
||||
* turboEnabled={props.turboMode}
|
||||
* onClick={toggleTurboMode}
|
||||
* {...props}
|
||||
* />
|
||||
* )}</TurboMode>
|
||||
*/
|
||||
class TurboMode extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'toggleTurboMode'
|
||||
]);
|
||||
}
|
||||
toggleTurboMode () {
|
||||
this.props.vm.setTurboMode(!this.props.turboMode);
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
/* eslint-disable no-unused-vars */
|
||||
children,
|
||||
vm,
|
||||
/* eslint-enable no-unused-vars */
|
||||
...props
|
||||
} = this.props;
|
||||
return this.props.children(this.toggleTurboMode, props);
|
||||
}
|
||||
}
|
||||
|
||||
TurboMode.propTypes = {
|
||||
children: PropTypes.func,
|
||||
turboMode: PropTypes.bool,
|
||||
vm: PropTypes.shape({
|
||||
setTurboMode: PropTypes.func
|
||||
})
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
vm: state.scratchGui.vm,
|
||||
turboMode: state.scratchGui.vmStatus.turbo
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
() => ({}) // omit dispatch prop
|
||||
)(TurboMode);
|
||||
@@ -10,6 +10,7 @@ import {getEventXY} from '../lib/touch-utils';
|
||||
import VideoProvider from '../lib/video/video-provider';
|
||||
import {SVGRenderer as V2SVGAdapter} from 'scratch-svg-renderer';
|
||||
import {BitmapAdapter as V2BitmapAdapter} from 'scratch-svg-renderer';
|
||||
import RubberCanvas from '../lib/rubber-canvas';
|
||||
|
||||
import StageComponent from '../components/stage/stage.jsx';
|
||||
|
||||
@@ -39,9 +40,11 @@ class Stage extends React.Component {
|
||||
'updateRect',
|
||||
'questionListener',
|
||||
'setDragCanvas',
|
||||
'setWobblyDragCanvas',
|
||||
'clearDragCanvas',
|
||||
'drawDragCanvas',
|
||||
'positionDragCanvas'
|
||||
'positionDragCanvas',
|
||||
'_updateWobblyCanvas'
|
||||
]);
|
||||
this.state = {
|
||||
mouseDownTimeoutId: null,
|
||||
@@ -77,6 +80,13 @@ class Stage extends React.Component {
|
||||
this.attachMouseEvents(this.canvas);
|
||||
this.updateRect();
|
||||
this.props.vm.runtime.addListener('QUESTION', this.questionListener);
|
||||
|
||||
// The GUI component seems to be recreated when entering/exiting editor mode.
|
||||
// If we only update mystery mode in componentDidUpdate, it won't
|
||||
// update when switching between editor and player mode.
|
||||
if (this.props.mysteryMode !== this.renderer._mystery.modeActive) {
|
||||
this.renderer.setMysteryMode(this.props.mysteryMode);
|
||||
}
|
||||
}
|
||||
shouldComponentUpdate (nextProps, nextState) {
|
||||
return this.props.stageSize !== nextProps.stageSize ||
|
||||
@@ -85,7 +95,9 @@ class Stage extends React.Component {
|
||||
this.props.isFullScreen !== nextProps.isFullScreen ||
|
||||
this.state.question !== nextState.question ||
|
||||
this.props.micIndicator !== nextProps.micIndicator ||
|
||||
this.props.isStarted !== nextProps.isStarted;
|
||||
this.props.isStarted !== nextProps.isStarted ||
|
||||
this.props.mysteryMode !== nextProps.mysteryMode ||
|
||||
this.props.wobblyDragging !== nextProps.wobblyDragging;
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.props.isColorPicking && !prevProps.isColorPicking) {
|
||||
@@ -93,6 +105,9 @@ class Stage extends React.Component {
|
||||
} else if (!this.props.isColorPicking && prevProps.isColorPicking) {
|
||||
this.stopColorPickingLoop();
|
||||
}
|
||||
if (this.props.mysteryMode !== prevProps.mysteryMode) {
|
||||
this.renderer.setMysteryMode(this.props.mysteryMode);
|
||||
}
|
||||
this.updateRect();
|
||||
this.renderer.resize(this.rect.width, this.rect.height);
|
||||
}
|
||||
@@ -197,7 +212,11 @@ class Stage extends React.Component {
|
||||
// Editor drag style only updates the drag canvas, does full update at the end of drag
|
||||
// Non-editor drag style just updates the sprite continuously.
|
||||
if (this.props.useEditorDragStyle) {
|
||||
this.positionDragCanvas(mousePosition[0], mousePosition[1]);
|
||||
if (this.props.wobblyDragging) {
|
||||
this.wobblyDragCanvas.updateMousePosition(mousePosition);
|
||||
} else {
|
||||
this.positionDragCanvas(mousePosition[0], mousePosition[1]);
|
||||
}
|
||||
} else {
|
||||
const spritePosition = this.getScratchCoords(mousePosition[0], mousePosition[1]);
|
||||
this.props.vm.postSpriteInfo({
|
||||
@@ -326,14 +345,19 @@ class Stage extends React.Component {
|
||||
this.dragCanvas.style.display = 'block';
|
||||
}
|
||||
clearDragCanvas () {
|
||||
this.dragCanvas.width = this.dragCanvas.height = 0;
|
||||
this.dragCanvas.style.display = 'none';
|
||||
const dragCanvas = this.props.wobblyDragging ? this.wobblyDragCanvas._canvas : this.dragCanvas;
|
||||
dragCanvas.width = dragCanvas.height = 0;
|
||||
dragCanvas.style.display = 'none';
|
||||
}
|
||||
positionDragCanvas (mouseX, mouseY) {
|
||||
// mouseX/Y are relative to stage top/left, and dragCanvas is already
|
||||
// positioned so that the pick location is at (0,0).
|
||||
this.dragCanvas.style.transform = `translate(${mouseX}px, ${mouseY}px)`;
|
||||
}
|
||||
_updateWobblyCanvas (timestamp) {
|
||||
this.wobblyDragCanvas.step(timestamp);
|
||||
if (this.state.isDragging) window.requestAnimationFrame(this._updateWobblyCanvas);
|
||||
}
|
||||
onStartDrag (x, y) {
|
||||
if (this.state.dragId) return;
|
||||
const drawableId = this.renderer.pick(x, y);
|
||||
@@ -349,6 +373,8 @@ class Stage extends React.Component {
|
||||
// Dragging always brings the target to the front
|
||||
target.goToFront();
|
||||
|
||||
// Ensure drag canvas' bounds are tight by forcing renderer to generate tight bounds
|
||||
this.renderer.getBounds(drawableId);
|
||||
// Extract the drawable art
|
||||
const drawableData = this.renderer.extractDrawable(drawableId, x, y);
|
||||
|
||||
@@ -357,10 +383,17 @@ class Stage extends React.Component {
|
||||
isDragging: true,
|
||||
dragId: targetId,
|
||||
dragOffset: drawableData.scratchOffset
|
||||
}, () => {
|
||||
if (this.props.wobblyDragging && this.props.useEditorDragStyle) this._updateWobblyCanvas(performance.now());
|
||||
});
|
||||
if (this.props.useEditorDragStyle) {
|
||||
this.drawDragCanvas(drawableData);
|
||||
this.positionDragCanvas(x, y);
|
||||
if (this.props.wobblyDragging) {
|
||||
this.wobblyDragCanvas.reinit(drawableData, x, y);
|
||||
} else {
|
||||
this.drawDragCanvas(drawableData);
|
||||
this.positionDragCanvas(x, y);
|
||||
}
|
||||
|
||||
this.props.vm.postSpriteInfo({visible: false});
|
||||
}
|
||||
}
|
||||
@@ -400,6 +433,13 @@ class Stage extends React.Component {
|
||||
setDragCanvas (canvas) {
|
||||
this.dragCanvas = canvas;
|
||||
}
|
||||
setWobblyDragCanvas (canvas) {
|
||||
if (this.wobblyDragCanvas) {
|
||||
this.wobblyDragCanvas.destroy();
|
||||
this.wobblyDragCanvas = null;
|
||||
}
|
||||
if (canvas) this.wobblyDragCanvas = new RubberCanvas(canvas);
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
vm, // eslint-disable-line no-unused-vars
|
||||
@@ -414,6 +454,7 @@ class Stage extends React.Component {
|
||||
question={this.state.question}
|
||||
onDoubleClick={this.handleDoubleClick}
|
||||
onQuestionAnswered={this.handleQuestionAnswered}
|
||||
wobblyDragRef={this.setWobblyDragCanvas}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -429,7 +470,9 @@ Stage.propTypes = {
|
||||
onDeactivateColorPicker: PropTypes.func,
|
||||
stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired,
|
||||
useEditorDragStyle: PropTypes.bool,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
vm: PropTypes.instanceOf(VM).isRequired,
|
||||
mysteryMode: PropTypes.bool,
|
||||
wobblyDragging: PropTypes.bool
|
||||
};
|
||||
|
||||
Stage.defaultProps = {
|
||||
@@ -442,7 +485,10 @@ const mapStateToProps = state => ({
|
||||
isStarted: state.scratchGui.vmStatus.started,
|
||||
micIndicator: state.scratchGui.micIndicator,
|
||||
// Do not use editor drag style in fullscreen or player mode.
|
||||
useEditorDragStyle: !(state.scratchGui.mode.isFullScreen || state.scratchGui.mode.isPlayerOnly)
|
||||
useEditorDragStyle: !(state.scratchGui.mode.isFullScreen || state.scratchGui.mode.isPlayerOnly),
|
||||
mysteryMode: state.scratchGui.mysteryMode && !(
|
||||
state.scratchGui.mode.isFullScreen || state.scratchGui.mode.isPlayerOnly),
|
||||
wobblyDragging: state.scratchGui.wobblyDragging
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
@@ -51,6 +51,9 @@ import libraryCodeCartoon from './thumbnails/code-a-cartoon.jpg';
|
||||
// Talking Tales
|
||||
import libraryTalking from './thumbnails/talking.png';
|
||||
|
||||
// Sprite Mystery
|
||||
import librarySpriteMystery from './thumbnails/sprite-mystery.png';
|
||||
|
||||
// Videos
|
||||
import recordASound from './thumbnails/record-a-sound.jpg';
|
||||
import glideAroundThumb from './thumbnails/glide-around.jpg';
|
||||
@@ -497,6 +500,21 @@ export default {
|
||||
urlId: 'animations-that-talk'
|
||||
},
|
||||
|
||||
'sprite-mystery': {
|
||||
name: 'How to Find a Sprite',
|
||||
img: librarySpriteMystery,
|
||||
steps: [{
|
||||
video: 'sprite-mystery'
|
||||
}, {
|
||||
deckIds: [
|
||||
'intro-move-sayhello',
|
||||
'add-sprite'
|
||||
]
|
||||
}
|
||||
],
|
||||
urlId: 'sprite-mystery'
|
||||
},
|
||||
|
||||
'talking': {
|
||||
name: (
|
||||
<FormattedMessage
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
@@ -129,6 +129,9 @@ const videos = {
|
||||
},
|
||||
'talking': {
|
||||
en: 'j0208mq4qi'
|
||||
},
|
||||
'sprite-mystery': {
|
||||
en: 's69h0y6cs4'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D u_image0;
|
||||
varying vec2 v_texCoord;
|
||||
|
||||
void main () {
|
||||
gl_FragColor = texture2D(u_image0, v_texCoord);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
precision mediump float;
|
||||
|
||||
uniform vec4 u_bounds;
|
||||
attribute vec2 a_position;
|
||||
attribute vec2 a_texCoord;
|
||||
|
||||
varying vec2 v_texCoord;
|
||||
|
||||
void main () {
|
||||
vec2 boundsSize = u_bounds.zw - u_bounds.xy;
|
||||
vec2 adjusted = a_position - vec2(u_bounds.x, u_bounds.y);
|
||||
vec4 pos = vec4(((adjusted / boundsSize) - 0.5) * vec2(2.0, -2.0), 0.0, 1.0);
|
||||
v_texCoord = a_texCoord;
|
||||
gl_Position = pos;
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
import VERTEX_SOURCE from 'raw-loader!./rubber-canvas-vert.glsl';
|
||||
import FRAGMENT_SOURCE from 'raw-loader!./rubber-canvas-frag.glsl';
|
||||
|
||||
const DAMPING = 20;
|
||||
class _Spring {
|
||||
constructor (opts) {
|
||||
this.reinit(opts);
|
||||
}
|
||||
|
||||
reinit (opts) {
|
||||
if (Object.prototype.hasOwnProperty.call(opts, 'stiffness')) this.stiffness = opts.stiffness;
|
||||
if (Object.prototype.hasOwnProperty.call(opts, 'mass')) this.mass = opts.mass;
|
||||
this.anchor = opts.anchor;
|
||||
this.position = opts.anchor.slice(0);
|
||||
this._velocity = [0, 0];
|
||||
}
|
||||
|
||||
step (deltaTime) {
|
||||
const springForceX = -this.stiffness * (this.position[0] - this.anchor[0]);
|
||||
const springForceY = -this.stiffness * (this.position[1] - this.anchor[1]);
|
||||
|
||||
const dampingForceX = (DAMPING * this.mass) * this._velocity[0];
|
||||
const dampingForceY = (DAMPING * this.mass) * this._velocity[1];
|
||||
|
||||
const forceX = springForceX - dampingForceX;
|
||||
const forceY = springForceY - dampingForceY;
|
||||
|
||||
const accelerationX = forceX / this.mass;
|
||||
const accelerationY = forceY / this.mass;
|
||||
|
||||
this._velocity[0] += accelerationX * deltaTime;
|
||||
this._velocity[1] += accelerationY * deltaTime;
|
||||
|
||||
this.position[0] += this._velocity[0] * deltaTime;
|
||||
this.position[1] += this._velocity[1] * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
const createShader = (gl, source, type) => {
|
||||
const shader = gl.createShader(type);
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
const info = gl.getShaderInfoLog(shader);
|
||||
throw new Error(`Could not compile WebGL program. \n${info}`);
|
||||
}
|
||||
|
||||
return shader;
|
||||
};
|
||||
|
||||
const createProgram = (gl, vertSource, fragSource) => {
|
||||
const vertShader = createShader(gl, vertSource, gl.VERTEX_SHADER);
|
||||
const fragShader = createShader(gl, fragSource, gl.FRAGMENT_SHADER);
|
||||
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, vertShader);
|
||||
gl.attachShader(program, fragShader);
|
||||
gl.linkProgram(program);
|
||||
|
||||
const programInfo = {
|
||||
uniforms: {},
|
||||
attribs: {},
|
||||
program
|
||||
};
|
||||
|
||||
// Construct maps of uniform + attrib locations for convenience
|
||||
const numActiveUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
|
||||
for (let i = 0; i < numActiveUniforms; i++) {
|
||||
const uniformInfo = gl.getActiveUniform(program, i);
|
||||
programInfo.uniforms[uniformInfo.name] = gl.getUniformLocation(program, uniformInfo.name);
|
||||
}
|
||||
|
||||
const numActiveAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
|
||||
for (let i = 0; i < numActiveAttributes; i++) {
|
||||
const attribInfo = gl.getActiveAttrib(program, i);
|
||||
programInfo.attribs[attribInfo.name] = gl.getAttribLocation(program, attribInfo.name);
|
||||
}
|
||||
|
||||
return programInfo;
|
||||
};
|
||||
|
||||
// Resolution of the grid in terms of grid squares. In this case, that's 16 squares and 25 vertices.
|
||||
const SUBDIVISIONS = 4;
|
||||
|
||||
class RubberCanvas {
|
||||
constructor (canvas) {
|
||||
this._canvas = canvas;
|
||||
this._gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
||||
this._size = [0, 0];
|
||||
this._mousePosition = [0, 0];
|
||||
|
||||
const numPoints = (SUBDIVISIONS + 1) ** 2;
|
||||
this._springs = [];
|
||||
for (let i = 0; i < numPoints; i++) {
|
||||
this._springs.push(new _Spring({
|
||||
anchor: [0, 0],
|
||||
position: [0, 0]
|
||||
}));
|
||||
}
|
||||
|
||||
this._vertexData = new Float32Array(numPoints * 2);
|
||||
this._texCoordData = new Float32Array(numPoints * 2);
|
||||
this._vertexIndices = new Uint16Array(numPoints * 6);
|
||||
|
||||
// Initialize texture coordinates for all grid points
|
||||
for (let x = 0; x <= SUBDIVISIONS; x++) {
|
||||
for (let y = 0; y <= SUBDIVISIONS; y++) {
|
||||
const index = (y * (SUBDIVISIONS + 1)) + x;
|
||||
this._texCoordData[index * 2] = x / SUBDIVISIONS;
|
||||
this._texCoordData[(index * 2) + 1] = y / SUBDIVISIONS;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize vertex indices
|
||||
for (let x = 0; x < SUBDIVISIONS; x++) {
|
||||
for (let y = 0; y < SUBDIVISIONS; y++) {
|
||||
const topLeftIndex = (y * (SUBDIVISIONS + 1)) + x;
|
||||
const topRightIndex = (y * (SUBDIVISIONS + 1)) + x + 1;
|
||||
const bottomLeftIndex = ((y + 1) * (SUBDIVISIONS + 1)) + x;
|
||||
const bottomRightIndex = ((y + 1) * (SUBDIVISIONS + 1)) + x + 1;
|
||||
|
||||
this._vertexIndices[topLeftIndex * 6] = topLeftIndex;
|
||||
this._vertexIndices[(topLeftIndex * 6) + 1] = topRightIndex;
|
||||
this._vertexIndices[(topLeftIndex * 6) + 2] = bottomLeftIndex;
|
||||
|
||||
this._vertexIndices[(topLeftIndex * 6) + 3] = bottomLeftIndex;
|
||||
this._vertexIndices[(topLeftIndex * 6) + 4] = topRightIndex;
|
||||
this._vertexIndices[(topLeftIndex * 6) + 5] = bottomRightIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Used for delta-time calculation
|
||||
this._lastTimestamp = 0;
|
||||
|
||||
const gl = this._gl;
|
||||
this._shader = createProgram(gl, VERTEX_SOURCE, FRAGMENT_SOURCE);
|
||||
|
||||
this._vertexBuffer = gl.createBuffer();
|
||||
this._indexBuffer = gl.createBuffer();
|
||||
this._texCoordBuffer = gl.createBuffer();
|
||||
gl.enableVertexAttribArray(this._shader.attribs.a_position);
|
||||
gl.enableVertexAttribArray(this._shader.attribs.a_texCoord);
|
||||
|
||||
// The texture coordinates and vertex indices will never change, so send them over to the GPU here
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
|
||||
gl.bufferData(
|
||||
gl.ELEMENT_ARRAY_BUFFER,
|
||||
this._vertexIndices,
|
||||
gl.STATIC_DRAW
|
||||
);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordBuffer);
|
||||
gl.vertexAttribPointer(
|
||||
this._shader.attribs.a_texCoord,
|
||||
2, // vec2
|
||||
gl.FLOAT,
|
||||
false,
|
||||
0,
|
||||
0
|
||||
);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
this._texCoordData,
|
||||
gl.STATIC_DRAW
|
||||
);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
this._texture = gl.createTexture();
|
||||
|
||||
// This will be correct if https://github.com/LLK/scratch-render/pull/556 goes in in time
|
||||
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
||||
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
reinit (drawableData, x, y) {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
x: grabX,
|
||||
y: grabY
|
||||
} = drawableData;
|
||||
|
||||
this._setData(drawableData);
|
||||
|
||||
// Reinitialize springs' mass based on their distance to the point at which the sprite was grabbed
|
||||
for (let i = 0; i <= SUBDIVISIONS; i++) {
|
||||
const springX = (i / SUBDIVISIONS) * width;
|
||||
|
||||
for (let j = 0; j <= SUBDIVISIONS; j++) {
|
||||
const springY = (j / SUBDIVISIONS) * height;
|
||||
|
||||
const distance = Math.hypot(springX - grabX, springY - grabY);
|
||||
const distanceScaled = distance / Math.hypot(width, height);
|
||||
this._springs[(j * (SUBDIVISIONS + 1)) + i].reinit({
|
||||
anchor: [springX + x, springY + y],
|
||||
position: [springX + x, springY + y],
|
||||
stiffness: 15,
|
||||
mass: (distanceScaled * 0.05) + 0.025
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._lastTimestamp = performance.now();
|
||||
const {x: parentX, y: parentY} = this._canvas.parentElement.getBoundingClientRect();
|
||||
this._canvas.style.left = `${-grabX + parentX}px`;
|
||||
this._canvas.style.top = `${-grabY + parentY}px`;
|
||||
this._canvas.style.display = 'block';
|
||||
this.step();
|
||||
}
|
||||
|
||||
// Calculate the bounds of the dragged sprite after being jellofied.
|
||||
_calculateBounds () {
|
||||
let left = Infinity;
|
||||
let right = -Infinity;
|
||||
let top = Infinity;
|
||||
let bottom = -Infinity;
|
||||
|
||||
for (const spring of this._springs) {
|
||||
left = Math.min(left, spring.position[0]);
|
||||
right = Math.max(right, spring.position[0]);
|
||||
top = Math.min(top, spring.position[1]);
|
||||
bottom = Math.max(bottom, spring.position[1]);
|
||||
}
|
||||
|
||||
left = Math.floor(left);
|
||||
right = Math.ceil(right);
|
||||
bottom = Math.ceil(bottom);
|
||||
top = Math.floor(top);
|
||||
|
||||
return {left, right, top, bottom};
|
||||
}
|
||||
|
||||
_setData (data) {
|
||||
this._size[0] = data.width;
|
||||
this._size[1] = data.height;
|
||||
|
||||
const gl = this._gl;
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._texture);
|
||||
// disable automatic mipmapping
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, data.width, data.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data.data);
|
||||
}
|
||||
|
||||
updateMousePosition ([x, y]) {
|
||||
this._mousePosition[0] = x;
|
||||
this._mousePosition[1] = y;
|
||||
|
||||
// update springs' destinations to match new mouse position
|
||||
for (let i = 0; i <= SUBDIVISIONS; i++) {
|
||||
const springX = (i / SUBDIVISIONS) * this._size[0];
|
||||
|
||||
for (let j = 0; j <= SUBDIVISIONS; j++) {
|
||||
const springY = (j / SUBDIVISIONS) * this._size[1];
|
||||
|
||||
this._springs[(j * (SUBDIVISIONS + 1)) + i].anchor[0] = springX + x;
|
||||
this._springs[(j * (SUBDIVISIONS + 1)) + i].anchor[1] = springY + y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
step (timestamp) {
|
||||
// Slightly above 33.3ms (30 FPS)
|
||||
const maxTimeStep = 35;
|
||||
|
||||
let deltaTime = timestamp - this._lastTimestamp;
|
||||
|
||||
// Take multiple steps if deltaTime is too large
|
||||
// This prevents explodage
|
||||
let timeSubdivisions = 1;
|
||||
if (deltaTime > maxTimeStep) {
|
||||
timeSubdivisions = Math.ceil(deltaTime / maxTimeStep);
|
||||
deltaTime /= timeSubdivisions;
|
||||
}
|
||||
this._lastTimestamp = timestamp;
|
||||
if (deltaTime > 0) {
|
||||
for (let i = 0; i < timeSubdivisions; i++) {
|
||||
for (const spring of this._springs) {
|
||||
// I've got a step() in my _Spring
|
||||
spring.step(deltaTime / 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update vertex data to match springs' positions
|
||||
for (let i = 0; i < this._springs.length; i++) {
|
||||
const spring = this._springs[i];
|
||||
this._vertexData[i * 2] = spring.position[0];
|
||||
this._vertexData[(i * 2) + 1] = spring.position[1];
|
||||
}
|
||||
|
||||
const gl = this._gl;
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
|
||||
gl.vertexAttribPointer(
|
||||
this._shader.attribs.a_position,
|
||||
2, // vec2
|
||||
gl.FLOAT,
|
||||
false,
|
||||
0,
|
||||
0
|
||||
);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
this._vertexData,
|
||||
gl.DYNAMIC_DRAW
|
||||
);
|
||||
|
||||
const bounds = this._calculateBounds();
|
||||
|
||||
// 10 pixels of slack space to avoid excessive canvas resizing
|
||||
bounds.right += 10;
|
||||
bounds.bottom += 10;
|
||||
|
||||
// Only scale the canvas up, not down-- this also avoids resizing the canvas too much
|
||||
if (bounds.right - bounds.left < this._canvas.width) bounds.right = bounds.left + this._canvas.width;
|
||||
if (bounds.bottom - bounds.top < this._canvas.height) bounds.bottom = bounds.top + this._canvas.height;
|
||||
|
||||
const width = bounds.right - bounds.left;
|
||||
const height = bounds.bottom - bounds.top;
|
||||
|
||||
if (width > 2048 || height > 2048) {
|
||||
// Avoid creating lag spikes from a huge WebGL canvas if the spring simulation blows up
|
||||
return;
|
||||
}
|
||||
this._canvas.width = width;
|
||||
this._canvas.height = height;
|
||||
|
||||
gl.useProgram(this._shader.program);
|
||||
gl.uniform1i(this._shader.uniforms.u_image0, 0);
|
||||
gl.uniform4f(this._shader.uniforms.u_bounds, bounds.left, bounds.top, bounds.right, bounds.bottom);
|
||||
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._texture);
|
||||
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.viewport(0, 0, width, height);
|
||||
gl.drawElements(gl.TRIANGLES, this._vertexIndices.length, gl.UNSIGNED_SHORT, 0);
|
||||
|
||||
this._canvas.style.transform = `translate(${bounds.left}px, ${bounds.top}px)`;
|
||||
}
|
||||
|
||||
destroy () {
|
||||
const gl = this._gl;
|
||||
|
||||
gl.deleteBuffer(this._texCoordBuffer);
|
||||
gl.deleteBuffer(this._vertexBuffer);
|
||||
gl.deleteBuffer(this._indexBuffer);
|
||||
gl.deleteTexture(this._texture);
|
||||
gl.deleteProgram(this._shader.program);
|
||||
}
|
||||
}
|
||||
|
||||
export default RubberCanvas;
|
||||
+8
-2
@@ -14,6 +14,8 @@ import modalReducer, {modalsInitialState} from './modals';
|
||||
import modeReducer, {modeInitialState} from './mode';
|
||||
import monitorReducer, {monitorsInitialState} from './monitors';
|
||||
import monitorLayoutReducer, {monitorLayoutInitialState} from './monitor-layout';
|
||||
import mysteryModeReducer, {mysteryModeInitialState} from './mystery-mode';
|
||||
import wobblyDragReducer, {wobblyDragInitialState} from './wobbly-dragging';
|
||||
import projectChangedReducer, {projectChangedInitialState} from './project-changed';
|
||||
import projectStateReducer, {projectStateInitialState} from './project-state';
|
||||
import projectTitleReducer, {projectTitleInitialState} from './project-title';
|
||||
@@ -48,6 +50,7 @@ const guiInitialState = {
|
||||
modals: modalsInitialState,
|
||||
monitors: monitorsInitialState,
|
||||
monitorLayout: monitorLayoutInitialState,
|
||||
mysteryMode: mysteryModeInitialState,
|
||||
projectChanged: projectChangedInitialState,
|
||||
projectState: projectStateInitialState,
|
||||
projectTitle: projectTitleInitialState,
|
||||
@@ -57,7 +60,8 @@ const guiInitialState = {
|
||||
timeout: timeoutInitialState,
|
||||
toolbox: toolboxInitialState,
|
||||
vm: vmInitialState,
|
||||
vmStatus: vmStatusInitialState
|
||||
vmStatus: vmStatusInitialState,
|
||||
wobblyDragging: wobblyDragInitialState
|
||||
};
|
||||
|
||||
const initPlayer = function (currentState) {
|
||||
@@ -146,6 +150,7 @@ const guiReducer = combineReducers({
|
||||
modals: modalReducer,
|
||||
monitors: monitorReducer,
|
||||
monitorLayout: monitorLayoutReducer,
|
||||
mysteryMode: mysteryModeReducer,
|
||||
projectChanged: projectChangedReducer,
|
||||
projectState: projectStateReducer,
|
||||
projectTitle: projectTitleReducer,
|
||||
@@ -155,7 +160,8 @@ const guiReducer = combineReducers({
|
||||
timeout: timeoutReducer,
|
||||
toolbox: toolboxReducer,
|
||||
vm: vmReducer,
|
||||
vmStatus: vmStatusReducer
|
||||
vmStatus: vmStatusReducer,
|
||||
wobblyDragging: wobblyDragReducer
|
||||
});
|
||||
|
||||
export {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
const TOGGLE_MYSTERY_MODE = 'scratch-gui/mystery-mode/TOGGLE_MYSTERY_MODE';
|
||||
|
||||
const initialState = false;
|
||||
|
||||
const reducer = function (state, action) {
|
||||
if (typeof state === 'undefined') state = initialState;
|
||||
|
||||
switch (action.type) {
|
||||
case TOGGLE_MYSTERY_MODE:
|
||||
return !state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMysteryMode = function () {
|
||||
return {
|
||||
type: TOGGLE_MYSTERY_MODE
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
reducer as default,
|
||||
initialState as mysteryModeInitialState,
|
||||
toggleMysteryMode
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
const TOGGLE_WOBBLE = 'scratch-gui/mystery-mode/TOGGLE_WOBBLE';
|
||||
|
||||
const initialState = true;
|
||||
|
||||
const reducer = function (state, action) {
|
||||
if (typeof state === 'undefined') state = initialState;
|
||||
|
||||
switch (action.type) {
|
||||
case TOGGLE_WOBBLE:
|
||||
return !state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleWobblyDragging = function () {
|
||||
return {
|
||||
type: TOGGLE_WOBBLE
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
reducer as default,
|
||||
initialState as wobblyDragInitialState,
|
||||
toggleWobblyDragging
|
||||
};
|
||||
Reference in new issue
Block a user