mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
add comments and clean up
This commit is contained in:
parent
dc66283bd2
commit
5db66af0a1
12 changed files with 109 additions and 90 deletions
|
@ -4,5 +4,7 @@
|
||||||
margin: auto;
|
margin: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
/* Turn off anti-aliasing for the drawing canvas. Each time it's updated it switches
|
||||||
|
back and forth from aliased to unaliased and that looks bad */
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
|
||||||
const SelectionHOV = function (WrappedComponent) {
|
const SelectionHOC = function (WrappedComponent) {
|
||||||
class SelectionComponent extends React.Component {
|
class SelectionComponent extends React.Component {
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
if (this.props.hoveredItem) {
|
if (this.props.hoveredItem) {
|
||||||
|
@ -43,4 +43,4 @@ const SelectionHOV = function (WrappedComponent) {
|
||||||
)(SelectionComponent);
|
)(SelectionComponent);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SelectionHOV;
|
export default SelectionHOC;
|
|
@ -1,6 +1,6 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
import {getGuideLayer} from './layer';
|
import {getGuideLayer} from './layer';
|
||||||
import {removePaperItemsByTags, removePaperItemsByDataTags} from './helper';
|
import {getAllPaperItems} from './selection';
|
||||||
|
|
||||||
const GUIDE_BLUE = '#009dec';
|
const GUIDE_BLUE = '#009dec';
|
||||||
const GUIDE_GREY = '#aaaaaa';
|
const GUIDE_GREY = '#aaaaaa';
|
||||||
|
@ -65,12 +65,34 @@ const getGuideColor = function (colorName) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const removeHelperItems = function () {
|
const removeHelperItems = function () {
|
||||||
removePaperItemsByDataTags(['isHelperItem']);
|
_removePaperItemsByDataTags(['isHelperItem']);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeAllGuides = function () {
|
const removeAllGuides = function () {
|
||||||
removePaperItemsByTags(['guide']);
|
_removePaperItemsByTags(['guide']);
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
import paper from 'paper';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {boolean} includeGuides True if guide layer items like the bounding box should
|
|
||||||
* be included in the returned items.
|
|
||||||
* @return {Array<paper.item>} all top-level (direct descendants of a paper.Layer) items
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
};
|
|
|
@ -24,7 +24,7 @@ const Modes = keyMirror({
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A paper.Tool that handles transforming the selection and drawing a bounding box with handles.
|
* 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
|
* 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
|
* (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.
|
* delegates actions to the MoveTool, RotateTool or ScaleTool accordingly.
|
||||||
|
|
|
@ -3,6 +3,9 @@ import {isCompoundPathItem, getRootItem} from '../item';
|
||||||
import {snapDeltaToAngle} from '../math';
|
import {snapDeltaToAngle} from '../math';
|
||||||
import {clearSelection, cloneSelection, getSelectedItems, setItemSelection} from '../selection';
|
import {clearSelection, cloneSelection, getSelectedItems, setItemSelection} from '../selection';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool to handle dragging an item to reposition it in a selection mode.
|
||||||
|
*/
|
||||||
class MoveTool {
|
class MoveTool {
|
||||||
/**
|
/**
|
||||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool to handle rotation when dragging the rotation handle in the bounding box tool.
|
||||||
|
*/
|
||||||
class RotateTool {
|
class RotateTool {
|
||||||
/**
|
/**
|
||||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool to handle scaling items by pulling on the handles around the edges of the bounding
|
||||||
|
* box when in the bounding box tool.
|
||||||
|
*/
|
||||||
class ScaleTool {
|
class ScaleTool {
|
||||||
/**
|
/**
|
||||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
|
@ -34,9 +38,9 @@ class ScaleTool {
|
||||||
this.boundsPath = boundsPath;
|
this.boundsPath = boundsPath;
|
||||||
this.boundsScaleHandles = boundsScaleHandles;
|
this.boundsScaleHandles = boundsScaleHandles;
|
||||||
this.boundsRotHandles = boundsRotHandles;
|
this.boundsRotHandles = boundsRotHandles;
|
||||||
this.pivot = this.boundsPath.bounds[this.getOpposingRectCornerNameByIndex(index)].clone();
|
this.pivot = this.boundsPath.bounds[this._getOpposingRectCornerNameByIndex(index)].clone();
|
||||||
this.origPivot = 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.corner = this.boundsPath.bounds[this._getRectCornerNameByIndex(index)].clone();
|
||||||
this.origSize = this.corner.subtract(this.pivot);
|
this.origSize = this.corner.subtract(this.pivot);
|
||||||
this.origCenter = this.boundsPath.bounds.center;
|
this.origCenter = this.boundsPath.bounds.center;
|
||||||
for (const item of selectedItems) {
|
for (const item of selectedItems) {
|
||||||
|
@ -105,14 +109,14 @@ class ScaleTool {
|
||||||
|
|
||||||
for (let i = 0; i < this.boundsScaleHandles.length; i++) {
|
for (let i = 0; i < this.boundsScaleHandles.length; i++) {
|
||||||
const handle = this.boundsScaleHandles[i];
|
const handle = this.boundsScaleHandles[i];
|
||||||
handle.position = this.itemGroup.bounds[this.getRectCornerNameByIndex(i)];
|
handle.position = this.itemGroup.bounds[this._getRectCornerNameByIndex(i)];
|
||||||
handle.bringToFront();
|
handle.bringToFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < this.boundsRotHandles.length; i++) {
|
for (let i = 0; i < this.boundsRotHandles.length; i++) {
|
||||||
const handle = this.boundsRotHandles[i];
|
const handle = this.boundsRotHandles[i];
|
||||||
if (handle) {
|
if (handle) {
|
||||||
handle.position = this.itemGroup.bounds[this.getRectCornerNameByIndex(i)] + handle.data.offset;
|
handle.position = this.itemGroup.bounds[this._getRectCornerNameByIndex(i)] + handle.data.offset;
|
||||||
handle.bringToFront();
|
handle.bringToFront();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,7 +160,7 @@ class ScaleTool {
|
||||||
// @todo add back undo
|
// @todo add back undo
|
||||||
this.onUpdateSvg();
|
this.onUpdateSvg();
|
||||||
}
|
}
|
||||||
getRectCornerNameByIndex (index) {
|
_getRectCornerNameByIndex (index) {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
return 'bottomLeft';
|
return 'bottomLeft';
|
||||||
|
@ -176,7 +180,7 @@ class ScaleTool {
|
||||||
return 'bottomCenter';
|
return 'bottomCenter';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getOpposingRectCornerNameByIndex (index) {
|
_getOpposingRectCornerNameByIndex (index) {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
return 'topRight';
|
return 'topRight';
|
||||||
|
|
|
@ -6,10 +6,23 @@ import BoundingBoxTool from './bounding-box-tool';
|
||||||
import SelectionBoxTool from './selection-box-tool';
|
import SelectionBoxTool from './selection-box-tool';
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* paper.Tool that handles select mode. This is made up of 2 subtools.
|
||||||
|
* - The selection box tool is active when the user clicks an empty space and drags.
|
||||||
|
* It selects all items in the rectangle.
|
||||||
|
* - The bounding box tool is active if the user clicks on a non-empty space. It handles
|
||||||
|
* reshaping the item that was clicked.
|
||||||
|
*/
|
||||||
class SelectTool extends paper.Tool {
|
class SelectTool extends paper.Tool {
|
||||||
|
/** The distance within which mouse events count as a hit against an item */
|
||||||
static get TOLERANCE () {
|
static get TOLERANCE () {
|
||||||
return 6;
|
return 6;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @param {function} setHoveredItem Callback to set the hovered item
|
||||||
|
* @param {function} clearHoveredItem Callback to clear the hovered item
|
||||||
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
|
*/
|
||||||
constructor (setHoveredItem, clearHoveredItem, onUpdateSvg) {
|
constructor (setHoveredItem, clearHoveredItem, onUpdateSvg) {
|
||||||
super();
|
super();
|
||||||
this.setHoveredItem = setHoveredItem;
|
this.setHoveredItem = setHoveredItem;
|
||||||
|
@ -18,13 +31,6 @@ class SelectTool extends paper.Tool {
|
||||||
this.boundingBoxTool = new BoundingBoxTool(onUpdateSvg);
|
this.boundingBoxTool = new BoundingBoxTool(onUpdateSvg);
|
||||||
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT);
|
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT);
|
||||||
this.selectionBoxMode = false;
|
this.selectionBoxMode = false;
|
||||||
this._hitOptions = {
|
|
||||||
segments: true,
|
|
||||||
stroke: true,
|
|
||||||
curves: true,
|
|
||||||
fill: true,
|
|
||||||
guide: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// We have to set these functions instead of just declaring them because
|
// We have to set these functions instead of just declaring them because
|
||||||
// paper.js tools hook up the listeners in the setter functions.
|
// paper.js tools hook up the listeners in the setter functions.
|
||||||
|
@ -37,21 +43,42 @@ class SelectTool extends paper.Tool {
|
||||||
selectRootItem();
|
selectRootItem();
|
||||||
this.boundingBoxTool.setSelectionBounds();
|
this.boundingBoxTool.setSelectionBounds();
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* To be called when the hovered item changes. When the select tool hovers over a
|
||||||
|
* new item, it compares against this to see if a hover item change event needs to
|
||||||
|
* be fired.
|
||||||
|
* @param {paper.Item} prevHoveredItem The highlight that indicates the mouse is over
|
||||||
|
* a given item currently
|
||||||
|
*/
|
||||||
setPrevHoveredItem (prevHoveredItem) {
|
setPrevHoveredItem (prevHoveredItem) {
|
||||||
this.prevHoveredItem = prevHoveredItem;
|
this.prevHoveredItem = prevHoveredItem;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Returns the hit options to use when conducting hit tests.
|
||||||
|
* @param {boolean} preselectedOnly True if we should only return results that are already
|
||||||
|
* selected.
|
||||||
|
* @return {object} See paper.Item.hitTest for definition of options
|
||||||
|
*/
|
||||||
getHitOptions (preselectedOnly) {
|
getHitOptions (preselectedOnly) {
|
||||||
this._hitOptions.tolerance = SelectTool.TOLERANCE / paper.view.zoom;
|
// Tolerance needs to be scaled when the view is zoomed in in order to represent the same
|
||||||
|
// distance for the user to move the mouse.
|
||||||
|
const hitOptions = {
|
||||||
|
segments: true,
|
||||||
|
stroke: true,
|
||||||
|
curves: true,
|
||||||
|
fill: true,
|
||||||
|
guide: false,
|
||||||
|
tolerance: SelectTool.TOLERANCE / paper.view.zoom
|
||||||
|
};
|
||||||
if (preselectedOnly) {
|
if (preselectedOnly) {
|
||||||
this._hitOptions.selected = true;
|
hitOptions.selected = true;
|
||||||
} else {
|
|
||||||
delete this._hitOptions.selected;
|
|
||||||
}
|
}
|
||||||
return this._hitOptions;
|
return hitOptions;
|
||||||
}
|
}
|
||||||
handleMouseDown (event) {
|
handleMouseDown (event) {
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
|
|
||||||
|
// If bounding box tool does not find an item that was hit, use selection box tool.
|
||||||
this.clearHoveredItem();
|
this.clearHoveredItem();
|
||||||
if (!this.boundingBoxTool
|
if (!this.boundingBoxTool
|
||||||
.onMouseDown(
|
.onMouseDown(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {rectSelect} from '../guides';
|
import {rectSelect} from '../guides';
|
||||||
import {clearSelection, processRectangularSelection} from '../selection';
|
import {clearSelection, processRectangularSelection} from '../selection';
|
||||||
|
|
||||||
|
/** Tool to handle drag selection. A dotted line box appears and everything enclosed is selected. */
|
||||||
class SelectionBoxTool {
|
class SelectionBoxTool {
|
||||||
constructor (mode) {
|
constructor (mode) {
|
||||||
this.selectionRect = null;
|
this.selectionRect = null;
|
||||||
|
|
|
@ -1,11 +1,30 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
import Modes from '../modes/modes';
|
import Modes from '../modes/modes';
|
||||||
|
|
||||||
import {getAllPaperItems} from './helper';
|
|
||||||
import {getItemsGroup, isGroup} from './group';
|
import {getItemsGroup, isGroup} from './group';
|
||||||
import {getRootItem, isCompoundPathItem, isBoundsItem, isPathItem, isPGTextItem} from './item';
|
import {getRootItem, isCompoundPathItem, isBoundsItem, isPathItem, isPGTextItem} from './item';
|
||||||
import {getItemsCompoundPath, isCompoundPath, isCompoundPathChild} from './compound-path';
|
import {getItemsCompoundPath, isCompoundPath, isCompoundPathChild} from './compound-path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} includeGuides True if guide layer items like the bounding box should
|
||||||
|
* be included in the returned items.
|
||||||
|
* @return {Array<paper.item>} all top-level (direct descendants of a paper.Layer) items
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Array<paper.item>} all top-level (direct descendants of a paper.Layer) items
|
* @return {Array<paper.item>} all top-level (direct descendants of a paper.Layer) items
|
||||||
* that aren't guide items or helper items.
|
* that aren't guide items or helper items.
|
||||||
|
@ -489,6 +508,7 @@ const shouldShowSelectAll = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
getAllPaperItems,
|
||||||
selectAllItems,
|
selectAllItems,
|
||||||
selectAllSegments,
|
selectAllSegments,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import PaintEditor from './containers/paint-editor.jsx';
|
import PaintEditor from './containers/paint-editor.jsx';
|
||||||
import SelectionHOV from './containers/selection-hov.jsx';
|
import SelectionHOV from './containers/selection-hoc.jsx';
|
||||||
import ScratchPaintReducer from './reducers/scratch-paint-reducer';
|
import ScratchPaintReducer from './reducers/scratch-paint-reducer';
|
||||||
|
|
||||||
const Wrapped = SelectionHOV(PaintEditor);
|
const Wrapped = SelectionHOV(PaintEditor);
|
||||||
|
|
Loading…
Reference in a new issue