Add group and ungroup

This commit is contained in:
DD 2017-10-23 15:38:52 -04:00
parent 091201756b
commit 083bf63869
12 changed files with 112 additions and 42 deletions

View file

@ -113,7 +113,7 @@ class PaintEditorComponent extends React.Component {
imgAlt="Ungroup Icon"
imgSrc={ungroupIcon}
title="Ungroup"
onClick={function () {}}
onClick={this.props.onUngroup}
/>
</InputGroup>

View file

@ -1,3 +1,4 @@
import paper from '@scratch/paper';
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
@ -28,6 +29,9 @@ class OvalMode extends React.Component {
if (this.tool && nextProps.colorState !== this.props.colorState) {
this.tool.setColorState(nextProps.colorState);
}
if (this.tool && nextProps.selectedItems !== this.props.selectedItems) {
this.tool.onSelectionChanged(nextProps.selectedItems);
}
if (nextProps.isOvalModeActive && !this.props.isOvalModeActive) {
this.activateTool();
@ -73,12 +77,14 @@ OvalMode.propTypes = {
handleMouseDown: PropTypes.func.isRequired,
isOvalModeActive: PropTypes.bool.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item)),
setSelectedItems: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
colorState: state.scratchPaint.color,
isOvalModeActive: state.scratchPaint.mode === Modes.OVAL
isOvalModeActive: state.scratchPaint.mode === Modes.OVAL,
selectedItems: state.scratchPaint.selectedItems
});
const mapDispatchToProps = dispatch => ({
clearSelectedItems: () => {
@ -89,8 +95,6 @@ const mapDispatchToProps = dispatch => ({
},
handleMouseDown: () => {
dispatch(changeMode(Modes.OVAL));
},
deactivateTool () {
}
});

View file

@ -4,10 +4,13 @@ import PaintEditorComponent from '../components/paint-editor/paint-editor.jsx';
import {changeMode} from '../reducers/modes';
import {undo, redo, undoSnapshot} from '../reducers/undo';
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
import {getGuideLayer} from '../helper/layer';
import {performUndo, performRedo, performSnapshot} from '../helper/undo';
import {bringToFront, sendBackward, sendToBack, bringForward} from '../helper/order';
import {groupSelection, ungroupSelection} from '../helper/group';
import {getSelectedLeafItems} from '../helper/selection';
import Modes from '../modes/modes';
import {connect} from 'react-redux';
@ -24,7 +27,9 @@ class PaintEditor extends React.Component {
'handleSendBackward',
'handleSendForward',
'handleSendToBack',
'handleSendToFront'
'handleSendToFront',
'handleGroup',
'handleUngroup'
]);
}
componentDidMount () {
@ -51,10 +56,16 @@ class PaintEditor extends React.Component {
paper.project.addLayer(guideLayer);
}
handleUndo () {
performUndo(this.props.undoState, this.props.onUndo, this.handleUpdateSvg);
performUndo(this.props.undoState, this.props.onUndo, this.props.setSelectedItems, this.handleUpdateSvg);
}
handleRedo () {
performRedo(this.props.undoState, this.props.onRedo, this.handleUpdateSvg);
performRedo(this.props.undoState, this.props.onRedo, this.props.setSelectedItems, this.handleUpdateSvg);
}
handleGroup () {
groupSelection(this.props.clearSelectedItems, this.props.setSelectedItems, this.handleUpdateSvg);
}
handleUngroup () {
ungroupSelection(this.props.clearSelectedItems, this.props.setSelectedItems, this.handleUpdateSvg);
}
handleSendBackward () {
sendBackward(this.handleUpdateSvg);
@ -76,12 +87,14 @@ class PaintEditor extends React.Component {
rotationCenterY={this.props.rotationCenterY}
svg={this.props.svg}
svgId={this.props.svgId}
onGroup={this.handleGroup}
onRedo={this.handleRedo}
onSendBackward={this.handleSendBackward}
onSendForward={this.handleSendForward}
onSendToBack={this.handleSendToBack}
onSendToFront={this.handleSendToFront}
onUndo={this.handleUndo}
onUngroup={this.handleUngroup}
onUpdateName={this.props.onUpdateName}
onUpdateSvg={this.handleUpdateSvg}
/>
@ -90,6 +103,7 @@ class PaintEditor extends React.Component {
}
PaintEditor.propTypes = {
clearSelectedItems: PropTypes.func.isRequired,
name: PropTypes.string,
onKeyPress: PropTypes.func.isRequired,
onRedo: PropTypes.func.isRequired,
@ -98,6 +112,7 @@ PaintEditor.propTypes = {
onUpdateSvg: PropTypes.func.isRequired,
rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number,
setSelectedItems: PropTypes.func.isRequired,
svg: PropTypes.string,
svgId: PropTypes.string,
undoSnapshot: PropTypes.func.isRequired,
@ -122,6 +137,12 @@ const mapDispatchToProps = dispatch => ({
dispatch(changeMode(Modes.SELECT));
}
},
clearSelectedItems: () => {
dispatch(clearSelectedItems());
},
setSelectedItems: () => {
dispatch(setSelectedItems(getSelectedLeafItems()));
},
onUndo: () => {
dispatch(undo());
},

View file

@ -1,3 +1,4 @@
import paper from '@scratch/paper';
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
@ -28,6 +29,9 @@ class RectMode extends React.Component {
if (this.tool && nextProps.colorState !== this.props.colorState) {
this.tool.setColorState(nextProps.colorState);
}
if (this.tool && nextProps.selectedItems !== this.props.selectedItems) {
this.tool.onSelectionChanged(nextProps.selectedItems);
}
if (nextProps.isRectModeActive && !this.props.isRectModeActive) {
this.activateTool();
@ -73,12 +77,14 @@ RectMode.propTypes = {
handleMouseDown: PropTypes.func.isRequired,
isRectModeActive: PropTypes.bool.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item)),
setSelectedItems: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
colorState: state.scratchPaint.color,
isRectModeActive: state.scratchPaint.mode === Modes.RECT
isRectModeActive: state.scratchPaint.mode === Modes.RECT,
selectedItems: state.scratchPaint.selectedItems
});
const mapDispatchToProps = dispatch => ({
clearSelectedItems: () => {

View file

@ -1,3 +1,4 @@
import paper from '@scratch/paper';
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
@ -29,6 +30,9 @@ class SelectMode extends React.Component {
if (this.tool && nextProps.hoveredItemId !== this.props.hoveredItemId) {
this.tool.setPrevHoveredItemId(nextProps.hoveredItemId);
}
if (this.tool && nextProps.selectedItems !== this.props.selectedItems) {
this.tool.onSelectionChanged(nextProps.selectedItems);
}
if (nextProps.isSelectModeActive && !this.props.isSelectModeActive) {
this.activateTool();
@ -71,13 +75,15 @@ SelectMode.propTypes = {
hoveredItemId: PropTypes.number,
isSelectModeActive: PropTypes.bool.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item)),
setHoveredItem: PropTypes.func.isRequired,
setSelectedItems: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
isSelectModeActive: state.scratchPaint.mode === Modes.SELECT,
hoveredItemId: state.scratchPaint.hoveredItemId
hoveredItemId: state.scratchPaint.hoveredItemId,
selectedItems: state.scratchPaint.selectedItems
});
const mapDispatchToProps = dispatch => ({
setHoveredItem: hoveredItemId => {

View file

@ -6,8 +6,7 @@ const isGroup = function (item) {
return isGroupItem(item);
};
const groupSelection = function (clearSelectedItems) {
const items = getSelectedRootItems();
const groupItems = function (items, clearSelectedItems, setSelectedItems, onUpdateSvg) {
if (items.length > 0) {
const group = new paper.Group(items);
clearSelection(clearSelectedItems);
@ -15,15 +14,20 @@ const groupSelection = function (clearSelectedItems) {
for (let i = 0; i < group.children.length; i++) {
group.children[i].selected = true;
}
setSelectedItems();
// @todo: Set selection bounds; enable/disable grouping icons
// @todo add back undo
// pg.undo.snapshot('groupSelection');
onUpdateSvg();
return group;
}
return false;
};
const ungroupLoop = function (group, recursive, selectUngroupedItems) {
const groupSelection = function (clearSelectedItems, setSelectedItems, onUpdateSvg) {
const items = getSelectedRootItems();
return groupItems(items, clearSelectedItems, setSelectedItems, onUpdateSvg);
};
const ungroupLoop = function (group, recursive, setSelectedItems) {
// Can't ungroup items that are not groups
if (!group || !group.children || !isGroup(group)) return;
@ -34,7 +38,7 @@ const ungroupLoop = function (group, recursive, selectUngroupedItems) {
if (groupChild.hasChildren()) {
// recursion (groups can contain groups, ie. from SVG import)
if (recursive) {
ungroupLoop(groupChild, recursive, selectUngroupedItems);
ungroupLoop(groupChild, recursive, setSelectedItems);
continue;
}
if (groupChild.children.length === 1) {
@ -44,7 +48,7 @@ const ungroupLoop = function (group, recursive, selectUngroupedItems) {
groupChild.applyMatrix = true;
// move items from the group to the activeLayer (ungrouping)
groupChild.insertBelow(group);
if (selectUngroupedItems) {
if (setSelectedItems) {
groupChild.selected = true;
}
i--;
@ -52,44 +56,38 @@ const ungroupLoop = function (group, recursive, selectUngroupedItems) {
};
// ungroup items (only top hierarchy)
const ungroupItems = function (items, selectUngroupedItems) {
const ungroupItems = function (items, setSelectedItems, onUpdateSvg) {
if (items.length === 0) {
return;
}
const emptyGroups = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (isGroup(item) && !item.data.isPGTextItem) {
ungroupLoop(item, false /* recursive */, selectUngroupedItems /* selectUngroupedItems */);
ungroupLoop(item, false /* recursive */, setSelectedItems);
if (!item.hasChildren()) {
emptyGroups.push(item);
}
}
}
if (setSelectedItems) {
setSelectedItems();
}
// remove all empty groups after ungrouping
for (let j = 0; j < emptyGroups.length; j++) {
emptyGroups[j].remove();
}
// @todo: Set selection bounds; enable/disable grouping icons
// @todo add back undo
// pg.undo.snapshot('ungroupItems');
if (onUpdateSvg) {
onUpdateSvg();
}
};
const ungroupSelection = function (clearSelectedItems) {
const ungroupSelection = function (clearSelectedItems, setSelectedItems, onUpdateSvg) {
const items = getSelectedRootItems();
clearSelection(clearSelectedItems);
ungroupItems(items, true /* selectUngroupedItems */);
};
const groupItems = function (items) {
if (items.length > 0) {
const group = new paper.Group(items);
// @todo: Set selection bounds; enable/disable grouping icons
// @todo add back undo
// pg.undo.snapshot('groupItems');
return group;
}
return false;
ungroupItems(items, setSelectedItems, onUpdateSvg);
};
const getItemsGroup = function (item) {

View file

@ -49,6 +49,18 @@ class BoundingBoxTool {
this._modeMap[BoundingBoxModes.MOVE] = new MoveTool(mode, setSelectedItems, clearSelectedItems, onUpdateSvg);
}
/**
* Should be called if the selection changes to update the bounds of the bounding box.
* @param {Array<paper.Item>} selectedItems Array of selected items.
*/
onSelectionChanged (selectedItems) {
if (selectedItems) {
this.setSelectionBounds();
} else {
this.removeBoundsPath();
}
}
/**
* @param {!MouseEvent} event The mouse event
* @param {boolean} clone Whether to clone on mouse down (e.g. alt key held)

View file

@ -57,6 +57,13 @@ class SelectTool extends paper.Tool {
setPrevHoveredItemId (prevHoveredItemId) {
this.prevHoveredItemId = prevHoveredItemId;
}
/**
* Should be called if the selection changes to update the bounds of the bounding box.
* @param {Array<paper.Item>} selectedItems Array of selected items.
*/
onSelectionChanged (selectedItems) {
this.boundingBoxTool.onSelectionChanged(selectedItems);
}
/**
* Returns the hit options to use when conducting hit tests.
* @param {boolean} preselectedOnly True if we should only return results that are already

View file

@ -47,6 +47,13 @@ class OvalTool extends paper.Tool {
tolerance: OvalTool.TOLERANCE / paper.view.zoom
};
}
/**
* Should be called if the selection changes to update the bounds of the bounding box.
* @param {Array<paper.Item>} selectedItems Array of selected items.
*/
onSelectionChanged (selectedItems) {
this.boundingBoxTool.onSelectionChanged(selectedItems);
}
setColorState (colorState) {
this.colorState = colorState;
}

View file

@ -47,6 +47,13 @@ class RectTool extends paper.Tool {
tolerance: RectTool.TOLERANCE / paper.view.zoom
};
}
/**
* Should be called if the selection changes to update the bounds of the bounding box.
* @param {Array<paper.Item>} selectedItems Array of selected items.
*/
onSelectionChanged (selectedItems) {
this.boundingBoxTool.onSelectionChanged(selectedItems);
}
setColorState (colorState) {
this.colorState = colorState;
}

View file

@ -11,19 +11,21 @@ const performSnapshot = function (dispatchPerformSnapshot) {
// updateButtonVisibility();
};
const _restore = function (entry, onUpdateSvg) {
const _restore = function (entry, setSelectedItems, onUpdateSvg) {
for (const layer of paper.project.layers) {
layer.removeChildren();
}
paper.project.clear();
paper.project.importJSON(entry.json);
paper.view.update();
setSelectedItems();
onUpdateSvg(true /* skipSnapshot */);
};
const performUndo = function (undoState, dispatchPerformUndo, onUpdateSvg) {
const performUndo = function (undoState, dispatchPerformUndo, setSelectedItems, onUpdateSvg) {
if (undoState.pointer > 0) {
_restore(undoState.stack[undoState.pointer - 1], onUpdateSvg);
_restore(undoState.stack[undoState.pointer - 1], setSelectedItems, onUpdateSvg);
dispatchPerformUndo();
// @todo enable/disable buttons
@ -32,9 +34,9 @@ const performUndo = function (undoState, dispatchPerformUndo, onUpdateSvg) {
};
const performRedo = function (undoState, dispatchPerformRedo, onUpdateSvg) {
const performRedo = function (undoState, dispatchPerformRedo, setSelectedItems, onUpdateSvg) {
if (undoState.pointer >= 0 && undoState.pointer < undoState.stack.length - 1) {
_restore(undoState.stack[undoState.pointer + 1], onUpdateSvg);
_restore(undoState.stack[undoState.pointer + 1], setSelectedItems, onUpdateSvg);
dispatchPerformRedo();
// @todo enable/disable buttons

View file

@ -1,8 +1,8 @@
import PaintEditor from './containers/paint-editor.jsx';
import SelectionHOV from './containers/selection-hoc.jsx';
import SelectionHOC from './containers/selection-hoc.jsx';
import ScratchPaintReducer from './reducers/scratch-paint-reducer';
const Wrapped = SelectionHOV(PaintEditor);
const Wrapped = SelectionHOC(PaintEditor);
export {
Wrapped as default,