import paper from '@scratch/paper'; import Modes from '../../lib/modes'; import {styleShape} from '../style-path'; import {clearSelection} from '../selection'; import BoundingBoxTool from '../selection-tools/bounding-box-tool'; import NudgeTool from '../selection-tools/nudge-tool'; /** * Tool for drawing rectangles. */ class RectTool extends paper.Tool { static get TOLERANCE () { return 6; } /** * @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 (setSelectedItems, clearSelectedItems, onUpdateSvg) { super(); this.setSelectedItems = setSelectedItems; this.clearSelectedItems = clearSelectedItems; this.onUpdateSvg = onUpdateSvg; this.boundingBoxTool = new BoundingBoxTool(Modes.RECT, setSelectedItems, clearSelectedItems, onUpdateSvg); const nudgeTool = new NudgeTool(this.boundingBoxTool, 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.onMouseDrag = this.handleMouseDrag; this.onMouseUp = this.handleMouseUp; this.onKeyUp = nudgeTool.onKeyUp; this.onKeyDown = nudgeTool.onKeyDown; this.rect = null; this.colorState = null; this.isBoundingBoxMode = null; this.active = false; } 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 }; } /** * Should be called if the selection changes to update the bounds of the bounding box. * @param {Array} selectedItems Array of selected items. */ onSelectionChanged (selectedItems) { this.boundingBoxTool.onSelectionChanged(selectedItems); } setColorState (colorState) { this.colorState = colorState; } handleMouseDown (event) { if (event.event.button > 0) return; // only first mouse button this.active = true; if (this.boundingBoxTool.onMouseDown(event, false /* clone */, false /* multiselect */, this.getHitOptions())) { this.isBoundingBoxMode = true; } else { this.isBoundingBoxMode = false; clearSelection(this.clearSelectedItems); } } handleMouseDrag (event) { if (event.event.button > 0 || !this.active) 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 (event) { if (event.event.button > 0 || !this.active) 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.setSelectedItems(); this.onUpdateSvg(); this.rect = null; } } this.active = false; } deactivateTool () { this.boundingBoxTool.removeBoundsPath(); } } export default RectTool;