mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-24 22:42:28 -05:00
145 lines
5.8 KiB
JavaScript
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;
|