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;