mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -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 PaperCanvas from '../containers/paper-canvas.jsx';
|
||||||
import BrushMode from '../containers/brush-mode.jsx';
|
import BrushMode from '../containers/brush-mode.jsx';
|
||||||
import EraserMode from '../containers/eraser-mode.jsx';
|
import EraserMode from '../containers/eraser-mode.jsx';
|
||||||
|
import SelectMode from '../containers/select-mode.jsx';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import LineMode from '../containers/line-mode.jsx';
|
import LineMode from '../containers/line-mode.jsx';
|
||||||
import FillColorIndicatorComponent from '../containers/fill-color-indicator.jsx';
|
import FillColorIndicatorComponent from '../containers/fill-color-indicator.jsx';
|
||||||
|
@ -126,6 +127,9 @@ class PaintEditorComponent extends React.Component {
|
||||||
canvas={this.state.canvas}
|
canvas={this.state.canvas}
|
||||||
onUpdateSvg={this.props.onUpdateSvg}
|
onUpdateSvg={this.props.onUpdateSvg}
|
||||||
/>
|
/>
|
||||||
|
<SelectMode
|
||||||
|
onUpdateSvg={this.props.onUpdateSvg}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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 = {
|
StrokeWidthIndicatorComponent.propTypes = {
|
||||||
onChangeStrokeWidth: PropTypes.func.isRequired,
|
onChangeStrokeWidth: PropTypes.func.isRequired,
|
||||||
strokeWidth: PropTypes.string.isRequired
|
strokeWidth: PropTypes.number.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StrokeWidthIndicatorComponent;
|
export default StrokeWidthIndicatorComponent;
|
||||||
|
|
|
@ -3,11 +3,16 @@ import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import bindAll from 'lodash.bindall';
|
import bindAll from 'lodash.bindall';
|
||||||
import Modes from '../modes/modes';
|
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 {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 SelectModeComponent from '../components/select-mode.jsx';
|
||||||
|
import BoundingBoxTool from '../helper/bounding-box/bounding-box-tool';
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
|
||||||
class SelectMode extends React.Component {
|
class SelectMode extends React.Component {
|
||||||
|
@ -33,7 +38,9 @@ class SelectMode extends React.Component {
|
||||||
fill: true,
|
fill: true,
|
||||||
guide: false
|
guide: false
|
||||||
};
|
};
|
||||||
|
this.boundingBoxTool = new BoundingBoxTool();
|
||||||
|
this.selectionBoxMode = false;
|
||||||
|
this.selectionRect = null;
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
if (this.props.isSelectModeActive) {
|
if (this.props.isSelectModeActive) {
|
||||||
|
@ -50,47 +57,60 @@ class SelectMode extends React.Component {
|
||||||
shouldComponentUpdate () {
|
shouldComponentUpdate () {
|
||||||
return false; // Static component, for now
|
return false; // Static component, for now
|
||||||
}
|
}
|
||||||
getHitOptions () {
|
getHitOptions (preselectedOnly) {
|
||||||
this._hitOptions.tolerance = SelectMode.TOLERANCE / paper.view.zoom;
|
this._hitOptions.tolerance = SelectMode.TOLERANCE / paper.view.zoom;
|
||||||
|
if (preselectedOnly) {
|
||||||
|
this._hitOptions.selected = true;
|
||||||
|
} else {
|
||||||
|
delete this._hitOptions.selected;
|
||||||
|
}
|
||||||
return this._hitOptions;
|
return this._hitOptions;
|
||||||
}
|
}
|
||||||
activateTool () {
|
activateTool () {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
this.preProcessSelection();
|
selectRootItem();
|
||||||
this.tool = new paper.Tool();
|
this.tool = new paper.Tool();
|
||||||
|
|
||||||
|
|
||||||
this.tool.onMouseDown = function (event) {
|
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.tool.onMouseMove = function (event) {
|
||||||
this.props.setHoveredItem(getHoveredItem(this.getHitOptions()));
|
this.props.setHoveredItem(getHoveredItem(event, this.getHitOptions()));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.tool.onMouseDrag = function (event) {
|
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.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();
|
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 () {
|
deactivateTool () {
|
||||||
this.props.setHoveredItem();
|
this.props.setHoveredItem();
|
||||||
this.tool.remove();
|
this.tool.remove();
|
||||||
|
@ -105,9 +125,11 @@ class SelectMode extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectMode.propTypes = {
|
SelectMode.propTypes = {
|
||||||
|
clearHoveredItem: PropTypes.func.isRequired,
|
||||||
handleMouseDown: PropTypes.func.isRequired,
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
isSelectModeActive: PropTypes.bool.isRequired,
|
isSelectModeActive: PropTypes.bool.isRequired,
|
||||||
onUpdateSvg: PropTypes.func.isRequired
|
onUpdateSvg: PropTypes.func.isRequired,
|
||||||
|
setHoveredItem: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -117,6 +139,9 @@ const mapDispatchToProps = dispatch => ({
|
||||||
setHoveredItem: hoveredItem => {
|
setHoveredItem: hoveredItem => {
|
||||||
dispatch(setHoveredItem(hoveredItem));
|
dispatch(setHoveredItem(hoveredItem));
|
||||||
},
|
},
|
||||||
|
clearHoveredItem: () => {
|
||||||
|
dispatch(clearHoveredItem());
|
||||||
|
},
|
||||||
handleMouseDown: () => {
|
handleMouseDown: () => {
|
||||||
dispatch(changeMode(Modes.SELECT));
|
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() {
|
const groupSelection = function () {
|
||||||
var items = pg.selection.getSelectedItems();
|
const items = getSelectedItems();
|
||||||
if(items.length > 0) {
|
if (items.length > 0) {
|
||||||
var group = new paper.Group(items);
|
const group = new paper.Group(items);
|
||||||
pg.selection.clearSelection();
|
clearSelection();
|
||||||
pg.selection.setItemSelection(group, true);
|
setItemSelection(group, true);
|
||||||
for (var i = 0; i < group.children.length; i++) {
|
for (let i = 0; i < group.children.length; i++) {
|
||||||
group.children[i].selected = true;
|
group.children[i].selected = true;
|
||||||
}
|
}
|
||||||
pg.undo.snapshot('groupSelection');
|
// jQuery(document).trigger('Grouped');
|
||||||
jQuery(document).trigger('Grouped');
|
// @todo add back undo
|
||||||
return group;
|
// pg.undo.snapshot('groupSelection');
|
||||||
} else {
|
return group;
|
||||||
return false;
|
}
|
||||||
}
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ungroupLoop = function (group, recursive) {
|
||||||
var ungroupSelection = function() {
|
// don't ungroup items that are not groups
|
||||||
var items = pg.selection.getSelectedItems();
|
if (!group || !group.children || !isGroup(group)) return;
|
||||||
ungroupItems(items);
|
|
||||||
pg.statusbar.update();
|
group.applyMatrix = true;
|
||||||
};
|
// iterate over group children recursively
|
||||||
|
for (let i = 0; i < group.children.length; i++) {
|
||||||
|
const groupChild = group.children[i];
|
||||||
var groupItems = function(items) {
|
if (groupChild.hasChildren()) {
|
||||||
if(items.length > 0) {
|
// recursion (groups can contain groups, ie. from SVG import)
|
||||||
var group = new paper.Group(items);
|
if (recursive) {
|
||||||
jQuery(document).trigger('Grouped');
|
ungroupLoop(groupChild, true /* recursive */);
|
||||||
pg.undo.snapshot('groupItems');
|
continue;
|
||||||
return group;
|
}
|
||||||
} else {
|
}
|
||||||
return false;
|
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)
|
const groupItems = function (items) {
|
||||||
var ungroupItems = function(items) {
|
if (items.length > 0) {
|
||||||
pg.selection.clearSelection();
|
const group = new paper.Group(items);
|
||||||
var emptyGroups = [];
|
// jQuery(document).trigger('Grouped');
|
||||||
for(var i=0; i<items.length; i++) {
|
// @todo add back undo
|
||||||
var item = items[i];
|
// pg.undo.snapshot('groupItems');
|
||||||
if(isGroup(item) && !item.data.isPGTextItem) {
|
return group;
|
||||||
ungroupLoop(item, false /* recursive */);
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
if(!item.hasChildren()) {
|
const getItemsGroup = function (item) {
|
||||||
emptyGroups.push(item);
|
const itemParent = item.parent;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove all empty groups after ungrouping
|
if (isGroup(itemParent)) {
|
||||||
for(var j=0; j<emptyGroups.length; j++) {
|
return itemParent;
|
||||||
emptyGroups[j].remove();
|
}
|
||||||
}
|
return null;
|
||||||
jQuery(document).trigger('Ungrouped');
|
};
|
||||||
pg.undo.snapshot('ungroupItems');
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const isGroupChild = function (item) {
|
||||||
|
const rootItem = getRootItem(item);
|
||||||
|
return isGroup(rootItem);
|
||||||
|
};
|
||||||
|
|
||||||
var ungroupLoop = function(group, recursive) {
|
const shouldShowGroup = function () {
|
||||||
// don't ungroup items that are not groups
|
const items = getSelectedItems();
|
||||||
if(!group || !group.children || !isGroup(group)) return;
|
return items.length > 1;
|
||||||
|
};
|
||||||
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 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) {
|
export {
|
||||||
var itemParent = item.parent;
|
groupSelection,
|
||||||
|
ungroupSelection,
|
||||||
if(isGroup(itemParent)) {
|
groupItems,
|
||||||
return itemParent;
|
ungroupItems,
|
||||||
} else {
|
getItemsGroup,
|
||||||
return null;
|
isGroup,
|
||||||
}
|
isGroupChild,
|
||||||
};
|
shouldShowGroup,
|
||||||
|
shouldShowUngroup
|
||||||
|
};
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
}();
|
|
||||||
|
|
|
@ -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() {
|
const GUIDE_BLUE = '#009dec';
|
||||||
|
const GUIDE_GREY = '#aaaaaa';
|
||||||
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();
|
|
||||||
|
|
||||||
return clone;
|
const setDefaultGuideStyle = function (item) {
|
||||||
};
|
item.strokeWidth = 1 / paper.view.zoom;
|
||||||
|
item.opacity = 1;
|
||||||
|
item.blendMode = 'normal';
|
||||||
var hoverBounds = function(item) {
|
item.guide = true;
|
||||||
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();
|
|
||||||
|
|
||||||
return rect;
|
const hoverItem = function (hitResult) {
|
||||||
};
|
const segments = hitResult.item.segments;
|
||||||
|
const clone = new paper.Path(segments);
|
||||||
|
setDefaultGuideStyle(clone);
|
||||||
var rectSelect = function(event, color) {
|
if (hitResult.item.closed) {
|
||||||
var half = new paper.Point(0.5 / paper.view.zoom, 0.5 / paper.view.zoom);
|
clone.closed = true;
|
||||||
var start = event.downPoint.add(half);
|
}
|
||||||
var end = event.point.add(half);
|
clone.parent = getGuideLayer();
|
||||||
var rect = new paper.Path.Rectangle(start, end);
|
clone.strokeColor = GUIDE_BLUE;
|
||||||
var zoom = 1.0/paper.view.zoom;
|
clone.fillColor = null;
|
||||||
setDefaultGuideStyle(rect);
|
clone.data.isHelperItem = true;
|
||||||
if(!color) color = guideGrey;
|
clone.bringToFront();
|
||||||
rect.parent = pg.layer.getGuideLayer();
|
|
||||||
rect.strokeColor = color;
|
return clone;
|
||||||
rect.data.isRectSelect = true;
|
};
|
||||||
rect.data.isHelperItem = true;
|
|
||||||
rect.dashArray = [3.0*zoom, 3.0*zoom];
|
const hoverBounds = function (item) {
|
||||||
return rect;
|
const rect = new paper.Path.Rectangle(item.internalBounds);
|
||||||
};
|
rect.matrix = item.matrix;
|
||||||
|
setDefaultGuideStyle(rect);
|
||||||
|
rect.parent = getGuideLayer();
|
||||||
var line = function(from, to, color) {
|
rect.strokeColor = GUIDE_BLUE;
|
||||||
var line = new paper.Path.Line(from, to);
|
rect.fillColor = null;
|
||||||
var zoom = 1/paper.view.zoom;
|
rect.data.isHelperItem = true;
|
||||||
setDefaultGuideStyle(line);
|
rect.bringToFront();
|
||||||
if (!color) color = guideGrey;
|
|
||||||
line.parent = pg.layer.getGuideLayer();
|
return rect;
|
||||||
line.strokeColor = color;
|
};
|
||||||
line.strokeColor = color;
|
|
||||||
line.dashArray = [5*zoom, 5*zoom];
|
const rectSelect = function (event, color) {
|
||||||
line.data.isHelperItem = true;
|
const half = new paper.Point(0.5 / paper.view.zoom, 0.5 / paper.view.zoom);
|
||||||
return line;
|
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) {
|
const removeHelperItems = function () {
|
||||||
var zoom = 1/paper.view.zoom;
|
removePaperItemsByDataTags(['isHelperItem']);
|
||||||
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);
|
|
||||||
|
|
||||||
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;
|
const removeAllGuides = function () {
|
||||||
};
|
removePaperItemsByTags(['guide']);
|
||||||
|
};
|
||||||
|
|
||||||
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 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 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 {!MouseEvent} event mouse event
|
||||||
* @param event mouse event
|
* @param {?object} hitOptions hit options to use
|
||||||
* @return the hovered item or null if there is none
|
* @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);
|
const hitResults = paper.project.hitTestAll(event.point, hitOptions);
|
||||||
if (hitResults.length === 0) {
|
if (hitResults.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -23,28 +25,14 @@ const getHoveredItem = function (hitOptions, event) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pg.item.isBoundsItem(hitResult.item)) {
|
if (isBoundsItem(hitResult.item)) {
|
||||||
return pg.guides.hoverBounds(hitResult.item);
|
return hoverBounds(hitResult.item);
|
||||||
|
} else if (isGroupChild(hitResult.item)) {
|
||||||
} else if(pg.group.isGroupChild(hitResult.item)) {
|
return hoverBounds(getRootItem(hitResult.item));
|
||||||
return pg.guides.hoverBounds(pg.item.getRootItem(hitResult.item));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return pg.guides.hoverItem(hitResult);
|
|
||||||
}
|
}
|
||||||
|
return hoverItem(hitResult);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Action creators ==================================
|
|
||||||
const clearHoveredItem = function () {
|
|
||||||
return {
|
|
||||||
type: CLEAR_HOVERED_ITEM
|
|
||||||
};
|
|
||||||
// TODO: paper.view.update();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getHoveredItem,
|
getHoveredItem
|
||||||
clearHoveredItem
|
|
||||||
};
|
};
|
||||||
|
|
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 PaintEditor from './containers/paint-editor.jsx';
|
||||||
|
import SelectionHOV from './containers/selection-hov.jsx';
|
||||||
import ScratchPaintReducer from './reducers/scratch-paint-reducer';
|
import ScratchPaintReducer from './reducers/scratch-paint-reducer';
|
||||||
|
|
||||||
|
const Wrapped = SelectionHOV(PaintEditor);
|
||||||
|
|
||||||
export {
|
export {
|
||||||
PaintEditor as default,
|
Wrapped as default,
|
||||||
ScratchPaintReducer
|
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 brushModeReducer from './brush-mode';
|
||||||
import eraserModeReducer from './eraser-mode';
|
import eraserModeReducer from './eraser-mode';
|
||||||
import colorReducer from './color';
|
import colorReducer from './color';
|
||||||
|
import hoverReducer from './hover';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
mode: modeReducer,
|
mode: modeReducer,
|
||||||
brushMode: brushModeReducer,
|
brushMode: brushModeReducer,
|
||||||
eraserMode: eraserModeReducer,
|
eraserMode: eraserModeReducer,
|
||||||
color: colorReducer
|
color: colorReducer,
|
||||||
|
hoveredItem: hoverReducer
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue