Merge pull request #81 from fsih/rectTool

Rectangle drawing tool
This commit is contained in:
Paul Kaplan 2017-10-20 15:57:52 -04:00 committed by GitHub
commit 6e9a2ef178
6 changed files with 109 additions and 57 deletions

View file

@ -11,7 +11,6 @@ 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';
@ -159,9 +158,6 @@ class PaintEditorComponent extends React.Component {
<RectMode
onUpdateSvg={this.props.onUpdateSvg}
/>
<RoundedRectMode
onUpdateSvg={this.props.onUpdateSvg}
/>
</div>
) : null}

View file

@ -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 RectTool from '../helper/tools/rect-tool';
import RectModeComponent from '../components/rect-mode/rect-mode.jsx';
@ -26,8 +25,8 @@ class RectMode 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.isRectModeActive && !this.props.isRectModeActive) {
@ -40,13 +39,13 @@ class RectMode extends React.Component {
return nextProps.isRectModeActive !== this.props.isRectModeActive;
}
activateTool () {
clearSelection(this.props.clearSelectedItems);
this.tool = new RectTool(
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 RectMode extends React.Component {
}
RectMode.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,
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
colorState: state.scratchPaint.color,
isRectModeActive: state.scratchPaint.mode === Modes.RECT
});
const mapDispatchToProps = dispatch => ({
setHoveredItem: hoveredItemId => {
dispatch(setHoveredItem(hoveredItemId));
},
clearHoveredItem: () => {
dispatch(clearHoveredItem());
},
clearSelectedItems: () => {
dispatch(clearSelectedItems());
},

View file

@ -84,8 +84,8 @@ class BoundingBoxTool {
const hitProperties = {
hitResult: hitResult,
clone: event.modifiers.alt,
multiselect: event.modifiers.shift
clone: clone,
multiselect: multiselect
};
if (this.mode === BoundingBoxModes.MOVE) {
this._modeMap[this.mode].onMouseDown(hitProperties);

View file

@ -126,22 +126,18 @@ const clearSelection = function (dispatchClearSelect) {
* @return {Array<paper.Item>} in increasing Z order.
*/
const getSelectedRootItems = function () {
const allItems = paper.project.selectedItems;
const itemsAndGroups = [];
const allItems = getAllSelectableRootItems();
const items = [];
for (let i = 0; i < allItems.length; i++) {
const item = allItems[i];
if ((isGroup(item) && !isGroup(item.parent)) ||
!isGroup(item.parent)) {
if (item.data && !item.data.isSelectionBound) {
itemsAndGroups.push(item);
}
for (const item of allItems) {
if (item.selected) {
items.push(item);
}
}
// sort items by index (0 at bottom)
itemsAndGroups.sort((a, b) => parseFloat(a.index) - parseFloat(b.index));
return itemsAndGroups;
items.sort((a, b) => parseFloat(a.index) - parseFloat(b.index));
return items;
};
/**
@ -155,7 +151,7 @@ const getSelectedLeafItems = function () {
for (let i = 0; i < allItems.length; i++) {
const item = allItems[i];
if (!isGroup(item) && item.data && !item.data.isSelectionBound) {
if (!(item instanceof paper.Layer) && !isGroup(item) && item.data && !item.data.isSelectionBound) {
items.push(item);
}
}

View file

@ -230,6 +230,12 @@ const styleCursorPreview = function (path, options) {
}
};
const styleShape = function (path, options) {
path.fillColor = options.fillColor;
path.strokeColor = options.strokeColor;
path.strokeWidth = options.strokeWidth;
};
export {
applyFillColorToSelection,
applyStrokeColorToSelection,
@ -237,6 +243,7 @@ export {
getColorsFromSelection,
MIXED,
styleBlob,
styleShape,
stylePath,
styleCursorPreview
};

View file

@ -1,53 +1,111 @@
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 rectangles.
*/
class RectTool 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.RECT, 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.rect = 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: RectTool.TOLERANCE / paper.view.zoom
};
}
handleMouseDown () {
log.warn('Rectangle 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);
}
}
handleMouseDrag () {
handleMouseDrag (event) {
if (event.event.button > 0) return; // only first mouse button
if (this.isBoundingBoxMode) {
this.boundingBoxTool.onMouseDrag(event);
return;
}
if (this.rect) {
this.rect.remove();
}
const rect = new paper.Rectangle(event.downPoint, event.point);
if (event.modifiers.shift) {
rect.height = rect.width;
}
this.rect = new paper.Path.Rectangle(rect);
if (event.modifiers.alt) {
this.rect.position = event.downPoint;
}
styleShape(this.rect, this.colorState);
}
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.rect) {
if (this.rect.area < RectTool.TOLERANCE / paper.view.zoom) {
// Tiny rectangle created unintentionally?
this.rect.remove();
this.rect = null;
} else {
this.rect.selected = true;
this.boundingBoxTool.setSelectionBounds();
this.onUpdateSvg();
this.rect = null;
}
}
}
deactivateTool () {
this.boundingBoxTool.removeBoundsPath();
}
}