mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 13:32:28 -05:00
Add more selection files
This commit is contained in:
parent
ef367646fb
commit
448ff9bfe4
23 changed files with 1718 additions and 769 deletions
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||
import PaperCanvas from '../containers/paper-canvas.jsx';
|
||||
import BrushMode from '../containers/brush-mode.jsx';
|
||||
import EraserMode from '../containers/eraser-mode.jsx';
|
||||
import SelectMode from '../containers/select-mode.jsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import LineMode from '../containers/line-mode.jsx';
|
||||
import FillColorIndicatorComponent from '../containers/fill-color-indicator.jsx';
|
||||
|
@ -126,6 +127,9 @@ class PaintEditorComponent extends React.Component {
|
|||
canvas={this.state.canvas}
|
||||
onUpdateSvg={this.props.onUpdateSvg}
|
||||
/>
|
||||
<SelectMode
|
||||
onUpdateSvg={this.props.onUpdateSvg}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
|
19
src/components/select-mode.jsx
Normal file
19
src/components/select-mode.jsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
const SelectModeComponent = props => (
|
||||
<button onClick={props.onMouseDown}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Select"
|
||||
description="Label for the select tool, which allows selecting, moving, and resizing shapes"
|
||||
id="paint.selectMode.select"
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
|
||||
SelectModeComponent.propTypes = {
|
||||
onMouseDown: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectModeComponent;
|
|
@ -23,7 +23,7 @@ const StrokeWidthIndicatorComponent = props => (
|
|||
|
||||
StrokeWidthIndicatorComponent.propTypes = {
|
||||
onChangeStrokeWidth: PropTypes.func.isRequired,
|
||||
strokeWidth: PropTypes.string.isRequired
|
||||
strokeWidth: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export default StrokeWidthIndicatorComponent;
|
||||
|
|
|
@ -3,11 +3,16 @@ import React from 'react';
|
|||
import {connect} from 'react-redux';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import Modes from '../modes/modes';
|
||||
import {clearSelection} from '../reducers/selection';
|
||||
import {setHoveredItem} from '../reducers/hover';
|
||||
import {getHoveredItem} from '../helper/hover';
|
||||
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {setHoveredItem, clearHoveredItem} from '../reducers/hover';
|
||||
|
||||
import {getHoveredItem} from '../helper/hover';
|
||||
import {rectSelect} from '../helper/guides';
|
||||
import {clearSelection, selectRootItem, processRectangularSelection} from '../helper/selection';
|
||||
|
||||
import SelectModeComponent from '../components/select-mode.jsx';
|
||||
import BoundingBoxTool from '../helper/bounding-box/bounding-box-tool';
|
||||
import paper from 'paper';
|
||||
|
||||
class SelectMode extends React.Component {
|
||||
|
@ -33,7 +38,9 @@ class SelectMode extends React.Component {
|
|||
fill: true,
|
||||
guide: false
|
||||
};
|
||||
|
||||
this.boundingBoxTool = new BoundingBoxTool();
|
||||
this.selectionBoxMode = false;
|
||||
this.selectionRect = null;
|
||||
}
|
||||
componentDidMount () {
|
||||
if (this.props.isSelectModeActive) {
|
||||
|
@ -50,47 +57,60 @@ class SelectMode extends React.Component {
|
|||
shouldComponentUpdate () {
|
||||
return false; // Static component, for now
|
||||
}
|
||||
getHitOptions () {
|
||||
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 () {
|
||||
clearSelection();
|
||||
this.preProcessSelection();
|
||||
selectRootItem();
|
||||
this.tool = new paper.Tool();
|
||||
|
||||
|
||||
this.tool.onMouseDown = function (event) {
|
||||
this.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, true /* preselectedOnly */)) {
|
||||
this.selectionBoxMode = true;
|
||||
}
|
||||
};
|
||||
|
||||
this.tool.onMouseMove = function (event) {
|
||||
this.props.setHoveredItem(getHoveredItem(this.getHitOptions()));
|
||||
this.props.setHoveredItem(getHoveredItem(event, this.getHitOptions()));
|
||||
};
|
||||
|
||||
|
||||
this.tool.onMouseDrag = function (event) {
|
||||
this.onMouseDrag(event);
|
||||
if (event.event.button > 0) return; // only first mouse button
|
||||
if (this.selectionBoxMode) {
|
||||
this.selectionRect = rectSelect(event);
|
||||
// Remove this rect on the next drag and up event
|
||||
this.selectionRect.removeOnDrag();
|
||||
} else {
|
||||
this.boundingBoxTool.onMouseDrag(event);
|
||||
}
|
||||
};
|
||||
|
||||
this.tool.onMouseUp = function (event) {
|
||||
this.onMouseUp(event);
|
||||
if (event.event.button > 0) return; // only first mouse button
|
||||
if (this.selectionBoxMode) {
|
||||
processRectangularSelection(event, this.selectionRect);
|
||||
this.selectionRect.remove();
|
||||
} else {
|
||||
this.boundingBoxTool.onMouseUp(event);
|
||||
this.props.onUpdateSvg();
|
||||
}
|
||||
this.selectionBoxMode = false;
|
||||
this.selectionRect = null;
|
||||
};
|
||||
this.tool.activate();
|
||||
}
|
||||
preProcessSelection () {
|
||||
// when switching to the select tool while having a child object of a
|
||||
// compound path selected, deselect the child and select the compound path
|
||||
// instead. (otherwise the compound path breaks because of scale-grouping)
|
||||
const items = this.props.selectedItems;
|
||||
for (let item of items) {
|
||||
if(isCompoundPathChild(item)) {
|
||||
var cp = getItemsCompoundPath(item);
|
||||
setItemSelection(item, false);
|
||||
setItemSelection(cp, true);
|
||||
}
|
||||
};
|
||||
};
|
||||
deactivateTool () {
|
||||
this.props.setHoveredItem();
|
||||
this.tool.remove();
|
||||
|
@ -105,9 +125,11 @@ class SelectMode extends React.Component {
|
|||
}
|
||||
|
||||
SelectMode.propTypes = {
|
||||
clearHoveredItem: PropTypes.func.isRequired,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
isSelectModeActive: PropTypes.bool.isRequired,
|
||||
onUpdateSvg: PropTypes.func.isRequired
|
||||
onUpdateSvg: PropTypes.func.isRequired,
|
||||
setHoveredItem: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
@ -117,6 +139,9 @@ const mapDispatchToProps = dispatch => ({
|
|||
setHoveredItem: hoveredItem => {
|
||||
dispatch(setHoveredItem(hoveredItem));
|
||||
},
|
||||
clearHoveredItem: () => {
|
||||
dispatch(clearHoveredItem());
|
||||
},
|
||||
handleMouseDown: () => {
|
||||
dispatch(changeMode(Modes.SELECT));
|
||||
}
|
||||
|
|
45
src/containers/selection-hov.jsx
Normal file
45
src/containers/selection-hov.jsx
Normal file
|
@ -0,0 +1,45 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import paper from 'paper';
|
||||
|
||||
const SelectionHOV = function (WrappedComponent) {
|
||||
class SelectionComponent extends React.Component {
|
||||
componentDidMount () {
|
||||
if (this.props.hoveredItem) {
|
||||
paper.view.update();
|
||||
}
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.props.hoveredItem && this.props.hoveredItem !== prevProps.hoveredItem) {
|
||||
// A hover item has been added. Update the view
|
||||
paper.view.update();
|
||||
} else if (!this.props.hoveredItem && prevProps.hoveredItem) {
|
||||
// Remove the hover item
|
||||
prevProps.hoveredItem.remove();
|
||||
paper.view.update();
|
||||
}
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
hoveredItem, // eslint-disable-line no-unused-vars
|
||||
...props
|
||||
} = this.props;
|
||||
return (
|
||||
<WrappedComponent {...props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
SelectionComponent.propTypes = {
|
||||
hoveredItem: PropTypes.instanceOf(paper.Item)
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
hoveredItem: state.scratchPaint.hoveredItem
|
||||
});
|
||||
return connect(
|
||||
mapStateToProps
|
||||
)(SelectionComponent);
|
||||
};
|
||||
|
||||
export default SelectionHOV;
|
|
@ -1,326 +0,0 @@
|
|||
import paper from 'paper';
|
||||
|
||||
var mode = 'none';
|
||||
var selectionRect;
|
||||
|
||||
var itemGroup;
|
||||
var pivot;
|
||||
var corner;
|
||||
var origPivot;
|
||||
var origSize;
|
||||
var origCenter;
|
||||
var scaleItems;
|
||||
var scaleItemsInsertBelow;
|
||||
|
||||
var rotItems = [];
|
||||
var rotGroupPivot;
|
||||
var prevRot = [];
|
||||
|
||||
class BoundingBoxTool extends paper.Tool {
|
||||
onMouseDown: If BoundingBoxTool got a hit result, switch to bounding box tool as the primary tool.
|
||||
Else switch to the default tool.
|
||||
|
||||
Where should the move tool be handled? Might make sense on bounding box tool since whenever the bounding
|
||||
box is active, move is possible
|
||||
|
||||
Shift button handling? If you shift click, bounding box tool wants to add it to the selection. But shape tools
|
||||
probably don't.
|
||||
- If shift is held down during mouse click, don't switch to the bounding box tool even if it gets a hit?
|
||||
Then we can decide how to deal with it differently for different modes.
|
||||
|
||||
Alt button handling?
|
||||
- Same as shift?
|
||||
|
||||
|
||||
|
||||
|
||||
onMouseDown (event) {
|
||||
if(event.event.button > 0) return; // only first mouse button
|
||||
clearHoveredItem();
|
||||
|
||||
const hitResults = paper.project.hitTestAll(event.point, this.getHitOptions());
|
||||
// Prefer scale to trigger over rotate, since their regions overlap
|
||||
if (hitResults && hitResults.length > 0) {
|
||||
let hitResult = hitResults[0];
|
||||
for (let i = 0; i < hitResults.length; i++) {
|
||||
if (hitResults[i].item.data && hitResults[i].item.data.isScaleHandle) {
|
||||
hitResult = hitResults[i];
|
||||
this.mode = 'scale';
|
||||
break;
|
||||
} else if (hitResults[i].item.data && hitResults[i].item.data.isRotHandle) {
|
||||
hitResult = hitResults[i];
|
||||
this.mode = 'rotate';
|
||||
}
|
||||
}
|
||||
if (mode === 'rotate') {
|
||||
rotGroupPivot = boundsPath.bounds.center;
|
||||
rotItems = pg.selection.getSelectedItems();
|
||||
|
||||
jQuery.each(rotItems, function(i, item) {
|
||||
prevRot[i] = (event.point - rotGroupPivot).angle;
|
||||
});
|
||||
} else if (mode === 'scale') {
|
||||
var index = hitResult.item.data.index;
|
||||
pivot = boundsPath.bounds[getOpposingRectCornerNameByIndex(index)].clone();
|
||||
origPivot = boundsPath.bounds[getOpposingRectCornerNameByIndex(index)].clone();
|
||||
corner = boundsPath.bounds[getRectCornerNameByIndex(index)].clone();
|
||||
origSize = corner.subtract(pivot);
|
||||
origCenter = boundsPath.bounds.center;
|
||||
scaleItems = pg.selection.getSelectedItems();
|
||||
}
|
||||
else { // Move mode
|
||||
// deselect all by default if the shift key isn't pressed
|
||||
// also needs some special love for compound paths and groups,
|
||||
// as their children are not marked as "selected"
|
||||
// deselect a currently selected item if shift is pressed
|
||||
var root = pg.item.getRootItem(hitResult.item);
|
||||
if(pg.item.isCompoundPathItem(root) || pg.group.isGroup(root)) {
|
||||
if(!root.selected) {
|
||||
if (!event.modifiers.shift) {
|
||||
pg.selection.clearSelection()
|
||||
}
|
||||
root.selected = true;
|
||||
for (var i = 0; i < root.children.length; i++) {
|
||||
root.children[i].selected = true;
|
||||
}
|
||||
jQuery(document).trigger('SelectionChanged');
|
||||
if(event.modifiers.alt) {
|
||||
mode = 'cloneMove';
|
||||
pg.selection.cloneSelection();
|
||||
|
||||
} else {
|
||||
mode = 'move';
|
||||
}
|
||||
} else {
|
||||
if (event.modifiers.shift) {
|
||||
root.selected = false;
|
||||
for (var i = 0; i < root.children.length; i++) {
|
||||
root.children[i].selected = false;
|
||||
}
|
||||
} else {
|
||||
if(event.modifiers.alt) {
|
||||
mode = 'cloneMove';
|
||||
pg.selection.cloneSelection();
|
||||
|
||||
} else {
|
||||
mode = 'move';
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(hitResult.item.selected) {
|
||||
if (event.modifiers.shift) {
|
||||
pg.selection.setItemSelection(hitResult.item, false);
|
||||
} else {
|
||||
if(event.modifiers.alt) {
|
||||
mode = 'cloneMove';
|
||||
pg.selection.cloneSelection();
|
||||
|
||||
} else {
|
||||
mode = 'move';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!event.modifiers.shift) {
|
||||
pg.selection.clearSelection()
|
||||
}
|
||||
pg.selection.setItemSelection(hitResult.item, true);
|
||||
|
||||
if(event.modifiers.alt) {
|
||||
mode = 'cloneMove';
|
||||
pg.selection.cloneSelection();
|
||||
|
||||
} else {
|
||||
mode = 'move';
|
||||
}
|
||||
}
|
||||
}
|
||||
// while transforming object, never show the bounds stuff
|
||||
removeBoundsPath();
|
||||
} else {
|
||||
if (!event.modifiers.shift) {
|
||||
removeBoundsPath();
|
||||
pg.selection.clearSelection();
|
||||
}
|
||||
mode = 'rectSelection';
|
||||
}
|
||||
}
|
||||
onMouseDrag (event) {
|
||||
if(event.event.button > 0) return; // only first mouse button
|
||||
|
||||
var modOrigSize = origSize;
|
||||
|
||||
if(mode == 'rectSelection') {
|
||||
selectionRect = pg.guides.rectSelect(event);
|
||||
// Remove this rect on the next drag and up event
|
||||
selectionRect.removeOnDrag();
|
||||
|
||||
} else if(mode == 'scale') {
|
||||
// get index of scale items
|
||||
var items = paper.project.getItems({
|
||||
'match': function(item) {
|
||||
if (item instanceof Layer) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < scaleItems.length; i++) {
|
||||
if (!scaleItems[i].isBelow(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (items.length > 0) {
|
||||
// Lowest item above all scale items in z index
|
||||
scaleItemsInsertBelow = items[0];
|
||||
}
|
||||
|
||||
itemGroup = new paper.Group(scaleItems);
|
||||
itemGroup.insertBelow(scaleItemsInsertBelow);
|
||||
itemGroup.addChild(boundsPath);
|
||||
itemGroup.data.isHelperItem = true;
|
||||
itemGroup.strokeScaling = false;
|
||||
itemGroup.applyMatrix = false;
|
||||
|
||||
if (event.modifiers.alt) {
|
||||
pivot = origCenter;
|
||||
modOrigSize = origSize*0.5;
|
||||
} else {
|
||||
pivot = origPivot;
|
||||
}
|
||||
|
||||
corner = corner.add(event.delta);
|
||||
var size = corner.subtract(pivot);
|
||||
var sx = 1.0, sy = 1.0;
|
||||
if (Math.abs(modOrigSize.x) > 0.0000001) {
|
||||
sx = size.x / modOrigSize.x;
|
||||
}
|
||||
if (Math.abs(modOrigSize.y) > 0.0000001) {
|
||||
sy = size.y / modOrigSize.y;
|
||||
}
|
||||
|
||||
if (event.modifiers.shift) {
|
||||
var signx = sx > 0 ? 1 : -1;
|
||||
var signy = sy > 0 ? 1 : -1;
|
||||
sx = sy = Math.max(Math.abs(sx), Math.abs(sy));
|
||||
sx *= signx;
|
||||
sy *= signy;
|
||||
}
|
||||
|
||||
itemGroup.scale(sx, sy, pivot);
|
||||
|
||||
jQuery.each(boundsScaleHandles, function(index, handle) {
|
||||
handle.position = itemGroup.bounds[getRectCornerNameByIndex(index)];
|
||||
handle.bringToFront();
|
||||
});
|
||||
|
||||
jQuery.each(boundsRotHandles, function(index, handle) {
|
||||
if(handle) {
|
||||
handle.position = itemGroup.bounds[getRectCornerNameByIndex(index)]+handle.data.offset;
|
||||
handle.bringToFront();
|
||||
}
|
||||
});
|
||||
|
||||
} else if(mode == 'rotate') {
|
||||
var rotAngle = (event.point - rotGroupPivot).angle;
|
||||
|
||||
jQuery.each(rotItems, function(i, item) {
|
||||
|
||||
if(!item.data.origRot) {
|
||||
item.data.origRot = item.rotation;
|
||||
}
|
||||
|
||||
if(event.modifiers.shift) {
|
||||
rotAngle = Math.round(rotAngle / 45) *45;
|
||||
item.applyMatrix = false;
|
||||
item.pivot = rotGroupPivot;
|
||||
item.rotation = rotAngle;
|
||||
|
||||
} else {
|
||||
item.rotate(rotAngle - prevRot[i], rotGroupPivot);
|
||||
}
|
||||
prevRot[i] = rotAngle;
|
||||
});
|
||||
|
||||
} else if(mode == 'move' || mode == 'cloneMove') {
|
||||
|
||||
var dragVector = (event.point - event.downPoint);
|
||||
var selectedItems = pg.selection.getSelectedItems();
|
||||
|
||||
for(var i=0; i<selectedItems.length; i++) {
|
||||
var item = selectedItems[i];
|
||||
// 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 +
|
||||
pg.math.snapDeltaToAngle(dragVector, Math.PI*2/8);
|
||||
|
||||
} else {
|
||||
item.position += event.delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onMouseUp (event) {
|
||||
if(event.event.button > 0) return; // only first mouse button
|
||||
|
||||
if(mode == 'rectSelection' && selectionRect) {
|
||||
pg.selection.processRectangularSelection(event, selectionRect);
|
||||
selectionRect.remove();
|
||||
|
||||
} else if(mode == 'move' || mode == 'cloneMove') {
|
||||
|
||||
// resetting the items origin point for the next usage
|
||||
var selectedItems = pg.selection.getSelectedItems();
|
||||
|
||||
jQuery.each(selectedItems, function(index, item) {
|
||||
// remove the orig pos again
|
||||
item.data.origPos = null;
|
||||
});
|
||||
pg.undo.snapshot('moveSelection');
|
||||
|
||||
} else if(mode == 'scale') {
|
||||
if (itemGroup) {
|
||||
itemGroup.applyMatrix = true;
|
||||
|
||||
// mark text items as scaled (for later use on font size calc)
|
||||
for(var i=0; i<itemGroup.children.length; i++) {
|
||||
var child = itemGroup.children[i];
|
||||
if(child.data.isPGTextItem) {
|
||||
child.data.wasScaled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (scaleItemsInsertBelow) {
|
||||
// No increment step because itemGroup.children is getting depleted
|
||||
for (var i = 0; i < itemGroup.children.length;) {
|
||||
itemGroup.children[i].insertBelow(scaleItemsInsertBelow);
|
||||
}
|
||||
scaleItemsInsertBelow = null;
|
||||
} else if (itemGroup.layer) {
|
||||
itemGroup.layer.addChildren(itemGroup.children);
|
||||
}
|
||||
itemGroup.remove();
|
||||
pg.undo.snapshot('scaleSelection');
|
||||
}
|
||||
|
||||
} else if(mode == 'rotate') {
|
||||
jQuery.each(rotItems, function(i, item) {
|
||||
item.applyMatrix = true;
|
||||
});
|
||||
pg.undo.snapshot('rotateSelection');
|
||||
}
|
||||
|
||||
mode = 'none';
|
||||
selectionRect = null;
|
||||
|
||||
if(pg.selection.getSelectedItems().length <= 0) {
|
||||
removeBoundsPath();
|
||||
} else {
|
||||
setSelectionBounds();
|
||||
}
|
||||
}
|
||||
}
|
181
src/helper/bounding-box/bounding-box-tool.js
Normal file
181
src/helper/bounding-box/bounding-box-tool.js
Normal file
|
@ -0,0 +1,181 @@
|
|||
import paper from 'paper';
|
||||
import keyMirror from 'keymirror';
|
||||
|
||||
import {clearSelection, getSelectedItems} from '../selection';
|
||||
import {getGuideColor, removeHelperItems} from '../guides';
|
||||
import {getGuideLayer} from '../layer';
|
||||
|
||||
import ScaleTool from './scale-tool';
|
||||
import RotateTool from './rotate-tool';
|
||||
import MoveTool from './move-tool';
|
||||
|
||||
/** SVG for the rotation icon on the bounding box */
|
||||
const ARROW_PATH = 'M19.28,1.09C19.28.28,19,0,18.2,0c-1.67,0-3.34,0-5,0-.34,0-.88.24-1,.47a1.4,1.4,0,0,0,.36,1.08,15.27,15.27,0,0,0,1.46,1.36A6.4,6.4,0,0,1,6.52,4,5.85,5.85,0,0,1,5.24,3,15.27,15.27,0,0,0,6.7,1.61,1.4,1.4,0,0,0,7.06.54C7,.3,6.44.07,6.1.06c-1.67,0-3.34,0-5,0C.28,0,0,.31,0,1.12c0,1.67,0,3.34,0,5a1.23,1.23,0,0,0,.49,1,1.22,1.22,0,0,0,1-.31A14.38,14.38,0,0,0,2.84,5.26l.73.62a9.45,9.45,0,0,0,7.34,2,9.45,9.45,0,0,0,4.82-2.05l.73-.62a14.38,14.38,0,0,0,1.29,1.51,1.22,1.22,0,0,0,1,.31,1.23,1.23,0,0,0,.49-1C19.31,4.43,19.29,2.76,19.28,1.09Z';
|
||||
/** Modes of the bounding box tool, which can do many things depending on how it's used. */
|
||||
const Modes = keyMirror({
|
||||
SCALE: null,
|
||||
ROTATE: null,
|
||||
MOVE: null
|
||||
});
|
||||
|
||||
/**
|
||||
* A paper.Tool that handles transforming the selection and drawing a bounding box with handles.
|
||||
* 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
|
||||
* delegates actions to the MoveTool, RotateTool or ScaleTool accordingly.
|
||||
*/
|
||||
class BoundingBoxTool {
|
||||
constructor () {
|
||||
this.mode = null;
|
||||
this.boundsPath = null;
|
||||
this.boundsScaleHandles = [];
|
||||
this.boundsRotHandles = [];
|
||||
this._modeMap = {};
|
||||
this._modeMap[Modes.SCALE] = new ScaleTool();
|
||||
this._modeMap[Modes.ROTATE] = new RotateTool();
|
||||
this._modeMap[Modes.MOVE] = new MoveTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!MouseEvent} event The mouse event
|
||||
* @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)
|
||||
* @param {boolean} preselectedOnly If true, only get hit results on items that are already selected
|
||||
* @return {boolean} True if there was a hit, false otherwise
|
||||
*/
|
||||
onMouseDown (event, clone, multiselect, preselectedOnly) {
|
||||
const hitResults = paper.project.hitTestAll(event.point, this.getHitOptions(preselectedOnly));
|
||||
if (!hitResults || hitResults.length === 0) {
|
||||
if (!multiselect) {
|
||||
this.removeBoundsPath();
|
||||
clearSelection();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Prefer scale to trigger over rotate, and scale and rotate to trigger over other hits
|
||||
let hitResult = hitResults[0];
|
||||
for (let i = 0; i < hitResults.length; i++) {
|
||||
if (hitResults[i].item.data && hitResults[i].item.data.isScaleHandle) {
|
||||
hitResult = hitResults[i];
|
||||
this.mode = Modes.SCALE;
|
||||
this._modeMap[this.mode].onMouseDown(hitResult, this.boundsPath, getSelectedItems());
|
||||
break;
|
||||
} else if (hitResults[i].item.data && hitResults[i].item.data.isRotHandle) {
|
||||
hitResult = hitResults[i];
|
||||
this.mode = Modes.ROTATE;
|
||||
this._modeMap[this.mode].onMouseDown(hitResult, this.boundsPath, getSelectedItems());
|
||||
} else {
|
||||
this.mode = Modes.MOVE;
|
||||
this._modeMap[this.mode].onMouseDown(hitResult, clone, multiselect);
|
||||
}
|
||||
}
|
||||
|
||||
// while transforming object, never show the bounds stuff
|
||||
this.removeBoundsPath();
|
||||
}
|
||||
onMouseDrag (event) {
|
||||
if (event.event.button > 0) return; // only first mouse button
|
||||
this._modeMap[this.mode].onMouseDrag(event);
|
||||
}
|
||||
onMouseUp (event) {
|
||||
if (event.event.button > 0) return; // only first mouse button
|
||||
this._modeMap[this.mode].onMouseUp(event);
|
||||
|
||||
this.mode = null;
|
||||
|
||||
if (getSelectedItems().length <= 0) {
|
||||
this.removeBoundsPath();
|
||||
} else {
|
||||
this.setSelectionBounds();
|
||||
}
|
||||
}
|
||||
setSelectionBounds () {
|
||||
this.removeBoundsPath();
|
||||
|
||||
const items = getSelectedItems();
|
||||
if (items.length <= 0) return;
|
||||
|
||||
let rect = null;
|
||||
for (const item of items) {
|
||||
if (rect) {
|
||||
rect = rect.unite(item.bounds);
|
||||
} else {
|
||||
rect = item.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.boundsPath) {
|
||||
this.boundsPath = new paper.Path.Rectangle(rect);
|
||||
this.boundsPath.curves[0].divideAtTime(0.5);
|
||||
this.boundsPath.curves[2].divideAtTime(0.5);
|
||||
this.boundsPath.curves[4].divideAtTime(0.5);
|
||||
this.boundsPath.curves[6].divideAtTime(0.5);
|
||||
}
|
||||
this.boundsPath.guide = true;
|
||||
this.boundsPath.data.isSelectionBound = true;
|
||||
this.boundsPath.data.isHelperItem = true;
|
||||
this.boundsPath.fillColor = null;
|
||||
this.boundsPath.strokeScaling = false;
|
||||
this.boundsPath.fullySelected = true;
|
||||
this.boundsPath.parent = getGuideLayer();
|
||||
|
||||
for (let index = 0; index < this.boundsPath.segments; index++) {
|
||||
const segment = this.boundsPath.segments[index];
|
||||
let size = 4;
|
||||
|
||||
if (index % 2 === 0) {
|
||||
size = 6;
|
||||
}
|
||||
|
||||
if (index === 7) {
|
||||
const offset = new paper.Point(0, 20);
|
||||
|
||||
const arrows = new paper.Path(ARROW_PATH);
|
||||
arrows.translate(segment.point + offset + [-10.5, -5]);
|
||||
|
||||
const line = new paper.Path.Rectangle(
|
||||
segment.point + offset - [1, 0],
|
||||
segment.point + [1, 0]);
|
||||
|
||||
const rotHandle = arrows.unite(line);
|
||||
line.remove();
|
||||
arrows.remove();
|
||||
rotHandle.scale(1 / paper.view.zoom, segment.point);
|
||||
rotHandle.data = {
|
||||
offset: offset,
|
||||
isRotHandle: true,
|
||||
isHelperItem: true,
|
||||
noSelect: true,
|
||||
noHover: true
|
||||
};
|
||||
rotHandle.fillColor = getGuideColor('blue');
|
||||
rotHandle.parent = getGuideLayer();
|
||||
this.boundsRotHandles[index] = rotHandle;
|
||||
}
|
||||
|
||||
this.boundsScaleHandles[index] =
|
||||
new paper.Path.Rectangle({
|
||||
center: segment.point,
|
||||
data: {
|
||||
index: index,
|
||||
isScaleHandle: true,
|
||||
isHelperItem: true,
|
||||
noSelect: true,
|
||||
noHover: true
|
||||
},
|
||||
size: [size / paper.view.zoom, size / paper.view.zoom],
|
||||
fillColor: getGuideColor('blue'),
|
||||
parent: getGuideLayer()
|
||||
});
|
||||
}
|
||||
}
|
||||
removeBoundsPath () {
|
||||
removeHelperItems();
|
||||
this.boundsPath = null;
|
||||
this.boundsScaleHandles.length = 0;
|
||||
this.boundsRotHandles.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export default BoundingBoxTool;
|
69
src/helper/bounding-box/move-tool.js
Normal file
69
src/helper/bounding-box/move-tool.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {isGroup} from '../group';
|
||||
import {isCompoundPathItem, getRootItem} from '../item';
|
||||
import {snapDeltaToAngle} from '../math';
|
||||
import {clearSelection, cloneSelection, getSelectedItems, setItemSelection, setGroupSelection} 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) {
|
||||
// deselect all by default if multiselect isn't on
|
||||
if (!multiselect) {
|
||||
clearSelection();
|
||||
}
|
||||
// also needs some special love for compound paths and groups,
|
||||
// as their children are not marked as "selected"
|
||||
// deselect a currently selected item if multiselect is on
|
||||
const root = getRootItem(hitResult.item);
|
||||
if (isCompoundPathItem(root) || isGroup(root)) {
|
||||
if (!root.selected) {
|
||||
setGroupSelection(root, true);
|
||||
} else if (multiselect) {
|
||||
setGroupSelection(root, false);
|
||||
}
|
||||
} else if (multiselect && hitResult.item.selected) {
|
||||
setItemSelection(hitResult.item, false);
|
||||
} else {
|
||||
setItemSelection(hitResult.item, true);
|
||||
}
|
||||
if (clone) cloneSelection();
|
||||
this.selectedItems = getSelectedItems();
|
||||
}
|
||||
onMouseDrag (event) {
|
||||
const dragVector = (event.point - 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 +
|
||||
snapDeltaToAngle(dragVector, Math.PI / 4);
|
||||
} else {
|
||||
item.position += event.delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
52
src/helper/bounding-box/rotate-tool.js
Normal file
52
src/helper/bounding-box/rotate-tool.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
class RotateTool {
|
||||
constructor () {
|
||||
this.rotItems = [];
|
||||
this.rotGroupPivot = null;
|
||||
this.prevRot = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!paper.HitResult} hitResult Data about the location of the mouse click
|
||||
* @param {!object} boundsPath Where the boundaries of the hit item are
|
||||
* @param {!Array.<paper.Item>} selectedItems Set of selected paper.Items
|
||||
*/
|
||||
onMouseDown (boundsPath, selectedItems) {
|
||||
this.rotGroupPivot = boundsPath.bounds.center;
|
||||
this.rotItems = selectedItems;
|
||||
|
||||
for (let i = 0; i < this.rotItems.length; i++) {
|
||||
this.prevRot[i] = (event.point - this.rotGroupPivot).angle;
|
||||
}
|
||||
}
|
||||
onMouseDrag (event) {
|
||||
let rotAngle = (event.point - this.rotGroupPivot).angle;
|
||||
|
||||
for (let i = 0; i < this.rotItems.length; i++) {
|
||||
const item = this.rotItems[i];
|
||||
|
||||
if (!item.data.origRot) {
|
||||
item.data.origRot = item.rotation;
|
||||
}
|
||||
|
||||
if (event.modifiers.shift) {
|
||||
rotAngle = Math.round(rotAngle / 45) * 45;
|
||||
item.applyMatrix = false;
|
||||
item.pivot = this.rotGroupPivot;
|
||||
item.rotation = rotAngle;
|
||||
} else {
|
||||
item.rotate(rotAngle - this.prevRot[i], this.rotGroupPivot);
|
||||
}
|
||||
this.prevRot[i] = rotAngle;
|
||||
}
|
||||
}
|
||||
onMouseUp (event) {
|
||||
if (event.event.button > 0) return; // only first mouse button
|
||||
for (const item of this.rotItems) {
|
||||
item.applyMatrix = true;
|
||||
}
|
||||
// @todo add back undo
|
||||
// pg.undo.snapshot('rotateSelection');
|
||||
}
|
||||
}
|
||||
|
||||
export default RotateTool;
|
183
src/helper/bounding-box/scale-tool.js
Normal file
183
src/helper/bounding-box/scale-tool.js
Normal file
|
@ -0,0 +1,183 @@
|
|||
import paper from 'paper';
|
||||
|
||||
class ScaleTool {
|
||||
constructor () {
|
||||
this.pivot = null;
|
||||
this.origPivot = null;
|
||||
this.corner = null;
|
||||
this.origSize = null;
|
||||
this.origCenter = null;
|
||||
this.scaleItems = null;
|
||||
this.itemGroup = null;
|
||||
this.boundsPath = null;
|
||||
// Lowest item above all scale items in z index
|
||||
this.itemToInsertBelow = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!paper.HitResult} hitResult Data about the location of the mouse click
|
||||
* @param {!object} boundsPath Where the boundaries of the hit item are
|
||||
* @param {!Array.<paper.Item>} selectedItems Set of selected paper.Items
|
||||
* @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, boundsPath, selectedItems) {
|
||||
const index = hitResult.item.data.index;
|
||||
this.pivot = this.boundsPath.bounds[this.getOpposingRectCornerNameByIndex(index)].clone();
|
||||
this.origPivot = this.boundsPath.bounds[this.getOpposingRectCornerNameByIndex(index)].clone();
|
||||
this.corner = this.boundsPath.bounds[this.getRectCornerNameByIndex(index)].clone();
|
||||
this.origSize = this.corner.subtract(this.pivot);
|
||||
this.origCenter = this.boundsPath.bounds.center;
|
||||
this.boundsPath = boundsPath;
|
||||
this.scaleItems = selectedItems;
|
||||
}
|
||||
onMouseDrag (event) {
|
||||
const modOrigSize = this.origSize;
|
||||
|
||||
// get item to insert below so that scaled items stay in same z position
|
||||
const items = paper.project.getItems({
|
||||
match: function (item) {
|
||||
if (item instanceof paper.Layer) {
|
||||
return false;
|
||||
}
|
||||
for (const scaleItem of this.scaleItems) {
|
||||
if (!scaleItem.isBelow(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (items.length > 0) {
|
||||
this.itemToInsertBelow = items[0];
|
||||
}
|
||||
|
||||
this.itemGroup = new paper.Group(this.scaleItems);
|
||||
this.itemGroup.insertBelow(this.itemToInsertBelow);
|
||||
this.itemGroup.addChild(this.boundsPath);
|
||||
this.itemGroup.data.isHelperItem = true;
|
||||
this.itemGroup.strokeScaling = false;
|
||||
this.itemGroup.applyMatrix = false;
|
||||
|
||||
if (event.modifiers.alt) {
|
||||
this.pivot = this.origCenter;
|
||||
this.modOrigSize = this.origSize * 0.5;
|
||||
} else {
|
||||
this.pivot = this.origPivot;
|
||||
}
|
||||
|
||||
this.corner = this.corner.add(event.delta);
|
||||
const size = this.corner.subtract(this.pivot);
|
||||
let sx = 1.0;
|
||||
let sy = 1.0;
|
||||
if (Math.abs(modOrigSize.x) > 0.0000001) {
|
||||
sx = size.x / modOrigSize.x;
|
||||
}
|
||||
if (Math.abs(modOrigSize.y) > 0.0000001) {
|
||||
sy = size.y / modOrigSize.y;
|
||||
}
|
||||
|
||||
if (event.modifiers.shift) {
|
||||
const signx = sx > 0 ? 1 : -1;
|
||||
const signy = sy > 0 ? 1 : -1;
|
||||
sx = sy = Math.max(Math.abs(sx), Math.abs(sy));
|
||||
sx *= signx;
|
||||
sy *= signy;
|
||||
}
|
||||
|
||||
this.itemGroup.scale(sx, sy, this.pivot);
|
||||
|
||||
for (let i = 0; i < this.boundsScaleHandles.length; i++) {
|
||||
const handle = this.boundsScaleHandles[i];
|
||||
handle.position = this.itemGroup.bounds[this.getRectCornerNameByIndex(i)];
|
||||
handle.bringToFront();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.boundsRotHandles.length; i++) {
|
||||
const handle = this.boundsRotHandles[i];
|
||||
if (handle) {
|
||||
handle.position = this.itemGroup.bounds[this.getRectCornerNameByIndex(i)] + handle.data.offset;
|
||||
handle.bringToFront();
|
||||
}
|
||||
}
|
||||
}
|
||||
onMouseUp () {
|
||||
this.pivot = null;
|
||||
this.origPivot = null;
|
||||
this.corner = null;
|
||||
this.origSize = null;
|
||||
this.origCenter = null;
|
||||
this.scaleItems = null;
|
||||
this.boundsPath = null;
|
||||
|
||||
if (!this.itemGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.itemGroup.applyMatrix = true;
|
||||
|
||||
// mark text items as scaled (for later use on font size calc)
|
||||
for (let i = 0; i < this.itemGroup.children.length; i++) {
|
||||
const child = this.itemGroup.children[i];
|
||||
if (child.data.isPGTextItem) {
|
||||
child.data.wasScaled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.itemToInsertBelow) {
|
||||
// No increment step because itemGroup.children is getting depleted
|
||||
for (const i = 0; i < this.itemGroup.children.length;) {
|
||||
this.itemGroup.children[i].insertBelow(this.itemToInsertBelow);
|
||||
}
|
||||
this.itemToInsertBelow = null;
|
||||
} else if (this.itemGroup.layer) {
|
||||
this.itemGroup.layer.addChildren(this.itemGroup.children);
|
||||
}
|
||||
this.itemGroup.remove();
|
||||
|
||||
// @todo add back undo
|
||||
// pg.undo.snapshot('scaleSelection');
|
||||
}
|
||||
getRectCornerNameByIndex (index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return 'bottomLeft';
|
||||
case 1:
|
||||
return 'leftCenter';
|
||||
case 2:
|
||||
return 'topLeft';
|
||||
case 3:
|
||||
return 'topCenter';
|
||||
case 4:
|
||||
return 'topRight';
|
||||
case 5:
|
||||
return 'rightCenter';
|
||||
case 6:
|
||||
return 'bottomRight';
|
||||
case 7:
|
||||
return 'bottomCenter';
|
||||
}
|
||||
}
|
||||
getOpposingRectCornerNameByIndex (index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return 'topRight';
|
||||
case 1:
|
||||
return 'rightCenter';
|
||||
case 2:
|
||||
return 'bottomRight';
|
||||
case 3:
|
||||
return 'bottomCenter';
|
||||
case 4:
|
||||
return 'bottomLeft';
|
||||
case 5:
|
||||
return 'leftCenter';
|
||||
case 6:
|
||||
return 'topLeft';
|
||||
case 7:
|
||||
return 'topCenter';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ScaleTool;
|
78
src/helper/compound-path.js
Normal file
78
src/helper/compound-path.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
const isCompoundPath = function (item) {
|
||||
return item && item.className === 'CompoundPath';
|
||||
};
|
||||
|
||||
const isCompoundPathChild = function (item) {
|
||||
if (item.parent) {
|
||||
return item.parent.className === 'CompoundPath';
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
const getItemsCompoundPath = function (item) {
|
||||
const itemParent = item.parent;
|
||||
|
||||
if (isCompoundPath(itemParent)) {
|
||||
return itemParent;
|
||||
}
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
|
||||
// const createFromSelection = function () {
|
||||
// const items = getSelectedPaths();
|
||||
// if (items.length < 2) return;
|
||||
|
||||
// const path = new paper.CompoundPath({fillRule: 'evenodd'});
|
||||
|
||||
// for (let i = 0; i < items.length; i++) {
|
||||
// path.addChild(items[i]);
|
||||
// items[i].selected = false;
|
||||
// }
|
||||
|
||||
// path = pg.stylebar.applyActiveToolbarStyle(path);
|
||||
|
||||
// pg.selection.setItemSelection(path, true);
|
||||
// pg.undo.snapshot('createCompoundPathFromSelection');
|
||||
// };
|
||||
|
||||
|
||||
// const releaseSelection = function () {
|
||||
// const items = pg.selection.getSelectedItems();
|
||||
|
||||
// const cPathsToDelete = [];
|
||||
// for (const i=0; i<items.length; i++) {
|
||||
// const item = items[i];
|
||||
|
||||
// if (isCompoundPath(item)) {
|
||||
|
||||
// for (const j=0; j<item.children.length; j++) {
|
||||
// const path = item.children[j];
|
||||
// path.parent = item.layer;
|
||||
// pg.selection.setItemSelection(path, true);
|
||||
// j--;
|
||||
// }
|
||||
// cPathsToDelete.push(item);
|
||||
// pg.selection.setItemSelection(item, false);
|
||||
|
||||
// } else {
|
||||
// items[i].parent = item.layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (const j=0; j<cPathsToDelete.length; j++) {
|
||||
// cPathsToDelete[j].remove();
|
||||
// }
|
||||
// pg.undo.snapshot('releaseCompoundPath');
|
||||
// };
|
||||
|
||||
|
||||
export {
|
||||
isCompoundPath,
|
||||
isCompoundPathChild,
|
||||
getItemsCompoundPath
|
||||
// createFromSelection,
|
||||
// releaseSelection
|
||||
};
|
|
@ -1,86 +0,0 @@
|
|||
|
||||
|
||||
pg.compoundPath = function() {
|
||||
|
||||
|
||||
var isCompoundPath = function(item) {
|
||||
return item && item.className === 'CompoundPath';
|
||||
};
|
||||
|
||||
var isCompoundPathChild = function(item) {
|
||||
if(item.parent) {
|
||||
return item.parent.className === 'CompoundPath';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var getItemsCompoundPath = function(item) {
|
||||
var itemParent = item.parent;
|
||||
|
||||
if(isCompoundPath(itemParent)) {
|
||||
return itemParent;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
var createFromSelection = function() {
|
||||
var items = pg.selection.getSelectedPaths();
|
||||
if(items.length < 2) return;
|
||||
|
||||
var path = new paper.CompoundPath({fillRule: 'evenodd'});
|
||||
|
||||
for(var i=0; i<items.length; i++) {
|
||||
path.addChild(items[i]);
|
||||
items[i].selected = false;
|
||||
}
|
||||
|
||||
path = pg.stylebar.applyActiveToolbarStyle(path);
|
||||
|
||||
pg.selection.setItemSelection(path, true);
|
||||
pg.undo.snapshot('createCompoundPathFromSelection');
|
||||
};
|
||||
|
||||
|
||||
var releaseSelection = function() {
|
||||
var items = pg.selection.getSelectedItems();
|
||||
|
||||
var cPathsToDelete = [];
|
||||
for(var i=0; i<items.length; i++) {
|
||||
var item = items[i];
|
||||
|
||||
if(isCompoundPath(item)) {
|
||||
|
||||
for(var j=0; j<item.children.length; j++) {
|
||||
var path = item.children[j];
|
||||
path.parent = item.layer;
|
||||
pg.selection.setItemSelection(path, true);
|
||||
j--;
|
||||
}
|
||||
cPathsToDelete.push(item);
|
||||
pg.selection.setItemSelection(item, false);
|
||||
|
||||
} else {
|
||||
items[i].parent = item.layer;
|
||||
}
|
||||
}
|
||||
|
||||
for(var j=0; j<cPathsToDelete.length; j++) {
|
||||
cPathsToDelete[j].remove();
|
||||
}
|
||||
pg.undo.snapshot('releaseCompoundPath');
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
isCompoundPath: isCompoundPath,
|
||||
isCompoundPathChild: isCompoundPathChild,
|
||||
getItemsCompoundPath: getItemsCompoundPath,
|
||||
createFromSelection: createFromSelection,
|
||||
releaseSelection: releaseSelection
|
||||
};
|
||||
}();
|
|
@ -1,139 +1,132 @@
|
|||
// function related to groups and grouping
|
||||
import paper from 'paper';
|
||||
import {getRootItem, isGroupItem} from './item';
|
||||
import {clearSelection, getSelectedItems, setItemSelection} from './selection';
|
||||
|
||||
pg.group = function() {
|
||||
const isGroup = function (item) {
|
||||
return isGroupItem(item);
|
||||
};
|
||||
|
||||
var groupSelection = function() {
|
||||
var items = pg.selection.getSelectedItems();
|
||||
if(items.length > 0) {
|
||||
var group = new paper.Group(items);
|
||||
pg.selection.clearSelection();
|
||||
pg.selection.setItemSelection(group, true);
|
||||
for (var i = 0; i < group.children.length; i++) {
|
||||
group.children[i].selected = true;
|
||||
}
|
||||
pg.undo.snapshot('groupSelection');
|
||||
jQuery(document).trigger('Grouped');
|
||||
return group;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var ungroupSelection = function() {
|
||||
var items = pg.selection.getSelectedItems();
|
||||
ungroupItems(items);
|
||||
pg.statusbar.update();
|
||||
};
|
||||
|
||||
|
||||
var groupItems = function(items) {
|
||||
if(items.length > 0) {
|
||||
var group = new paper.Group(items);
|
||||
jQuery(document).trigger('Grouped');
|
||||
pg.undo.snapshot('groupItems');
|
||||
return group;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const groupSelection = function () {
|
||||
const items = getSelectedItems();
|
||||
if (items.length > 0) {
|
||||
const group = new paper.Group(items);
|
||||
clearSelection();
|
||||
setItemSelection(group, true);
|
||||
for (let i = 0; i < group.children.length; i++) {
|
||||
group.children[i].selected = true;
|
||||
}
|
||||
// jQuery(document).trigger('Grouped');
|
||||
// @todo add back undo
|
||||
// pg.undo.snapshot('groupSelection');
|
||||
return group;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const ungroupLoop = function (group, recursive) {
|
||||
// don't ungroup items that are not groups
|
||||
if (!group || !group.children || !isGroup(group)) return;
|
||||
|
||||
group.applyMatrix = true;
|
||||
// iterate over group children recursively
|
||||
for (let i = 0; i < group.children.length; i++) {
|
||||
const groupChild = group.children[i];
|
||||
if (groupChild.hasChildren()) {
|
||||
// recursion (groups can contain groups, ie. from SVG import)
|
||||
if (recursive) {
|
||||
ungroupLoop(groupChild, true /* recursive */);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
groupChild.applyMatrix = true;
|
||||
// move items from the group to the activeLayer (ungrouping)
|
||||
groupChild.insertBelow(group);
|
||||
groupChild.selected = true;
|
||||
i--;
|
||||
}
|
||||
};
|
||||
|
||||
// ungroup items (only top hierarchy)
|
||||
const ungroupItems = function (items) {
|
||||
clearSelection();
|
||||
const emptyGroups = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (isGroup(item) && !item.data.isPGTextItem) {
|
||||
ungroupLoop(item, false /* recursive */);
|
||||
|
||||
if (!item.hasChildren()) {
|
||||
emptyGroups.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove all empty groups after ungrouping
|
||||
for (let j = 0; j < emptyGroups.length; j++) {
|
||||
emptyGroups[j].remove();
|
||||
}
|
||||
// jQuery(document).trigger('Ungrouped');
|
||||
// @todo add back undo
|
||||
// pg.undo.snapshot('ungroupItems');
|
||||
};
|
||||
|
||||
const ungroupSelection = function () {
|
||||
const items = getSelectedItems();
|
||||
ungroupItems(items);
|
||||
|
||||
// pg.statusbar.update();
|
||||
};
|
||||
|
||||
|
||||
// ungroup items (only top hierarchy)
|
||||
var ungroupItems = function(items) {
|
||||
pg.selection.clearSelection();
|
||||
var emptyGroups = [];
|
||||
for(var i=0; i<items.length; i++) {
|
||||
var item = items[i];
|
||||
if(isGroup(item) && !item.data.isPGTextItem) {
|
||||
ungroupLoop(item, false /* recursive */);
|
||||
const groupItems = function (items) {
|
||||
if (items.length > 0) {
|
||||
const group = new paper.Group(items);
|
||||
// jQuery(document).trigger('Grouped');
|
||||
// @todo add back undo
|
||||
// pg.undo.snapshot('groupItems');
|
||||
return group;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if(!item.hasChildren()) {
|
||||
emptyGroups.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
const getItemsGroup = function (item) {
|
||||
const itemParent = item.parent;
|
||||
|
||||
// remove all empty groups after ungrouping
|
||||
for(var j=0; j<emptyGroups.length; j++) {
|
||||
emptyGroups[j].remove();
|
||||
}
|
||||
jQuery(document).trigger('Ungrouped');
|
||||
pg.undo.snapshot('ungroupItems');
|
||||
};
|
||||
if (isGroup(itemParent)) {
|
||||
return itemParent;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const isGroupChild = function (item) {
|
||||
const rootItem = getRootItem(item);
|
||||
return isGroup(rootItem);
|
||||
};
|
||||
|
||||
var ungroupLoop = function(group, recursive) {
|
||||
// don't ungroup items that are not groups
|
||||
if(!group || !group.children || !isGroup(group)) return;
|
||||
|
||||
group.applyMatrix = true;
|
||||
// iterate over group children recursively
|
||||
for(var i=0; i<group.children.length; i++) {
|
||||
var groupChild = group.children[i];
|
||||
if(groupChild.hasChildren()) {
|
||||
// recursion (groups can contain groups, ie. from SVG import)
|
||||
if(recursive) {
|
||||
ungroupLoop(groupChild, true /* recursive */);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
groupChild.applyMatrix = true;
|
||||
// move items from the group to the activeLayer (ungrouping)
|
||||
groupChild.insertBelow(group);
|
||||
groupChild.selected = true;
|
||||
i--;
|
||||
}
|
||||
};
|
||||
const shouldShowGroup = function () {
|
||||
const items = getSelectedItems();
|
||||
return items.length > 1;
|
||||
};
|
||||
|
||||
const shouldShowUngroup = function () {
|
||||
const items = getSelectedItems();
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (isGroup(item) && !item.data.isPGTextItem && item.children && item.children.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var getItemsGroup = function(item) {
|
||||
var itemParent = item.parent;
|
||||
|
||||
if(isGroup(itemParent)) {
|
||||
return itemParent;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var isGroup = function(item) {
|
||||
return pg.item.isGroupItem(item);
|
||||
};
|
||||
|
||||
|
||||
var isGroupChild = function(item) {
|
||||
var rootItem = pg.item.getRootItem(item);
|
||||
return isGroup(rootItem);
|
||||
};
|
||||
|
||||
var shouldShowGroup = function() {
|
||||
var items = pg.selection.getSelectedItems();
|
||||
return items.length > 1;
|
||||
};
|
||||
|
||||
var shouldShowUngroup = function() {
|
||||
var items = pg.selection.getSelectedItems();
|
||||
for(var i=0; i<items.length; i++) {
|
||||
var item = items[i];
|
||||
if(isGroup(item) && !item.data.isPGTextItem && item.children && item.children.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return {
|
||||
groupSelection: groupSelection,
|
||||
ungroupSelection: ungroupSelection,
|
||||
groupItems: groupItems,
|
||||
ungroupItems: ungroupItems,
|
||||
getItemsGroup: getItemsGroup,
|
||||
isGroup: isGroup,
|
||||
isGroupChild:isGroupChild,
|
||||
shouldShowGroup:shouldShowGroup,
|
||||
shouldShowUngroup:shouldShowUngroup
|
||||
};
|
||||
|
||||
}();
|
||||
export {
|
||||
groupSelection,
|
||||
ungroupSelection,
|
||||
groupItems,
|
||||
ungroupItems,
|
||||
getItemsGroup,
|
||||
isGroup,
|
||||
isGroupChild,
|
||||
shouldShowGroup,
|
||||
shouldShowUngroup
|
||||
};
|
||||
|
|
|
@ -1,184 +1,172 @@
|
|||
// functions related to guide items
|
||||
import paper from 'paper';
|
||||
import {getGuideLayer} from './layer';
|
||||
import {removePaperItemsByTags, removePaperItemsByDataTags} from './helper';
|
||||
|
||||
pg.guides = function() {
|
||||
|
||||
var guideBlue = '#009dec';
|
||||
var guideGrey = '#aaaaaa';
|
||||
|
||||
var hoverItem = function(hitResult) {
|
||||
var segments = hitResult.item.segments;
|
||||
var clone = new paper.Path(segments);
|
||||
setDefaultGuideStyle(clone);
|
||||
if(hitResult.item.closed) {
|
||||
clone.closed = true;
|
||||
}
|
||||
clone.parent = pg.layer.getGuideLayer();
|
||||
clone.strokeColor = guideBlue;
|
||||
clone.fillColor = null;
|
||||
clone.data.isHelperItem = true;
|
||||
clone.bringToFront();
|
||||
const GUIDE_BLUE = '#009dec';
|
||||
const GUIDE_GREY = '#aaaaaa';
|
||||
|
||||
return clone;
|
||||
};
|
||||
|
||||
|
||||
var hoverBounds = function(item) {
|
||||
var rect = new paper.Path.Rectangle(item.internalBounds);
|
||||
rect.matrix = item.matrix;
|
||||
setDefaultGuideStyle(rect);
|
||||
rect.parent = pg.layer.getGuideLayer();
|
||||
rect.strokeColor = guideBlue;
|
||||
rect.fillColor = null;
|
||||
rect.data.isHelperItem = true;
|
||||
rect.bringToFront();
|
||||
const setDefaultGuideStyle = function (item) {
|
||||
item.strokeWidth = 1 / paper.view.zoom;
|
||||
item.opacity = 1;
|
||||
item.blendMode = 'normal';
|
||||
item.guide = true;
|
||||
};
|
||||
|
||||
return rect;
|
||||
};
|
||||
|
||||
|
||||
var rectSelect = function(event, color) {
|
||||
var half = new paper.Point(0.5 / paper.view.zoom, 0.5 / paper.view.zoom);
|
||||
var start = event.downPoint.add(half);
|
||||
var end = event.point.add(half);
|
||||
var rect = new paper.Path.Rectangle(start, end);
|
||||
var zoom = 1.0/paper.view.zoom;
|
||||
setDefaultGuideStyle(rect);
|
||||
if(!color) color = guideGrey;
|
||||
rect.parent = pg.layer.getGuideLayer();
|
||||
rect.strokeColor = color;
|
||||
rect.data.isRectSelect = true;
|
||||
rect.data.isHelperItem = true;
|
||||
rect.dashArray = [3.0*zoom, 3.0*zoom];
|
||||
return rect;
|
||||
};
|
||||
|
||||
|
||||
var line = function(from, to, color) {
|
||||
var line = new paper.Path.Line(from, to);
|
||||
var zoom = 1/paper.view.zoom;
|
||||
setDefaultGuideStyle(line);
|
||||
if (!color) color = guideGrey;
|
||||
line.parent = pg.layer.getGuideLayer();
|
||||
line.strokeColor = color;
|
||||
line.strokeColor = color;
|
||||
line.dashArray = [5*zoom, 5*zoom];
|
||||
line.data.isHelperItem = true;
|
||||
return line;
|
||||
};
|
||||
const hoverItem = function (hitResult) {
|
||||
const segments = hitResult.item.segments;
|
||||
const clone = new paper.Path(segments);
|
||||
setDefaultGuideStyle(clone);
|
||||
if (hitResult.item.closed) {
|
||||
clone.closed = true;
|
||||
}
|
||||
clone.parent = getGuideLayer();
|
||||
clone.strokeColor = GUIDE_BLUE;
|
||||
clone.fillColor = null;
|
||||
clone.data.isHelperItem = true;
|
||||
clone.bringToFront();
|
||||
|
||||
return clone;
|
||||
};
|
||||
|
||||
const hoverBounds = function (item) {
|
||||
const rect = new paper.Path.Rectangle(item.internalBounds);
|
||||
rect.matrix = item.matrix;
|
||||
setDefaultGuideStyle(rect);
|
||||
rect.parent = getGuideLayer();
|
||||
rect.strokeColor = GUIDE_BLUE;
|
||||
rect.fillColor = null;
|
||||
rect.data.isHelperItem = true;
|
||||
rect.bringToFront();
|
||||
|
||||
return rect;
|
||||
};
|
||||
|
||||
const rectSelect = function (event, color) {
|
||||
const half = new paper.Point(0.5 / paper.view.zoom, 0.5 / paper.view.zoom);
|
||||
const start = event.downPoint.add(half);
|
||||
const end = event.point.add(half);
|
||||
const rect = new paper.Path.Rectangle(start, end);
|
||||
const zoom = 1.0 / paper.view.zoom;
|
||||
setDefaultGuideStyle(rect);
|
||||
if (!color) color = GUIDE_GREY;
|
||||
rect.parent = getGuideLayer();
|
||||
rect.strokeColor = color;
|
||||
rect.data.isRectSelect = true;
|
||||
rect.data.isHelperItem = true;
|
||||
rect.dashArray = [3.0 * zoom, 3.0 * zoom];
|
||||
return rect;
|
||||
};
|
||||
|
||||
const line = function (from, to, color) {
|
||||
const theLine = new paper.Path.Line(from, to);
|
||||
const zoom = 1 / paper.view.zoom;
|
||||
setDefaultGuideStyle(theLine);
|
||||
if (!color) color = GUIDE_GREY;
|
||||
theLine.parent = getGuideLayer();
|
||||
theLine.strokeColor = color;
|
||||
theLine.strokeColor = color;
|
||||
theLine.dashArray = [5 * zoom, 5 * zoom];
|
||||
theLine.data.isHelperItem = true;
|
||||
return theLine;
|
||||
};
|
||||
|
||||
const crossPivot = function (center, color) {
|
||||
const zoom = 1 / paper.view.zoom;
|
||||
const star = new paper.Path.Star(center, 4, 4 * zoom, 0.5 * zoom);
|
||||
setDefaultGuideStyle(star);
|
||||
if (!color) color = GUIDE_BLUE;
|
||||
star.parent = getGuideLayer();
|
||||
star.fillColor = color;
|
||||
star.strokeColor = color;
|
||||
star.strokeWidth = 0.5 * zoom;
|
||||
star.data.isHelperItem = true;
|
||||
star.rotate(45);
|
||||
|
||||
return star;
|
||||
};
|
||||
|
||||
const rotPivot = function (center, color) {
|
||||
const zoom = 1 / paper.view.zoom;
|
||||
const path = new paper.Path.Circle(center, 3 * zoom);
|
||||
setDefaultGuideStyle(path);
|
||||
if (!color) color = GUIDE_BLUE;
|
||||
path.parent = getGuideLayer();
|
||||
path.fillColor = color;
|
||||
path.data.isHelperItem = true;
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
const label = function (pos, content, color) {
|
||||
const text = new paper.PointText(pos);
|
||||
if (!color) color = GUIDE_GREY;
|
||||
text.parent = getGuideLayer();
|
||||
text.fillColor = color;
|
||||
text.content = content;
|
||||
};
|
||||
|
||||
const getGuideColor = function (colorName) {
|
||||
if (colorName === 'blue') {
|
||||
return GUIDE_BLUE;
|
||||
} else if (colorName === 'grey') {
|
||||
return GUIDE_GREY;
|
||||
}
|
||||
};
|
||||
|
||||
const getAllGuides = function () {
|
||||
const allItems = [];
|
||||
for (let i = 0; i < paper.project.layers.length; i++) {
|
||||
const layer = paper.project.layers[i];
|
||||
for (let j = 0; j < layer.children.length; j++) {
|
||||
const child = layer.children[j];
|
||||
// only give guides
|
||||
if (!child.guide) {
|
||||
continue;
|
||||
}
|
||||
allItems.push(child);
|
||||
}
|
||||
}
|
||||
return allItems;
|
||||
};
|
||||
|
||||
const getExportRectGuide = function () {
|
||||
const guides = getAllGuides();
|
||||
for (let i = 0; i < guides.length; i++){
|
||||
if (guides[i].data && guides[i].data.isExportRect) {
|
||||
return guides[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var crossPivot = function(center, color) {
|
||||
var zoom = 1/paper.view.zoom;
|
||||
var star = new paper.Path.Star(center, 4, 4*zoom, 0.5*zoom);
|
||||
setDefaultGuideStyle(star);
|
||||
if(!color) color = guideBlue;
|
||||
star.parent = pg.layer.getGuideLayer();
|
||||
star.fillColor = color;
|
||||
star.strokeColor = color;
|
||||
star.strokeWidth = 0.5*zoom;
|
||||
star.data.isHelperItem = true;
|
||||
star.rotate(45);
|
||||
const removeHelperItems = function () {
|
||||
removePaperItemsByDataTags(['isHelperItem']);
|
||||
};
|
||||
|
||||
return star;
|
||||
};
|
||||
|
||||
|
||||
var rotPivot = function(center, color) {
|
||||
var zoom = 1/paper.view.zoom;
|
||||
var path = new paper.Path.Circle(center, 3*zoom);
|
||||
setDefaultGuideStyle(path);
|
||||
if(!color) color = guideBlue;
|
||||
path.parent = pg.layer.getGuideLayer();
|
||||
path.fillColor = color;
|
||||
path.data.isHelperItem = true;
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
|
||||
var label = function(pos, content, color) {
|
||||
var text = new paper.PointText(pos);
|
||||
if(!color) color = guideGrey;
|
||||
text.parent = pg.layer.getGuideLayer();
|
||||
text.fillColor = color;
|
||||
text.content = content;
|
||||
};
|
||||
|
||||
|
||||
var setDefaultGuideStyle = function(item) {
|
||||
item.strokeWidth = 1/paper.view.zoom;
|
||||
item.opacity = 1;
|
||||
item.blendMode = 'normal';
|
||||
item.guide = true;
|
||||
};
|
||||
|
||||
|
||||
var getGuideColor = function(colorName) {
|
||||
if(colorName == 'blue') {
|
||||
return guideBlue;
|
||||
} else if(colorName == 'grey') {
|
||||
return guideGrey;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var getAllGuides = function() {
|
||||
var allItems = [];
|
||||
for(var i=0; i<paper.project.layers.length; i++) {
|
||||
var layer = paper.project.layers[i];
|
||||
for(var j=0; j<layer.children.length; j++) {
|
||||
var child = layer.children[j];
|
||||
// only give guides
|
||||
if(!child.guide) {
|
||||
continue;
|
||||
}
|
||||
allItems.push(child);
|
||||
}
|
||||
}
|
||||
return allItems;
|
||||
};
|
||||
|
||||
|
||||
var getExportRectGuide = function() {
|
||||
var guides = getAllGuides();
|
||||
for(var i=0; i<guides.length; i++){
|
||||
if(guides[i].data && guides[i].data.isExportRect) {
|
||||
return guides[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var removeHelperItems = function() {
|
||||
pg.helper.removePaperItemsByDataTags(['isHelperItem']);
|
||||
};
|
||||
|
||||
|
||||
var removeAllGuides = function() {
|
||||
pg.helper.removePaperItemsByTags(['guide']);
|
||||
};
|
||||
|
||||
|
||||
var removeExportRectGuide = function() {
|
||||
pg.helper.removePaperItemsByDataTags(['isExportRect']);
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
hoverItem: hoverItem,
|
||||
hoverBounds: hoverBounds,
|
||||
rectSelect: rectSelect,
|
||||
line: line,
|
||||
crossPivot: crossPivot,
|
||||
rotPivot: rotPivot,
|
||||
label: label,
|
||||
removeAllGuides: removeAllGuides,
|
||||
removeHelperItems: removeHelperItems,
|
||||
removeExportRectGuide: removeExportRectGuide,
|
||||
getAllGuides: getAllGuides,
|
||||
getExportRectGuide: getExportRectGuide,
|
||||
getGuideColor: getGuideColor,
|
||||
setDefaultGuideStyle:setDefaultGuideStyle
|
||||
};
|
||||
const removeAllGuides = function () {
|
||||
removePaperItemsByTags(['guide']);
|
||||
};
|
||||
|
||||
}();
|
||||
|
||||
const removeExportRectGuide = function () {
|
||||
removePaperItemsByDataTags(['isExportRect']);
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
hoverItem,
|
||||
hoverBounds,
|
||||
rectSelect,
|
||||
line,
|
||||
crossPivot,
|
||||
rotPivot,
|
||||
label,
|
||||
removeAllGuides,
|
||||
removeHelperItems,
|
||||
removeExportRectGuide,
|
||||
getAllGuides,
|
||||
getExportRectGuide,
|
||||
getGuideColor,
|
||||
setDefaultGuideStyle
|
||||
};
|
||||
|
|
61
src/helper/helper.js
Normal file
61
src/helper/helper.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
import paper from 'paper';
|
||||
|
||||
const getAllPaperItems = function (includeGuides) {
|
||||
includeGuides = includeGuides || false;
|
||||
const allItems = [];
|
||||
for (const layer of paper.project.layers) {
|
||||
for (const child of layer.children) {
|
||||
// don't give guides back
|
||||
if (!includeGuides && child.guide) {
|
||||
continue;
|
||||
}
|
||||
allItems.push(child);
|
||||
}
|
||||
}
|
||||
return allItems;
|
||||
};
|
||||
|
||||
const getPaperItemsByTags = function (tags) {
|
||||
const allItems = getAllPaperItems(true);
|
||||
const foundItems = [];
|
||||
for (const item of allItems) {
|
||||
for (const tag of tags) {
|
||||
if (item[tag] && foundItems.indexOf(item) === -1) {
|
||||
foundItems.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundItems;
|
||||
};
|
||||
|
||||
|
||||
const removePaperItemsByDataTags = function (tags) {
|
||||
const allItems = getAllPaperItems(true);
|
||||
for (const item of allItems) {
|
||||
for (const tag of tags) {
|
||||
if (item.data && item.data[tag]) {
|
||||
item.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const removePaperItemsByTags = function (tags) {
|
||||
const allItems = getAllPaperItems(true);
|
||||
for (const item of allItems) {
|
||||
for (const tag of tags) {
|
||||
if (item[tag]) {
|
||||
item.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
getAllPaperItems,
|
||||
getPaperItemsByTags,
|
||||
removePaperItemsByDataTags,
|
||||
removePaperItemsByTags
|
||||
};
|
|
@ -1,12 +1,14 @@
|
|||
import paper from 'paper';
|
||||
import {isBoundsItem, getRootItem} from './item';
|
||||
import {hoverBounds, hoverItem} from './guides';
|
||||
import {isGroupChild} from './group';
|
||||
|
||||
const CLEAR_HOVERED_ITEM = 'scratch-paint/hover/CLEAR_HOVERED_ITEM';
|
||||
/**
|
||||
* @param hitOptions hit options to use
|
||||
* @param event mouse event
|
||||
* @return the hovered item or null if there is none
|
||||
* @param {!MouseEvent} event mouse event
|
||||
* @param {?object} hitOptions hit options to use
|
||||
* @return {paper.Item} the hovered item or null if there is none
|
||||
*/
|
||||
const getHoveredItem = function (hitOptions, event) {
|
||||
const getHoveredItem = function (event, hitOptions) {
|
||||
const hitResults = paper.project.hitTestAll(event.point, hitOptions);
|
||||
if (hitResults.length === 0) {
|
||||
return null;
|
||||
|
@ -23,28 +25,14 @@ const getHoveredItem = function (hitOptions, event) {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (pg.item.isBoundsItem(hitResult.item)) {
|
||||
return pg.guides.hoverBounds(hitResult.item);
|
||||
|
||||
} else if(pg.group.isGroupChild(hitResult.item)) {
|
||||
return pg.guides.hoverBounds(pg.item.getRootItem(hitResult.item));
|
||||
|
||||
} else {
|
||||
return pg.guides.hoverItem(hitResult);
|
||||
if (isBoundsItem(hitResult.item)) {
|
||||
return hoverBounds(hitResult.item);
|
||||
} else if (isGroupChild(hitResult.item)) {
|
||||
return hoverBounds(getRootItem(hitResult.item));
|
||||
}
|
||||
return hoverItem(hitResult);
|
||||
};
|
||||
|
||||
|
||||
// Action creators ==================================
|
||||
const clearHoveredItem = function () {
|
||||
return {
|
||||
type: CLEAR_HOVERED_ITEM
|
||||
};
|
||||
// TODO: paper.view.update();
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
getHoveredItem,
|
||||
clearHoveredItem
|
||||
getHoveredItem
|
||||
};
|
||||
|
|
78
src/helper/item.js
Normal file
78
src/helper/item.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
import paper from 'paper';
|
||||
|
||||
const getRootItem = function (item) {
|
||||
if (item.parent.className === 'Layer') {
|
||||
return item;
|
||||
}
|
||||
return getRootItem(item.parent);
|
||||
};
|
||||
|
||||
const isBoundsItem = function (item) {
|
||||
if (item.className === 'PointText' ||
|
||||
item.className === 'Shape' ||
|
||||
item.className === 'PlacedSymbol' ||
|
||||
item.className === 'Raster') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
const isPathItem = function (item) {
|
||||
return item.className === 'Path';
|
||||
};
|
||||
|
||||
|
||||
const isCompoundPathItem = function (item) {
|
||||
return item.className === 'CompoundPath';
|
||||
};
|
||||
|
||||
|
||||
const isGroupItem = function (item) {
|
||||
return item && item.className && item.className === 'Group';
|
||||
};
|
||||
|
||||
|
||||
const isPointTextItem = function (item) {
|
||||
return item.className === 'PointText';
|
||||
};
|
||||
|
||||
|
||||
const isPGTextItem = function (item) {
|
||||
return getRootItem(item).data.isPGTextItem;
|
||||
};
|
||||
|
||||
const setPivot = function (item, point) {
|
||||
if (isBoundsItem(item)) {
|
||||
item.pivot = item.globalToLocal(point);
|
||||
} else {
|
||||
item.pivot = point;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const getPositionInView = function (item) {
|
||||
const itemPos = new paper.Point();
|
||||
itemPos.x = item.position.x - paper.view.bounds.x;
|
||||
itemPos.y = item.position.y - paper.view.bounds.y;
|
||||
return itemPos;
|
||||
};
|
||||
|
||||
|
||||
const setPositionInView = function (item, pos) {
|
||||
item.position.x = paper.view.bounds.x + pos.x;
|
||||
item.position.y = paper.view.bounds.y + pos.y;
|
||||
};
|
||||
|
||||
export {
|
||||
isBoundsItem,
|
||||
isPathItem,
|
||||
isCompoundPathItem,
|
||||
isGroupItem,
|
||||
isPointTextItem,
|
||||
isPGTextItem,
|
||||
setPivot,
|
||||
getPositionInView,
|
||||
setPositionInView,
|
||||
getRootItem
|
||||
};
|
18
src/helper/layer.js
Normal file
18
src/helper/layer.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import paper from 'paper';
|
||||
|
||||
const getGuideLayer = function () {
|
||||
for (let i = 0; i < paper.project.layers.length; i++) {
|
||||
const layer = paper.project.layers[i];
|
||||
if (layer.data && layer.data.isGuideLayer) {
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
|
||||
// Create if it doesn't exist
|
||||
const guideLayer = new paper.Layer();
|
||||
guideLayer.data.isGuideLayer = true;
|
||||
guideLayer.bringToFront();
|
||||
return guideLayer;
|
||||
};
|
||||
|
||||
export default getGuideLayer;
|
36
src/helper/math.js
Normal file
36
src/helper/math.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
import paper from 'paper';
|
||||
|
||||
const checkPointsClose = function (startPos, eventPoint, threshold) {
|
||||
const xOff = Math.abs(startPos.x - eventPoint.x);
|
||||
const yOff = Math.abs(startPos.y - eventPoint.y);
|
||||
if (xOff < threshold && yOff < threshold) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const getRandomInt = function (min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
};
|
||||
|
||||
const getRandomBoolean = function () {
|
||||
return getRandomInt(0, 2) === 1;
|
||||
};
|
||||
|
||||
// Thanks Mikko Mononen! https://github.com/memononen/stylii
|
||||
const snapDeltaToAngle = function (delta, snapAngle) {
|
||||
let angle = Math.atan2(delta.y, delta.x);
|
||||
angle = Math.round(angle / snapAngle) * snapAngle;
|
||||
const dirx = Math.cos(angle);
|
||||
const diry = Math.sin(angle);
|
||||
const d = (dirx * delta.x) + (diry * delta.y);
|
||||
return new paper.Point(dirx * d, diry * d);
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
checkPointsClose,
|
||||
getRandomInt,
|
||||
getRandomBoolean,
|
||||
snapDeltaToAngle
|
||||
};
|
505
src/helper/selection.js
Normal file
505
src/helper/selection.js
Normal file
|
@ -0,0 +1,505 @@
|
|||
import paper from 'paper';
|
||||
import Modes from '../modes/modes';
|
||||
|
||||
import {getAllPaperItems} from './helper';
|
||||
import {getItemsGroup, isGroup} from './group';
|
||||
import {getRootItem, isBoundsItem, isCompoundPathItem, isPathItem, isPGTextItem} from './item';
|
||||
import {getItemsCompoundPath, isCompoundPath, isCompoundPathChild} from './compound-path';
|
||||
|
||||
const getAllSelectableItems = function () {
|
||||
const allItems = getAllPaperItems();
|
||||
const selectables = [];
|
||||
for (let i = 0; i < allItems.length; i++) {
|
||||
if (allItems[i].data && !allItems[i].data.isHelperItem) {
|
||||
selectables.push(allItems[i]);
|
||||
}
|
||||
}
|
||||
return selectables;
|
||||
};
|
||||
|
||||
const selectItemSegments = function (item, state) {
|
||||
if (item.children) {
|
||||
for (let i = 0; i < item.children.length; i++) {
|
||||
const child = item.children[i];
|
||||
if (child.children && child.children.length > 0) {
|
||||
selectItemSegments(child, state);
|
||||
} else {
|
||||
child.fullySelected = state;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < item.segments.length; i++) {
|
||||
item.segments[i].selected = state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setGroupSelection = function (root, selected) {
|
||||
// fully selected segments need to be unselected first
|
||||
root.fullySelected = false;
|
||||
// then the item can be normally selected
|
||||
root.selected = selected;
|
||||
// select children of compound-path or group
|
||||
if (isCompoundPath(root) || isGroup(root)) {
|
||||
const children = root.children;
|
||||
if (children) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
children[i].selected = selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setItemSelection = function (item, state) {
|
||||
const parentGroup = getItemsGroup(item);
|
||||
const itemsCompoundPath = getItemsCompoundPath(item);
|
||||
|
||||
// if selection is in a group, select group not individual items
|
||||
if (parentGroup) {
|
||||
// do it recursive
|
||||
setItemSelection(parentGroup, state);
|
||||
|
||||
} else if (itemsCompoundPath) {
|
||||
setItemSelection(itemsCompoundPath, state);
|
||||
|
||||
} else {
|
||||
if (item.data && item.data.noSelect) {
|
||||
return;
|
||||
}
|
||||
setGroupSelection(item, state);
|
||||
}
|
||||
// pg.statusbar.update();
|
||||
// pg.stylebar.updateFromSelection();
|
||||
// pg.stylebar.blurInputs();
|
||||
|
||||
// jQuery(document).trigger('SelectionChanged');
|
||||
|
||||
};
|
||||
|
||||
const selectAllItems = function () {
|
||||
const items = getAllSelectableItems();
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
setItemSelection(items[i], true);
|
||||
}
|
||||
};
|
||||
|
||||
const selectAllSegments = function () {
|
||||
const items = getAllSelectableItems();
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
selectItemSegments(items[i], true);
|
||||
}
|
||||
};
|
||||
|
||||
const clearSelection = function () {
|
||||
paper.project.deselectAll();
|
||||
|
||||
// pg.statusbar.update();
|
||||
// pg.stylebar.blurInputs();
|
||||
// jQuery(document).trigger('SelectionChanged');
|
||||
};
|
||||
|
||||
// this gets all selected non-grouped items and groups
|
||||
// (alternative to paper.project.selectedItems, which includes
|
||||
// group children in addition to the group)
|
||||
// Returns in increasing Z order
|
||||
const getSelectedItems = function (recursive) {
|
||||
const allItems = paper.project.selectedItems;
|
||||
const itemsAndGroups = [];
|
||||
|
||||
if (recursive) {
|
||||
for (let i = 0; i < allItems.length; i++) {
|
||||
const item = allItems[i];
|
||||
if (item.data && !item.data.isSelectionBound) {
|
||||
itemsAndGroups.push(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < allItems.length; i++) {
|
||||
const item = allItems[i];
|
||||
if ((isGroup(item) && !isGroup(item.parent)) ||
|
||||
!isGroup(item.parent)) {
|
||||
if (item.data && !item.data.isSelectionBound) {
|
||||
itemsAndGroups.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// sort items by index (0 at bottom)
|
||||
itemsAndGroups.sort((a, b) => parseFloat(a.index) - parseFloat(b.index));
|
||||
return itemsAndGroups;
|
||||
};
|
||||
|
||||
const deleteItemSelection = function () {
|
||||
const items = getSelectedItems();
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].remove();
|
||||
}
|
||||
|
||||
// jQuery(document).trigger('DeleteItems');
|
||||
// jQuery(document).trigger('SelectionChanged');
|
||||
paper.project.view.update();
|
||||
// @todo add back undo
|
||||
// pg.undo.snapshot('deleteItemSelection');
|
||||
};
|
||||
|
||||
const removeSelectedSegments = function () {
|
||||
// @todo add back undo
|
||||
// pg.undo.snapshot('removeSelectedSegments');
|
||||
|
||||
const items = getSelectedItems();
|
||||
const segmentsToRemove = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const segments = items[i].segments;
|
||||
for (let j = 0; j < segments.length; j++) {
|
||||
const seg = segments[j];
|
||||
if (seg.selected) {
|
||||
segmentsToRemove.push(seg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let removedSegments = false;
|
||||
for (let i = 0; i < segmentsToRemove.length; i++) {
|
||||
const seg = segmentsToRemove[i];
|
||||
seg.remove();
|
||||
removedSegments = true;
|
||||
}
|
||||
return removedSegments;
|
||||
};
|
||||
|
||||
const deleteSelection = function (mode) {
|
||||
if (mode === Modes.RESHAPE) {
|
||||
// If there are points selected remove them. If not delete the item selected.
|
||||
if (!removeSelectedSegments()) {
|
||||
deleteItemSelection();
|
||||
}
|
||||
} else {
|
||||
deleteItemSelection();
|
||||
}
|
||||
};
|
||||
|
||||
const splitPathRetainSelection = function (path, index, deselectSplitSegments) {
|
||||
const selectedPoints = [];
|
||||
|
||||
// collect points of selected segments, so we can reselect them
|
||||
// once the path is split.
|
||||
for (let i = 0; i < path.segments.length; i++) {
|
||||
const seg = path.segments[i];
|
||||
if (seg.selected) {
|
||||
if (deselectSplitSegments && i === index) {
|
||||
continue;
|
||||
}
|
||||
selectedPoints.push(seg.point);
|
||||
}
|
||||
}
|
||||
|
||||
const newPath = path.split(index, 0);
|
||||
if (!newPath) return;
|
||||
|
||||
// reselect all of the newPaths segments that are in the exact same location
|
||||
// as the ones that are stored in selectedPoints
|
||||
for (let i = 0; i < newPath.segments.length; i++) {
|
||||
const seg = newPath.segments[i];
|
||||
for (let j = 0; j < selectedPoints.length; j++) {
|
||||
const point = selectedPoints[j];
|
||||
if (point.x === seg.point.x && point.y === seg.point.y) {
|
||||
seg.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only do this if path and newPath are different
|
||||
// (split at more than one point)
|
||||
if (path !== newPath) {
|
||||
for (let i = 0; i < path.segments.length; i++) {
|
||||
const seg = path.segments[i];
|
||||
for (let j = 0; j < selectedPoints.length; j++) {
|
||||
const point = selectedPoints[j];
|
||||
if (point.x === seg.point.x && point.y === seg.point.y) {
|
||||
seg.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const splitPathAtSelectedSegments = function () {
|
||||
const items = getSelectedItems();
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const segments = item.segments;
|
||||
for (let j = 0; j < segments.length; j++) {
|
||||
const segment = segments[j];
|
||||
if (segment.selected) {
|
||||
if (item.closed ||
|
||||
(segment.next &&
|
||||
!segment.next.selected &&
|
||||
segment.previous &&
|
||||
!segment.previous.selected)) {
|
||||
splitPathRetainSelection(item, j, true);
|
||||
splitPathAtSelectedSegments();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteSegments = function (item) {
|
||||
if (item.children) {
|
||||
for (let i = 0; i < item.children.length; i++) {
|
||||
const child = item.children[i];
|
||||
deleteSegments(child);
|
||||
}
|
||||
} else {
|
||||
const segments = item.segments;
|
||||
for (let j = 0; j < segments.length; j++) {
|
||||
const segment = segments[j];
|
||||
if (segment.selected) {
|
||||
if (item.closed ||
|
||||
(segment.next &&
|
||||
!segment.next.selected &&
|
||||
segment.previous &&
|
||||
!segment.previous.selected)) {
|
||||
|
||||
splitPathRetainSelection(item, j);
|
||||
deleteSelection();
|
||||
return;
|
||||
|
||||
} else if (!item.closed) {
|
||||
segment.remove();
|
||||
j--; // decrease counter if we removed one from the loop
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove items with no segments left
|
||||
if (item.segments.length <= 0) {
|
||||
item.remove();
|
||||
}
|
||||
};
|
||||
|
||||
const deleteSegmentSelection = function () {
|
||||
|
||||
const items = getSelectedItems();
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
deleteSegments(items[i]);
|
||||
}
|
||||
|
||||
// jQuery(document).trigger('DeleteSegments');
|
||||
// jQuery(document).trigger('SelectionChanged');
|
||||
paper.project.view.update();
|
||||
// @todo add back undo
|
||||
// pg.undo.snapshot('deleteSegmentSelection');
|
||||
};
|
||||
|
||||
const cloneSelection = function () {
|
||||
const selectedItems = getSelectedItems();
|
||||
for (let i = 0; i < selectedItems.length; i++) {
|
||||
const item = selectedItems[i];
|
||||
item.clone();
|
||||
item.selected = false;
|
||||
}
|
||||
// @todo add back undo
|
||||
// pg.undo.snapshot('cloneSelection');
|
||||
};
|
||||
|
||||
// only returns paths, no compound paths, groups or any other stuff
|
||||
const getSelectedPaths = function () {
|
||||
const allPaths = getSelectedItems();
|
||||
const paths = [];
|
||||
|
||||
for (let i = 0; i < allPaths.length; i++) {
|
||||
const path = allPaths[i];
|
||||
if (path.className === 'Path') {
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
};
|
||||
|
||||
const checkBoundsItem = function (selectionRect, item, event) {
|
||||
const itemBounds = new paper.Path([
|
||||
item.localToGlobal(item.internalBounds.topLeft),
|
||||
item.localToGlobal(item.internalBounds.topRight),
|
||||
item.localToGlobal(item.internalBounds.bottomRight),
|
||||
item.localToGlobal(item.internalBounds.bottomLeft)
|
||||
]);
|
||||
itemBounds.closed = true;
|
||||
itemBounds.guide = true;
|
||||
|
||||
for (let i = 0; i < itemBounds.segments.length; i++) {
|
||||
const seg = itemBounds.segments[i];
|
||||
if (selectionRect.contains(seg.point) ||
|
||||
(i === 0 && selectionRect.getIntersections(itemBounds).length > 0)) {
|
||||
if (event.modifiers.shift && item.selected) {
|
||||
setItemSelection(item, false);
|
||||
|
||||
} else {
|
||||
setItemSelection(item, true);
|
||||
}
|
||||
itemBounds.remove();
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
itemBounds.remove();
|
||||
};
|
||||
|
||||
const handleRectangularSelectionItems = function (item, event, rect, mode) {
|
||||
if (isPathItem(item)) {
|
||||
let segmentMode = false;
|
||||
|
||||
// first round checks for segments inside the selectionRect
|
||||
for (let j = 0; j < item.segments.length; j++) {
|
||||
const seg = item.segments[j];
|
||||
if (rect.contains(seg.point)) {
|
||||
if (mode === 'detail') {
|
||||
if (event.modifiers.shift && seg.selected) {
|
||||
seg.selected = false;
|
||||
} else {
|
||||
seg.selected = true;
|
||||
}
|
||||
segmentMode = true;
|
||||
|
||||
} else {
|
||||
if (event.modifiers.shift && item.selected) {
|
||||
setItemSelection(item, false);
|
||||
|
||||
} else {
|
||||
setItemSelection(item, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// second round checks for path intersections
|
||||
const intersections = item.getIntersections(rect);
|
||||
if (intersections.length > 0 && !segmentMode) {
|
||||
// if in detail select mode, select the curves that intersect
|
||||
// with the selectionRect
|
||||
if (mode === 'detail') {
|
||||
for (let k = 0; k < intersections.length; k++) {
|
||||
const curve = intersections[k].curve;
|
||||
// intersections contains every curve twice because
|
||||
// the selectionRect intersects a circle always at
|
||||
// two points. so we skip every other curve
|
||||
if (k % 2 === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.modifiers.shift) {
|
||||
curve.selected = !curve.selected;
|
||||
} else {
|
||||
curve.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (event.modifiers.shift && item.selected) {
|
||||
setItemSelection(item, false);
|
||||
|
||||
} else {
|
||||
setItemSelection(item, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// pg.statusbar.update();
|
||||
|
||||
} else if (isBoundsItem(item)) {
|
||||
if (checkBoundsItem(rect, item, event)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// if the rectangular selection found a group, drill into it recursively
|
||||
const rectangularSelectionGroupLoop = function (group, rect, root, event, mode) {
|
||||
for (let i = 0; i < group.children.length; i++) {
|
||||
const child = group.children[i];
|
||||
|
||||
if (isGroup(child) || isCompoundPathItem(child)) {
|
||||
rectangularSelectionGroupLoop(child, rect, root, event, mode);
|
||||
|
||||
} else if (!handleRectangularSelectionItems(child, event, rect, mode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const processRectangularSelection = function (event, rect, mode) {
|
||||
const allItems = getAllSelectableItems();
|
||||
|
||||
itemLoop:
|
||||
for (let i = 0; i < allItems.length; i++) {
|
||||
const item = allItems[i];
|
||||
if (mode === 'detail' && isPGTextItem(getRootItem(item))) {
|
||||
continue itemLoop;
|
||||
}
|
||||
// check for item segment points inside selectionRect
|
||||
if (isGroup(item) || isCompoundPathItem(item)) {
|
||||
if (!rectangularSelectionGroupLoop(item, rect, item, event, mode)) {
|
||||
continue itemLoop;
|
||||
}
|
||||
|
||||
} else if (!handleRectangularSelectionItems(item, event, rect, mode)) {
|
||||
continue itemLoop;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const selectRootItem = function () {
|
||||
// when switching to the select tool while having a child object of a
|
||||
// compound path selected, deselect the child and select the compound path
|
||||
// instead. (otherwise the compound path breaks because of scale-grouping)
|
||||
const items = getSelectedItems();
|
||||
for (const item of items) {
|
||||
if (isCompoundPathChild(item)) {
|
||||
const cp = getItemsCompoundPath(item);
|
||||
setItemSelection(item, false);
|
||||
setItemSelection(cp, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const shouldShowIfSelection = function () {
|
||||
return getSelectedItems().length > 0;
|
||||
};
|
||||
|
||||
const shouldShowIfSelectionRecursive = function () {
|
||||
return getSelectedItems(true /* recursive */).length > 0;
|
||||
};
|
||||
|
||||
const shouldShowSelectAll = function () {
|
||||
return paper.project.getItems({class: paper.PathItem}).length > 0;
|
||||
};
|
||||
|
||||
export {
|
||||
selectAllItems,
|
||||
selectAllSegments,
|
||||
clearSelection,
|
||||
deleteSelection,
|
||||
deleteItemSelection,
|
||||
deleteSegmentSelection,
|
||||
splitPathAtSelectedSegments,
|
||||
cloneSelection,
|
||||
setItemSelection,
|
||||
setGroupSelection,
|
||||
getSelectedItems,
|
||||
getSelectedPaths,
|
||||
removeSelectedSegments,
|
||||
processRectangularSelection,
|
||||
selectRootItem,
|
||||
shouldShowIfSelection,
|
||||
shouldShowIfSelectionRecursive,
|
||||
shouldShowSelectAll
|
||||
};
|
|
@ -1,7 +1,10 @@
|
|||
import PaintEditor from './containers/paint-editor.jsx';
|
||||
import SelectionHOV from './containers/selection-hov.jsx';
|
||||
import ScratchPaintReducer from './reducers/scratch-paint-reducer';
|
||||
|
||||
const Wrapped = SelectionHOV(PaintEditor);
|
||||
|
||||
export {
|
||||
PaintEditor as default,
|
||||
Wrapped as default,
|
||||
ScratchPaintReducer
|
||||
};
|
||||
|
|
33
src/reducers/hover.js
Normal file
33
src/reducers/hover.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
const CHANGE_HOVERED = 'scratch-paint/hover/CHANGE_HOVERED';
|
||||
const initialState = null;
|
||||
|
||||
const reducer = function (state, action) {
|
||||
if (typeof state === 'undefined') state = initialState;
|
||||
switch (action.type) {
|
||||
case CHANGE_HOVERED:
|
||||
return action.hoveredItem;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Action creators ==================================
|
||||
const setHoveredItem = function (hoveredItem) {
|
||||
return {
|
||||
type: CHANGE_HOVERED,
|
||||
hoveredItem: hoveredItem
|
||||
};
|
||||
};
|
||||
|
||||
const clearHoveredItem = function () {
|
||||
return {
|
||||
type: CHANGE_HOVERED,
|
||||
hoveredItem: null
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
reducer as default,
|
||||
setHoveredItem,
|
||||
clearHoveredItem
|
||||
};
|
|
@ -3,10 +3,12 @@ import modeReducer from './modes';
|
|||
import brushModeReducer from './brush-mode';
|
||||
import eraserModeReducer from './eraser-mode';
|
||||
import colorReducer from './color';
|
||||
import hoverReducer from './hover';
|
||||
|
||||
export default combineReducers({
|
||||
mode: modeReducer,
|
||||
brushMode: brushModeReducer,
|
||||
eraserMode: eraserModeReducer,
|
||||
color: colorReducer
|
||||
color: colorReducer,
|
||||
hoveredItem: hoverReducer
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue