mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-23 05:52:42 -05:00
parent
6f89d8956a
commit
4fe7c0ab8b
10 changed files with 199 additions and 34 deletions
|
@ -97,7 +97,7 @@ class PaperCanvas extends React.Component {
|
||||||
for (const layer of paper.project.layers) {
|
for (const layer of paper.project.layers) {
|
||||||
if (layer.data.isRasterLayer) {
|
if (layer.data.isRasterLayer) {
|
||||||
clearRaster();
|
clearRaster();
|
||||||
} else if (!layer.data.isBackgroundGuideLayer) {
|
} else if (!layer.data.isBackgroundGuideLayer && !layer.data.isDragCrosshairLayer) {
|
||||||
layer.removeChildren();
|
layer.removeChildren();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
src/helper/icons/costume-anchor.svg
Normal file
16
src/helper/icons/costume-anchor.svg
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 58 (84663) - https://sketch.com -->
|
||||||
|
<title>Paint/Center Anchor/Active</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="Paint/Center-Anchor/Active" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.65" stroke-linecap="round">
|
||||||
|
<g id="anchor" transform="translate(8.000000, 8.000000)">
|
||||||
|
<circle id="Oval-4" stroke="#FFFFFF" stroke-width="9" cx="12" cy="12" r="9"></circle>
|
||||||
|
<path d="M12,0 L12,24" id="Line" stroke="#FFFFFF" stroke-width="9"></path>
|
||||||
|
<path d="M12,0 L12,24" id="Line" stroke="#FFFFFF" stroke-width="9" transform="translate(12.000000, 12.000000) rotate(-270.000000) translate(-12.000000, -12.000000) "></path>
|
||||||
|
<circle id="Oval-4" stroke="#000000" stroke-width="3" cx="12" cy="12" r="9"></circle>
|
||||||
|
<path d="M12,0 L12,24" id="Line" stroke="#000000" stroke-width="3"></path>
|
||||||
|
<path d="M12,0 L12,24" id="Line" stroke="#000000" stroke-width="3" transform="translate(12.000000, 12.000000) rotate(-270.000000) translate(-12.000000, -12.000000) "></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
12
src/helper/icons/selection-anchor-expanded.svg
Normal file
12
src/helper/icons/selection-anchor-expanded.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
|
||||||
|
<title>Paint/Center Anchor/Artwork Center Expanded</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="Paint/Center-Anchor/Artwork-Center-Expanded" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.65">
|
||||||
|
<g id="anchor" transform="translate(3.000000, 3.000000)" fill-rule="nonzero">
|
||||||
|
<path d="M7,0 C7.55228475,0 8,0.44771525 8,1 L8,1 L8,5.999 L13,6 C13.5128358,6 13.9355072,6.38604019 13.9932723,6.88337887 L14,7 C14,7.55228475 13.5522847,8 13,8 L13,8 L8,7.999 L8,13 C8,13.5128358 7.61395981,13.9355072 7.11662113,13.9932723 L7,14 C6.44771525,14 6,13.5522847 6,13 L6,13 L6,7.999 L1,8 C0.487164161,8 0.0644928393,7.61395981 0.00672773133,7.11662113 L1.77635684e-15,7 C1.77635684e-15,6.44771525 0.44771525,6 1,6 L1,6 L6,5.999 L6,1 C6,0.487164161 6.38604019,0.0644928393 6.88337887,0.00672773133 Z" id="White-Outline" fill="#FFFFFF"></path>
|
||||||
|
<path d="M7,0.5 C7.27614237,0.5 7.5,0.723857625 7.5,1 L7.5,1 L7.5,6.499 L13,6.5 C13.2454599,6.5 13.4496084,6.67687516 13.4919443,6.91012437 L13.5,7 C13.5,7.27614237 13.2761424,7.5 13,7.5 L13,7.5 L7.5,7.499 L7.5,13 C7.5,13.2454599 7.32312484,13.4496084 7.08987563,13.4919443 L7,13.5 C6.72385763,13.5 6.5,13.2761424 6.5,13 L6.5,13 L6.5,7.499 L1,7.5 C0.754540111,7.5 0.55039163,7.32312484 0.508055669,7.08987563 L0.5,7 C0.5,6.72385763 0.723857625,6.5 1,6.5 L1,6.5 L6.5,6.499 L6.5,1 C6.5,0.754540111 6.67687516,0.55039163 6.91012437,0.508055669 Z" id="Black-Outline" fill="#000000"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -1,6 +1,10 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import log from '../log/log';
|
import log from '../log/log';
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from './view';
|
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from './view';
|
||||||
|
import {isGroupItem} from './item';
|
||||||
|
import costumeAnchorIcon from './icons/costume-anchor.svg';
|
||||||
|
|
||||||
|
const CROSSHAIR_SIZE = 28;
|
||||||
|
|
||||||
const _getLayer = function (layerString) {
|
const _getLayer = function (layerString) {
|
||||||
for (const layer of paper.project.layers) {
|
for (const layer of paper.project.layers) {
|
||||||
|
@ -50,6 +54,10 @@ const getRaster = function () {
|
||||||
return _getLayer('isRasterLayer').children[0];
|
return _getLayer('isRasterLayer').children[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDragCrosshairLayer = function () {
|
||||||
|
return _getLayer('isDragCrosshairLayer');
|
||||||
|
};
|
||||||
|
|
||||||
const getBackgroundGuideLayer = function () {
|
const getBackgroundGuideLayer = function () {
|
||||||
return _getLayer('isBackgroundGuideLayer');
|
return _getLayer('isBackgroundGuideLayer');
|
||||||
};
|
};
|
||||||
|
@ -69,6 +77,16 @@ const getGuideLayer = function () {
|
||||||
return layer;
|
return layer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setGuideItem = function (item) {
|
||||||
|
item.locked = true;
|
||||||
|
item.guide = true;
|
||||||
|
if (isGroupItem(item)) {
|
||||||
|
for (let i = 0; i < item.children.length; i++) {
|
||||||
|
setGuideItem(item.children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the guide layers, e.g. for purposes of exporting the image. Must call showGuideLayers to re-add them.
|
* Removes the guide layers, e.g. for purposes of exporting the image. Must call showGuideLayers to re-add them.
|
||||||
* @param {boolean} includeRaster true if the raster layer should also be hidden
|
* @param {boolean} includeRaster true if the raster layer should also be hidden
|
||||||
|
@ -76,7 +94,9 @@ const getGuideLayer = function () {
|
||||||
*/
|
*/
|
||||||
const hideGuideLayers = function (includeRaster) {
|
const hideGuideLayers = function (includeRaster) {
|
||||||
const backgroundGuideLayer = getBackgroundGuideLayer();
|
const backgroundGuideLayer = getBackgroundGuideLayer();
|
||||||
|
const dragCrosshairLayer = getDragCrosshairLayer();
|
||||||
const guideLayer = getGuideLayer();
|
const guideLayer = getGuideLayer();
|
||||||
|
dragCrosshairLayer.remove();
|
||||||
guideLayer.remove();
|
guideLayer.remove();
|
||||||
backgroundGuideLayer.remove();
|
backgroundGuideLayer.remove();
|
||||||
let rasterLayer;
|
let rasterLayer;
|
||||||
|
@ -85,6 +105,7 @@ const hideGuideLayers = function (includeRaster) {
|
||||||
rasterLayer.remove();
|
rasterLayer.remove();
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
dragCrosshairLayer: dragCrosshairLayer,
|
||||||
guideLayer: guideLayer,
|
guideLayer: guideLayer,
|
||||||
backgroundGuideLayer: backgroundGuideLayer,
|
backgroundGuideLayer: backgroundGuideLayer,
|
||||||
rasterLayer: rasterLayer
|
rasterLayer: rasterLayer
|
||||||
|
@ -98,6 +119,7 @@ const hideGuideLayers = function (includeRaster) {
|
||||||
*/
|
*/
|
||||||
const showGuideLayers = function (guideLayers) {
|
const showGuideLayers = function (guideLayers) {
|
||||||
const backgroundGuideLayer = guideLayers.backgroundGuideLayer;
|
const backgroundGuideLayer = guideLayers.backgroundGuideLayer;
|
||||||
|
const dragCrosshairLayer = guideLayers.dragCrosshairLayer;
|
||||||
const guideLayer = guideLayers.guideLayer;
|
const guideLayer = guideLayers.guideLayer;
|
||||||
const rasterLayer = guideLayers.rasterLayer;
|
const rasterLayer = guideLayers.rasterLayer;
|
||||||
if (rasterLayer && !rasterLayer.index) {
|
if (rasterLayer && !rasterLayer.index) {
|
||||||
|
@ -108,6 +130,10 @@ const showGuideLayers = function (guideLayers) {
|
||||||
paper.project.addLayer(backgroundGuideLayer);
|
paper.project.addLayer(backgroundGuideLayer);
|
||||||
backgroundGuideLayer.sendToBack();
|
backgroundGuideLayer.sendToBack();
|
||||||
}
|
}
|
||||||
|
if (!dragCrosshairLayer.index) {
|
||||||
|
paper.project.addLayer(dragCrosshairLayer);
|
||||||
|
dragCrosshairLayer.bringToFront();
|
||||||
|
}
|
||||||
if (!guideLayer.index) {
|
if (!guideLayer.index) {
|
||||||
paper.project.addLayer(guideLayer);
|
paper.project.addLayer(guideLayer);
|
||||||
guideLayer.bringToFront();
|
guideLayer.bringToFront();
|
||||||
|
@ -163,6 +189,29 @@ const _makeBackgroundPaper = function (width, height, color) {
|
||||||
return vGroup;
|
return vGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function for drawing a crosshair
|
||||||
|
const _makeCrosshair = function (opacity, parent) {
|
||||||
|
paper.project.importSVG(costumeAnchorIcon, {
|
||||||
|
applyMatrix: false,
|
||||||
|
onLoad: function (item) {
|
||||||
|
item.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
||||||
|
item.opacity = opacity;
|
||||||
|
item.parent = parent;
|
||||||
|
parent.dragCrosshair = item;
|
||||||
|
item.scale(CROSSHAIR_SIZE / item.bounds.width / paper.view.zoom);
|
||||||
|
setGuideItem(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const _makeDragCrosshairLayer = function () {
|
||||||
|
const dragCrosshairLayer = new paper.Layer();
|
||||||
|
_makeCrosshair(1, dragCrosshairLayer);
|
||||||
|
dragCrosshairLayer.data.isDragCrosshairLayer = true;
|
||||||
|
dragCrosshairLayer.visible = false;
|
||||||
|
return dragCrosshairLayer;
|
||||||
|
};
|
||||||
|
|
||||||
const _makeBackgroundGuideLayer = function () {
|
const _makeBackgroundGuideLayer = function () {
|
||||||
const guideLayer = new paper.Layer();
|
const guideLayer = new paper.Layer();
|
||||||
guideLayer.locked = true;
|
guideLayer.locked = true;
|
||||||
|
@ -173,26 +222,7 @@ const _makeBackgroundGuideLayer = function () {
|
||||||
vBackground.guide = true;
|
vBackground.guide = true;
|
||||||
vBackground.locked = true;
|
vBackground.locked = true;
|
||||||
|
|
||||||
const vLine = new paper.Path.Line(new paper.Point(0, -7), new paper.Point(0, 7));
|
_makeCrosshair(0.25, guideLayer);
|
||||||
vLine.strokeWidth = 2;
|
|
||||||
vLine.strokeColor = '#ccc';
|
|
||||||
vLine.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
|
||||||
vLine.guide = true;
|
|
||||||
vLine.locked = true;
|
|
||||||
|
|
||||||
const hLine = new paper.Path.Line(new paper.Point(-7, 0), new paper.Point(7, 0));
|
|
||||||
hLine.strokeWidth = 2;
|
|
||||||
hLine.strokeColor = '#ccc';
|
|
||||||
hLine.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
|
||||||
hLine.guide = true;
|
|
||||||
hLine.locked = true;
|
|
||||||
|
|
||||||
const circle = new paper.Shape.Circle(new paper.Point(0, 0), 5);
|
|
||||||
circle.strokeWidth = 2;
|
|
||||||
circle.strokeColor = '#ccc';
|
|
||||||
circle.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
|
||||||
circle.guide = true;
|
|
||||||
circle.locked = true;
|
|
||||||
|
|
||||||
guideLayer.data.isBackgroundGuideLayer = true;
|
guideLayer.data.isBackgroundGuideLayer = true;
|
||||||
return guideLayer;
|
return guideLayer;
|
||||||
|
@ -202,19 +232,24 @@ const setupLayers = function () {
|
||||||
const backgroundGuideLayer = _makeBackgroundGuideLayer();
|
const backgroundGuideLayer = _makeBackgroundGuideLayer();
|
||||||
_makeRasterLayer();
|
_makeRasterLayer();
|
||||||
const paintLayer = _makePaintingLayer();
|
const paintLayer = _makePaintingLayer();
|
||||||
|
const dragCrosshairLayer = _makeDragCrosshairLayer();
|
||||||
const guideLayer = _makeGuideLayer();
|
const guideLayer = _makeGuideLayer();
|
||||||
backgroundGuideLayer.sendToBack();
|
backgroundGuideLayer.sendToBack();
|
||||||
|
dragCrosshairLayer.bringToFront();
|
||||||
guideLayer.bringToFront();
|
guideLayer.bringToFront();
|
||||||
paintLayer.activate();
|
paintLayer.activate();
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
CROSSHAIR_SIZE,
|
||||||
createCanvas,
|
createCanvas,
|
||||||
hideGuideLayers,
|
hideGuideLayers,
|
||||||
showGuideLayers,
|
showGuideLayers,
|
||||||
|
getDragCrosshairLayer,
|
||||||
getGuideLayer,
|
getGuideLayer,
|
||||||
getBackgroundGuideLayer,
|
getBackgroundGuideLayer,
|
||||||
clearRaster,
|
clearRaster,
|
||||||
getRaster,
|
getRaster,
|
||||||
|
setGuideItem,
|
||||||
setupLayers
|
setupLayers
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,13 +3,15 @@ import keyMirror from 'keymirror';
|
||||||
|
|
||||||
import {getSelectedRootItems} from '../selection';
|
import {getSelectedRootItems} from '../selection';
|
||||||
import {getGuideColor, removeBoundsPath, removeBoundsHandles} from '../guides';
|
import {getGuideColor, removeBoundsPath, removeBoundsHandles} from '../guides';
|
||||||
import {getGuideLayer} from '../layer';
|
import {getGuideLayer, setGuideItem} from '../layer';
|
||||||
|
import selectionAnchorIcon from '../icons/selection-anchor-expanded.svg';
|
||||||
|
|
||||||
import Cursors from '../../lib/cursors';
|
import Cursors from '../../lib/cursors';
|
||||||
import ScaleTool from './scale-tool';
|
import ScaleTool from './scale-tool';
|
||||||
import RotateTool from './rotate-tool';
|
import RotateTool from './rotate-tool';
|
||||||
import MoveTool from './move-tool';
|
import MoveTool from './move-tool';
|
||||||
|
|
||||||
|
const SELECTION_ANCHOR_SIZE = 20;
|
||||||
/** SVG for the rotation icon on the bounding box */
|
/** SVG for the rotation icon on the bounding box */
|
||||||
const ARROW_PATH = 'M19.28,1.09C19.28.28,19,0,18.2,0c-1.67,0-3.34,0-5,0-.34,0-.88.24-1,.47a1.4,1.4,' +
|
const ARROW_PATH = 'M19.28,1.09C19.28.28,19,0,18.2,0c-1.67,0-3.34,0-5,0-.34,0-.88.24-1,.47a1.4,1.4,' +
|
||||||
'0,0,0,.36,1.08,15.27,15.27,0,0,0,1.46,1.36A6.4,6.4,0,0,1,6.52,4,5.85,5.85,0,0,1,5.24,3,15.27,15.27,' +
|
'0,0,0,.36,1.08,15.27,15.27,0,0,0,1.46,1.36A6.4,6.4,0,0,1,6.52,4,5.85,5.85,0,0,1,5.24,3,15.27,15.27,' +
|
||||||
|
@ -23,6 +25,7 @@ const BoundingBoxModes = keyMirror({
|
||||||
ROTATE: null,
|
ROTATE: null,
|
||||||
MOVE: null
|
MOVE: null
|
||||||
});
|
});
|
||||||
|
let anchorIcon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -77,6 +80,17 @@ class BoundingBoxTool {
|
||||||
* @return {boolean} True if there was a hit, false otherwise
|
* @return {boolean} True if there was a hit, false otherwise
|
||||||
*/
|
*/
|
||||||
onMouseDown (event, clone, multiselect, doubleClicked, hitOptions) {
|
onMouseDown (event, clone, multiselect, doubleClicked, hitOptions) {
|
||||||
|
if (!anchorIcon) {
|
||||||
|
paper.project.importSVG(selectionAnchorIcon, {
|
||||||
|
onLoad: function (item) {
|
||||||
|
anchorIcon = item;
|
||||||
|
item.visible = false;
|
||||||
|
item.parent = getGuideLayer();
|
||||||
|
setGuideItem(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
const {hitResult, mode} = this._determineMode(event, multiselect, hitOptions);
|
const {hitResult, mode} = this._determineMode(event, multiselect, hitOptions);
|
||||||
if (!hitResult) {
|
if (!hitResult) {
|
||||||
|
@ -204,11 +218,17 @@ class BoundingBoxTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.boundsPath) {
|
if (!this.boundsPath) {
|
||||||
this.boundsPath = new paper.Path.Rectangle(rect);
|
this.boundsPath = new paper.Group();
|
||||||
this.boundsPath.curves[0].divideAtTime(0.5);
|
this.boundsRect = paper.Path.Rectangle(rect);
|
||||||
this.boundsPath.curves[2].divideAtTime(0.5);
|
this.boundsRect.curves[0].divideAtTime(0.5);
|
||||||
this.boundsPath.curves[4].divideAtTime(0.5);
|
this.boundsRect.curves[2].divideAtTime(0.5);
|
||||||
this.boundsPath.curves[6].divideAtTime(0.5);
|
this.boundsRect.curves[4].divideAtTime(0.5);
|
||||||
|
this.boundsRect.curves[6].divideAtTime(0.5);
|
||||||
|
this.boundsPath.addChild(this.boundsRect);
|
||||||
|
if (anchorIcon) {
|
||||||
|
this.boundsPath.addChild(anchorIcon);
|
||||||
|
this.boundsPath.selectionAnchor = anchorIcon;
|
||||||
|
}
|
||||||
this._modeMap[BoundingBoxModes.MOVE].setBoundsPath(this.boundsPath);
|
this._modeMap[BoundingBoxModes.MOVE].setBoundsPath(this.boundsPath);
|
||||||
}
|
}
|
||||||
this.boundsPath.guide = true;
|
this.boundsPath.guide = true;
|
||||||
|
@ -219,6 +239,12 @@ class BoundingBoxTool {
|
||||||
this.boundsPath.strokeWidth = 1 / paper.view.zoom;
|
this.boundsPath.strokeWidth = 1 / paper.view.zoom;
|
||||||
this.boundsPath.strokeColor = getGuideColor();
|
this.boundsPath.strokeColor = getGuideColor();
|
||||||
|
|
||||||
|
if (anchorIcon) {
|
||||||
|
anchorIcon.visible = true;
|
||||||
|
anchorIcon.scale(SELECTION_ANCHOR_SIZE / paper.view.zoom / anchorIcon.bounds.width);
|
||||||
|
anchorIcon.position = rect.center;
|
||||||
|
}
|
||||||
|
|
||||||
// Make a template to copy
|
// Make a template to copy
|
||||||
const boundsScaleCircleShadow =
|
const boundsScaleCircleShadow =
|
||||||
new paper.Path.Circle({
|
new paper.Path.Circle({
|
||||||
|
@ -247,8 +273,8 @@ class BoundingBoxTool {
|
||||||
const boundsScaleHandle = new paper.Group([boundsScaleCircleShadow, boundsScaleCircle]);
|
const boundsScaleHandle = new paper.Group([boundsScaleCircleShadow, boundsScaleCircle]);
|
||||||
boundsScaleHandle.parent = getGuideLayer();
|
boundsScaleHandle.parent = getGuideLayer();
|
||||||
|
|
||||||
for (let index = 0; index < this.boundsPath.segments.length; index++) {
|
for (let index = 0; index < this.boundsRect.segments.length; index++) {
|
||||||
const segment = this.boundsPath.segments[index];
|
const segment = this.boundsRect.segments[index];
|
||||||
|
|
||||||
if (index === 7) {
|
if (index === 7) {
|
||||||
const offset = new paper.Point(0, 20);
|
const offset = new paper.Point(0, 20);
|
||||||
|
@ -295,8 +321,12 @@ class BoundingBoxTool {
|
||||||
removeBoundsPath () {
|
removeBoundsPath () {
|
||||||
removeBoundsPath();
|
removeBoundsPath();
|
||||||
this.boundsPath = null;
|
this.boundsPath = null;
|
||||||
|
this.boundsRect = null;
|
||||||
this.boundsScaleHandles.length = 0;
|
this.boundsScaleHandles.length = 0;
|
||||||
this.boundsRotHandles.length = 0;
|
this.boundsRotHandles.length = 0;
|
||||||
|
if (anchorIcon) {
|
||||||
|
anchorIcon.visible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
removeBoundsHandles () {
|
removeBoundsHandles () {
|
||||||
removeBoundsHandles();
|
removeBoundsHandles();
|
||||||
|
|
|
@ -2,10 +2,14 @@ import paper from '@scratch/paper';
|
||||||
import Modes from '../../lib/modes';
|
import Modes from '../../lib/modes';
|
||||||
import {isGroup} from '../group';
|
import {isGroup} from '../group';
|
||||||
import {isCompoundPathItem, getRootItem} from '../item';
|
import {isCompoundPathItem, getRootItem} from '../item';
|
||||||
import {snapDeltaToAngle} from '../math';
|
import {checkPointsClose, snapDeltaToAngle} from '../math';
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from '../view';
|
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from '../view';
|
||||||
import {clearSelection, cloneSelection, getSelectedLeafItems, getSelectedRootItems, setItemSelection}
|
import {clearSelection, cloneSelection, getSelectedLeafItems, getSelectedRootItems, setItemSelection}
|
||||||
from '../selection';
|
from '../selection';
|
||||||
|
import {getDragCrosshairLayer} from '../layer';
|
||||||
|
|
||||||
|
/** Snap to align selection center to rotation center within this distance */
|
||||||
|
const SNAPPING_THRESHOLD = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool to handle dragging an item to reposition it in a selection mode.
|
* Tool to handle dragging an item to reposition it in a selection mode.
|
||||||
|
@ -23,6 +27,7 @@ class MoveTool {
|
||||||
this.setSelectedItems = setSelectedItems;
|
this.setSelectedItems = setSelectedItems;
|
||||||
this.clearSelectedItems = clearSelectedItems;
|
this.clearSelectedItems = clearSelectedItems;
|
||||||
this.selectedItems = null;
|
this.selectedItems = null;
|
||||||
|
this.selectionCenter = null;
|
||||||
this.onUpdateImage = onUpdateImage;
|
this.onUpdateImage = onUpdateImage;
|
||||||
this.switchToTextTool = switchToTextTool;
|
this.switchToTextTool = switchToTextTool;
|
||||||
this.boundsPath = null;
|
this.boundsPath = null;
|
||||||
|
@ -66,10 +71,27 @@ class MoveTool {
|
||||||
this._select(item, true, hitProperties.subselect);
|
this._select(item, true, hitProperties.subselect);
|
||||||
}
|
}
|
||||||
if (hitProperties.clone) cloneSelection(hitProperties.subselect, this.onUpdateImage);
|
if (hitProperties.clone) cloneSelection(hitProperties.subselect, this.onUpdateImage);
|
||||||
|
|
||||||
this.selectedItems = this.mode === Modes.RESHAPE ? getSelectedLeafItems() : getSelectedRootItems();
|
this.selectedItems = this.mode === Modes.RESHAPE ? getSelectedLeafItems() : getSelectedRootItems();
|
||||||
|
if (this.selectedItems.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectionBounds;
|
||||||
|
for (const selectedItem of this.selectedItems) {
|
||||||
|
if (selectionBounds) {
|
||||||
|
selectionBounds = selectionBounds.unite(selectedItem.bounds);
|
||||||
|
} else {
|
||||||
|
selectionBounds = selectedItem.bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.selectionCenter = selectionBounds.center;
|
||||||
|
|
||||||
if (this.boundsPath) {
|
if (this.boundsPath) {
|
||||||
this.selectedItems.push(this.boundsPath);
|
this.selectedItems.push(this.boundsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
setBoundsPath (boundsPath) {
|
setBoundsPath (boundsPath) {
|
||||||
this.boundsPath = boundsPath;
|
this.boundsPath = boundsPath;
|
||||||
|
@ -101,7 +123,21 @@ class MoveTool {
|
||||||
const point = event.point;
|
const point = event.point;
|
||||||
point.x = Math.max(0, Math.min(point.x, ART_BOARD_WIDTH));
|
point.x = Math.max(0, Math.min(point.x, ART_BOARD_WIDTH));
|
||||||
point.y = Math.max(0, Math.min(point.y, ART_BOARD_HEIGHT));
|
point.y = Math.max(0, Math.min(point.y, ART_BOARD_HEIGHT));
|
||||||
|
|
||||||
const dragVector = point.subtract(event.downPoint);
|
const dragVector = point.subtract(event.downPoint);
|
||||||
|
let snapVector;
|
||||||
|
|
||||||
|
// Snapping to align center. Not in reshape mode, because reshape doesn't show center crosshair
|
||||||
|
const center = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
||||||
|
if (!event.modifiers.shift && this.mode !== Modes.RESHAPE) {
|
||||||
|
if (checkPointsClose(
|
||||||
|
this.selectionCenter.add(dragVector),
|
||||||
|
center,
|
||||||
|
SNAPPING_THRESHOLD / paper.view.zoom /* threshold */)) {
|
||||||
|
|
||||||
|
snapVector = center.subtract(this.selectionCenter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const item of this.selectedItems) {
|
for (const item of this.selectedItems) {
|
||||||
// add the position of the item before the drag started
|
// add the position of the item before the drag started
|
||||||
|
@ -110,12 +146,20 @@ class MoveTool {
|
||||||
item.data.origPos = item.position;
|
item.data.origPos = item.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.modifiers.shift) {
|
if (snapVector) {
|
||||||
|
item.position = item.data.origPos.add(snapVector);
|
||||||
|
} else if (event.modifiers.shift) {
|
||||||
item.position = item.data.origPos.add(snapDeltaToAngle(dragVector, Math.PI / 4));
|
item.position = item.data.origPos.add(snapDeltaToAngle(dragVector, Math.PI / 4));
|
||||||
} else {
|
} else {
|
||||||
item.position = item.data.origPos.add(dragVector);
|
item.position = item.data.origPos.add(dragVector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Show the center crosshair above the selected item while dragging. This makes it easier to center sprites.
|
||||||
|
// Yes, we're calling it once per drag event, but it's better than having the crosshair pop up
|
||||||
|
// for a split second every time you click a sprite.
|
||||||
|
getDragCrosshairLayer().visible = true;
|
||||||
}
|
}
|
||||||
onMouseUp () {
|
onMouseUp () {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
|
@ -127,10 +171,14 @@ class MoveTool {
|
||||||
item.data.origPos = null;
|
item.data.origPos = null;
|
||||||
}
|
}
|
||||||
this.selectedItems = null;
|
this.selectedItems = null;
|
||||||
|
this.selectionCenter = null;
|
||||||
|
|
||||||
if (moved) {
|
if (moved) {
|
||||||
this.onUpdateImage();
|
this.onUpdateImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide the crosshair we showed earlier.
|
||||||
|
getDragCrosshairLayer().visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ class ScaleTool {
|
||||||
this.pivot = boundsPath.bounds[this._getOpposingRectCornerNameByIndex(index)].clone();
|
this.pivot = boundsPath.bounds[this._getOpposingRectCornerNameByIndex(index)].clone();
|
||||||
this.origPivot = boundsPath.bounds[this._getOpposingRectCornerNameByIndex(index)].clone();
|
this.origPivot = boundsPath.bounds[this._getOpposingRectCornerNameByIndex(index)].clone();
|
||||||
this.corner = boundsPath.bounds[this._getRectCornerNameByIndex(index)].clone();
|
this.corner = boundsPath.bounds[this._getRectCornerNameByIndex(index)].clone();
|
||||||
|
this.selectionAnchor = boundsPath.selectionAnchor;
|
||||||
this.origSize = this.corner.subtract(this.pivot);
|
this.origSize = this.corner.subtract(this.pivot);
|
||||||
this.origCenter = boundsPath.bounds.center;
|
this.origCenter = boundsPath.bounds.center;
|
||||||
this.isCorner = this._isCorner(index);
|
this.isCorner = this._isCorner(index);
|
||||||
|
@ -86,6 +87,9 @@ class ScaleTool {
|
||||||
// Reset position if we were just in alt
|
// Reset position if we were just in alt
|
||||||
this.centered = false;
|
this.centered = false;
|
||||||
this.itemGroup.scale(1 / this.lastSx, 1 / this.lastSy, this.pivot);
|
this.itemGroup.scale(1 / this.lastSx, 1 / this.lastSy, this.pivot);
|
||||||
|
if (this.selectionAnchor) {
|
||||||
|
this.selectionAnchor.scale(this.lastSx, this.lastSy);
|
||||||
|
}
|
||||||
this.lastSx = 1;
|
this.lastSx = 1;
|
||||||
this.lastSy = 1;
|
this.lastSy = 1;
|
||||||
}
|
}
|
||||||
|
@ -114,6 +118,9 @@ class ScaleTool {
|
||||||
sy *= signy;
|
sy *= signy;
|
||||||
}
|
}
|
||||||
this.itemGroup.scale(sx / this.lastSx, sy / this.lastSy, this.pivot);
|
this.itemGroup.scale(sx / this.lastSx, sy / this.lastSy, this.pivot);
|
||||||
|
if (this.selectionAnchor) {
|
||||||
|
this.selectionAnchor.scale(this.lastSx / sx, this.lastSy / sy);
|
||||||
|
}
|
||||||
this.lastSx = sx;
|
this.lastSx = sx;
|
||||||
this.lastSy = sy;
|
this.lastSy = sy;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,10 @@ import {sortItemsByZIndex} from './math';
|
||||||
*/
|
*/
|
||||||
const getItems = function (options) {
|
const getItems = function (options) {
|
||||||
const newMatcher = function (item) {
|
const newMatcher = function (item) {
|
||||||
return !(item instanceof paper.Layer) && !item.locked &&
|
return !(item instanceof paper.Layer) &&
|
||||||
|
item.layer.data && item.layer.data.isPaintingLayer &&
|
||||||
|
!item.locked &&
|
||||||
|
!item.isClipMask() &&
|
||||||
!(item.data && item.data.isHelperItem) &&
|
!(item.data && item.data.isHelperItem) &&
|
||||||
(!options.match || options.match(item));
|
(!options.match || options.match(item));
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,7 +27,7 @@ const performSnapshot = function (dispatchPerformSnapshot, format) {
|
||||||
const _restore = function (entry, setSelectedItems, onUpdateImage, isBitmapMode) {
|
const _restore = function (entry, setSelectedItems, onUpdateImage, isBitmapMode) {
|
||||||
for (let i = paper.project.layers.length - 1; i >= 0; i--) {
|
for (let i = paper.project.layers.length - 1; i >= 0; i--) {
|
||||||
const layer = paper.project.layers[i];
|
const layer = paper.project.layers[i];
|
||||||
if (!layer.data.isBackgroundGuideLayer) {
|
if (!layer.data.isBackgroundGuideLayer && !layer.data.isDragCrosshairLayer) {
|
||||||
layer.removeChildren();
|
layer.removeChildren();
|
||||||
layer.remove();
|
layer.remove();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import {getSelectedRootItems} from './selection';
|
import {getSelectedRootItems} from './selection';
|
||||||
import {getRaster} from './layer';
|
import {CROSSHAIR_SIZE, getBackgroundGuideLayer, getDragCrosshairLayer, getRaster} from './layer';
|
||||||
import {getHitBounds} from './bitmap';
|
import {getHitBounds} from './bitmap';
|
||||||
|
|
||||||
// Vectors are imported and exported at SVG_ART_BOARD size.
|
// Vectors are imported and exported at SVG_ART_BOARD size.
|
||||||
|
@ -30,6 +30,17 @@ const clampViewBounds = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _resizeCrosshair = () => {
|
||||||
|
if (getDragCrosshairLayer() && getDragCrosshairLayer().dragCrosshair) {
|
||||||
|
getDragCrosshairLayer().dragCrosshair.scale(
|
||||||
|
CROSSHAIR_SIZE / getDragCrosshairLayer().dragCrosshair.bounds.width / paper.view.zoom);
|
||||||
|
}
|
||||||
|
if (getBackgroundGuideLayer() && getBackgroundGuideLayer().dragCrosshair) {
|
||||||
|
getBackgroundGuideLayer().dragCrosshair.scale(
|
||||||
|
CROSSHAIR_SIZE / getBackgroundGuideLayer().dragCrosshair.bounds.width / paper.view.zoom);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Zoom keeping a project-space point fixed.
|
// Zoom keeping a project-space point fixed.
|
||||||
// This article was helpful http://matthiasberth.com/tech/stable-zoom-and-pan-in-paperjs
|
// This article was helpful http://matthiasberth.com/tech/stable-zoom-and-pan-in-paperjs
|
||||||
const zoomOnFixedPoint = (deltaZoom, fixedPoint) => {
|
const zoomOnFixedPoint = (deltaZoom, fixedPoint) => {
|
||||||
|
@ -43,6 +54,7 @@ const zoomOnFixedPoint = (deltaZoom, fixedPoint) => {
|
||||||
view.zoom = newZoom;
|
view.zoom = newZoom;
|
||||||
view.translate(postZoomOffset.multiply(-1));
|
view.translate(postZoomOffset.multiply(-1));
|
||||||
clampViewBounds();
|
clampViewBounds();
|
||||||
|
_resizeCrosshair();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zoom keeping the selection center (if any) fixed.
|
// Zoom keeping the selection center (if any) fixed.
|
||||||
|
@ -67,6 +79,7 @@ const zoomOnSelection = deltaZoom => {
|
||||||
|
|
||||||
const resetZoom = () => {
|
const resetZoom = () => {
|
||||||
paper.project.view.zoom = .5;
|
paper.project.view.zoom = .5;
|
||||||
|
_resizeCrosshair();
|
||||||
clampViewBounds();
|
clampViewBounds();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -92,6 +105,7 @@ const zoomToFit = isBitmap => {
|
||||||
if (ratio < 1) {
|
if (ratio < 1) {
|
||||||
paper.view.center = bounds.center;
|
paper.view.center = bounds.center;
|
||||||
paper.view.zoom = paper.view.zoom / ratio;
|
paper.view.zoom = paper.view.zoom / ratio;
|
||||||
|
_resizeCrosshair();
|
||||||
clampViewBounds();
|
clampViewBounds();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue