diff --git a/src/containers/paper-canvas.css b/src/containers/paper-canvas.css index d2f149fa..16edbb7a 100644 --- a/src/containers/paper-canvas.css +++ b/src/containers/paper-canvas.css @@ -4,4 +4,5 @@ margin: auto; position: relative; background-color: #fff; + image-rendering: pixelated; } diff --git a/src/containers/select-mode.jsx b/src/containers/select-mode.jsx index cf652b5e..79dbe330 100644 --- a/src/containers/select-mode.jsx +++ b/src/containers/select-mode.jsx @@ -24,12 +24,7 @@ class SelectMode extends React.Component { bindAll(this, [ 'activateTool', 'deactivateTool', - 'getHitOptions', - 'preProcessSelection', - 'onMouseDown', - 'onMouseMove', - 'onMouseDrag', - 'onMouseUp' + 'getHitOptions' ]); this._hitOptions = { segments: true, @@ -71,48 +66,66 @@ class SelectMode extends React.Component { selectRootItem(); this.tool = new paper.Tool(); - + const selectMode = this; this.tool.onMouseDown = function (event) { if (event.event.button > 0) return; // only first mouse button - this.props.clearHoveredItem(); - if (!this.boundingBoxTool.onMouseDown( - event, event.modifiers.alt, event.modifiers.shift, true /* preselectedOnly */)) { - this.selectionBoxMode = true; + + selectMode.props.clearHoveredItem(); + if (!selectMode.boundingBoxTool.onMouseDown( + event, + event.modifiers.alt, + event.modifiers.shift, + selectMode.getHitOptions(false /* preseelectedOnly */))) { + selectMode.selectionBoxMode = true; } }; this.tool.onMouseMove = function (event) { - this.props.setHoveredItem(getHoveredItem(event, this.getHitOptions())); + const hoveredItem = getHoveredItem(event, selectMode.getHitOptions()); + const oldHoveredItem = selectMode.props.hoveredItem; + if ((!hoveredItem && oldHoveredItem) || // There is no longer a hovered item + (hoveredItem && !oldHoveredItem) || // There is now a hovered item + (hoveredItem && oldHoveredItem && hoveredItem.id !== oldHoveredItem.id)) { // hovered item changed + if (oldHoveredItem) { + oldHoveredItem.remove(); + } + selectMode.props.setHoveredItem(hoveredItem); + } }; this.tool.onMouseDrag = function (event) { if (event.event.button > 0) return; // only first mouse button - if (this.selectionBoxMode) { - this.selectionRect = rectSelect(event); + + if (selectMode.selectionBoxMode) { + selectMode.selectionRect = rectSelect(event); // Remove this rect on the next drag and up event - this.selectionRect.removeOnDrag(); + selectMode.selectionRect.removeOnDrag(); } else { - this.boundingBoxTool.onMouseDrag(event); + selectMode.boundingBoxTool.onMouseDrag(event); } }; this.tool.onMouseUp = function (event) { if (event.event.button > 0) return; // only first mouse button - if (this.selectionBoxMode) { - processRectangularSelection(event, this.selectionRect); - this.selectionRect.remove(); + + if (selectMode.selectionBoxMode) { + if (selectMode.selectionRect) { + processRectangularSelection(event, selectMode.selectionRect); + selectMode.selectionRect.remove(); + } + selectMode.boundingBoxTool.setSelectionBounds(); } else { - this.boundingBoxTool.onMouseUp(event); - this.props.onUpdateSvg(); + selectMode.boundingBoxTool.onMouseUp(event); + selectMode.props.onUpdateSvg(); } - this.selectionBoxMode = false; - this.selectionRect = null; + selectMode.selectionBoxMode = false; + selectMode.selectionRect = null; }; this.tool.activate(); } deactivateTool () { - this.props.setHoveredItem(); + this.props.clearHoveredItem(); this.tool.remove(); this.tool = null; this.hitResult = null; @@ -127,13 +140,15 @@ class SelectMode extends React.Component { SelectMode.propTypes = { clearHoveredItem: PropTypes.func.isRequired, handleMouseDown: PropTypes.func.isRequired, + hoveredItem: PropTypes.instanceOf(paper.Item), isSelectModeActive: PropTypes.bool.isRequired, onUpdateSvg: PropTypes.func.isRequired, setHoveredItem: PropTypes.func.isRequired }; const mapStateToProps = state => ({ - isSelectModeActive: state.scratchPaint.mode === Modes.SELECT + isSelectModeActive: state.scratchPaint.mode === Modes.SELECT, + hoveredItem: state.scratchPaint.hoveredItem }); const mapDispatchToProps = dispatch => ({ setHoveredItem: hoveredItem => { diff --git a/src/helper/bounding-box/bounding-box-tool.js b/src/helper/bounding-box/bounding-box-tool.js index 457e83f0..cf9aceae 100644 --- a/src/helper/bounding-box/bounding-box-tool.js +++ b/src/helper/bounding-box/bounding-box-tool.js @@ -40,17 +40,18 @@ class BoundingBoxTool { * @param {!MouseEvent} event The mouse event * @param {boolean} clone Whether to clone on mouse down (e.g. alt key held) * @param {boolean} multiselect Whether to multiselect on mouse down (e.g. shift key held) - * @param {boolean} preselectedOnly If true, only get hit results on items that are already selected + * @param {paper.hitOptions} hitOptions The options with which to detect whether mouse down has hit + * anything editable * @return {boolean} True if there was a hit, false otherwise */ - onMouseDown (event, clone, multiselect, preselectedOnly) { - const hitResults = paper.project.hitTestAll(event.point, this.getHitOptions(preselectedOnly)); + onMouseDown (event, clone, multiselect, hitOptions) { + const hitResults = paper.project.hitTestAll(event.point, hitOptions); if (!hitResults || hitResults.length === 0) { if (!multiselect) { this.removeBoundsPath(); clearSelection(); } - return null; + return false; } // Prefer scale to trigger over rotate, and scale and rotate to trigger over other hits @@ -65,14 +66,17 @@ class BoundingBoxTool { hitResult = hitResults[i]; this.mode = Modes.ROTATE; this._modeMap[this.mode].onMouseDown(hitResult, this.boundsPath, getSelectedItems()); - } else { - this.mode = Modes.MOVE; - this._modeMap[this.mode].onMouseDown(hitResult, clone, multiselect); } } + if (!this.mode) { + this.mode = Modes.MOVE; + this._modeMap[this.mode].onMouseDown(hitResult, clone, multiselect); + } + // while transforming object, never show the bounds stuff this.removeBoundsPath(); + return true; } onMouseDrag (event) { if (event.event.button > 0) return; // only first mouse button @@ -83,12 +87,7 @@ class BoundingBoxTool { this._modeMap[this.mode].onMouseUp(event); this.mode = null; - - if (getSelectedItems().length <= 0) { - this.removeBoundsPath(); - } else { - this.setSelectionBounds(); - } + this.setSelectionBounds(); } setSelectionBounds () { this.removeBoundsPath(); diff --git a/src/helper/bounding-box/move-tool.js b/src/helper/bounding-box/move-tool.js index c21c46ca..6303d6cd 100644 --- a/src/helper/bounding-box/move-tool.js +++ b/src/helper/bounding-box/move-tool.js @@ -1,7 +1,7 @@ import {isGroup} from '../group'; import {isCompoundPathItem, getRootItem} from '../item'; import {snapDeltaToAngle} from '../math'; -import {clearSelection, cloneSelection, getSelectedItems, setItemSelection, setGroupSelection} from '../selection'; +import {clearSelection, cloneSelection, getSelectedItems, setItemSelection} from '../selection'; class MoveTool { constructor () { @@ -14,31 +14,22 @@ class MoveTool { * @param {boolean} multiselect Whether to multiselect on mouse down (e.g. shift key held) */ onMouseDown (hitResult, clone, multiselect) { - // deselect all by default if multiselect isn't on - if (!multiselect) { - clearSelection(); - } - // also needs some special love for compound paths and groups, - // as their children are not marked as "selected" - // deselect a currently selected item if multiselect is on const root = getRootItem(hitResult.item); - if (isCompoundPathItem(root) || isGroup(root)) { - if (!root.selected) { - setGroupSelection(root, true); - } else if (multiselect) { - setGroupSelection(root, false); + const item = isCompoundPathItem(root) || isGroup(root) ? root : hitResult.item; + if (!item.selected) { + // deselect all by default if multiselect isn't on + if (!multiselect) { + clearSelection(); } - } else if (multiselect && hitResult.item.selected) { - setItemSelection(hitResult.item, false); - } else { - setItemSelection(hitResult.item, true); + setItemSelection(item, true); + } else if (multiselect) { + setItemSelection(item, false); } if (clone) cloneSelection(); this.selectedItems = getSelectedItems(); } onMouseDrag (event) { - const dragVector = (event.point - event.downPoint); - + const dragVector = event.point.subtract(event.downPoint); for (const item of this.selectedItems) { // add the position of the item before the drag started // for later use in the snap calculation @@ -47,10 +38,9 @@ class MoveTool { } if (event.modifiers.shift) { - item.position = item.data.origPos + - snapDeltaToAngle(dragVector, Math.PI / 4); + item.position = item.data.origPos.add(snapDeltaToAngle(dragVector, Math.PI / 4)); } else { - item.position += event.delta; + item.position = item.data.origPos.add(dragVector); } } } diff --git a/src/helper/hover.js b/src/helper/hover.js index ee326b8e..b4c0bc1f 100644 --- a/src/helper/hover.js +++ b/src/helper/hover.js @@ -16,7 +16,7 @@ const getHoveredItem = function (event, hitOptions) { let hitResult; for (const result of hitResults) { - if (!(result.item.data && result.item.data.noHover) && !hitResult.item.selected) { + if (!(result.item.data && result.item.data.noHover) && !result.item.selected) { hitResult = result; break; } diff --git a/src/helper/layer.js b/src/helper/layer.js index 404196f3..0e33649a 100644 --- a/src/helper/layer.js +++ b/src/helper/layer.js @@ -15,4 +15,4 @@ const getGuideLayer = function () { return guideLayer; }; -export default getGuideLayer; +export {getGuideLayer}; diff --git a/src/modes/modes.js b/src/modes/modes.js index c1b64738..82394802 100644 --- a/src/modes/modes.js +++ b/src/modes/modes.js @@ -3,7 +3,8 @@ import keyMirror from 'keymirror'; const Modes = keyMirror({ BRUSH: null, ERASER: null, - LINE: null + LINE: null, + SELECT: null }); export default Modes; diff --git a/src/reducers/hover.js b/src/reducers/hover.js index 552ccece..0c4353b0 100644 --- a/src/reducers/hover.js +++ b/src/reducers/hover.js @@ -1,3 +1,5 @@ +import log from '../log/log'; + const CHANGE_HOVERED = 'scratch-paint/hover/CHANGE_HOVERED'; const initialState = null; @@ -5,6 +7,10 @@ const reducer = function (state, action) { if (typeof state === 'undefined') state = initialState; switch (action.type) { case CHANGE_HOVERED: + if (typeof action.hoveredItem === 'undefined') { + log.warn(`Hovered item should not be set to undefined. Use null.`); + return state; + } return action.hoveredItem; default: return state;