scratch-paint/src/helper/selection-tools/select-tool.js

147 lines
5.8 KiB
JavaScript
Raw Normal View History

2017-09-21 18:39:18 -04:00
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 'paper';
2017-09-22 12:12:07 -04:00
/**
* 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.
*/
2017-09-21 18:39:18 -04:00
class SelectTool extends paper.Tool {
2017-09-22 12:12:07 -04:00
/** The distance within which mouse events count as a hit against an item */
2017-09-21 18:39:18 -04:00
static get TOLERANCE () {
return 6;
}
2017-09-22 12:12:07 -04:00
/**
* @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
2017-09-22 12:12:07 -04:00
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
*/
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
2017-09-21 18:39:18 -04:00
super();
this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem;
this.onUpdateSvg = onUpdateSvg;
this.boundingBoxTool = new BoundingBoxTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems);
2017-09-21 18:39:18 -04:00
this.selectionBoxMode = false;
2017-09-22 14:02:18 -04:00
this.prevHoveredItemId = null;
2017-09-21 18:39:18 -04:00
// 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();
2017-09-21 18:39:18 -04:00
this.boundingBoxTool.setSelectionBounds();
}
2017-09-22 12:12:07 -04:00
/**
* 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
2017-09-22 12:12:07 -04:00
*/
setPrevHoveredItemId (prevHoveredItemId) {
this.prevHoveredItemId = prevHoveredItemId;
2017-09-21 18:39:18 -04:00
}
2017-09-22 12:12:07 -04:00
/**
* 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
*/
2017-09-21 18:39:18 -04:00
getHitOptions (preselectedOnly) {
2017-09-22 12:12:07 -04:00
// 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
};
2017-09-21 18:39:18 -04:00
if (preselectedOnly) {
2017-09-22 12:12:07 -04:00
hitOptions.selected = true;
2017-09-21 18:39:18 -04:00
}
2017-09-22 12:12:07 -04:00
return hitOptions;
2017-09-21 18:39:18 -04:00
}
handleMouseDown (event) {
if (event.event.button > 0) return; // only first mouse button
2017-09-22 12:12:07 -04:00
// If bounding box tool does not find an item that was hit, use selection box tool.
2017-09-21 18:39:18 -04:00
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);
2017-09-21 18:39:18 -04:00
}
}
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.clearHoveredItem();
this.boundingBoxTool.removeBoundsPath();
2017-09-21 18:39:18 -04:00
this.onUpdateSvg();
}
}
deactivateTool () {
this.clearHoveredItem();
this.boundingBoxTool.removeBoundsPath();
2017-09-22 14:02:18 -04:00
this.setHoveredItem = null;
this.clearHoveredItem = null;
this.onUpdateSvg = null;
this.boundingBoxTool = null;
this.selectionBoxTool = null;
2017-09-21 18:39:18 -04:00
}
}
export default SelectTool;