From cc350e9056a854ccd0faab3b0d020ef72f67cbe0 Mon Sep 17 00:00:00 2001 From: DD Date: Fri, 20 Oct 2017 14:50:43 -0400 Subject: [PATCH] oval mode --- src/containers/oval-mode.jsx | 29 ++++----- src/helper/tools/oval-tool.js | 108 +++++++++++++++++++++++++++------- 2 files changed, 98 insertions(+), 39 deletions(-) diff --git a/src/containers/oval-mode.jsx b/src/containers/oval-mode.jsx index 703301f5..5e727a92 100644 --- a/src/containers/oval-mode.jsx +++ b/src/containers/oval-mode.jsx @@ -5,10 +5,9 @@ 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 {clearSelection, getSelectedLeafItems} from '../helper/selection'; import OvalTool from '../helper/tools/oval-tool'; import OvalModeComponent from '../components/oval-mode/oval-mode.jsx'; @@ -26,8 +25,8 @@ class OvalMode extends React.Component { } } componentWillReceiveProps (nextProps) { - if (this.tool && nextProps.hoveredItemId !== this.props.hoveredItemId) { - this.tool.setPrevHoveredItemId(nextProps.hoveredItemId); + if (this.tool && nextProps.colorState !== this.props.colorState) { + this.tool.setColorState(nextProps.colorState); } if (nextProps.isOvalModeActive && !this.props.isOvalModeActive) { @@ -40,13 +39,13 @@ class OvalMode extends React.Component { return nextProps.isOvalModeActive !== this.props.isOvalModeActive; } activateTool () { + clearSelection(this.props.clearSelectedItems); this.tool = new OvalTool( - this.props.setHoveredItem, - this.props.clearHoveredItem, this.props.setSelectedItems, this.props.clearSelectedItems, this.props.onUpdateSvg ); + this.tool.setColorState(this.props.colorState); this.tool.activate(); } deactivateTool () { @@ -65,27 +64,23 @@ class OvalMode extends React.Component { } OvalMode.propTypes = { - clearHoveredItem: PropTypes.func.isRequired, clearSelectedItems: PropTypes.func.isRequired, + colorState: PropTypes.shape({ + fillColor: PropTypes.string, + strokeColor: PropTypes.string, + strokeWidth: PropTypes.number + }).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 + colorState: state.scratchPaint.color, + isOvalModeActive: state.scratchPaint.mode === Modes.OVAL }); const mapDispatchToProps = dispatch => ({ - setHoveredItem: hoveredItemId => { - dispatch(setHoveredItem(hoveredItemId)); - }, - clearHoveredItem: () => { - dispatch(clearHoveredItem()); - }, clearSelectedItems: () => { dispatch(clearSelectedItems()); }, diff --git a/src/helper/tools/oval-tool.js b/src/helper/tools/oval-tool.js index ceb796d6..935d9f58 100644 --- a/src/helper/tools/oval-tool.js +++ b/src/helper/tools/oval-tool.js @@ -1,53 +1,117 @@ import paper from '@scratch/paper'; -import log from '../../log/log'; +import Modes from '../../modes/modes'; +import {styleShape} from '../style-path'; +import {clearSelection} from '../selection'; +import BoundingBoxTool from '../selection-tools/bounding-box-tool'; /** * Tool for drawing ovals. */ class OvalTool extends paper.Tool { + static get TOLERANCE () { + return 6; + } /** - * @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) { + constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) { super(); - this.setHoveredItem = setHoveredItem; - this.clearHoveredItem = clearHoveredItem; - this.setSelectedItems = setSelectedItems; this.clearSelectedItems = clearSelectedItems; this.onUpdateSvg = onUpdateSvg; this.prevHoveredItemId = null; + this.boundingBoxTool = new BoundingBoxTool(Modes.OVAL, setSelectedItems, clearSelectedItems, onUpdateSvg); // 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; + + this.downPoint = null; + this.oval = null; + this.colorState = null; + this.isBoundingBoxMode = null; } - /** - * 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; + getHitOptions () { + return { + segments: true, + stroke: true, + curves: true, + fill: true, + guide: false, + match: hitResult => + (hitResult.item.data && hitResult.item.data.isHelperItem) || + hitResult.item.selected, // Allow hits on bounding box and selected only + tolerance: OvalTool.TOLERANCE / paper.view.zoom + }; } - handleMouseDown () { - log.warn('Circle tool not yet implemented'); + setColorState (colorState) { + this.colorState = colorState; } - handleMouseMove () { + handleMouseDown (event) { + if (this.boundingBoxTool.onMouseDown(event, false /* clone */, false /* multiselect */, this.getHitOptions())) { + this.isBoundingBoxMode = true; + } else { + this.isBoundingBoxMode = false; + clearSelection(this.clearSelectedItems); + this.oval = new paper.Shape.Ellipse({ + point: event.downPoint, + size: 0 + }); + styleShape(this.oval, this.colorState); + } } - handleMouseDrag () { + handleMouseDrag (event) { + if (event.event.button > 0) return; // only first mouse button + + if (this.isBoundingBoxMode) { + this.boundingBoxTool.onMouseDrag(event); + return; + } + + const downPoint = new paper.Point(event.downPoint.x, event.downPoint.y); + const point = new paper.Point(event.point.x, event.point.y); + if (event.modifiers.shift) { + this.oval.size = new paper.Point(event.downPoint.x - event.point.x, event.downPoint.x - event.point.x); + } else { + this.oval.size = downPoint.subtract(point); + } + if (event.modifiers.alt) { + this.oval.position = downPoint; + } else { + this.oval.position = downPoint.subtract(this.oval.size.multiply(0.5)); + } + } - handleMouseUp () { + handleMouseUp (event) { + if (event.event.button > 0) return; // only first mouse button + + if (this.isBoundingBoxMode) { + this.boundingBoxTool.onMouseUp(event); + this.isBoundingBoxMode = null; + return; + } + + if (this.oval) { + if (Math.abs(this.oval.size.width * this.oval.size.height) < OvalTool.TOLERANCE / paper.view.zoom) { + // Tiny oval created unintentionally? + this.oval.remove(); + this.oval = null; + } else { + const ovalPath = this.oval.toPath(true /* insert */); + this.oval.remove(); + this.oval = null; + + ovalPath.selected = true; + this.boundingBoxTool.setSelectionBounds(); + this.onUpdateSvg(); + } + } } deactivateTool () { + this.boundingBoxTool.removeBoundsPath(); } }