mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
Move select tool out of select mode
This commit is contained in:
parent
f15a3dbe02
commit
4bc4d92415
12 changed files with 135 additions and 199 deletions
|
@ -35,7 +35,15 @@ class PaperCanvas extends React.Component {
|
||||||
onLoad: function (item) {
|
onLoad: function (item) {
|
||||||
// Remove viewbox
|
// Remove viewbox
|
||||||
if (item.clipped) {
|
if (item.clipped) {
|
||||||
|
let mask;
|
||||||
|
for (const child of item.children) {
|
||||||
|
if (child.isClipMask()) {
|
||||||
|
mask = child;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
item.clipped = false;
|
item.clipped = false;
|
||||||
|
mask.remove();
|
||||||
// Consider removing clip mask here?
|
// Consider removing clip mask here?
|
||||||
}
|
}
|
||||||
while (item.reduce() !== item) {
|
while (item.reduce() !== item) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ 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 {selectSubItems} from '../helper/selection';
|
|
||||||
import ReshapeTool from '../helper/selection-tools/reshape-tool';
|
import ReshapeTool from '../helper/selection-tools/reshape-tool';
|
||||||
import ReshapeModeComponent from '../components/reshape-mode.jsx';
|
import ReshapeModeComponent from '../components/reshape-mode.jsx';
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
@ -41,15 +40,12 @@ class ReshapeMode extends React.Component {
|
||||||
return false; // Static component, for now
|
return false; // Static component, for now
|
||||||
}
|
}
|
||||||
activateTool () {
|
activateTool () {
|
||||||
selectSubItems();
|
this.tool = new ReshapeTool(this.props.setHoveredItem, this.props.clearHoveredItem, this.props.onUpdateSvg);
|
||||||
this.tool = new ReshapeTool(this.props.setHoveredItem, this.props.clearHoveredItem);
|
|
||||||
this.tool.setPrevHoveredItem(this.props.hoveredItem);
|
this.tool.setPrevHoveredItem(this.props.hoveredItem);
|
||||||
this.tool.activate();
|
this.tool.activate();
|
||||||
paper.settings.handleSize = 8;
|
|
||||||
}
|
}
|
||||||
deactivateTool () {
|
deactivateTool () {
|
||||||
paper.settings.handleSize = 0;
|
this.tool.deactivateTool();
|
||||||
this.props.clearHoveredItem();
|
|
||||||
this.tool.remove();
|
this.tool.remove();
|
||||||
this.tool = null;
|
this.tool = null;
|
||||||
this.hitResult = null;
|
this.hitResult = null;
|
||||||
|
@ -64,10 +60,10 @@ class ReshapeMode extends React.Component {
|
||||||
ReshapeMode.propTypes = {
|
ReshapeMode.propTypes = {
|
||||||
clearHoveredItem: PropTypes.func.isRequired,
|
clearHoveredItem: PropTypes.func.isRequired,
|
||||||
handleMouseDown: PropTypes.func.isRequired,
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
hoveredItem: PropTypes.instanceOf(paper.Item), // eslint-disable-line react/no-unused-prop-types
|
hoveredItem: PropTypes.instanceOf(paper.Item),
|
||||||
isReshapeModeActive: PropTypes.bool.isRequired,
|
isReshapeModeActive: PropTypes.bool.isRequired,
|
||||||
onUpdateSvg: PropTypes.func.isRequired, // eslint-disable-line react/no-unused-prop-types
|
onUpdateSvg: PropTypes.func.isRequired,
|
||||||
setHoveredItem: PropTypes.func.isRequired // eslint-disable-line react/no-unused-prop-types
|
setHoveredItem: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
|
|
@ -7,36 +7,17 @@ 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 SelectTool from '../helper/selection-tools/select-tool';
|
||||||
import {rectSelect} from '../helper/guides';
|
|
||||||
import {selectRootItem, processRectangularSelection} from '../helper/selection';
|
|
||||||
|
|
||||||
import SelectModeComponent from '../components/select-mode.jsx';
|
import SelectModeComponent from '../components/select-mode.jsx';
|
||||||
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 {
|
||||||
static get TOLERANCE () {
|
|
||||||
return 6;
|
|
||||||
}
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'activateTool',
|
'activateTool',
|
||||||
'deactivateTool',
|
'deactivateTool'
|
||||||
'getHitOptions'
|
|
||||||
]);
|
]);
|
||||||
this._hitOptions = {
|
|
||||||
segments: true,
|
|
||||||
stroke: true,
|
|
||||||
curves: true,
|
|
||||||
fill: true,
|
|
||||||
guide: false
|
|
||||||
};
|
|
||||||
this.boundingBoxTool = new BoundingBoxTool();
|
|
||||||
this.selectionBoxTool = new SelectionBoxTool();
|
|
||||||
this.selectionBoxMode = false;
|
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
if (this.props.isSelectModeActive) {
|
if (this.props.isSelectModeActive) {
|
||||||
|
@ -44,6 +25,10 @@ class SelectMode extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (this.tool && nextProps.hoveredItem !== this.props.hoveredItem) {
|
||||||
|
this.tool.setPrevHoveredItem(nextProps.hoveredItem);
|
||||||
|
}
|
||||||
|
|
||||||
if (nextProps.isSelectModeActive && !this.props.isSelectModeActive) {
|
if (nextProps.isSelectModeActive && !this.props.isSelectModeActive) {
|
||||||
this.activateTool();
|
this.activateTool();
|
||||||
} else if (!nextProps.isSelectModeActive && this.props.isSelectModeActive) {
|
} else if (!nextProps.isSelectModeActive && this.props.isSelectModeActive) {
|
||||||
|
@ -53,87 +38,14 @@ class SelectMode extends React.Component {
|
||||||
shouldComponentUpdate () {
|
shouldComponentUpdate () {
|
||||||
return false; // Static component, for now
|
return false; // Static component, for now
|
||||||
}
|
}
|
||||||
getHitOptions (preselectedOnly) {
|
|
||||||
this._hitOptions.tolerance = SelectMode.TOLERANCE / paper.view.zoom;
|
|
||||||
if (preselectedOnly) {
|
|
||||||
this._hitOptions.selected = true;
|
|
||||||
} else {
|
|
||||||
delete this._hitOptions.selected;
|
|
||||||
}
|
|
||||||
return this._hitOptions;
|
|
||||||
}
|
|
||||||
activateTool () {
|
activateTool () {
|
||||||
debugger;
|
this.tool = new SelectTool(this.props.setHoveredItem, this.props.clearHoveredItem, this.props.onUpdateSvg);
|
||||||
selectRootItem();
|
|
||||||
this.boundingBoxTool.setSelectionBounds();
|
|
||||||
this.tool = new paper.Tool();
|
|
||||||
|
|
||||||
// Define these to sate linter
|
|
||||||
const selectMode = this;
|
|
||||||
|
|
||||||
this.tool.onMouseDown = event => {
|
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
|
||||||
|
|
||||||
this.props.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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.tool.onMouseMove = function (event) {
|
|
||||||
const hoveredItem = getHoveredItem(event, selectMode.getHitOptions());
|
|
||||||
const oldHoveredItem = selectMode.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
|
|
||||||
selectMode.props.setHoveredItem(hoveredItem);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
this.tool.onMouseDrag = function (event) {
|
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
|
||||||
|
|
||||||
if (selectMode.selectionBoxMode) {
|
|
||||||
selectMode.selectionRect = rectSelect(event);
|
|
||||||
// Remove this rect on the next drag and up event
|
|
||||||
selectMode.selectionRect.removeOnDrag();
|
|
||||||
} else {
|
|
||||||
selectMode.boundingBoxTool.onMouseDrag(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.tool.onMouseUp = function (event) {
|
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
|
||||||
|
|
||||||
if (selectMode.selectionBoxMode) {
|
|
||||||
if (selectMode.selectionRect) {
|
|
||||||
processRectangularSelection(event, selectMode.selectionRect, Modes.SELECT);
|
|
||||||
selectMode.selectionRect.remove();
|
|
||||||
}
|
|
||||||
selectMode.boundingBoxTool.setSelectionBounds();
|
|
||||||
} else {
|
|
||||||
selectMode.boundingBoxTool.onMouseUp(event);
|
|
||||||
selectMode.props.onUpdateSvg();
|
|
||||||
}
|
|
||||||
selectMode.selectionBoxMode = false;
|
|
||||||
selectMode.selectionRect = null;
|
|
||||||
};
|
|
||||||
this.tool.activate();
|
this.tool.activate();
|
||||||
}
|
}
|
||||||
deactivateTool () {
|
deactivateTool () {
|
||||||
debugger;
|
this.tool.deactivateTool();
|
||||||
this.props.clearHoveredItem();
|
|
||||||
this.boundingBoxTool.removeBoundsPath();
|
|
||||||
this.tool.remove();
|
this.tool.remove();
|
||||||
this.tool = null;
|
this.tool = null;
|
||||||
this.hitResult = null;
|
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
|
@ -145,10 +57,10 @@ class SelectMode extends React.Component {
|
||||||
SelectMode.propTypes = {
|
SelectMode.propTypes = {
|
||||||
clearHoveredItem: PropTypes.func.isRequired,
|
clearHoveredItem: PropTypes.func.isRequired,
|
||||||
handleMouseDown: PropTypes.func.isRequired,
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
hoveredItem: PropTypes.instanceOf(paper.Item), // eslint-disable-line react/no-unused-prop-types
|
hoveredItem: PropTypes.instanceOf(paper.Item),
|
||||||
isSelectModeActive: PropTypes.bool.isRequired,
|
isSelectModeActive: PropTypes.bool.isRequired,
|
||||||
onUpdateSvg: PropTypes.func.isRequired, // eslint-disable-line react/no-unused-prop-types
|
onUpdateSvg: PropTypes.func.isRequired,
|
||||||
setHoveredItem: PropTypes.func.isRequired // eslint-disable-line react/no-unused-prop-types
|
setHoveredItem: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
|
|
@ -28,17 +28,19 @@ const Modes = keyMirror({
|
||||||
* On mouse down, the type of function (move, scale, rotate) is determined based on what is clicked
|
* On mouse down, the type of function (move, scale, rotate) is determined based on what is clicked
|
||||||
* (scale handle, rotate handle, the object itself). This determines the mode of the tool, which then
|
* (scale handle, rotate handle, the object itself). This determines the mode of the tool, which then
|
||||||
* delegates actions to the MoveTool, RotateTool or ScaleTool accordingly.
|
* delegates actions to the MoveTool, RotateTool or ScaleTool accordingly.
|
||||||
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
*/
|
*/
|
||||||
class BoundingBoxTool {
|
class BoundingBoxTool {
|
||||||
constructor () {
|
constructor (onUpdateSvg) {
|
||||||
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
this.mode = null;
|
this.mode = null;
|
||||||
this.boundsPath = null;
|
this.boundsPath = null;
|
||||||
this.boundsScaleHandles = [];
|
this.boundsScaleHandles = [];
|
||||||
this.boundsRotHandles = [];
|
this.boundsRotHandles = [];
|
||||||
this._modeMap = {};
|
this._modeMap = {};
|
||||||
this._modeMap[Modes.SCALE] = new ScaleTool();
|
this._modeMap[Modes.SCALE] = new ScaleTool(onUpdateSvg);
|
||||||
this._modeMap[Modes.ROTATE] = new RotateTool();
|
this._modeMap[Modes.ROTATE] = new RotateTool(onUpdateSvg);
|
||||||
this._modeMap[Modes.MOVE] = new MoveTool();
|
this._modeMap[Modes.MOVE] = new MoveTool(onUpdateSvg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,8 +77,13 @@ class BoundingBoxTool {
|
||||||
this.mode = Modes.MOVE;
|
this.mode = Modes.MOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hitProperties = {
|
||||||
|
hitResult: hitResult,
|
||||||
|
clone: event.modifiers.alt,
|
||||||
|
multiselect: event.modifiers.shift
|
||||||
|
};
|
||||||
if (this.mode === Modes.MOVE) {
|
if (this.mode === Modes.MOVE) {
|
||||||
this._modeMap[this.mode].onMouseDown(hitResult, clone, multiselect);
|
this._modeMap[this.mode].onMouseDown(hitProperties);
|
||||||
} else if (this.mode === Modes.SCALE) {
|
} else if (this.mode === Modes.SCALE) {
|
||||||
this._modeMap[this.mode].onMouseDown(
|
this._modeMap[this.mode].onMouseDown(
|
||||||
hitResult, this.boundsPath, this.boundsScaleHandles, this.boundsRotHandles, getSelectedItems());
|
hitResult, this.boundsPath, this.boundsScaleHandles, this.boundsRotHandles, getSelectedItems());
|
||||||
|
@ -100,10 +107,9 @@ class BoundingBoxTool {
|
||||||
this.setSelectionBounds();
|
this.setSelectionBounds();
|
||||||
}
|
}
|
||||||
setSelectionBounds () {
|
setSelectionBounds () {
|
||||||
debugger;
|
|
||||||
this.removeBoundsPath();
|
this.removeBoundsPath();
|
||||||
|
|
||||||
const items = getSelectedItems();
|
const items = getSelectedItems(true /* recursive */);
|
||||||
if (items.length <= 0) return;
|
if (items.length <= 0) return;
|
||||||
|
|
||||||
let rect = null;
|
let rect = null;
|
||||||
|
@ -181,7 +187,6 @@ class BoundingBoxTool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeBoundsPath () {
|
removeBoundsPath () {
|
||||||
debugger;
|
|
||||||
removeHelperItems();
|
removeHelperItems();
|
||||||
this.boundsPath = null;
|
this.boundsPath = null;
|
||||||
this.boundsScaleHandles.length = 0;
|
this.boundsScaleHandles.length = 0;
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import {clearSelection, getSelectedItems} from '../selection';
|
import {clearSelection, getSelectedItems} from '../selection';
|
||||||
|
|
||||||
class HandleTool {
|
class HandleTool {
|
||||||
constructor () {
|
/**
|
||||||
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
|
*/
|
||||||
|
constructor (onUpdateSvg) {
|
||||||
this.hitType = null;
|
this.hitType = null;
|
||||||
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {!object} hitProperties Describes the mouse event
|
* @param {!object} hitProperties Describes the mouse event
|
||||||
|
@ -58,6 +62,7 @@ class HandleTool {
|
||||||
}
|
}
|
||||||
onMouseUp () {
|
onMouseUp () {
|
||||||
// @todo add back undo
|
// @todo add back undo
|
||||||
|
this.onUpdateSvg();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,12 @@ import {snapDeltaToAngle} from '../math';
|
||||||
import {clearSelection, cloneSelection, getSelectedItems, setItemSelection} from '../selection';
|
import {clearSelection, cloneSelection, getSelectedItems, setItemSelection} from '../selection';
|
||||||
|
|
||||||
class MoveTool {
|
class MoveTool {
|
||||||
constructor () {
|
/**
|
||||||
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
|
*/
|
||||||
|
constructor (onUpdateSvg) {
|
||||||
this.selectedItems = null;
|
this.selectedItems = null;
|
||||||
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,7 +45,7 @@ class MoveTool {
|
||||||
this._select(item, true, hitProperties.subselect);
|
this._select(item, true, hitProperties.subselect);
|
||||||
}
|
}
|
||||||
if (hitProperties.clone) cloneSelection(hitProperties.subselect);
|
if (hitProperties.clone) cloneSelection(hitProperties.subselect);
|
||||||
this.selectedItems = getSelectedItems(hitProperties.subselect);
|
this.selectedItems = getSelectedItems(true /* subselect */);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sets the selection state of an item.
|
* Sets the selection state of an item.
|
||||||
|
@ -90,6 +94,7 @@ class MoveTool {
|
||||||
|
|
||||||
// @todo add back undo
|
// @todo add back undo
|
||||||
// pg.undo.snapshot('moveSelection');
|
// pg.undo.snapshot('moveSelection');
|
||||||
|
this.onUpdateSvg();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,10 @@ import {clearSelection, getSelectedItems} from '../selection';
|
||||||
|
|
||||||
/** Subtool of ReshapeTool for moving control points. */
|
/** Subtool of ReshapeTool for moving control points. */
|
||||||
class PointTool {
|
class PointTool {
|
||||||
constructor () {
|
/**
|
||||||
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
|
*/
|
||||||
|
constructor (onUpdateSvg) {
|
||||||
/**
|
/**
|
||||||
* Deselection often does not happen until mouse up. If the mouse is dragged before
|
* 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.
|
* mouse up, deselection is cancelled. This variable keeps track of which paper.Item to deselect.
|
||||||
|
@ -21,6 +24,7 @@ class PointTool {
|
||||||
*/
|
*/
|
||||||
this.invertDeselect = false;
|
this.invertDeselect = false;
|
||||||
this.selectedItems = null;
|
this.selectedItems = null;
|
||||||
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -185,9 +189,8 @@ class PointTool {
|
||||||
}
|
}
|
||||||
this.selectedItems = null;
|
this.selectedItems = null;
|
||||||
// @todo add back undo
|
// @todo add back undo
|
||||||
|
this.onUpdateSvg();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PointTool;
|
export default PointTool;
|
||||||
|
|
||||||
// - bounding box when switching between select and reshape
|
|
||||||
|
|
|
@ -26,10 +26,11 @@ class ReshapeTool extends paper.Tool {
|
||||||
static get DOUBLE_CLICK_MILLIS () {
|
static get DOUBLE_CLICK_MILLIS () {
|
||||||
return 250;
|
return 250;
|
||||||
}
|
}
|
||||||
constructor (setHoveredItem, clearHoveredItem) {
|
constructor (setHoveredItem, clearHoveredItem, onUpdateSvg) {
|
||||||
super();
|
super();
|
||||||
this.setHoveredItem = setHoveredItem;
|
this.setHoveredItem = setHoveredItem;
|
||||||
this.clearHoveredItem = clearHoveredItem;
|
this.clearHoveredItem = clearHoveredItem;
|
||||||
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
this.prevHoveredItem = null;
|
this.prevHoveredItem = null;
|
||||||
this._hitOptionsSelected = {
|
this._hitOptionsSelected = {
|
||||||
match: function (item) {
|
match: function (item) {
|
||||||
|
@ -72,10 +73,10 @@ class ReshapeTool extends paper.Tool {
|
||||||
this.mode = ReshapeModes.SELECTION_BOX;
|
this.mode = ReshapeModes.SELECTION_BOX;
|
||||||
this.selectionRect = null;
|
this.selectionRect = null;
|
||||||
this._modeMap = {};
|
this._modeMap = {};
|
||||||
this._modeMap[ReshapeModes.FILL] = new MoveTool();
|
this._modeMap[ReshapeModes.FILL] = new MoveTool(onUpdateSvg);
|
||||||
this._modeMap[ReshapeModes.POINT] = new PointTool();
|
this._modeMap[ReshapeModes.POINT] = new PointTool(onUpdateSvg);
|
||||||
this._modeMap[ReshapeModes.HANDLE] = new HandleTool();
|
this._modeMap[ReshapeModes.HANDLE] = new HandleTool(onUpdateSvg);
|
||||||
this._modeMap[ReshapeModes.SELECTION_BOX] = new SelectionBoxTool();
|
this._modeMap[ReshapeModes.SELECTION_BOX] = new SelectionBoxTool(Modes.RESHAPE);
|
||||||
|
|
||||||
// We have to set these functions instead of just declaring them because
|
// We have to set these functions instead of just declaring them because
|
||||||
// paper.js tools hook up the listeners in the setter functions.
|
// paper.js tools hook up the listeners in the setter functions.
|
||||||
|
@ -84,6 +85,8 @@ class ReshapeTool extends paper.Tool {
|
||||||
this.onMouseDrag = this.handleMouseDrag;
|
this.onMouseDrag = this.handleMouseDrag;
|
||||||
this.onMouseUp = this.handleMouseUp;
|
this.onMouseUp = this.handleMouseUp;
|
||||||
this.onKeyUp = this.handleKeyUp;
|
this.onKeyUp = this.handleKeyUp;
|
||||||
|
|
||||||
|
paper.settings.handleSize = 8;
|
||||||
}
|
}
|
||||||
getHitOptions (preselectedOnly) {
|
getHitOptions (preselectedOnly) {
|
||||||
this._hitOptions.tolerance = ReshapeTool.TOLERANCE / paper.view.zoom;
|
this._hitOptions.tolerance = ReshapeTool.TOLERANCE / paper.view.zoom;
|
||||||
|
@ -193,8 +196,13 @@ class ReshapeTool extends paper.Tool {
|
||||||
// Backspace, delete
|
// Backspace, delete
|
||||||
if (event.key === 'delete' || event.key === 'backspace') {
|
if (event.key === 'delete' || event.key === 'backspace') {
|
||||||
deleteSelection(Modes.RESHAPE);
|
deleteSelection(Modes.RESHAPE);
|
||||||
|
this.onUpdateSvg();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
deactivateTool() {
|
||||||
|
paper.settings.handleSize = 0;
|
||||||
|
this.clearHoveredItem();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ReshapeTool;
|
export default ReshapeTool;
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
|
||||||
class RotateTool {
|
class RotateTool {
|
||||||
constructor () {
|
/**
|
||||||
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
|
*/
|
||||||
|
constructor (onUpdateSvg) {
|
||||||
this.rotItems = [];
|
this.rotItems = [];
|
||||||
this.rotGroupPivot = null;
|
this.rotGroupPivot = null;
|
||||||
this.prevRot = [];
|
this.prevRot = [];
|
||||||
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +61,7 @@ class RotateTool {
|
||||||
this.prevRot = [];
|
this.prevRot = [];
|
||||||
|
|
||||||
// @todo add back undo
|
// @todo add back undo
|
||||||
// pg.undo.snapshot('rotateSelection');
|
this.onUpdateSvg();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
|
||||||
class ScaleTool {
|
class ScaleTool {
|
||||||
constructor () {
|
/**
|
||||||
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
|
*/
|
||||||
|
constructor (onUpdateSvg) {
|
||||||
this.pivot = null;
|
this.pivot = null;
|
||||||
this.origPivot = null;
|
this.origPivot = null;
|
||||||
this.corner = null;
|
this.corner = null;
|
||||||
|
@ -14,6 +17,7 @@ class ScaleTool {
|
||||||
this.scaleItems = [];
|
this.scaleItems = [];
|
||||||
this.boundsScaleHandles = [];
|
this.boundsScaleHandles = [];
|
||||||
this.boundsRotHandles = [];
|
this.boundsRotHandles = [];
|
||||||
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,7 +117,7 @@ class ScaleTool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMouseUp (event) {
|
onMouseUp () {
|
||||||
this.pivot = null;
|
this.pivot = null;
|
||||||
this.origPivot = null;
|
this.origPivot = null;
|
||||||
this.corner = null;
|
this.corner = null;
|
||||||
|
@ -150,7 +154,7 @@ class ScaleTool {
|
||||||
this.itemGroup.remove();
|
this.itemGroup.remove();
|
||||||
|
|
||||||
// @todo add back undo
|
// @todo add back undo
|
||||||
// pg.undo.snapshot('scaleSelection');
|
this.onUpdateSvg();
|
||||||
}
|
}
|
||||||
getRectCornerNameByIndex (index) {
|
getRectCornerNameByIndex (index) {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import Modes from '../../modes/modes';
|
import Modes from '../../modes/modes';
|
||||||
import {rectSelect} from '../guides';
|
import {rectSelect} from '../guides';
|
||||||
import {clearSelection, processRectangularSelection} from '../selection';
|
import {clearSelection, processRectangularSelection} from '../selection';
|
||||||
import {getHoveredItem} from '../hover';
|
|
||||||
|
|
||||||
class SelectionBoxTool {
|
class SelectionBoxTool {
|
||||||
constructor () {
|
constructor (mode) {
|
||||||
this.selectionRect = null;
|
this.selectionRect = null;
|
||||||
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {boolean} multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
* @param {boolean} multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
||||||
|
|
|
@ -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, isGroupItem, isCompoundPathItem, isPathItem, isPGTextItem} from './item';
|
import {getRootItem, isCompoundPathItem, isBoundsItem, isPathItem, isPGTextItem} from './item';
|
||||||
import {getItemsCompoundPath, isCompoundPath, isCompoundPathChild} from './compound-path';
|
import {getItemsCompoundPath, isCompoundPath, isCompoundPathChild} from './compound-path';
|
||||||
|
|
||||||
const getAllSelectableItems = function () {
|
const getAllSelectableItems = function () {
|
||||||
|
@ -34,37 +34,40 @@ const selectItemSegments = function (item, state) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setGroupSelection = function (root, selected) {
|
const setGroupSelection = function (root, selected, fullySelected) {
|
||||||
// fully selected segments need to be unselected first
|
root.fullySelected = fullySelected;
|
||||||
root.fullySelected = false;
|
|
||||||
// then the item can be normally selected
|
|
||||||
root.selected = selected;
|
root.selected = selected;
|
||||||
// select children of compound-path or group
|
// select children of compound-path or group
|
||||||
if (isCompoundPath(root) || isGroup(root)) {
|
if (isCompoundPath(root) || isGroup(root)) {
|
||||||
const children = root.children;
|
const children = root.children;
|
||||||
if (children) {
|
if (children) {
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (const child of children) {
|
||||||
children[i].selected = selected;
|
if (isGroup(child)) {
|
||||||
|
setGroupSelection(child, selected, fullySelected);
|
||||||
|
} else {
|
||||||
|
child.fullySelected = fullySelected;
|
||||||
|
child.selected = selected;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setItemSelection = function (item, state) {
|
const setItemSelection = function (item, state, fullySelected) {
|
||||||
const parentGroup = getItemsGroup(item);
|
const parentGroup = getItemsGroup(item);
|
||||||
const itemsCompoundPath = getItemsCompoundPath(item);
|
const itemsCompoundPath = getItemsCompoundPath(item);
|
||||||
|
|
||||||
// if selection is in a group, select group
|
// if selection is in a group, select group
|
||||||
if (parentGroup) {
|
if (parentGroup) {
|
||||||
// do it recursive
|
// do it recursive
|
||||||
setItemSelection(parentGroup, state);
|
setItemSelection(parentGroup, state, fullySelected);
|
||||||
} else if (itemsCompoundPath) {
|
} else if (itemsCompoundPath) {
|
||||||
setGroupSelection(itemsCompoundPath, state);
|
setGroupSelection(itemsCompoundPath, state, fullySelected);
|
||||||
} else {
|
} else {
|
||||||
if (item.data && item.data.noSelect) {
|
if (item.data && item.data.noSelect) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setGroupSelection(item, state);
|
setGroupSelection(item, state, fullySelected);
|
||||||
}
|
}
|
||||||
// @todo: Update toolbar state on change
|
// @todo: Update toolbar state on change
|
||||||
|
|
||||||
|
@ -311,34 +314,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)) {
|
||||||
|
@ -355,11 +358,9 @@ const handleRectangularSelectionItems = function (item, event, rect, mode) {
|
||||||
seg.selected = true;
|
seg.selected = true;
|
||||||
}
|
}
|
||||||
segmentMode = true;
|
segmentMode = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -371,7 +372,7 @@ const handleRectangularSelectionItems = function (item, event, rect, mode) {
|
||||||
// second round checks for path intersections
|
// second round checks for path intersections
|
||||||
const intersections = item.getIntersections(rect);
|
const intersections = item.getIntersections(rect);
|
||||||
if (intersections.length > 0 && !segmentMode) {
|
if (intersections.length > 0 && !segmentMode) {
|
||||||
// if in detail select mode, select the curves that intersect
|
// if in reshape mode, select the curves that intersect
|
||||||
// with the selectionRect
|
// with the selectionRect
|
||||||
if (mode === Modes.RESHAPE) {
|
if (mode === Modes.RESHAPE) {
|
||||||
for (let k = 0; k < intersections.length; k++) {
|
for (let k = 0; k < intersections.length; k++) {
|
||||||
|
@ -389,7 +390,6 @@ const handleRectangularSelectionItems = function (item, event, rect, mode) {
|
||||||
curve.selected = true;
|
curve.selected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (event.modifiers.shift && item.selected) {
|
if (event.modifiers.shift && item.selected) {
|
||||||
setItemSelection(item, false);
|
setItemSelection(item, false);
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -417,9 +417,8 @@ const rectangularSelectionGroupLoop = function (group, rect, root, event, mode)
|
||||||
|
|
||||||
if (isGroup(child) || isCompoundPathItem(child)) {
|
if (isGroup(child) || isCompoundPathItem(child)) {
|
||||||
rectangularSelectionGroupLoop(child, rect, root, event, mode);
|
rectangularSelectionGroupLoop(child, rect, root, event, mode);
|
||||||
|
} else {
|
||||||
} else if (!handleRectangularSelectionItems(child, event, rect, mode)) {
|
handleRectangularSelectionItems(child, event, rect, mode);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -428,20 +427,16 @@ const rectangularSelectionGroupLoop = function (group, rect, root, event, mode)
|
||||||
const processRectangularSelection = function (event, rect, mode) {
|
const processRectangularSelection = function (event, rect, mode) {
|
||||||
const allItems = getAllSelectableItems();
|
const allItems = getAllSelectableItems();
|
||||||
|
|
||||||
itemLoop:
|
|
||||||
for (let i = 0; i < allItems.length; i++) {
|
for (let i = 0; i < allItems.length; i++) {
|
||||||
const item = allItems[i];
|
const item = allItems[i];
|
||||||
if (mode === Modes.RESHAPE && isPGTextItem(getRootItem(item))) {
|
if (mode === Modes.RESHAPE && isPGTextItem(getRootItem(item))) {
|
||||||
continue itemLoop;
|
continue;
|
||||||
}
|
}
|
||||||
// check for item segment points inside selectionRect
|
// check for item segment points inside selectionRect
|
||||||
if (isGroup(item) || isCompoundPathItem(item)) {
|
if (isGroup(item) || isCompoundPathItem(item)) {
|
||||||
if (!rectangularSelectionGroupLoop(item, rect, item, event, mode)) {
|
rectangularSelectionGroupLoop(item, rect, item, event, mode);
|
||||||
continue itemLoop;
|
} else {
|
||||||
}
|
handleRectangularSelectionItems(item, event, rect, mode);
|
||||||
|
|
||||||
} else if (!handleRectangularSelectionItems(item, event, rect, mode)) {
|
|
||||||
continue itemLoop;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -455,23 +450,15 @@ const selectRootItem = function () {
|
||||||
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(cp, true);
|
setItemSelection(cp, true, true /* fullySelected */);
|
||||||
}
|
}
|
||||||
const rootItem = getRootItem(item);
|
const rootItem = getRootItem(item);
|
||||||
if (item !== rootItem) {
|
if (item !== rootItem) {
|
||||||
setItemSelection(item, false);
|
setItemSelection(rootItem, true, true /* fullySelected */);
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
@ -500,7 +487,6 @@ export {
|
||||||
removeSelectedSegments,
|
removeSelectedSegments,
|
||||||
processRectangularSelection,
|
processRectangularSelection,
|
||||||
selectRootItem,
|
selectRootItem,
|
||||||
selectSubItems,
|
|
||||||
shouldShowIfSelection,
|
shouldShowIfSelection,
|
||||||
shouldShowIfSelectionRecursive,
|
shouldShowIfSelectionRecursive,
|
||||||
shouldShowSelectAll
|
shouldShowSelectAll
|
||||||
|
|
Loading…
Reference in a new issue