mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -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