Skip to content
Snippets Groups Projects
Commit 523f2aa6 authored by Paul Kaplan's avatar Paul Kaplan
Browse files

Add sprite target highlighter on sprite-tile click

parent b18b428a
No related branches found
No related tags found
No related merge requests found
...@@ -89,7 +89,7 @@ to adjust for the border using a different method */ ...@@ -89,7 +89,7 @@ to adjust for the border using a different method */
padding-bottom: calc($stage-full-screen-stage-padding + $stage-full-screen-border-width); padding-bottom: calc($stage-full-screen-stage-padding + $stage-full-screen-border-width);
} }
.monitor-wrapper, .color-picker-wrapper { .monitor-wrapper, .color-picker-wrapper, .frame-wrapper {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
...@@ -127,3 +127,18 @@ to adjust for the border using a different method */ ...@@ -127,3 +127,18 @@ to adjust for the border using a different method */
.question-wrapper { .question-wrapper {
pointer-events: auto; pointer-events: auto;
} }
.frame {
box-sizing: content-box !important;
background: $motion-transparent;
border: 2px solid $motion-primary;
border-radius: 0.5rem;
animation-name: flash;
animation-duration: 0.75s;
animation-fill-mode: forwards; /* Leave at 0 opacity after animation */
}
@keyframes flash {
0% { opacity: 1; }
100% { opacity: 0; }
}
...@@ -6,6 +6,7 @@ import Box from '../box/box.jsx'; ...@@ -6,6 +6,7 @@ import Box from '../box/box.jsx';
import DOMElementRenderer from '../../containers/dom-element-renderer.jsx'; import DOMElementRenderer from '../../containers/dom-element-renderer.jsx';
import Loupe from '../loupe/loupe.jsx'; import Loupe from '../loupe/loupe.jsx';
import MonitorList from '../../containers/monitor-list.jsx'; import MonitorList from '../../containers/monitor-list.jsx';
import TargetHighlight from '../../containers/target-highlight.jsx';
import Question from '../../containers/question.jsx'; import Question from '../../containers/question.jsx';
import MicIndicator from '../mic-indicator/mic-indicator.jsx'; import MicIndicator from '../mic-indicator/mic-indicator.jsx';
import {STAGE_DISPLAY_SIZES} from '../../lib/layout-constants.js'; import {STAGE_DISPLAY_SIZES} from '../../lib/layout-constants.js';
...@@ -63,6 +64,9 @@ const StageComponent = props => { ...@@ -63,6 +64,9 @@ const StageComponent = props => {
stageSize={stageDimensions} stageSize={stageDimensions}
/> />
</Box> </Box>
<Box className={styles.frameWrapper}>
<TargetHighlight className={styles.frame} />
</Box>
{isColorPicking && colorInfo ? ( {isColorPicking && colorInfo ? (
<Box className={styles.colorPickerWrapper}> <Box className={styles.colorPickerWrapper}>
<Loupe colorInfo={colorInfo} /> <Loupe colorInfo={colorInfo} />
......
import bindAll from 'lodash.bindall';
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import VM from 'scratch-vm';
class TargetHighlight extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'getPageCoords'
]);
}
// Transform scratch coordinates into page coordinates
getPageCoords (x, y) {
const nativeSize = this.props.vm.renderer.getNativeSize();
const rect = this.props.vm.renderer.canvas.getBoundingClientRect();
return [
((rect.width / nativeSize[0]) * x) + (rect.width / 2),
-((rect.height / nativeSize[1]) * y) + (rect.height / 2)
];
}
render () {
const {
className,
highlightedTargetId,
highlightedTargetTime,
vm
} = this.props;
if (!(highlightedTargetId && vm && vm.renderer)) return null;
const target = vm.runtime.getTargetById(highlightedTargetId);
const bounds = vm.renderer.getBounds(target.drawableID);
const [left, top] = this.getPageCoords(bounds.left, bounds.top);
const [right, bottom] = this.getPageCoords(bounds.right, bounds.bottom);
const pad = 2; // px
return (
<div
className={className}
// Ensure new DOM element each update to restart animation
key={highlightedTargetTime}
style={{
position: 'absolute',
top: `${top - pad}px`,
left: `${left - pad}px`,
width: `${(right - left) + (2 * pad)}px`,
height: `${(bottom - top) + (2 * pad)}px`
}}
/>
);
}
}
TargetHighlight.propTypes = {
className: PropTypes.string,
highlightedTargetId: PropTypes.string,
highlightedTargetTime: PropTypes.number,
vm: PropTypes.instanceOf(VM)
};
const mapStateToProps = state => ({
highlightedTargetTime: state.scratchGui.targets.highlightedTargetTime,
highlightedTargetId: state.scratchGui.targets.highlightedTargetId,
vm: state.scratchGui.vm
});
const mapDispatchToProps = () => ({});
export default connect(
mapStateToProps,
mapDispatchToProps
)(TargetHighlight);
...@@ -18,6 +18,7 @@ import spriteLibraryContent from '../lib/libraries/sprites.json'; ...@@ -18,6 +18,7 @@ import spriteLibraryContent from '../lib/libraries/sprites.json';
import {handleFileUpload, spriteUpload} from '../lib/file-uploader.js'; import {handleFileUpload, spriteUpload} from '../lib/file-uploader.js';
import sharedMessages from '../lib/shared-messages'; import sharedMessages from '../lib/shared-messages';
import {emptySprite} from '../lib/empty-assets'; import {emptySprite} from '../lib/empty-assets';
import {highlightTarget} from '../reducers/targets';
class TargetPane extends React.Component { class TargetPane extends React.Component {
constructor (props) { constructor (props) {
...@@ -109,6 +110,7 @@ class TargetPane extends React.Component { ...@@ -109,6 +110,7 @@ class TargetPane extends React.Component {
} }
handleSelectSprite (id) { handleSelectSprite (id) {
this.props.vm.setEditingTarget(id); this.props.vm.setEditingTarget(id);
this.props.onHighlightTarget(id, Date.now());
} }
handleSurpriseSpriteClick () { handleSurpriseSpriteClick () {
const item = spriteLibraryContent[Math.floor(Math.random() * spriteLibraryContent.length)]; const item = spriteLibraryContent[Math.floor(Math.random() * spriteLibraryContent.length)];
...@@ -195,6 +197,7 @@ class TargetPane extends React.Component { ...@@ -195,6 +197,7 @@ class TargetPane extends React.Component {
const { const {
onActivateTab, // eslint-disable-line no-unused-vars onActivateTab, // eslint-disable-line no-unused-vars
onReceivedBlocks, // eslint-disable-line no-unused-vars onReceivedBlocks, // eslint-disable-line no-unused-vars
onHighlightTarget, // eslint-disable-line no-unused-vars
dispatchUpdateRestore, // eslint-disable-line no-unused-vars dispatchUpdateRestore, // eslint-disable-line no-unused-vars
...componentProps ...componentProps
} = this.props; } = this.props;
...@@ -265,6 +268,9 @@ const mapDispatchToProps = dispatch => ({ ...@@ -265,6 +268,9 @@ const mapDispatchToProps = dispatch => ({
}, },
dispatchUpdateRestore: restoreState => { dispatchUpdateRestore: restoreState => {
dispatch(setRestore(restoreState)); dispatch(setRestore(restoreState));
},
onHighlightTarget: (id, time) => {
dispatch(highlightTarget(id, time));
} }
}); });
......
const UPDATE_TARGET_LIST = 'scratch-gui/targets/UPDATE_TARGET_LIST'; const UPDATE_TARGET_LIST = 'scratch-gui/targets/UPDATE_TARGET_LIST';
const HIGHLIGHT_TARGET = 'scratch-gui/targets/HIGHLIGHT_TARGET';
const initialState = { const initialState = {
sprites: {}, sprites: {},
stage: {} stage: {},
highlightedTargetId: null,
highlightedTargetTime: null
}; };
const reducer = function (state, action) { const reducer = function (state, action) {
...@@ -23,6 +26,11 @@ const reducer = function (state, action) { ...@@ -23,6 +26,11 @@ const reducer = function (state, action) {
.filter(target => target.isStage)[0] || {}, .filter(target => target.isStage)[0] || {},
editingTarget: action.editingTarget editingTarget: action.editingTarget
}); });
case HIGHLIGHT_TARGET:
return Object.assign({}, state, {
highlightedTargetId: action.targetId,
highlightedTargetTime: action.updateTime
});
default: default:
return state; return state;
} }
...@@ -37,8 +45,16 @@ const updateTargets = function (targetList, editingTarget) { ...@@ -37,8 +45,16 @@ const updateTargets = function (targetList, editingTarget) {
} }
}; };
}; };
const highlightTarget = function (targetId, updateTime) {
return {
type: HIGHLIGHT_TARGET,
targetId: targetId,
updateTime: updateTime
};
};
export { export {
reducer as default, reducer as default,
initialState as targetsInitialState, initialState as targetsInitialState,
updateTargets updateTargets,
highlightTarget
}; };
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment