From cb960e5a28520539c8fe00e929878ae501d8df46 Mon Sep 17 00:00:00 2001 From: Paul Kaplan <pkaplan@media.mit.edu> Date: Mon, 2 Apr 2018 11:39:13 -0400 Subject: [PATCH] Add editor drag mode for popping sprites of stage. Also integrate the "ignore draggability, always drag" behavior of editor mode. Right now, editor drag mode is hooked up to the "isFullscreen" flag, since it is the closest thing we have to player mode now. --- src/components/stage/stage.css | 16 +++++-- src/components/stage/stage.jsx | 11 ++++- src/containers/stage.jsx | 88 ++++++++++++++++++++++++++++++---- 3 files changed, 102 insertions(+), 13 deletions(-) diff --git a/src/components/stage/stage.css b/src/components/stage/stage.css index dfce6ba24..17c6e87b4 100644 --- a/src/components/stage/stage.css +++ b/src/components/stage/stage.css @@ -8,6 +8,10 @@ */ display: block; + /* Attach border radius directly to canvas to prevent needing overflow:hidden; */ + border-radius: $space; + border: 1px solid $ui-black-transparent; + /* @todo: This is for overriding the value being set somewhere. Where is it being set? */ background-color: transparent; } @@ -30,10 +34,6 @@ .stage-wrapper { position: relative; - border-radius: $space; - border: 1px solid $ui-black-transparent; - /* Keep the canvas inside the border radius */ - overflow: hidden; } .stage-wrapper-overlay { @@ -78,3 +78,11 @@ pointer-events: none; overflow: hidden; } + +.dragging-sprite { + position: absolute; + top: 0; + left: 0; + z-index: 1000; /* Above everything so it is draggable into other panes */ + filter: drop-shadow(5px 5px 5px $ui-black-transparent); + } diff --git a/src/components/stage/stage.jsx b/src/components/stage/stage.jsx index f79bd3d77..37f12706b 100644 --- a/src/components/stage/stage.jsx +++ b/src/components/stage/stage.jsx @@ -12,6 +12,7 @@ import styles from './stage.css'; const StageComponent = props => { const { canvasRef, + dragRef, height, isColorPicking, isFullScreen, @@ -24,7 +25,7 @@ const StageComponent = props => { } = props; const stageSize = getStageSize(isFullScreen, height, width); - + return ( <div> <Box @@ -71,6 +72,12 @@ const StageComponent = props => { </div> </div> )} + <canvas + className={styles.draggingSprite} + height={0} + ref={dragRef} + width={0} + /> </Box> {isColorPicking ? ( <Box @@ -84,6 +91,7 @@ const StageComponent = props => { StageComponent.propTypes = { canvasRef: PropTypes.func, colorInfo: Loupe.propTypes.colorInfo, + dragRef: PropTypes.func, height: PropTypes.number, isColorPicking: PropTypes.bool, isFullScreen: PropTypes.bool.isRequired, @@ -94,6 +102,7 @@ StageComponent.propTypes = { }; StageComponent.defaultProps = { canvasRef: () => {}, + dragRef: () => {}, width: 480, height: 360 }; diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx index cf10d5001..688951f73 100644 --- a/src/containers/stage.jsx +++ b/src/containers/stage.jsx @@ -34,7 +34,11 @@ class Stage extends React.Component { 'onWheel', 'updateRect', 'questionListener', - 'setCanvas' + 'setCanvas', + 'setDragCanvas', + 'clearDragCanvas', + 'drawDragCanvas', + 'positionDragCanvas' ]); this.state = { mouseDownTimeoutId: null, @@ -170,6 +174,7 @@ class Stage extends React.Component { y: -(spritePosition[1] + this.state.dragOffset[1]), force: true }); + this.positionDragCanvas(mousePosition[0], mousePosition[1]); } const coordinates = { x: mousePosition[0], @@ -248,11 +253,47 @@ class Stage extends React.Component { } this.setState({mouseDownTimeoutId: null}); } + drawDragCanvas (drawableData) { + const { + data, + width, + height, + x, + y + } = drawableData; + this.dragCanvas.width = width; + this.dragCanvas.height = height; + // Need to convert uint8array from WebGL readPixels into Uint8ClampedArray + // for ImageData constructor. Shares underlying buffer, so it is fast. + const imageData = new ImageData( + new Uint8ClampedArray(data.buffer), width, height); + this.dragCanvas.getContext('2d').putImageData(imageData, 0, 0); + // Position so that pick location is at (0, 0) so that positionDragCanvas() + // can use translation to move to mouse position smoothly. + this.dragCanvas.style.left = `${-x}px`; + this.dragCanvas.style.top = `${-y}px`; + } + clearDragCanvas () { + this.dragCanvas.width = this.dragCanvas.height = 0; + } + 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)`; + } onStartDrag (x, y) { + if (this.state.dragId) return; const drawableId = this.renderer.pick(x, y); if (drawableId === null) return; const drawableData = this.renderer.extractDrawable(drawableId, x, y); const targetId = this.props.vm.getTargetIdForDrawableId(drawableId); + + // Only start drags on non-draggable targets in editor drag mode + if (!this.props.useEditorDragStyle) { + const target = this.props.vm.runtime.getTargetById(targetId); + if (!target.draggable) return; + } + if (targetId === null) return; this.props.vm.startDrag(targetId); this.setState({ @@ -260,18 +301,41 @@ class Stage extends React.Component { dragId: targetId, dragOffset: drawableData.scratchOffset }); + if (this.props.useEditorDragStyle) { + this.drawDragCanvas(drawableData); + this.positionDragCanvas(x, y); + this.props.vm.postSpriteInfo({visible: false}); + } } onStopDrag () { - this.props.vm.stopDrag(this.state.dragId); - this.setState({ - isDragging: false, - dragOffset: null, - dragId: null - }); + const dragId = this.state.dragId; + const commonStopDragActions = () => { + this.props.vm.stopDrag(dragId); + this.setState({ + isDragging: false, + dragOffset: null, + dragId: null + }); + }; + if (this.props.useEditorDragStyle) { + // Need to sequence these actions to prevent flickering. + this.props.vm.postSpriteInfo({visible: true}); + setTimeout(() => { + this.clearDragCanvas(); + setTimeout(() => { + commonStopDragActions(); + }, 30); + }, 30); + } else { + commonStopDragActions(); + } } setCanvas (canvas) { this.canvas = canvas; } + setDragCanvas (canvas) { + this.dragCanvas = canvas; + } render () { const { vm, // eslint-disable-line no-unused-vars @@ -282,6 +346,7 @@ class Stage extends React.Component { <StageComponent canvasRef={this.setCanvas} colorInfo={this.state.colorInfo} + dragRef={this.setDragCanvas} question={this.state.question} onDoubleClick={this.handleDoubleClick} onQuestionAnswered={this.handleQuestionAnswered} @@ -297,13 +362,20 @@ Stage.propTypes = { isFullScreen: PropTypes.bool.isRequired, onActivateColorPicker: PropTypes.func, onDeactivateColorPicker: PropTypes.func, + useEditorDragStyle: PropTypes.bool, vm: PropTypes.instanceOf(VM).isRequired, width: PropTypes.number }; +Stage.defaultProps = { + useEditorDragStyle: true +}; + const mapStateToProps = state => ({ isColorPicking: state.colorPicker.active, - isFullScreen: state.stageSize.isFullScreen + isFullScreen: state.stageSize.isFullScreen, + // Do not use editor drag style in fullscreen mode. + useEditorDragStyle: !state.stageSize.isFullScreen }); const mapDispatchToProps = dispatch => ({ -- GitLab