diff --git a/src/components/oval-mode.jsx b/src/components/oval-mode.jsx
new file mode 100644
index 00000000..eb179517
--- /dev/null
+++ b/src/components/oval-mode.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {FormattedMessage} from 'react-intl';
+
+const OvalModeComponent = props => (
+
+);
+
+OvalModeComponent.propTypes = {
+ onMouseDown: PropTypes.func.isRequired
+};
+
+export default OvalModeComponent;
diff --git a/src/components/paint-editor.jsx b/src/components/paint-editor.jsx
index f81262b5..be530590 100644
--- a/src/components/paint-editor.jsx
+++ b/src/components/paint-editor.jsx
@@ -10,6 +10,9 @@ import ReshapeMode from '../containers/reshape-mode.jsx';
import SelectMode from '../containers/select-mode.jsx';
import LineMode from '../containers/line-mode.jsx';
import PenMode from '../containers/pen-mode.jsx';
+import RectMode from '../containers/rect-mode.jsx';
+import RoundedRectMode from '../containers/rounded-rect-mode.jsx';
+import OvalMode from '../containers/oval-mode.jsx';
import FillColorIndicatorComponent from '../containers/fill-color-indicator.jsx';
import StrokeColorIndicatorComponent from '../containers/stroke-color-indicator.jsx';
@@ -150,6 +153,15 @@ class PaintEditorComponent extends React.Component {
+
+
+
) : null}
diff --git a/src/components/rect-mode.jsx b/src/components/rect-mode.jsx
new file mode 100644
index 00000000..1c18b067
--- /dev/null
+++ b/src/components/rect-mode.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {FormattedMessage} from 'react-intl';
+
+const RectModeComponent = props => (
+
+);
+
+RectModeComponent.propTypes = {
+ onMouseDown: PropTypes.func.isRequired
+};
+
+export default RectModeComponent;
diff --git a/src/components/rounded-rect-mode.jsx b/src/components/rounded-rect-mode.jsx
new file mode 100644
index 00000000..fe240c11
--- /dev/null
+++ b/src/components/rounded-rect-mode.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {FormattedMessage} from 'react-intl';
+
+const RoundedRectModeComponent = props => (
+
+);
+
+RoundedRectModeComponent.propTypes = {
+ onMouseDown: PropTypes.func.isRequired
+};
+
+export default RoundedRectModeComponent;
diff --git a/src/containers/oval-mode.jsx b/src/containers/oval-mode.jsx
new file mode 100644
index 00000000..34ccf091
--- /dev/null
+++ b/src/containers/oval-mode.jsx
@@ -0,0 +1,102 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import {connect} from 'react-redux';
+import bindAll from 'lodash.bindall';
+import Modes from '../modes/modes';
+
+import {changeMode} from '../reducers/modes';
+import {clearHoveredItem, setHoveredItem} from '../reducers/hover';
+import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
+
+import {getSelectedLeafItems} from '../helper/selection';
+import OvalTool from '../helper/tools/oval-tool';
+import OvalModeComponent from '../components/oval-mode.jsx';
+
+class OvalMode extends React.Component {
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'activateTool',
+ 'deactivateTool'
+ ]);
+ }
+ componentDidMount () {
+ if (this.props.isOvalModeActive) {
+ this.activateTool(this.props);
+ }
+ }
+ componentWillReceiveProps (nextProps) {
+ if (this.tool && nextProps.hoveredItemId !== this.props.hoveredItemId) {
+ this.tool.setPrevHoveredItemId(nextProps.hoveredItemId);
+ }
+
+ if (nextProps.isOvalModeActive && !this.props.isOvalModeActive) {
+ this.activateTool();
+ } else if (!nextProps.isOvalModeActive && this.props.isOvalModeActive) {
+ this.deactivateTool();
+ }
+ }
+ shouldComponentUpdate () {
+ return false; // Static component, for now
+ }
+ activateTool () {
+ this.tool = new OvalTool(
+ this.props.setHoveredItem,
+ this.props.clearHoveredItem,
+ this.props.setSelectedItems,
+ this.props.clearSelectedItems,
+ this.props.onUpdateSvg
+ );
+ this.tool.activate();
+ }
+ deactivateTool () {
+ this.tool.deactivateTool();
+ this.tool.remove();
+ this.tool = null;
+ }
+ render () {
+ return (
+
+ );
+ }
+}
+
+OvalMode.propTypes = {
+ clearHoveredItem: PropTypes.func.isRequired,
+ clearSelectedItems: PropTypes.func.isRequired,
+ handleMouseDown: PropTypes.func.isRequired,
+ hoveredItemId: PropTypes.number,
+ isOvalModeActive: PropTypes.bool.isRequired,
+ onUpdateSvg: PropTypes.func.isRequired,
+ setHoveredItem: PropTypes.func.isRequired,
+ setSelectedItems: PropTypes.func.isRequired
+};
+
+const mapStateToProps = state => ({
+ isOvalModeActive: state.scratchPaint.mode === Modes.OVAL,
+ hoveredItemId: state.scratchPaint.hoveredItemId
+});
+const mapDispatchToProps = dispatch => ({
+ setHoveredItem: hoveredItemId => {
+ dispatch(setHoveredItem(hoveredItemId));
+ },
+ clearHoveredItem: () => {
+ dispatch(clearHoveredItem());
+ },
+ clearSelectedItems: () => {
+ dispatch(clearSelectedItems());
+ },
+ setSelectedItems: () => {
+ dispatch(setSelectedItems(getSelectedLeafItems()));
+ },
+ handleMouseDown: () => {
+ dispatch(changeMode(Modes.OVAL));
+ },
+ deactivateTool () {
+ }
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(OvalMode);
diff --git a/src/containers/rect-mode.jsx b/src/containers/rect-mode.jsx
new file mode 100644
index 00000000..7a0d8cde
--- /dev/null
+++ b/src/containers/rect-mode.jsx
@@ -0,0 +1,102 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import {connect} from 'react-redux';
+import bindAll from 'lodash.bindall';
+import Modes from '../modes/modes';
+
+import {changeMode} from '../reducers/modes';
+import {clearHoveredItem, setHoveredItem} from '../reducers/hover';
+import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
+
+import {getSelectedLeafItems} from '../helper/selection';
+import RectTool from '../helper/tools/rect-tool';
+import RectModeComponent from '../components/rect-mode.jsx';
+
+class RectMode extends React.Component {
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'activateTool',
+ 'deactivateTool'
+ ]);
+ }
+ componentDidMount () {
+ if (this.props.isRectModeActive) {
+ this.activateTool(this.props);
+ }
+ }
+ componentWillReceiveProps (nextProps) {
+ if (this.tool && nextProps.hoveredItemId !== this.props.hoveredItemId) {
+ this.tool.setPrevHoveredItemId(nextProps.hoveredItemId);
+ }
+
+ if (nextProps.isRectModeActive && !this.props.isRectModeActive) {
+ this.activateTool();
+ } else if (!nextProps.isRectModeActive && this.props.isRectModeActive) {
+ this.deactivateTool();
+ }
+ }
+ shouldComponentUpdate () {
+ return false; // Static component, for now
+ }
+ activateTool () {
+ this.tool = new RectTool(
+ this.props.setHoveredItem,
+ this.props.clearHoveredItem,
+ this.props.setSelectedItems,
+ this.props.clearSelectedItems,
+ this.props.onUpdateSvg
+ );
+ this.tool.activate();
+ }
+ deactivateTool () {
+ this.tool.deactivateTool();
+ this.tool.remove();
+ this.tool = null;
+ }
+ render () {
+ return (
+
+ );
+ }
+}
+
+RectMode.propTypes = {
+ clearHoveredItem: PropTypes.func.isRequired,
+ clearSelectedItems: PropTypes.func.isRequired,
+ handleMouseDown: PropTypes.func.isRequired,
+ hoveredItemId: PropTypes.number,
+ isRectModeActive: PropTypes.bool.isRequired,
+ onUpdateSvg: PropTypes.func.isRequired,
+ setHoveredItem: PropTypes.func.isRequired,
+ setSelectedItems: PropTypes.func.isRequired
+};
+
+const mapStateToProps = state => ({
+ isRectModeActive: state.scratchPaint.mode === Modes.RECT,
+ hoveredItemId: state.scratchPaint.hoveredItemId
+});
+const mapDispatchToProps = dispatch => ({
+ setHoveredItem: hoveredItemId => {
+ dispatch(setHoveredItem(hoveredItemId));
+ },
+ clearHoveredItem: () => {
+ dispatch(clearHoveredItem());
+ },
+ clearSelectedItems: () => {
+ dispatch(clearSelectedItems());
+ },
+ setSelectedItems: () => {
+ dispatch(setSelectedItems(getSelectedLeafItems()));
+ },
+ handleMouseDown: () => {
+ dispatch(changeMode(Modes.RECT));
+ },
+ deactivateTool () {
+ }
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(RectMode);
diff --git a/src/containers/rounded-rect-mode.jsx b/src/containers/rounded-rect-mode.jsx
new file mode 100644
index 00000000..350835f7
--- /dev/null
+++ b/src/containers/rounded-rect-mode.jsx
@@ -0,0 +1,102 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import {connect} from 'react-redux';
+import bindAll from 'lodash.bindall';
+import Modes from '../modes/modes';
+
+import {changeMode} from '../reducers/modes';
+import {clearHoveredItem, setHoveredItem} from '../reducers/hover';
+import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
+
+import {getSelectedLeafItems} from '../helper/selection';
+import RoundedRectTool from '../helper/tools/rounded-rect-tool';
+import RoundedRectModeComponent from '../components/rounded-rect-mode.jsx';
+
+class RoundedRectMode extends React.Component {
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'activateTool',
+ 'deactivateTool'
+ ]);
+ }
+ componentDidMount () {
+ if (this.props.isRoundedRectModeActive) {
+ this.activateTool(this.props);
+ }
+ }
+ componentWillReceiveProps (nextProps) {
+ if (this.tool && nextProps.hoveredItemId !== this.props.hoveredItemId) {
+ this.tool.setPrevHoveredItemId(nextProps.hoveredItemId);
+ }
+
+ if (nextProps.isRoundedRectModeActive && !this.props.isRoundedRectModeActive) {
+ this.activateTool();
+ } else if (!nextProps.isRoundedRectModeActive && this.props.isRoundedRectModeActive) {
+ this.deactivateTool();
+ }
+ }
+ shouldComponentUpdate () {
+ return false; // Static component, for now
+ }
+ activateTool () {
+ this.tool = new RoundedRectTool(
+ this.props.setHoveredItem,
+ this.props.clearHoveredItem,
+ this.props.setSelectedItems,
+ this.props.clearSelectedItems,
+ this.props.onUpdateSvg
+ );
+ this.tool.activate();
+ }
+ deactivateTool () {
+ this.tool.deactivateTool();
+ this.tool.remove();
+ this.tool = null;
+ }
+ render () {
+ return (
+
+ );
+ }
+}
+
+RoundedRectMode.propTypes = {
+ clearHoveredItem: PropTypes.func.isRequired,
+ clearSelectedItems: PropTypes.func.isRequired,
+ handleMouseDown: PropTypes.func.isRequired,
+ hoveredItemId: PropTypes.number,
+ isRoundedRectModeActive: PropTypes.bool.isRequired,
+ onUpdateSvg: PropTypes.func.isRequired,
+ setHoveredItem: PropTypes.func.isRequired,
+ setSelectedItems: PropTypes.func.isRequired
+};
+
+const mapStateToProps = state => ({
+ isRoundedRectModeActive: state.scratchPaint.mode === Modes.ROUNDED_RECT,
+ hoveredItemId: state.scratchPaint.hoveredItemId
+});
+const mapDispatchToProps = dispatch => ({
+ setHoveredItem: hoveredItemId => {
+ dispatch(setHoveredItem(hoveredItemId));
+ },
+ clearHoveredItem: () => {
+ dispatch(clearHoveredItem());
+ },
+ clearSelectedItems: () => {
+ dispatch(clearSelectedItems());
+ },
+ setSelectedItems: () => {
+ dispatch(setSelectedItems(getSelectedLeafItems()));
+ },
+ handleMouseDown: () => {
+ dispatch(changeMode(Modes.ROUNDED_RECT));
+ },
+ deactivateTool () {
+ }
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(RoundedRectMode);
diff --git a/src/helper/tools/oval-tool.js b/src/helper/tools/oval-tool.js
new file mode 100644
index 00000000..ceb796d6
--- /dev/null
+++ b/src/helper/tools/oval-tool.js
@@ -0,0 +1,54 @@
+import paper from '@scratch/paper';
+import log from '../../log/log';
+
+/**
+ * Tool for drawing ovals.
+ */
+class OvalTool extends paper.Tool {
+ /**
+ * @param {function} setHoveredItem Callback to set the hovered item
+ * @param {function} clearHoveredItem Callback to clear the hovered item
+ * @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
+ * @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
+ * @param {!function} onUpdateSvg A callback to call when the image visibly changes
+ */
+ constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
+ super();
+ this.setHoveredItem = setHoveredItem;
+ this.clearHoveredItem = clearHoveredItem;
+ this.setSelectedItems = setSelectedItems;
+ this.clearSelectedItems = clearSelectedItems;
+ this.onUpdateSvg = onUpdateSvg;
+ this.prevHoveredItemId = null;
+
+ // We have to set these functions instead of just declaring them because
+ // paper.js tools hook up the listeners in the setter functions.
+ this.onMouseDown = this.handleMouseDown;
+ this.onMouseMove = this.handleMouseMove;
+ this.onMouseDrag = this.handleMouseDrag;
+ this.onMouseUp = this.handleMouseUp;
+ }
+ /**
+ * To be called when the hovered item changes. When the select tool hovers over a
+ * new item, it compares against this to see if a hover item change event needs to
+ * be fired.
+ * @param {paper.Item} prevHoveredItemId ID of the highlight item that indicates the mouse is
+ * over a given item currently
+ */
+ setPrevHoveredItemId (prevHoveredItemId) {
+ this.prevHoveredItemId = prevHoveredItemId;
+ }
+ handleMouseDown () {
+ log.warn('Circle tool not yet implemented');
+ }
+ handleMouseMove () {
+ }
+ handleMouseDrag () {
+ }
+ handleMouseUp () {
+ }
+ deactivateTool () {
+ }
+}
+
+export default OvalTool;
diff --git a/src/helper/tools/pen-tool.js b/src/helper/tools/pen-tool.js
index 76d5e838..1baffd89 100644
--- a/src/helper/tools/pen-tool.js
+++ b/src/helper/tools/pen-tool.js
@@ -47,6 +47,8 @@ class PenTool extends paper.Tool {
}
handleMouseUp () {
}
+ deactivateTool () {
+ }
}
export default PenTool;
diff --git a/src/helper/tools/rect-tool.js b/src/helper/tools/rect-tool.js
new file mode 100644
index 00000000..03d15449
--- /dev/null
+++ b/src/helper/tools/rect-tool.js
@@ -0,0 +1,54 @@
+import paper from '@scratch/paper';
+import log from '../../log/log';
+
+/**
+ * Tool for drawing rectangles.
+ */
+class RectTool extends paper.Tool {
+ /**
+ * @param {function} setHoveredItem Callback to set the hovered item
+ * @param {function} clearHoveredItem Callback to clear the hovered item
+ * @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
+ * @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
+ * @param {!function} onUpdateSvg A callback to call when the image visibly changes
+ */
+ constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
+ super();
+ this.setHoveredItem = setHoveredItem;
+ this.clearHoveredItem = clearHoveredItem;
+ this.setSelectedItems = setSelectedItems;
+ this.clearSelectedItems = clearSelectedItems;
+ this.onUpdateSvg = onUpdateSvg;
+ this.prevHoveredItemId = null;
+
+ // We have to set these functions instead of just declaring them because
+ // paper.js tools hook up the listeners in the setter functions.
+ this.onMouseDown = this.handleMouseDown;
+ this.onMouseMove = this.handleMouseMove;
+ this.onMouseDrag = this.handleMouseDrag;
+ this.onMouseUp = this.handleMouseUp;
+ }
+ /**
+ * To be called when the hovered item changes. When the select tool hovers over a
+ * new item, it compares against this to see if a hover item change event needs to
+ * be fired.
+ * @param {paper.Item} prevHoveredItemId ID of the highlight item that indicates the mouse is
+ * over a given item currently
+ */
+ setPrevHoveredItemId (prevHoveredItemId) {
+ this.prevHoveredItemId = prevHoveredItemId;
+ }
+ handleMouseDown () {
+ log.warn('Rectangle tool not yet implemented');
+ }
+ handleMouseMove () {
+ }
+ handleMouseDrag () {
+ }
+ handleMouseUp () {
+ }
+ deactivateTool () {
+ }
+}
+
+export default RectTool;
diff --git a/src/helper/tools/rounded-rect-tool.js b/src/helper/tools/rounded-rect-tool.js
new file mode 100644
index 00000000..ce92b47c
--- /dev/null
+++ b/src/helper/tools/rounded-rect-tool.js
@@ -0,0 +1,54 @@
+import paper from '@scratch/paper';
+import log from '../../log/log';
+
+/**
+ * Tool for drawing rounded rectangles
+ */
+class RoundedRectTool extends paper.Tool {
+ /**
+ * @param {function} setHoveredItem Callback to set the hovered item
+ * @param {function} clearHoveredItem Callback to clear the hovered item
+ * @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
+ * @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
+ * @param {!function} onUpdateSvg A callback to call when the image visibly changes
+ */
+ constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
+ super();
+ this.setHoveredItem = setHoveredItem;
+ this.clearHoveredItem = clearHoveredItem;
+ this.setSelectedItems = setSelectedItems;
+ this.clearSelectedItems = clearSelectedItems;
+ this.onUpdateSvg = onUpdateSvg;
+ this.prevHoveredItemId = null;
+
+ // We have to set these functions instead of just declaring them because
+ // paper.js tools hook up the listeners in the setter functions.
+ this.onMouseDown = this.handleMouseDown;
+ this.onMouseMove = this.handleMouseMove;
+ this.onMouseDrag = this.handleMouseDrag;
+ this.onMouseUp = this.handleMouseUp;
+ }
+ /**
+ * To be called when the hovered item changes. When the select tool hovers over a
+ * new item, it compares against this to see if a hover item change event needs to
+ * be fired.
+ * @param {paper.Item} prevHoveredItemId ID of the highlight item that indicates the mouse is
+ * over a given item currently
+ */
+ setPrevHoveredItemId (prevHoveredItemId) {
+ this.prevHoveredItemId = prevHoveredItemId;
+ }
+ handleMouseDown () {
+ log.warn('Rounded Rectangle tool not yet implemented');
+ }
+ handleMouseMove () {
+ }
+ handleMouseDrag () {
+ }
+ handleMouseUp () {
+ }
+ deactivateTool () {
+ }
+}
+
+export default RoundedRectTool;
diff --git a/src/modes/modes.js b/src/modes/modes.js
index f8262e5c..44529796 100644
--- a/src/modes/modes.js
+++ b/src/modes/modes.js
@@ -6,7 +6,10 @@ const Modes = keyMirror({
LINE: null,
SELECT: null,
RESHAPE: null,
- PEN: null
+ PEN: null,
+ OVAL: null,
+ RECT: null,
+ ROUNDED_RECT: null
});
export default Modes;