mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-23 05:52:42 -05:00
In-progress add select mode
This commit is contained in:
parent
a2cd53b159
commit
cf75703580
6 changed files with 913 additions and 0 deletions
128
src/containers/select-mode.jsx
Normal file
128
src/containers/select-mode.jsx
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import bindAll from 'lodash.bindall';
|
||||||
|
import Modes from '../modes/modes';
|
||||||
|
import {clearSelection} from '../reducers/selection';
|
||||||
|
import {setHoveredItem} from '../reducers/hover';
|
||||||
|
import {getHoveredItem} from '../helper/hover';
|
||||||
|
import {changeMode} from '../reducers/modes';
|
||||||
|
import SelectModeComponent from '../components/select-mode.jsx';
|
||||||
|
import paper from 'paper';
|
||||||
|
|
||||||
|
class SelectMode extends React.Component {
|
||||||
|
static get TOLERANCE () {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'activateTool',
|
||||||
|
'deactivateTool',
|
||||||
|
'getHitOptions',
|
||||||
|
'preProcessSelection',
|
||||||
|
'onMouseDown',
|
||||||
|
'onMouseMove',
|
||||||
|
'onMouseDrag',
|
||||||
|
'onMouseUp'
|
||||||
|
]);
|
||||||
|
this._hitOptions = {
|
||||||
|
segments: true,
|
||||||
|
stroke: true,
|
||||||
|
curves: true,
|
||||||
|
fill: true,
|
||||||
|
guide: false
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
componentDidMount () {
|
||||||
|
if (this.props.isSelectModeActive) {
|
||||||
|
this.activateTool(this.props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (nextProps.isSelectModeActive && !this.props.isSelectModeActive) {
|
||||||
|
this.activateTool();
|
||||||
|
} else if (!nextProps.isSelectModeActive && this.props.isSelectModeActive) {
|
||||||
|
this.deactivateTool();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shouldComponentUpdate () {
|
||||||
|
return false; // Static component, for now
|
||||||
|
}
|
||||||
|
getHitOptions () {
|
||||||
|
this._hitOptions.tolerance = SelectMode.TOLERANCE / paper.view.zoom;
|
||||||
|
return this._hitOptions;
|
||||||
|
}
|
||||||
|
activateTool () {
|
||||||
|
clearSelection();
|
||||||
|
this.preProcessSelection();
|
||||||
|
this.tool = new paper.Tool();
|
||||||
|
|
||||||
|
|
||||||
|
this.tool.onMouseDown = function (event) {
|
||||||
|
this.onMouseDown(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.tool.onMouseMove = function (event) {
|
||||||
|
this.props.setHoveredItem(getHoveredItem(this.getHitOptions()));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
this.tool.onMouseDrag = function (event) {
|
||||||
|
this.onMouseDrag(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.tool.onMouseUp = function (event) {
|
||||||
|
this.onMouseUp(event);
|
||||||
|
};
|
||||||
|
this.tool.activate();
|
||||||
|
}
|
||||||
|
preProcessSelection () {
|
||||||
|
// when switching to the select tool while having a child object of a
|
||||||
|
// compound path selected, deselect the child and select the compound path
|
||||||
|
// instead. (otherwise the compound path breaks because of scale-grouping)
|
||||||
|
const items = this.props.selectedItems;
|
||||||
|
for (let item of items) {
|
||||||
|
if(isCompoundPathChild(item)) {
|
||||||
|
var cp = getItemsCompoundPath(item);
|
||||||
|
setItemSelection(item, false);
|
||||||
|
setItemSelection(cp, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
deactivateTool () {
|
||||||
|
this.props.setHoveredItem();
|
||||||
|
this.tool.remove();
|
||||||
|
this.tool = null;
|
||||||
|
this.hitResult = null;
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<SelectModeComponent onMouseDown={this.props.handleMouseDown} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectMode.propTypes = {
|
||||||
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
|
isSelectModeActive: PropTypes.bool.isRequired,
|
||||||
|
onUpdateSvg: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
isSelectModeActive: state.scratchPaint.mode === Modes.SELECT
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
setHoveredItem: hoveredItem => {
|
||||||
|
dispatch(setHoveredItem(hoveredItem));
|
||||||
|
},
|
||||||
|
handleMouseDown: () => {
|
||||||
|
dispatch(changeMode(Modes.SELECT));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(SelectMode);
|
326
src/helper/bounding-box-tool.js
Normal file
326
src/helper/bounding-box-tool.js
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
src/helper/compoundPath.js
Normal file
86
src/helper/compoundPath.js
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}();
|
139
src/helper/group.js
Normal file
139
src/helper/group.js
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// function related to groups and grouping
|
||||||
|
|
||||||
|
pg.group = function() {
|
||||||
|
|
||||||
|
var groupSelection = function() {
|
||||||
|
var items = pg.selection.getSelectedItems();
|
||||||
|
if(items.length > 0) {
|
||||||
|
var group = new paper.Group(items);
|
||||||
|
pg.selection.clearSelection();
|
||||||
|
pg.selection.setItemSelection(group, true);
|
||||||
|
for (var i = 0; i < group.children.length; i++) {
|
||||||
|
group.children[i].selected = true;
|
||||||
|
}
|
||||||
|
pg.undo.snapshot('groupSelection');
|
||||||
|
jQuery(document).trigger('Grouped');
|
||||||
|
return group;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var ungroupSelection = function() {
|
||||||
|
var items = pg.selection.getSelectedItems();
|
||||||
|
ungroupItems(items);
|
||||||
|
pg.statusbar.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var groupItems = function(items) {
|
||||||
|
if(items.length > 0) {
|
||||||
|
var group = new paper.Group(items);
|
||||||
|
jQuery(document).trigger('Grouped');
|
||||||
|
pg.undo.snapshot('groupItems');
|
||||||
|
return group;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ungroup items (only top hierarchy)
|
||||||
|
var ungroupItems = function(items) {
|
||||||
|
pg.selection.clearSelection();
|
||||||
|
var emptyGroups = [];
|
||||||
|
for(var i=0; i<items.length; i++) {
|
||||||
|
var item = items[i];
|
||||||
|
if(isGroup(item) && !item.data.isPGTextItem) {
|
||||||
|
ungroupLoop(item, false /* recursive */);
|
||||||
|
|
||||||
|
if(!item.hasChildren()) {
|
||||||
|
emptyGroups.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all empty groups after ungrouping
|
||||||
|
for(var j=0; j<emptyGroups.length; j++) {
|
||||||
|
emptyGroups[j].remove();
|
||||||
|
}
|
||||||
|
jQuery(document).trigger('Ungrouped');
|
||||||
|
pg.undo.snapshot('ungroupItems');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var ungroupLoop = function(group, recursive) {
|
||||||
|
// don't ungroup items that are not groups
|
||||||
|
if(!group || !group.children || !isGroup(group)) return;
|
||||||
|
|
||||||
|
group.applyMatrix = true;
|
||||||
|
// iterate over group children recursively
|
||||||
|
for(var i=0; i<group.children.length; i++) {
|
||||||
|
var groupChild = group.children[i];
|
||||||
|
if(groupChild.hasChildren()) {
|
||||||
|
// recursion (groups can contain groups, ie. from SVG import)
|
||||||
|
if(recursive) {
|
||||||
|
ungroupLoop(groupChild, true /* recursive */);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupChild.applyMatrix = true;
|
||||||
|
// move items from the group to the activeLayer (ungrouping)
|
||||||
|
groupChild.insertBelow(group);
|
||||||
|
groupChild.selected = true;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var getItemsGroup = function(item) {
|
||||||
|
var itemParent = item.parent;
|
||||||
|
|
||||||
|
if(isGroup(itemParent)) {
|
||||||
|
return itemParent;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var isGroup = function(item) {
|
||||||
|
return pg.item.isGroupItem(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var isGroupChild = function(item) {
|
||||||
|
var rootItem = pg.item.getRootItem(item);
|
||||||
|
return isGroup(rootItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
var shouldShowGroup = function() {
|
||||||
|
var items = pg.selection.getSelectedItems();
|
||||||
|
return items.length > 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
var shouldShowUngroup = function() {
|
||||||
|
var items = pg.selection.getSelectedItems();
|
||||||
|
for(var i=0; i<items.length; i++) {
|
||||||
|
var item = items[i];
|
||||||
|
if(isGroup(item) && !item.data.isPGTextItem && item.children && item.children.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
groupSelection: groupSelection,
|
||||||
|
ungroupSelection: ungroupSelection,
|
||||||
|
groupItems: groupItems,
|
||||||
|
ungroupItems: ungroupItems,
|
||||||
|
getItemsGroup: getItemsGroup,
|
||||||
|
isGroup: isGroup,
|
||||||
|
isGroupChild:isGroupChild,
|
||||||
|
shouldShowGroup:shouldShowGroup,
|
||||||
|
shouldShowUngroup:shouldShowUngroup
|
||||||
|
};
|
||||||
|
|
||||||
|
}();
|
184
src/helper/guides.js
Normal file
184
src/helper/guides.js
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
// functions related to guide items
|
||||||
|
|
||||||
|
pg.guides = function() {
|
||||||
|
|
||||||
|
var guideBlue = '#009dec';
|
||||||
|
var guideGrey = '#aaaaaa';
|
||||||
|
|
||||||
|
var hoverItem = function(hitResult) {
|
||||||
|
var segments = hitResult.item.segments;
|
||||||
|
var clone = new paper.Path(segments);
|
||||||
|
setDefaultGuideStyle(clone);
|
||||||
|
if(hitResult.item.closed) {
|
||||||
|
clone.closed = true;
|
||||||
|
}
|
||||||
|
clone.parent = pg.layer.getGuideLayer();
|
||||||
|
clone.strokeColor = guideBlue;
|
||||||
|
clone.fillColor = null;
|
||||||
|
clone.data.isHelperItem = true;
|
||||||
|
clone.bringToFront();
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var hoverBounds = function(item) {
|
||||||
|
var rect = new paper.Path.Rectangle(item.internalBounds);
|
||||||
|
rect.matrix = item.matrix;
|
||||||
|
setDefaultGuideStyle(rect);
|
||||||
|
rect.parent = pg.layer.getGuideLayer();
|
||||||
|
rect.strokeColor = guideBlue;
|
||||||
|
rect.fillColor = null;
|
||||||
|
rect.data.isHelperItem = true;
|
||||||
|
rect.bringToFront();
|
||||||
|
|
||||||
|
return rect;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var rectSelect = function(event, color) {
|
||||||
|
var half = new paper.Point(0.5 / paper.view.zoom, 0.5 / paper.view.zoom);
|
||||||
|
var start = event.downPoint.add(half);
|
||||||
|
var end = event.point.add(half);
|
||||||
|
var rect = new paper.Path.Rectangle(start, end);
|
||||||
|
var zoom = 1.0/paper.view.zoom;
|
||||||
|
setDefaultGuideStyle(rect);
|
||||||
|
if(!color) color = guideGrey;
|
||||||
|
rect.parent = pg.layer.getGuideLayer();
|
||||||
|
rect.strokeColor = color;
|
||||||
|
rect.data.isRectSelect = true;
|
||||||
|
rect.data.isHelperItem = true;
|
||||||
|
rect.dashArray = [3.0*zoom, 3.0*zoom];
|
||||||
|
return rect;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var line = function(from, to, color) {
|
||||||
|
var line = new paper.Path.Line(from, to);
|
||||||
|
var zoom = 1/paper.view.zoom;
|
||||||
|
setDefaultGuideStyle(line);
|
||||||
|
if (!color) color = guideGrey;
|
||||||
|
line.parent = pg.layer.getGuideLayer();
|
||||||
|
line.strokeColor = color;
|
||||||
|
line.strokeColor = color;
|
||||||
|
line.dashArray = [5*zoom, 5*zoom];
|
||||||
|
line.data.isHelperItem = true;
|
||||||
|
return line;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var crossPivot = function(center, color) {
|
||||||
|
var zoom = 1/paper.view.zoom;
|
||||||
|
var star = new paper.Path.Star(center, 4, 4*zoom, 0.5*zoom);
|
||||||
|
setDefaultGuideStyle(star);
|
||||||
|
if(!color) color = guideBlue;
|
||||||
|
star.parent = pg.layer.getGuideLayer();
|
||||||
|
star.fillColor = color;
|
||||||
|
star.strokeColor = color;
|
||||||
|
star.strokeWidth = 0.5*zoom;
|
||||||
|
star.data.isHelperItem = true;
|
||||||
|
star.rotate(45);
|
||||||
|
|
||||||
|
return star;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var rotPivot = function(center, color) {
|
||||||
|
var zoom = 1/paper.view.zoom;
|
||||||
|
var path = new paper.Path.Circle(center, 3*zoom);
|
||||||
|
setDefaultGuideStyle(path);
|
||||||
|
if(!color) color = guideBlue;
|
||||||
|
path.parent = pg.layer.getGuideLayer();
|
||||||
|
path.fillColor = color;
|
||||||
|
path.data.isHelperItem = true;
|
||||||
|
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var label = function(pos, content, color) {
|
||||||
|
var text = new paper.PointText(pos);
|
||||||
|
if(!color) color = guideGrey;
|
||||||
|
text.parent = pg.layer.getGuideLayer();
|
||||||
|
text.fillColor = color;
|
||||||
|
text.content = content;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var setDefaultGuideStyle = function(item) {
|
||||||
|
item.strokeWidth = 1/paper.view.zoom;
|
||||||
|
item.opacity = 1;
|
||||||
|
item.blendMode = 'normal';
|
||||||
|
item.guide = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var getGuideColor = function(colorName) {
|
||||||
|
if(colorName == 'blue') {
|
||||||
|
return guideBlue;
|
||||||
|
} else if(colorName == 'grey') {
|
||||||
|
return guideGrey;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var getAllGuides = function() {
|
||||||
|
var allItems = [];
|
||||||
|
for(var i=0; i<paper.project.layers.length; i++) {
|
||||||
|
var layer = paper.project.layers[i];
|
||||||
|
for(var j=0; j<layer.children.length; j++) {
|
||||||
|
var child = layer.children[j];
|
||||||
|
// only give guides
|
||||||
|
if(!child.guide) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
allItems.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allItems;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var getExportRectGuide = function() {
|
||||||
|
var guides = getAllGuides();
|
||||||
|
for(var i=0; i<guides.length; i++){
|
||||||
|
if(guides[i].data && guides[i].data.isExportRect) {
|
||||||
|
return guides[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var removeHelperItems = function() {
|
||||||
|
pg.helper.removePaperItemsByDataTags(['isHelperItem']);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var removeAllGuides = function() {
|
||||||
|
pg.helper.removePaperItemsByTags(['guide']);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var removeExportRectGuide = function() {
|
||||||
|
pg.helper.removePaperItemsByDataTags(['isExportRect']);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
hoverItem: hoverItem,
|
||||||
|
hoverBounds: hoverBounds,
|
||||||
|
rectSelect: rectSelect,
|
||||||
|
line: line,
|
||||||
|
crossPivot: crossPivot,
|
||||||
|
rotPivot: rotPivot,
|
||||||
|
label: label,
|
||||||
|
removeAllGuides: removeAllGuides,
|
||||||
|
removeHelperItems: removeHelperItems,
|
||||||
|
removeExportRectGuide: removeExportRectGuide,
|
||||||
|
getAllGuides: getAllGuides,
|
||||||
|
getExportRectGuide: getExportRectGuide,
|
||||||
|
getGuideColor: getGuideColor,
|
||||||
|
setDefaultGuideStyle:setDefaultGuideStyle
|
||||||
|
};
|
||||||
|
|
||||||
|
}();
|
50
src/helper/hover.js
Normal file
50
src/helper/hover.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import paper from 'paper';
|
||||||
|
|
||||||
|
const CLEAR_HOVERED_ITEM = 'scratch-paint/hover/CLEAR_HOVERED_ITEM';
|
||||||
|
/**
|
||||||
|
* @param hitOptions hit options to use
|
||||||
|
* @param event mouse event
|
||||||
|
* @return the hovered item or null if there is none
|
||||||
|
*/
|
||||||
|
const getHoveredItem = function (hitOptions, event) {
|
||||||
|
const hitResults = paper.project.hitTestAll(event.point, hitOptions);
|
||||||
|
if (hitResults.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hitResult;
|
||||||
|
for (const result of hitResults) {
|
||||||
|
if (!(result.item.data && result.item.data.noHover) && !hitResult.item.selected) {
|
||||||
|
hitResult = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hitResult) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pg.item.isBoundsItem(hitResult.item)) {
|
||||||
|
return pg.guides.hoverBounds(hitResult.item);
|
||||||
|
|
||||||
|
} else if(pg.group.isGroupChild(hitResult.item)) {
|
||||||
|
return pg.guides.hoverBounds(pg.item.getRootItem(hitResult.item));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return pg.guides.hoverItem(hitResult);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Action creators ==================================
|
||||||
|
const clearHoveredItem = function () {
|
||||||
|
return {
|
||||||
|
type: CLEAR_HOVERED_ITEM
|
||||||
|
};
|
||||||
|
// TODO: paper.view.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
getHoveredItem,
|
||||||
|
clearHoveredItem
|
||||||
|
};
|
Loading…
Reference in a new issue