mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
Add reshape
This commit is contained in:
parent
b8de3dcc3a
commit
f15a3dbe02
13 changed files with 671 additions and 235 deletions
|
@ -7,74 +7,19 @@ import Modes from '../modes/modes';
|
||||||
import {changeMode} from '../reducers/modes';
|
import {changeMode} from '../reducers/modes';
|
||||||
import {setHoveredItem, clearHoveredItem} from '../reducers/hover';
|
import {setHoveredItem, clearHoveredItem} from '../reducers/hover';
|
||||||
|
|
||||||
import {getHoveredItem} from '../helper/hover';
|
import {selectSubItems} from '../helper/selection';
|
||||||
import {rectSelect} from '../helper/guides';
|
import ReshapeTool from '../helper/selection-tools/reshape-tool';
|
||||||
import {processRectangularSelection} from '../helper/selection';
|
|
||||||
|
|
||||||
import ReshapeModeComponent from '../components/reshape-mode.jsx';
|
import ReshapeModeComponent from '../components/reshape-mode.jsx';
|
||||||
import BoundingBoxTool from '../helper/bounding-box/bounding-box-tool';
|
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
|
||||||
|
|
||||||
class ReshapeMode extends React.Component {
|
class ReshapeMode extends React.Component {
|
||||||
static get TOLERANCE () {
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'activateTool',
|
'activateTool',
|
||||||
'deactivateTool',
|
'deactivateTool'
|
||||||
'getHitOptions'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this._hitOptionsSelected = {
|
|
||||||
match: function (item) {
|
|
||||||
if (!item.item || !item.item.selected) return;
|
|
||||||
if (item.type === 'handle-out' || item.type === 'handle-in') {
|
|
||||||
// Only hit test against handles that are visible, that is,
|
|
||||||
// their segment is selected
|
|
||||||
if (!item.segment.selected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If the entire shape is selected, handles are hidden
|
|
||||||
if (item.item.fullySelected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
segments: true,
|
|
||||||
stroke: true,
|
|
||||||
curves: true,
|
|
||||||
handles: true,
|
|
||||||
fill: true,
|
|
||||||
guide: false
|
|
||||||
};
|
|
||||||
this._hitOptions = {
|
|
||||||
match: function (item) {
|
|
||||||
if (item.type === 'handle-out' || item.type === 'handle-in') {
|
|
||||||
// Only hit test against handles that are visible, that is,
|
|
||||||
// their segment is selected
|
|
||||||
if (!item.segment.selected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If the entire shape is selected, handles are hidden
|
|
||||||
if (item.item.fullySelected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
segments: true,
|
|
||||||
stroke: true,
|
|
||||||
curves: true,
|
|
||||||
handles: true,
|
|
||||||
fill: true,
|
|
||||||
guide: false
|
|
||||||
};
|
|
||||||
this.boundingBoxTool = new BoundingBoxTool();
|
|
||||||
this.selectionBoxMode = false;
|
|
||||||
this.selectionRect = null;
|
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
if (this.props.isReshapeModeActive) {
|
if (this.props.isReshapeModeActive) {
|
||||||
|
@ -82,6 +27,10 @@ class ReshapeMode extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (this.tool && nextProps.hoveredItem !== this.props.hoveredItem) {
|
||||||
|
this.tool.setPrevHoveredItem(nextProps.hoveredItem);
|
||||||
|
}
|
||||||
|
|
||||||
if (nextProps.isReshapeModeActive && !this.props.isReshapeModeActive) {
|
if (nextProps.isReshapeModeActive && !this.props.isReshapeModeActive) {
|
||||||
this.activateTool();
|
this.activateTool();
|
||||||
} else if (!nextProps.isReshapeModeActive && this.props.isReshapeModeActive) {
|
} else if (!nextProps.isReshapeModeActive && this.props.isReshapeModeActive) {
|
||||||
|
@ -91,72 +40,12 @@ class ReshapeMode extends React.Component {
|
||||||
shouldComponentUpdate () {
|
shouldComponentUpdate () {
|
||||||
return false; // Static component, for now
|
return false; // Static component, for now
|
||||||
}
|
}
|
||||||
getHitOptions (preselectedOnly) {
|
|
||||||
this._hitOptions.tolerance = ReshapeMode.TOLERANCE / paper.view.zoom;
|
|
||||||
this._hitOptionsSelected.tolerance = ReshapeMode.TOLERANCE / paper.view.zoom;
|
|
||||||
return preselectedOnly ? this._hitOptionsSelected : this._hitOptions;
|
|
||||||
}
|
|
||||||
activateTool () {
|
activateTool () {
|
||||||
paper.settings.handleSize = 8;
|
selectSubItems();
|
||||||
this.boundingBoxTool.setSelectionBounds();
|
this.tool = new ReshapeTool(this.props.setHoveredItem, this.props.clearHoveredItem);
|
||||||
this.tool = new paper.Tool();
|
this.tool.setPrevHoveredItem(this.props.hoveredItem);
|
||||||
|
|
||||||
const reshapeMode = this;
|
|
||||||
|
|
||||||
this.tool.onMouseDown = function (event) {
|
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
|
||||||
|
|
||||||
reshapeMode.props.clearHoveredItem();
|
|
||||||
if (!reshapeMode.boundingBoxTool
|
|
||||||
.onMouseDown(
|
|
||||||
event,
|
|
||||||
event.modifiers.alt,
|
|
||||||
event.modifiers.shift,
|
|
||||||
reshapeMode.getHitOptions(false /* preseelectedOnly */))) {
|
|
||||||
reshapeMode.selectionBoxMode = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.tool.onMouseMove = function (event) {
|
|
||||||
const hoveredItem = getHoveredItem(event, reshapeMode.getHitOptions());
|
|
||||||
const oldHoveredItem = reshapeMode.props.hoveredItem;
|
|
||||||
if ((!hoveredItem && oldHoveredItem) || // There is no longer a hovered item
|
|
||||||
(hoveredItem && !oldHoveredItem) || // There is now a hovered item
|
|
||||||
(hoveredItem && oldHoveredItem && hoveredItem.id !== oldHoveredItem.id)) { // hovered item changed
|
|
||||||
reshapeMode.props.setHoveredItem(hoveredItem);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
this.tool.onMouseDrag = function (event) {
|
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
|
||||||
|
|
||||||
if (reshapeMode.selectionBoxMode) {
|
|
||||||
reshapeMode.selectionRect = rectSelect(event);
|
|
||||||
// Remove this rect on the next drag and up event
|
|
||||||
reshapeMode.selectionRect.removeOnDrag();
|
|
||||||
} else {
|
|
||||||
reshapeMode.boundingBoxTool.onMouseDrag(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.tool.onMouseUp = function (event) {
|
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
|
||||||
|
|
||||||
if (reshapeMode.selectionBoxMode) {
|
|
||||||
if (reshapeMode.selectionRect) {
|
|
||||||
processRectangularSelection(event, reshapeMode.selectionRect, Modes.RESHAPE);
|
|
||||||
reshapeMode.selectionRect.remove();
|
|
||||||
}
|
|
||||||
reshapeMode.boundingBoxTool.setSelectionBounds();
|
|
||||||
} else {
|
|
||||||
reshapeMode.boundingBoxTool.onMouseUp(event);
|
|
||||||
reshapeMode.props.onUpdateSvg();
|
|
||||||
}
|
|
||||||
reshapeMode.selectionBoxMode = false;
|
|
||||||
reshapeMode.selectionRect = null;
|
|
||||||
};
|
|
||||||
this.tool.activate();
|
this.tool.activate();
|
||||||
|
paper.settings.handleSize = 8;
|
||||||
}
|
}
|
||||||
deactivateTool () {
|
deactivateTool () {
|
||||||
paper.settings.handleSize = 0;
|
paper.settings.handleSize = 0;
|
||||||
|
|
|
@ -12,7 +12,8 @@ import {rectSelect} from '../helper/guides';
|
||||||
import {selectRootItem, processRectangularSelection} from '../helper/selection';
|
import {selectRootItem, processRectangularSelection} from '../helper/selection';
|
||||||
|
|
||||||
import SelectModeComponent from '../components/select-mode.jsx';
|
import SelectModeComponent from '../components/select-mode.jsx';
|
||||||
import BoundingBoxTool from '../helper/bounding-box/bounding-box-tool';
|
import BoundingBoxTool from '../helper/selection-tools/bounding-box-tool';
|
||||||
|
import SelectionBoxTool from '../helper/selection-tools/selection-box-tool';
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
|
||||||
class SelectMode extends React.Component {
|
class SelectMode extends React.Component {
|
||||||
|
@ -34,8 +35,8 @@ class SelectMode extends React.Component {
|
||||||
guide: false
|
guide: false
|
||||||
};
|
};
|
||||||
this.boundingBoxTool = new BoundingBoxTool();
|
this.boundingBoxTool = new BoundingBoxTool();
|
||||||
|
this.selectionBoxTool = new SelectionBoxTool();
|
||||||
this.selectionBoxMode = false;
|
this.selectionBoxMode = false;
|
||||||
this.selectionRect = null;
|
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
if (this.props.isSelectModeActive) {
|
if (this.props.isSelectModeActive) {
|
||||||
|
@ -62,6 +63,7 @@ class SelectMode extends React.Component {
|
||||||
return this._hitOptions;
|
return this._hitOptions;
|
||||||
}
|
}
|
||||||
activateTool () {
|
activateTool () {
|
||||||
|
debugger;
|
||||||
selectRootItem();
|
selectRootItem();
|
||||||
this.boundingBoxTool.setSelectionBounds();
|
this.boundingBoxTool.setSelectionBounds();
|
||||||
this.tool = new paper.Tool();
|
this.tool = new paper.Tool();
|
||||||
|
@ -69,17 +71,18 @@ class SelectMode extends React.Component {
|
||||||
// Define these to sate linter
|
// Define these to sate linter
|
||||||
const selectMode = this;
|
const selectMode = this;
|
||||||
|
|
||||||
this.tool.onMouseDown = function (event) {
|
this.tool.onMouseDown = event => {
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
|
|
||||||
selectMode.props.clearHoveredItem();
|
this.props.clearHoveredItem();
|
||||||
if (!selectMode.boundingBoxTool
|
if (!this.boundingBoxTool
|
||||||
.onMouseDown(
|
.onMouseDown(
|
||||||
event,
|
event,
|
||||||
event.modifiers.alt,
|
event.modifiers.alt,
|
||||||
event.modifiers.shift,
|
event.modifiers.shift,
|
||||||
selectMode.getHitOptions(false /* preseelectedOnly */))) {
|
this.getHitOptions(false /* preseelectedOnly */))) {
|
||||||
selectMode.selectionBoxMode = true;
|
this.selectionBoxMode = true;
|
||||||
|
this.selectionBoxTool.onMouseDown(event.modifiers.shift);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,6 +128,7 @@ class SelectMode extends React.Component {
|
||||||
this.tool.activate();
|
this.tool.activate();
|
||||||
}
|
}
|
||||||
deactivateTool () {
|
deactivateTool () {
|
||||||
|
debugger;
|
||||||
this.props.clearHoveredItem();
|
this.props.clearHoveredItem();
|
||||||
this.boundingBoxTool.removeBoundsPath();
|
this.boundingBoxTool.removeBoundsPath();
|
||||||
this.tool.remove();
|
this.tool.remove();
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
import {isGroup} from '../group';
|
|
||||||
import {isCompoundPathItem, getRootItem} from '../item';
|
|
||||||
import {snapDeltaToAngle} from '../math';
|
|
||||||
import {clearSelection, cloneSelection, getSelectedItems, setItemSelection} from '../selection';
|
|
||||||
|
|
||||||
class MoveTool {
|
|
||||||
constructor () {
|
|
||||||
this.selectedItems = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!paper.HitResult} hitResult Data about the location of the mouse click
|
|
||||||
* @param {boolean} clone Whether to clone on mouse down (e.g. alt key held)
|
|
||||||
* @param {boolean} multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
|
||||||
*/
|
|
||||||
onMouseDown (hitResult, clone, multiselect) {
|
|
||||||
const root = getRootItem(hitResult.item);
|
|
||||||
const item = isCompoundPathItem(root) || isGroup(root) ? root : hitResult.item;
|
|
||||||
if (!item.selected) {
|
|
||||||
// deselect all by default if multiselect isn't on
|
|
||||||
if (!multiselect) {
|
|
||||||
clearSelection();
|
|
||||||
}
|
|
||||||
setItemSelection(item, true);
|
|
||||||
} else if (multiselect) {
|
|
||||||
setItemSelection(item, false);
|
|
||||||
}
|
|
||||||
if (clone) cloneSelection();
|
|
||||||
this.selectedItems = getSelectedItems();
|
|
||||||
}
|
|
||||||
onMouseDrag (event) {
|
|
||||||
const dragVector = event.point.subtract(event.downPoint);
|
|
||||||
for (const item of this.selectedItems) {
|
|
||||||
// add the position of the item before the drag started
|
|
||||||
// for later use in the snap calculation
|
|
||||||
if (!item.data.origPos) {
|
|
||||||
item.data.origPos = item.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.modifiers.shift) {
|
|
||||||
item.position = item.data.origPos.add(snapDeltaToAngle(dragVector, Math.PI / 4));
|
|
||||||
} else {
|
|
||||||
item.position = item.data.origPos.add(dragVector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onMouseUp () {
|
|
||||||
// resetting the items origin point for the next usage
|
|
||||||
for (const item of this.selectedItems) {
|
|
||||||
item.data.origPos = null;
|
|
||||||
}
|
|
||||||
this.selectedItems = null;
|
|
||||||
|
|
||||||
// @todo add back undo
|
|
||||||
// pg.undo.snapshot('moveSelection');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MoveTool;
|
|
|
@ -6,9 +6,11 @@ import {isGroupChild} from './group';
|
||||||
/**
|
/**
|
||||||
* @param {!MouseEvent} event mouse event
|
* @param {!MouseEvent} event mouse event
|
||||||
* @param {?object} hitOptions hit options to use
|
* @param {?object} hitOptions hit options to use
|
||||||
|
* @param {?boolean} subselect Whether items within groups can be hovered. If false, the
|
||||||
|
* entire group should be hovered.
|
||||||
* @return {paper.Item} the hovered item or null if there is none
|
* @return {paper.Item} the hovered item or null if there is none
|
||||||
*/
|
*/
|
||||||
const getHoveredItem = function (event, hitOptions) {
|
const getHoveredItem = function (event, hitOptions, subselect) {
|
||||||
const hitResults = paper.project.hitTestAll(event.point, hitOptions);
|
const hitResults = paper.project.hitTestAll(event.point, hitOptions);
|
||||||
if (hitResults.length === 0) {
|
if (hitResults.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -27,7 +29,7 @@ const getHoveredItem = function (event, hitOptions) {
|
||||||
|
|
||||||
if (isBoundsItem(hitResult.item)) {
|
if (isBoundsItem(hitResult.item)) {
|
||||||
return hoverBounds(hitResult.item);
|
return hoverBounds(hitResult.item);
|
||||||
} else if (isGroupChild(hitResult.item)) {
|
} else if (!subselect && isGroupChild(hitResult.item)) {
|
||||||
return hoverBounds(getRootItem(hitResult.item));
|
return hoverBounds(getRootItem(hitResult.item));
|
||||||
}
|
}
|
||||||
return hoverItem(hitResult);
|
return hoverItem(hitResult);
|
||||||
|
|
|
@ -100,6 +100,7 @@ class BoundingBoxTool {
|
||||||
this.setSelectionBounds();
|
this.setSelectionBounds();
|
||||||
}
|
}
|
||||||
setSelectionBounds () {
|
setSelectionBounds () {
|
||||||
|
debugger;
|
||||||
this.removeBoundsPath();
|
this.removeBoundsPath();
|
||||||
|
|
||||||
const items = getSelectedItems();
|
const items = getSelectedItems();
|
||||||
|
@ -180,6 +181,7 @@ class BoundingBoxTool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeBoundsPath () {
|
removeBoundsPath () {
|
||||||
|
debugger;
|
||||||
removeHelperItems();
|
removeHelperItems();
|
||||||
this.boundsPath = null;
|
this.boundsPath = null;
|
||||||
this.boundsScaleHandles.length = 0;
|
this.boundsScaleHandles.length = 0;
|
64
src/helper/selection-tools/handle-tool.js
Normal file
64
src/helper/selection-tools/handle-tool.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import {clearSelection, getSelectedItems} from '../selection';
|
||||||
|
|
||||||
|
class HandleTool {
|
||||||
|
constructor () {
|
||||||
|
this.hitType = null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {!object} hitProperties Describes the mouse event
|
||||||
|
* @param {?boolean} hitProperties.multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
||||||
|
* select the whole group.
|
||||||
|
*/
|
||||||
|
onMouseDown (hitProperties) {
|
||||||
|
if (!hitProperties.multiselect) {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
hitProperties.hitResult.segment.handleIn.selected = true;
|
||||||
|
hitProperties.hitResult.segment.handleOut.selected = true;
|
||||||
|
this.hitType = hitProperties.hitResult.type;
|
||||||
|
}
|
||||||
|
onMouseDrag (event) {
|
||||||
|
const selectedItems = getSelectedItems(true /* recursive */);
|
||||||
|
|
||||||
|
for (const item of selectedItems) {
|
||||||
|
for (const seg of item.segments) {
|
||||||
|
// add the point of the segment before the drag started
|
||||||
|
// for later use in the snap calculation
|
||||||
|
if (!seg.origPoint) {
|
||||||
|
seg.origPoint = seg.point.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seg.handleOut.selected && this.hitType === 'handle-out'){
|
||||||
|
// if option is pressed or handles have been split,
|
||||||
|
// they're no longer parallel and move independently
|
||||||
|
if (event.modifiers.option ||
|
||||||
|
!seg.handleOut.isColinear(seg.handleIn)) {
|
||||||
|
seg.handleOut = seg.handleOut.add(event.delta);
|
||||||
|
} else {
|
||||||
|
const oldLength = seg.handleOut.length;
|
||||||
|
seg.handleOut = seg.handleOut.add(event.delta);
|
||||||
|
seg.handleIn = seg.handleOut.multiply(-seg.handleIn.length / oldLength);
|
||||||
|
}
|
||||||
|
} else if (seg.handleIn.selected && this.hitType === 'handle-in') {
|
||||||
|
// if option is pressed or handles have been split,
|
||||||
|
// they're no longer parallel and move independently
|
||||||
|
if (event.modifiers.option ||
|
||||||
|
!seg.handleOut.isColinear(seg.handleIn)) {
|
||||||
|
seg.handleIn = seg.handleIn.add(event.delta);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const oldLength = seg.handleIn.length;
|
||||||
|
seg.handleIn = seg.handleIn.add(event.delta);
|
||||||
|
seg.handleOut = seg.handleIn.multiply(-seg.handleOut.length / oldLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMouseUp () {
|
||||||
|
// @todo add back undo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HandleTool;
|
96
src/helper/selection-tools/move-tool.js
Normal file
96
src/helper/selection-tools/move-tool.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import {isGroup} from '../group';
|
||||||
|
import {isCompoundPathItem, getRootItem} from '../item';
|
||||||
|
import {snapDeltaToAngle} from '../math';
|
||||||
|
import {clearSelection, cloneSelection, getSelectedItems, setItemSelection} from '../selection';
|
||||||
|
|
||||||
|
class MoveTool {
|
||||||
|
constructor () {
|
||||||
|
this.selectedItems = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!object} hitProperties Describes the mouse event
|
||||||
|
* @param {!paper.HitResult} hitProperties.hitResult Data about the location of the mouse click
|
||||||
|
* @param {?boolean} hitProperties.clone Whether to clone on mouse down (e.g. alt key held)
|
||||||
|
* @param {?boolean} hitProperties.multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
||||||
|
* @param {?boolean} hitProperties.doubleClicked True if this is the second click in a short amout of time
|
||||||
|
* @param {?boolean} hitProperties.subselect True if we allow selection of subgroups, false if we should
|
||||||
|
* select the whole group.
|
||||||
|
*/
|
||||||
|
onMouseDown (hitProperties) {
|
||||||
|
let item = hitProperties.hitResult.item;
|
||||||
|
if (!hitProperties.subselect) {
|
||||||
|
const root = getRootItem(hitProperties.hitResult.item);
|
||||||
|
item = isCompoundPathItem(root) || isGroup(root) ? root : hitProperties.hitResult.item;
|
||||||
|
}
|
||||||
|
if (item.selected) {
|
||||||
|
// Double click causes all points to be selected in subselect mode.
|
||||||
|
if (hitProperties.doubleClicked) {
|
||||||
|
if (!hitProperties.multiselect) {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
this._select(item, true /* state */, hitProperties.subselect, true /* fullySelect */);
|
||||||
|
} else if (hitProperties.multiselect) {
|
||||||
|
this._select(item, false /* state */, hitProperties.subselect);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// deselect all by default if multiselect isn't on
|
||||||
|
if (!hitProperties.multiselect) {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
this._select(item, true, hitProperties.subselect);
|
||||||
|
}
|
||||||
|
if (hitProperties.clone) cloneSelection(hitProperties.subselect);
|
||||||
|
this.selectedItems = getSelectedItems(hitProperties.subselect);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sets the selection state of an item.
|
||||||
|
* @param {!paper.Item} item Item to select or deselect
|
||||||
|
* @param {?boolean} state True if item should be selected, false if deselected
|
||||||
|
* @param {?boolean} subselect True if a subset of all points in an item are allowed to be
|
||||||
|
* selected, false if items must be selected all or nothing.
|
||||||
|
* @param {?boolean} fullySelect True if in addition to the item being selected, all of its
|
||||||
|
* control points should be selected. False if the item should be selected but not its
|
||||||
|
* points. Only relevant when subselect is true.
|
||||||
|
*/
|
||||||
|
_select (item, state, subselect, fullySelect) {
|
||||||
|
if (subselect) {
|
||||||
|
item.selected = false;
|
||||||
|
if (fullySelect) {
|
||||||
|
item.fullySelected = state;
|
||||||
|
} else {
|
||||||
|
item.selected = state;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setItemSelection(item, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMouseDrag (event) {
|
||||||
|
const dragVector = event.point.subtract(event.downPoint);
|
||||||
|
for (const item of this.selectedItems) {
|
||||||
|
// add the position of the item before the drag started
|
||||||
|
// for later use in the snap calculation
|
||||||
|
if (!item.data.origPos) {
|
||||||
|
item.data.origPos = item.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.modifiers.shift) {
|
||||||
|
item.position = item.data.origPos.add(snapDeltaToAngle(dragVector, Math.PI / 4));
|
||||||
|
} else {
|
||||||
|
item.position = item.data.origPos.add(dragVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMouseUp () {
|
||||||
|
// resetting the items origin point for the next usage
|
||||||
|
for (const item of this.selectedItems) {
|
||||||
|
item.data.origPos = null;
|
||||||
|
}
|
||||||
|
this.selectedItems = null;
|
||||||
|
|
||||||
|
// @todo add back undo
|
||||||
|
// pg.undo.snapshot('moveSelection');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MoveTool;
|
193
src/helper/selection-tools/point-tool.js
Normal file
193
src/helper/selection-tools/point-tool.js
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
import paper from 'paper';
|
||||||
|
import {snapDeltaToAngle} from '../math';
|
||||||
|
import {clearSelection, getSelectedItems} from '../selection';
|
||||||
|
|
||||||
|
/** Subtool of ReshapeTool for moving control points. */
|
||||||
|
class PointTool {
|
||||||
|
constructor () {
|
||||||
|
/**
|
||||||
|
* Deselection often does not happen until mouse up. If the mouse is dragged before
|
||||||
|
* mouse up, deselection is cancelled. This variable keeps track of which paper.Item to deselect.
|
||||||
|
*/
|
||||||
|
this.deselectOnMouseUp = null;
|
||||||
|
/**
|
||||||
|
* Delete control point does not happen until mouse up. If the mouse is dragged before
|
||||||
|
* mouse up, delete is cancelled. This variable keeps track of the hitResult that triggers delete.
|
||||||
|
*/
|
||||||
|
this.deleteOnMouseUp = null;
|
||||||
|
/**
|
||||||
|
* There are 2 cases for deselection: Deselect this, or deselect everything but this.
|
||||||
|
* When invert deselect is true, deselect everything but the item in deselectOnMouseUp.
|
||||||
|
*/
|
||||||
|
this.invertDeselect = false;
|
||||||
|
this.selectedItems = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!object} hitProperties Describes the mouse event
|
||||||
|
* @param {!paper.HitResult} hitProperties.hitResult Data about the location of the mouse click
|
||||||
|
* @param {?boolean} hitProperties.multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
||||||
|
* @param {?boolean} hitProperties.doubleClicked Whether this is the second click in a short time
|
||||||
|
*/
|
||||||
|
onMouseDown (hitProperties) {
|
||||||
|
// Remove point
|
||||||
|
if (hitProperties.doubleClicked) {
|
||||||
|
this.deleteOnMouseUp = hitProperties.hitResult;
|
||||||
|
}
|
||||||
|
if (hitProperties.hitResult.segment.selected) {
|
||||||
|
// selected points with no handles get handles if selected again
|
||||||
|
if (hitProperties.multiselect) {
|
||||||
|
this.deselectOnMouseUp = hitProperties.hitResult.segment;
|
||||||
|
} else {
|
||||||
|
this.deselectOnMouseUp = hitProperties.hitResult.segment;
|
||||||
|
this.invertDeselect = true;
|
||||||
|
hitProperties.hitResult.segment.selected = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!hitProperties.multiselect) {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
hitProperties.hitResult.segment.selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedItems = getSelectedItems(true /* recursive */);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {!object} hitProperties Describes the mouse event
|
||||||
|
* @param {!paper.HitResult} hitProperties.hitResult Data about the location of the mouse click
|
||||||
|
* @param {?boolean} hitProperties.multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
||||||
|
*/
|
||||||
|
addPoint (hitProperties) {
|
||||||
|
// Length of curve from previous point to new point
|
||||||
|
const beforeCurveLength = hitProperties.hitResult.location.curveOffset;
|
||||||
|
const afterCurveLength =
|
||||||
|
hitProperties.hitResult.location.curve.length - hitProperties.hitResult.location.curveOffset;
|
||||||
|
|
||||||
|
// Handle length based on curve length until next point
|
||||||
|
let handleIn = hitProperties.hitResult.location.tangent.multiply(-beforeCurveLength / 2);
|
||||||
|
let handleOut = hitProperties.hitResult.location.tangent.multiply(afterCurveLength / 2);
|
||||||
|
// Don't let one handle overwhelm the other (results in path doubling back on itself weirdly)
|
||||||
|
if (handleIn.length > 3 * handleOut.length) {
|
||||||
|
handleIn = handleIn.multiply(3 * handleOut.length / handleIn.length);
|
||||||
|
}
|
||||||
|
if (handleOut.length > 3 * handleIn.length) {
|
||||||
|
handleOut = handleOut.multiply(3 * handleIn.length / handleOut.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeSegment = hitProperties.hitResult.item.segments[hitProperties.hitResult.location.index];
|
||||||
|
const afterSegment = hitProperties.hitResult.item.segments[hitProperties.hitResult.location.index + 1];
|
||||||
|
|
||||||
|
// Add segment
|
||||||
|
const newSegment = new paper.Segment(hitProperties.hitResult.location.point, handleIn, handleOut);
|
||||||
|
hitProperties.hitResult.item.insert(hitProperties.hitResult.location.index + 1, newSegment);
|
||||||
|
hitProperties.hitResult.segment = newSegment;
|
||||||
|
if (!hitProperties.multiselect) {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
newSegment.selected = true;
|
||||||
|
|
||||||
|
// Adjust handles of curve before and curve after to account for new curve length
|
||||||
|
if (beforeSegment && beforeSegment.handleOut) {
|
||||||
|
if (afterSegment) {
|
||||||
|
beforeSegment.handleOut =
|
||||||
|
beforeSegment.handleOut.multiply(beforeCurveLength / 2 / beforeSegment.handleOut.length);
|
||||||
|
} else {
|
||||||
|
beforeSegment.handleOut = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (afterSegment && afterSegment.handleIn) {
|
||||||
|
if (beforeSegment) {
|
||||||
|
afterSegment.handleIn =
|
||||||
|
afterSegment.handleIn.multiply(afterCurveLength / 2 / afterSegment.handleIn.length);
|
||||||
|
} else {
|
||||||
|
afterSegment.handleIn = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removePoint (hitResult) {
|
||||||
|
const index = hitResult.segment.index;
|
||||||
|
hitResult.item.removeSegment(index);
|
||||||
|
|
||||||
|
// Adjust handles of curve before and curve after to account for new curve length
|
||||||
|
const beforeSegment = hitResult.item.segments[index - 1];
|
||||||
|
const afterSegment = hitResult.item.segments[index];
|
||||||
|
const curveLength = beforeSegment ? beforeSegment.curve ? beforeSegment.curve.length : null : null;
|
||||||
|
if (beforeSegment && beforeSegment.handleOut) {
|
||||||
|
if (afterSegment) {
|
||||||
|
beforeSegment.handleOut =
|
||||||
|
beforeSegment.handleOut.multiply(curveLength / 2 / beforeSegment.handleOut.length);
|
||||||
|
} else {
|
||||||
|
beforeSegment.handleOut = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (afterSegment && afterSegment.handleIn) {
|
||||||
|
if (beforeSegment) {
|
||||||
|
afterSegment.handleIn = afterSegment.handleIn.multiply(curveLength / 2 / afterSegment.handleIn.length);
|
||||||
|
} else {
|
||||||
|
afterSegment.handleIn = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMouseDrag (event) {
|
||||||
|
// A click will deselect, but a drag will not
|
||||||
|
this.deselectOnMouseUp = null;
|
||||||
|
this.invertDeselect = false;
|
||||||
|
this.deleteOnMouseUp = null;
|
||||||
|
|
||||||
|
const dragVector = event.point.subtract(event.downPoint);
|
||||||
|
|
||||||
|
for (const item of this.selectedItems) {
|
||||||
|
if (!item.segments) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const seg of item.segments) {
|
||||||
|
// add the point of the segment before the drag started
|
||||||
|
// for later use in the snap calculation
|
||||||
|
if (!seg.origPoint) {
|
||||||
|
seg.origPoint = seg.point.clone();
|
||||||
|
}
|
||||||
|
if (seg.selected) {
|
||||||
|
if (event.modifiers.shift) {
|
||||||
|
seg.point = seg.origPoint.add(snapDeltaToAngle(dragVector, Math.PI / 4));
|
||||||
|
} else {
|
||||||
|
seg.point = seg.point.add(event.delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMouseUp () {
|
||||||
|
// resetting the items and segments origin points for the next usage
|
||||||
|
for (const item of this.selectedItems) {
|
||||||
|
if (!item.segments) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const seg of item.segments) {
|
||||||
|
seg.origPoint = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no drag occurred between mouse down and mouse up, then we can go through with deselect
|
||||||
|
// and delete
|
||||||
|
if (this.deselectOnMouseUp) {
|
||||||
|
if (this.invertDeselect) {
|
||||||
|
clearSelection();
|
||||||
|
this.deselectOnMouseUp.selected = true;
|
||||||
|
} else {
|
||||||
|
this.deselectOnMouseUp.selected = false;
|
||||||
|
}
|
||||||
|
this.deselectOnMouseUp = null;
|
||||||
|
this.invertDeselect = false;
|
||||||
|
}
|
||||||
|
if (this.deleteOnMouseUp) {
|
||||||
|
this.removePoint(this.deleteOnMouseUp);
|
||||||
|
this.deleteOnMouseUp = null;
|
||||||
|
}
|
||||||
|
this.selectedItems = null;
|
||||||
|
// @todo add back undo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PointTool;
|
||||||
|
|
||||||
|
// - bounding box when switching between select and reshape
|
200
src/helper/selection-tools/reshape-tool.js
Normal file
200
src/helper/selection-tools/reshape-tool.js
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
import paper from 'paper';
|
||||||
|
import log from '../../log/log';
|
||||||
|
import keyMirror from 'keymirror';
|
||||||
|
|
||||||
|
import Modes from '../../modes/modes';
|
||||||
|
import {getHoveredItem} from '../hover';
|
||||||
|
import {deleteSelection} from '../selection';
|
||||||
|
import {getRootItem, isPGTextItem} from '../item';
|
||||||
|
import MoveTool from './move-tool';
|
||||||
|
import PointTool from './point-tool';
|
||||||
|
import HandleTool from './handle-tool';
|
||||||
|
import SelectionBoxTool from './selection-box-tool';
|
||||||
|
|
||||||
|
/** Modes of the reshape tool, which can do many things depending on how it's used. */
|
||||||
|
const ReshapeModes = keyMirror({
|
||||||
|
FILL: null,
|
||||||
|
POINT: null,
|
||||||
|
HANDLE: null,
|
||||||
|
SELECTION_BOX: null
|
||||||
|
});
|
||||||
|
|
||||||
|
class ReshapeTool extends paper.Tool {
|
||||||
|
static get TOLERANCE () {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
static get DOUBLE_CLICK_MILLIS () {
|
||||||
|
return 250;
|
||||||
|
}
|
||||||
|
constructor (setHoveredItem, clearHoveredItem) {
|
||||||
|
super();
|
||||||
|
this.setHoveredItem = setHoveredItem;
|
||||||
|
this.clearHoveredItem = clearHoveredItem;
|
||||||
|
this.prevHoveredItem = null;
|
||||||
|
this._hitOptionsSelected = {
|
||||||
|
match: function (item) {
|
||||||
|
if (!item.item || !item.item.selected) return;
|
||||||
|
if (item.type === 'handle-out' || item.type === 'handle-in') {
|
||||||
|
// Only hit test against handles that are visible, that is,
|
||||||
|
// their segment is selected
|
||||||
|
if (!item.segment.selected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
segments: true,
|
||||||
|
stroke: true,
|
||||||
|
curves: true,
|
||||||
|
handles: true,
|
||||||
|
fill: true,
|
||||||
|
guide: false
|
||||||
|
};
|
||||||
|
this._hitOptions = {
|
||||||
|
match: function (item) {
|
||||||
|
if (item.type === 'handle-out' || item.type === 'handle-in') {
|
||||||
|
// Only hit test against handles that are visible, that is,
|
||||||
|
// their segment is selected
|
||||||
|
if (!item.segment.selected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
segments: true,
|
||||||
|
stroke: true,
|
||||||
|
curves: true,
|
||||||
|
handles: true,
|
||||||
|
fill: true,
|
||||||
|
guide: false
|
||||||
|
};
|
||||||
|
this.lastEvent = null;
|
||||||
|
this.mode = ReshapeModes.SELECTION_BOX;
|
||||||
|
this.selectionRect = null;
|
||||||
|
this._modeMap = {};
|
||||||
|
this._modeMap[ReshapeModes.FILL] = new MoveTool();
|
||||||
|
this._modeMap[ReshapeModes.POINT] = new PointTool();
|
||||||
|
this._modeMap[ReshapeModes.HANDLE] = new HandleTool();
|
||||||
|
this._modeMap[ReshapeModes.SELECTION_BOX] = new SelectionBoxTool();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
getHitOptions (preselectedOnly) {
|
||||||
|
this._hitOptions.tolerance = ReshapeTool.TOLERANCE / paper.view.zoom;
|
||||||
|
this._hitOptionsSelected.tolerance = ReshapeTool.TOLERANCE / paper.view.zoom;
|
||||||
|
return preselectedOnly ? this._hitOptionsSelected : this._hitOptions;
|
||||||
|
}
|
||||||
|
setPrevHoveredItem (prevHoveredItem) {
|
||||||
|
this.prevHoveredItem = prevHoveredItem;
|
||||||
|
}
|
||||||
|
handleMouseDown (event) {
|
||||||
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
|
this.clearHoveredItem();
|
||||||
|
|
||||||
|
// Check if double clicked
|
||||||
|
let doubleClicked = false;
|
||||||
|
if (this.lastEvent) {
|
||||||
|
if ((event.event.timeStamp - this.lastEvent.event.timeStamp) < ReshapeTool.DOUBLE_CLICK_MILLIS) {
|
||||||
|
doubleClicked = true;
|
||||||
|
} else {
|
||||||
|
doubleClicked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.lastEvent = event;
|
||||||
|
|
||||||
|
// Choose hit result ===========================================================
|
||||||
|
// Prefer hits on already selected items
|
||||||
|
let hitResults =
|
||||||
|
paper.project.hitTestAll(event.point, this.getHitOptions(true /* preselectedOnly */));
|
||||||
|
if (hitResults.length === 0) {
|
||||||
|
hitResults = paper.project.hitTestAll(event.point, this.getHitOptions());
|
||||||
|
}
|
||||||
|
if (hitResults.length === 0) {
|
||||||
|
this._modeMap[ReshapeModes.SELECTION_BOX].onMouseDown(event.modifiers.shift);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer hits on segments to other types of hits, to make sure handles are movable.
|
||||||
|
let hitResult = hitResults[0];
|
||||||
|
for (let i = 0; i < hitResults.length; i++) {
|
||||||
|
if (hitResults[i].type === 'segment') {
|
||||||
|
hitResult = hitResults[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow detail-selection of PGTextItem
|
||||||
|
if (isPGTextItem(getRootItem(hitResult.item))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hitProperties = {
|
||||||
|
hitResult: hitResult,
|
||||||
|
clone: event.modifiers.alt,
|
||||||
|
multiselect: event.modifiers.shift,
|
||||||
|
doubleClicked: doubleClicked,
|
||||||
|
subselect: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// If item is not yet selected, don't behave differently depending on if they clicked a segment
|
||||||
|
// or stroke (since those were invisible), just select the whole thing as if they clicked the fill.
|
||||||
|
if (!hitResult.item.selected ||
|
||||||
|
hitResult.type === 'fill' ||
|
||||||
|
(hitResult.type !== 'segment' && doubleClicked)) {
|
||||||
|
this.mode = ReshapeModes.FILL;
|
||||||
|
this._modeMap[this.mode].onMouseDown(hitProperties);
|
||||||
|
} else if (hitResult.type === 'segment') {
|
||||||
|
this.mode = ReshapeModes.POINT;
|
||||||
|
this._modeMap[this.mode].onMouseDown(hitProperties);
|
||||||
|
} else if (
|
||||||
|
hitResult.type === 'stroke' ||
|
||||||
|
hitResult.type === 'curve') {
|
||||||
|
this.mode = ReshapeModes.POINT;
|
||||||
|
this._modeMap[this.mode].addPoint(hitProperties);
|
||||||
|
this._modeMap[this.mode].onMouseDown(hitProperties);
|
||||||
|
} else if (
|
||||||
|
hitResult.type === 'handle-in' ||
|
||||||
|
hitResult.type === 'handle-out') {
|
||||||
|
this.mode = ReshapeModes.HANDLE;
|
||||||
|
this._modeMap[this.mode].onMouseDown(hitProperties);
|
||||||
|
} else {
|
||||||
|
log.warn(`Unhandled hit result type: ${hitResult.type}`);
|
||||||
|
this.mode = ReshapeModes.FILL;
|
||||||
|
this._modeMap[this.mode].onMouseDown(hitProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo Trigger selection changed. Update styles based on selection.
|
||||||
|
}
|
||||||
|
handleMouseMove (event) {
|
||||||
|
const hoveredItem = getHoveredItem(event, this.getHitOptions(), true /* subselect */);
|
||||||
|
if ((!hoveredItem && this.prevHoveredItem) || // There is no longer a hovered item
|
||||||
|
(hoveredItem && !this.prevHoveredItem) || // There is now a hovered item
|
||||||
|
(hoveredItem && this.prevHoveredItem &&
|
||||||
|
hoveredItem.id !== this.prevHoveredItem.id)) { // hovered item changed
|
||||||
|
this.setHoveredItem(hoveredItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleMouseDrag (event) {
|
||||||
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
|
this._modeMap[this.mode].onMouseDrag(event);
|
||||||
|
}
|
||||||
|
handleMouseUp (event) {
|
||||||
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
|
this._modeMap[this.mode].onMouseUp(event);
|
||||||
|
this.mode = ReshapeModes.SELECTION_BOX;
|
||||||
|
}
|
||||||
|
handleKeyUp (event) {
|
||||||
|
// Backspace, delete
|
||||||
|
if (event.key === 'delete' || event.key === 'backspace') {
|
||||||
|
deleteSelection(Modes.RESHAPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReshapeTool;
|
|
@ -113,7 +113,7 @@ class ScaleTool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMouseUp () {
|
onMouseUp (event) {
|
||||||
this.pivot = null;
|
this.pivot = null;
|
||||||
this.origPivot = null;
|
this.origPivot = null;
|
||||||
this.corner = null;
|
this.corner = null;
|
32
src/helper/selection-tools/selection-box-tool.js
Normal file
32
src/helper/selection-tools/selection-box-tool.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import Modes from '../../modes/modes';
|
||||||
|
import {rectSelect} from '../guides';
|
||||||
|
import {clearSelection, processRectangularSelection} from '../selection';
|
||||||
|
import {getHoveredItem} from '../hover';
|
||||||
|
|
||||||
|
class SelectionBoxTool {
|
||||||
|
constructor () {
|
||||||
|
this.selectionRect = null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {boolean} multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
||||||
|
*/
|
||||||
|
onMouseDown (multiselect) {
|
||||||
|
if (!multiselect) {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMouseDrag (event) {
|
||||||
|
this.selectionRect = rectSelect(event);
|
||||||
|
// Remove this rect on the next drag and up event
|
||||||
|
this.selectionRect.removeOnDrag();
|
||||||
|
}
|
||||||
|
onMouseUp (event) {
|
||||||
|
if (this.selectionRect) {
|
||||||
|
processRectangularSelection(event, this.selectionRect, Modes.RESHAPE);
|
||||||
|
this.selectionRect.remove();
|
||||||
|
this.selectionRect = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectionBoxTool;
|
|
@ -3,7 +3,7 @@ import Modes from '../modes/modes';
|
||||||
|
|
||||||
import {getAllPaperItems} from './helper';
|
import {getAllPaperItems} from './helper';
|
||||||
import {getItemsGroup, isGroup} from './group';
|
import {getItemsGroup, isGroup} from './group';
|
||||||
import {getRootItem, isBoundsItem, isCompoundPathItem, isPathItem, isPGTextItem} from './item';
|
import {getRootItem, isGroupItem, isCompoundPathItem, isPathItem, isPGTextItem} from './item';
|
||||||
import {getItemsCompoundPath, isCompoundPath, isCompoundPathChild} from './compound-path';
|
import {getItemsCompoundPath, isCompoundPath, isCompoundPathChild} from './compound-path';
|
||||||
|
|
||||||
const getAllSelectableItems = function () {
|
const getAllSelectableItems = function () {
|
||||||
|
@ -54,12 +54,12 @@ const setItemSelection = function (item, state) {
|
||||||
const parentGroup = getItemsGroup(item);
|
const parentGroup = getItemsGroup(item);
|
||||||
const itemsCompoundPath = getItemsCompoundPath(item);
|
const itemsCompoundPath = getItemsCompoundPath(item);
|
||||||
|
|
||||||
// if selection is in a group, select group not individual items
|
// if selection is in a group, select group
|
||||||
if (parentGroup) {
|
if (parentGroup) {
|
||||||
// do it recursive
|
// do it recursive
|
||||||
setItemSelection(parentGroup, state);
|
setItemSelection(parentGroup, state);
|
||||||
} else if (itemsCompoundPath) {
|
} else if (itemsCompoundPath) {
|
||||||
setItemSelection(itemsCompoundPath, state);
|
setGroupSelection(itemsCompoundPath, state);
|
||||||
} else {
|
} else {
|
||||||
if (item.data && item.data.noSelect) {
|
if (item.data && item.data.noSelect) {
|
||||||
return;
|
return;
|
||||||
|
@ -122,8 +122,8 @@ const getSelectedItems = function (recursive) {
|
||||||
return itemsAndGroups;
|
return itemsAndGroups;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteItemSelection = function () {
|
const deleteItemSelection = function (recursive) {
|
||||||
const items = getSelectedItems();
|
const items = getSelectedItems(recursive);
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
items[i].remove();
|
items[i].remove();
|
||||||
}
|
}
|
||||||
|
@ -134,11 +134,11 @@ const deleteItemSelection = function () {
|
||||||
// pg.undo.snapshot('deleteItemSelection');
|
// pg.undo.snapshot('deleteItemSelection');
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeSelectedSegments = function () {
|
const removeSelectedSegments = function (recursive) {
|
||||||
// @todo add back undo
|
// @todo add back undo
|
||||||
// pg.undo.snapshot('removeSelectedSegments');
|
// pg.undo.snapshot('removeSelectedSegments');
|
||||||
|
|
||||||
const items = getSelectedItems();
|
const items = getSelectedItems(recursive);
|
||||||
const segmentsToRemove = [];
|
const segmentsToRemove = [];
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
@ -163,8 +163,8 @@ const removeSelectedSegments = function () {
|
||||||
const deleteSelection = function (mode) {
|
const deleteSelection = function (mode) {
|
||||||
if (mode === Modes.RESHAPE) {
|
if (mode === Modes.RESHAPE) {
|
||||||
// If there are points selected remove them. If not delete the item selected.
|
// If there are points selected remove them. If not delete the item selected.
|
||||||
if (!removeSelectedSegments()) {
|
if (!removeSelectedSegments(true /* recursive */)) {
|
||||||
deleteItemSelection();
|
deleteItemSelection(true /* recursive */);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deleteItemSelection();
|
deleteItemSelection();
|
||||||
|
@ -286,8 +286,8 @@ const deleteSegmentSelection = function () {
|
||||||
// pg.undo.snapshot('deleteSegmentSelection');
|
// pg.undo.snapshot('deleteSegmentSelection');
|
||||||
};
|
};
|
||||||
|
|
||||||
const cloneSelection = function () {
|
const cloneSelection = function (recursive) {
|
||||||
const selectedItems = getSelectedItems();
|
const selectedItems = getSelectedItems(recursive);
|
||||||
for (let i = 0; i < selectedItems.length; i++) {
|
for (let i = 0; i < selectedItems.length; i++) {
|
||||||
const item = selectedItems[i];
|
const item = selectedItems[i];
|
||||||
item.clone();
|
item.clone();
|
||||||
|
@ -311,34 +311,34 @@ const getSelectedPaths = function () {
|
||||||
return paths;
|
return paths;
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkBoundsItem = function (selectionRect, item, event) {
|
// const checkBoundsItem = function (selectionRect, item, event) {
|
||||||
const itemBounds = new paper.Path([
|
// const itemBounds = new paper.Path([
|
||||||
item.localToGlobal(item.internalBounds.topLeft),
|
// item.localToGlobal(item.internalBounds.topLeft),
|
||||||
item.localToGlobal(item.internalBounds.topRight),
|
// item.localToGlobal(item.internalBounds.topRight),
|
||||||
item.localToGlobal(item.internalBounds.bottomRight),
|
// item.localToGlobal(item.internalBounds.bottomRight),
|
||||||
item.localToGlobal(item.internalBounds.bottomLeft)
|
// item.localToGlobal(item.internalBounds.bottomLeft)
|
||||||
]);
|
// ]);
|
||||||
itemBounds.closed = true;
|
// itemBounds.closed = true;
|
||||||
itemBounds.guide = true;
|
// itemBounds.guide = true;
|
||||||
|
|
||||||
for (let i = 0; i < itemBounds.segments.length; i++) {
|
// for (let i = 0; i < itemBounds.segments.length; i++) {
|
||||||
const seg = itemBounds.segments[i];
|
// const seg = itemBounds.segments[i];
|
||||||
if (selectionRect.contains(seg.point) ||
|
// if (selectionRect.contains(seg.point) ||
|
||||||
(i === 0 && selectionRect.getIntersections(itemBounds).length > 0)) {
|
// (i === 0 && selectionRect.getIntersections(itemBounds).length > 0)) {
|
||||||
if (event.modifiers.shift && item.selected) {
|
// if (event.modifiers.shift && item.selected) {
|
||||||
setItemSelection(item, false);
|
// setItemSelection(item, false);
|
||||||
|
|
||||||
} else {
|
// } else {
|
||||||
setItemSelection(item, true);
|
// setItemSelection(item, true);
|
||||||
}
|
// }
|
||||||
itemBounds.remove();
|
// itemBounds.remove();
|
||||||
return true;
|
// return true;
|
||||||
|
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
itemBounds.remove();
|
// itemBounds.remove();
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleRectangularSelectionItems = function (item, event, rect, mode) {
|
const handleRectangularSelectionItems = function (item, event, rect, mode) {
|
||||||
if (isPathItem(item)) {
|
if (isPathItem(item)) {
|
||||||
|
@ -402,10 +402,10 @@ const handleRectangularSelectionItems = function (item, event, rect, mode) {
|
||||||
}
|
}
|
||||||
// @todo: Update toolbar state on change
|
// @todo: Update toolbar state on change
|
||||||
|
|
||||||
} else if (isBoundsItem(item)) {
|
// } else if (isBoundsItem(item)) {
|
||||||
if (checkBoundsItem(rect, item, event)) {
|
// if (checkBoundsItem(rect, item, event)) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -448,18 +448,30 @@ const processRectangularSelection = function (event, rect, mode) {
|
||||||
|
|
||||||
const selectRootItem = function () {
|
const selectRootItem = function () {
|
||||||
// when switching to the select tool while having a child object of a
|
// when switching to the select tool while having a child object of a
|
||||||
// compound path selected, deselect the child and select the compound path
|
// compound path or group selected, select the whole compound path or
|
||||||
// instead. (otherwise the compound path breaks because of scale-grouping)
|
// group instead. (otherwise the compound path breaks because of
|
||||||
const items = getSelectedItems();
|
// scale-grouping)
|
||||||
|
const items = getSelectedItems(true /* recursive */);
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (isCompoundPathChild(item)) {
|
if (isCompoundPathChild(item)) {
|
||||||
const cp = getItemsCompoundPath(item);
|
const cp = getItemsCompoundPath(item);
|
||||||
setItemSelection(item, false);
|
|
||||||
setItemSelection(cp, true);
|
setItemSelection(cp, true);
|
||||||
}
|
}
|
||||||
|
const rootItem = getRootItem(item);
|
||||||
|
if (item !== rootItem) {
|
||||||
|
setItemSelection(item, false);
|
||||||
|
setItemSelection(rootItem, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectSubItems = function () {
|
||||||
|
// when switching to the reshape tool while having a compound path or group
|
||||||
|
// selected, deselect the group and select the children instead.
|
||||||
|
// TODO
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const shouldShowIfSelection = function () {
|
const shouldShowIfSelection = function () {
|
||||||
return getSelectedItems().length > 0;
|
return getSelectedItems().length > 0;
|
||||||
};
|
};
|
||||||
|
@ -488,6 +500,7 @@ export {
|
||||||
removeSelectedSegments,
|
removeSelectedSegments,
|
||||||
processRectangularSelection,
|
processRectangularSelection,
|
||||||
selectRootItem,
|
selectRootItem,
|
||||||
|
selectSubItems,
|
||||||
shouldShowIfSelection,
|
shouldShowIfSelection,
|
||||||
shouldShowIfSelectionRecursive,
|
shouldShowIfSelectionRecursive,
|
||||||
shouldShowSelectAll
|
shouldShowSelectAll
|
||||||
|
|
Loading…
Reference in a new issue