-
+
+
+
+
+
+
-
+
);
};
StageComponent.propTypes = {
canvasRef: PropTypes.func,
+ colorInfo: Loupe.propTypes.colorInfo,
height: PropTypes.number,
+ isColorPicking: PropTypes.bool,
isZoomed: PropTypes.bool.isRequired,
+ onDeactivateColorPicker: PropTypes.func,
width: PropTypes.number
};
StageComponent.defaultProps = {
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index fbd4ecb6d..b15718483 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -11,6 +11,7 @@ import BlocksComponent from '../components/blocks/blocks.jsx';
import {connect} from 'react-redux';
import {updateToolbox} from '../reducers/toolbox';
+import {activateColorPicker} from '../reducers/color-picker';
const addFunctionListener = (object, property, callback) => {
const oldFn = object[property];
@@ -50,6 +51,8 @@ class Blocks extends React.Component {
this.onTargetsUpdate = debounce(this.onTargetsUpdate, 100);
}
componentDidMount () {
+ this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker;
+
const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options);
this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig);
@@ -219,14 +222,17 @@ class Blocks extends React.Component {
this.setState({prompt: null});
}
render () {
+ /* eslint-disable no-unused-vars */
const {
- options, // eslint-disable-line no-unused-vars
- vm, // eslint-disable-line no-unused-vars
- isVisible, // eslint-disable-line no-unused-vars
- onExtensionAdded, // eslint-disable-line no-unused-vars
- toolboxXML, // eslint-disable-line no-unused-vars
+ options,
+ vm,
+ isVisible,
+ onActivateColorPicker,
+ onExtensionAdded,
+ toolboxXML,
...props
} = this.props;
+ /* eslint-enable no-unused-vars */
return (
({
});
const mapDispatchToProps = dispatch => ({
+ onActivateColorPicker: callback => dispatch(activateColorPicker(callback)),
onExtensionAdded: toolboxXML => {
dispatch(updateToolbox(toolboxXML));
}
diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx
index 51a60fda7..39e66a500 100644
--- a/src/containers/stage.jsx
+++ b/src/containers/stage.jsx
@@ -3,12 +3,19 @@ import PropTypes from 'prop-types';
import React from 'react';
import Renderer from 'scratch-render';
import VM from 'scratch-vm';
-import {getEventXY} from '../lib/touch-utils';
-
import {connect} from 'react-redux';
+import {getEventXY} from '../lib/touch-utils';
+
import StageComponent from '../components/stage/stage.jsx';
+import {
+ activateColorPicker,
+ deactivateColorPicker
+} from '../reducers/color-picker';
+
+const colorPickerRadius = 20;
+
class Stage extends React.Component {
constructor (props) {
super(props);
@@ -29,9 +36,10 @@ class Stage extends React.Component {
mouseDownTimeoutId: null,
mouseDownPosition: null,
isDragging: false,
+ isZoomed: false,
dragOffset: null,
dragId: null,
- isZoomed: false
+ colorInfo: null
};
}
componentDidMount () {
@@ -50,16 +58,32 @@ class Stage extends React.Component {
}
shouldComponentUpdate (nextProps, nextState) {
return this.props.width !== nextProps.width ||
- this.props.height !== nextProps.height ||
- this.state.isZoomed !== nextState.isZoomed;
+ this.props.height !== nextProps.height ||
+ this.props.isColorPicking !== nextProps.isColorPicking ||
+ this.state.colorInfo !== nextState.colorInfo ||
+ this.state.isZoomed !== nextState.isZoomed;
}
- componentDidUpdate () {
+ componentDidUpdate (prevProps) {
+ if (this.props.isColorPicking && !prevProps.isColorPicking) {
+ this.startColorPickingLoop();
+ } else if (!this.props.isColorPicking && prevProps.isColorPicking) {
+ this.stopColorPickingLoop();
+ }
this.updateRect();
this.renderer.resize(this.rect.width, this.rect.height);
}
componentWillUnmount () {
this.detachMouseEvents(this.canvas);
this.detachRectEvents();
+ this.stopColorPickingLoop();
+ }
+ startColorPickingLoop () {
+ this.intervalId = setInterval(() => {
+ this.setState({colorInfo: this.getColorInfo(this.pickX, this.pickY)});
+ }, 30);
+ }
+ stopColorPickingLoop () {
+ clearInterval(this.intervalId);
}
attachMouseEvents (canvas) {
document.addEventListener('mousemove', this.onMouseMove);
@@ -95,6 +119,13 @@ class Stage extends React.Component {
(nativeSize[1] / this.rect.height) * (y - (this.rect.height / 2))
];
}
+ getColorInfo (x, y) {
+ return {
+ x: x,
+ y: y,
+ ...this.renderer.extractColor(x, y, colorPickerRadius)
+ };
+ }
handleDoubleClick (e) {
const {x, y} = getEventXY(e);
// Set editing target from cursor position, if clicking on a sprite.
@@ -108,6 +139,11 @@ class Stage extends React.Component {
onMouseMove (e) {
const {x, y} = getEventXY(e);
const mousePosition = [x - this.rect.left, y - this.rect.top];
+
+ // Set the pickX/Y for the color picker loop to pick up
+ this.pickX = mousePosition[0];
+ this.pickY = mousePosition[1];
+
if (this.state.mouseDownTimeoutId !== null) {
this.cancelMouseDownTimeout();
if (this.state.mouseDown && !this.state.isDragging) {
@@ -173,6 +209,16 @@ class Stage extends React.Component {
if (e.preventDefault) {
e.preventDefault();
}
+ if (this.props.isColorPicking) {
+ const {r, g, b} = this.state.colorInfo.color;
+ const componentToString = c => {
+ const hex = c.toString(16);
+ return hex.length === 1 ? `0${hex}` : hex;
+ };
+ const colorString = `#${componentToString(r)}${componentToString(g)}${componentToString(b)}`;
+ this.props.onDeactivateColorPicker(colorString);
+ this.setState({colorInfo: null});
+ }
}
cancelMouseDownTimeout () {
if (this.state.mouseDownTimeoutId !== null) {
@@ -207,11 +253,13 @@ class Stage extends React.Component {
render () {
const {
vm, // eslint-disable-line no-unused-vars
+ onActivateColorPicker, // eslint-disable-line no-unused-vars
...props
} = this.props;
return (
({
+ isColorPicking: state.colorPicker.active,
isZoomed: state.isZoomed
});
+const mapDispatchToProps = dispatch => ({
+ onActivateColorPicker: () => dispatch(activateColorPicker()),
+ onDeactivateColorPicker: color => dispatch(deactivateColorPicker(color))
+});
+
export default connect(
- mapStateToProps
+ mapStateToProps,
+ mapDispatchToProps
)(Stage);
diff --git a/src/reducers/color-picker.js b/src/reducers/color-picker.js
new file mode 100644
index 000000000..fc16354a0
--- /dev/null
+++ b/src/reducers/color-picker.js
@@ -0,0 +1,40 @@
+const ACTIVATE_COLOR_PICKER = 'scratch-gui/color-picker/ACTIVATE_COLOR_PICKER';
+const DEACTIVATE_COLOR_PICKER = 'scratch-gui/color-picker/DEACTIVATE_COLOR_PICKER';
+const SET_CALLBACK = 'scratch-gui/color-picker/SET_CALLBACK';
+
+const initialState = {
+ active: false,
+ callback: () => {
+ throw new Error('Color picker callback not initialized');
+ }
+};
+
+const reducer = function (state, action) {
+ if (typeof state === 'undefined') state = initialState;
+ switch (action.type) {
+ case ACTIVATE_COLOR_PICKER:
+ return Object.assign({}, state, {active: true, callback: action.callback});
+ case DEACTIVATE_COLOR_PICKER:
+ // Can be called without a string to deactivate without setting color
+ // i.e. when clicking on the modal background
+ if (typeof action.color === 'string') {
+ state.callback(action.color);
+ }
+ return Object.assign({}, state, {active: false});
+ case SET_CALLBACK:
+ return Object.assign({}, state, {callback: action.callback});
+ default:
+ return state;
+ }
+};
+
+const activateColorPicker = callback => ({type: ACTIVATE_COLOR_PICKER, callback: callback});
+const deactivateColorPicker = color => ({type: DEACTIVATE_COLOR_PICKER, color: color});
+const setCallback = callback => ({type: SET_CALLBACK, callback: callback});
+
+export {
+ reducer as default,
+ activateColorPicker,
+ deactivateColorPicker,
+ setCallback
+};
diff --git a/src/reducers/gui.js b/src/reducers/gui.js
index f0b7eb6f5..9477566d4 100644
--- a/src/reducers/gui.js
+++ b/src/reducers/gui.js
@@ -1,4 +1,5 @@
import {combineReducers} from 'redux';
+import colorPickerReducer from './color-picker';
import intlReducer from './intl';
import modalReducer from './modals';
import monitorReducer from './monitors';
@@ -7,8 +8,8 @@ import toolboxReducer from './toolbox';
import vmReducer from './vm';
import zoomReducer from './zoom';
-
export default combineReducers({
+ colorPicker: colorPickerReducer,
intl: intlReducer,
isZoomed: zoomReducer,
modals: modalReducer,