scratch-paint/src/helper/selection-tools/select-tool.js
2017-10-12 18:35:30 -04:00

145 lines
5.8 KiB
JavaScript

import Modes from '../../modes/modes';
import {getHoveredItem} from '../hover';
import {deleteSelection, selectRootItem} from '../selection';
import BoundingBoxTool from './bounding-box-tool';
import SelectionBoxTool from './selection-box-tool';
import paper from '@scratch/paper';
/**
* paper.Tool that handles select mode. This is made up of 2 subtools.
* - The selection box tool is active when the user clicks an empty space and drags.
* It selects all items in the rectangle.
* - The bounding box tool is active if the user clicks on a non-empty space. It handles
* reshaping the item that was clicked.
*/
class SelectTool extends paper.Tool {
/** The distance within which mouse events count as a hit against an item */
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) {
super();
this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem;
this.onUpdateSvg = onUpdateSvg;
this.boundingBoxTool = new BoundingBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems, onUpdateSvg);
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems);
this.selectionBoxMode = false;
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;
this.onKeyUp = this.handleKeyUp;
selectRootItem();
setSelectedItems();
this.boundingBoxTool.setSelectionBounds();
}
/**
* 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;
}
/**
* Returns the hit options to use when conducting hit tests.
* @param {boolean} preselectedOnly True if we should only return results that are already
* selected.
* @return {object} See paper.Item.hitTest for definition of options
*/
getHitOptions (preselectedOnly) {
// Tolerance needs to be scaled when the view is zoomed in in order to represent the same
// distance for the user to move the mouse.
const hitOptions = {
segments: true,
stroke: true,
curves: true,
fill: true,
guide: false,
tolerance: SelectTool.TOLERANCE / paper.view.zoom
};
if (preselectedOnly) {
hitOptions.selected = true;
}
return hitOptions;
}
handleMouseDown (event) {
if (event.event.button > 0) return; // only first mouse button
// If bounding box tool does not find an item that was hit, use selection box tool.
this.clearHoveredItem();
if (!this.boundingBoxTool
.onMouseDown(
event,
event.modifiers.alt,
event.modifiers.shift,
this.getHitOptions(false /* preseelectedOnly */))) {
this.selectionBoxMode = true;
this.selectionBoxTool.onMouseDown(event.modifiers.shift);
}
}
handleMouseMove (event) {
const hoveredItem = getHoveredItem(event, this.getHitOptions());
if ((!hoveredItem && this.prevHoveredItemId) || // There is no longer a hovered item
(hoveredItem && !this.prevHoveredItemId) || // There is now a hovered item
(hoveredItem && this.prevHoveredItemId &&
hoveredItem.id !== this.prevHoveredItemId)) { // hovered item changed
this.setHoveredItem(hoveredItem ? hoveredItem.id : null);
}
}
handleMouseDrag (event) {
if (event.event.button > 0) return; // only first mouse button
if (this.selectionBoxMode) {
this.selectionBoxTool.onMouseDrag(event);
} else {
this.boundingBoxTool.onMouseDrag(event);
}
}
handleMouseUp (event) {
if (event.event.button > 0) return; // only first mouse button
if (this.selectionBoxMode) {
this.selectionBoxTool.onMouseUp(event);
this.boundingBoxTool.setSelectionBounds();
} else {
this.boundingBoxTool.onMouseUp(event);
}
this.selectionBoxMode = false;
}
handleKeyUp (event) {
// Backspace, delete
if (event.key === 'delete' || event.key === 'backspace') {
deleteSelection(Modes.SELECT, this.onUpdateSvg);
this.clearHoveredItem();
this.boundingBoxTool.removeBoundsPath();
}
}
deactivateTool () {
this.clearHoveredItem();
this.boundingBoxTool.removeBoundsPath();
this.setHoveredItem = null;
this.clearHoveredItem = null;
this.onUpdateSvg = null;
this.boundingBoxTool = null;
this.selectionBoxTool = null;
}
}
export default SelectTool;