mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-23 05:52:42 -05:00
Merge pull request #975 from fsih/fillSpace
Stretch to fill available space
This commit is contained in:
commit
616ed9b4d9
34 changed files with 463 additions and 137 deletions
|
@ -1,6 +1,7 @@
|
||||||
@import "../../css/units";
|
@import "../../css/units";
|
||||||
|
|
||||||
.button-group {
|
.button-group {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
padding: 0 $grid-unit;
|
padding: 0 $grid-unit;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,6 @@ $arrow-border-width: 14px;
|
||||||
min-width: 3.5rem;
|
min-width: 3.5rem;
|
||||||
color: $motion-primary;
|
color: $motion-primary;
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mod-open {
|
.mod-open {
|
||||||
|
|
|
@ -4,12 +4,7 @@ See https://github.com/LLK/scratch-paint/issues/13 */
|
||||||
|
|
||||||
@import "../../css/units.css";
|
@import "../../css/units.css";
|
||||||
@import "../../css/colors.css";
|
@import "../../css/colors.css";
|
||||||
|
@import "../input-group/input-group.css";
|
||||||
.input-group {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-label, .input-label-secondary {
|
.input-label, .input-label-secondary {
|
||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
@import '../../css/units.css';
|
@import '../../css/units.css';
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
[dir="ltr"] .input-group + .input-group {
|
[dir="ltr"] .input-group + .input-group {
|
||||||
margin-left: calc(2 * $grid-unit);
|
margin-left: calc(2 * $grid-unit);
|
||||||
}
|
}
|
||||||
|
@ -8,10 +14,6 @@
|
||||||
margin-right: calc(2 * $grid-unit);
|
margin-right: calc(2 * $grid-unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group {
|
|
||||||
display:flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled {
|
.disabled {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
/* Prevent any user actions */
|
/* Prevent any user actions */
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
@import "../../css/units.css";
|
@import "../../css/units.css";
|
||||||
|
|
||||||
.editor-container {
|
.editor-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: calc(3 * $grid-unit);
|
padding: calc(3 * $grid-unit);
|
||||||
|
@ -20,8 +22,10 @@
|
||||||
|
|
||||||
.top-align-row {
|
.top-align-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-top: calc(5 * $grid-unit);
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
height: 100%;
|
||||||
|
padding-top: calc(5 * $grid-unit);
|
||||||
|
min-width: 524px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row + .row {
|
.row + .row {
|
||||||
|
@ -114,9 +118,19 @@ $border-radius: 0.25rem;
|
||||||
margin-left: calc(2 * $grid-unit);
|
margin-left: calc(2 * $grid-unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.controls-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-left: calc(2 * $grid-unit);
|
||||||
|
margin-right: calc(2 * $grid-unit);
|
||||||
|
}
|
||||||
|
|
||||||
.canvas-container {
|
.canvas-container {
|
||||||
width: 480px;
|
width: 100%;
|
||||||
height: 360px;
|
flex-grow: 1;
|
||||||
|
min-width: 402px; /* Leave room for the border */
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
border: 1px solid #e8edf1;
|
border: 1px solid #e8edf1;
|
||||||
border-radius: .25rem;
|
border-radius: .25rem;
|
||||||
|
@ -126,7 +140,7 @@ $border-radius: 0.25rem;
|
||||||
|
|
||||||
.mode-selector {
|
.mode-selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: 6rem;
|
max-width: 7.5rem;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
@ -134,14 +148,6 @@ $border-radius: 0.25rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
[dir="ltr"] .mode-selector {
|
|
||||||
margin-right: calc(2 * $grid-unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .mode-selector {
|
|
||||||
margin-left: calc(2 * $grid-unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
.zoom-controls {
|
.zoom-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
|
@ -158,7 +164,8 @@ $border-radius: 0.25rem;
|
||||||
|
|
||||||
.canvas-controls {
|
.canvas-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: .25rem;
|
height: 36px;
|
||||||
|
margin-top: $grid-unit;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,10 +195,14 @@ $border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-selector {
|
.mode-selector {
|
||||||
margin-right: $grid-unit;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.controls-container {
|
||||||
|
margin-right: $grid-unit;
|
||||||
|
margin-left: $grid-unit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-area {
|
.text-area {
|
||||||
|
|
|
@ -196,7 +196,7 @@ const PaintEditorComponent = props => (
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div>
|
<div className={styles.controlsContainer}>
|
||||||
{/* Canvas */}
|
{/* Canvas */}
|
||||||
<ScrollableCanvas
|
<ScrollableCanvas
|
||||||
canvas={props.canvas}
|
canvas={props.canvas}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
$scrollbar-size: 8px;
|
$scrollbar-size: 8px;
|
||||||
$scrollbar-padding: 1px;
|
$scrollbar-padding: 4px;
|
||||||
|
|
||||||
.vertical-scrollbar, .horizontal-scrollbar {
|
.vertical-scrollbar, .horizontal-scrollbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -10,17 +10,17 @@ $scrollbar-padding: 1px;
|
||||||
.vertical-scrollbar-wrapper {
|
.vertical-scrollbar-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: $scrollbar-size;
|
width: $scrollbar-size;
|
||||||
right: 0;
|
right: $scrollbar-padding;
|
||||||
top: $scrollbar-padding;
|
top: $scrollbar-padding;
|
||||||
height: calc(100% - $scrollbar-size - $scrollbar-padding);
|
height: calc(100% - $scrollbar-size - 2 * $scrollbar-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
.horizontal-scrollbar-wrapper {
|
.horizontal-scrollbar-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: $scrollbar-size;
|
height: $scrollbar-size;
|
||||||
left: $scrollbar-padding;
|
left: $scrollbar-padding;
|
||||||
bottom: 0;
|
bottom: $scrollbar-padding;
|
||||||
width: calc(100% - $scrollbar-size - $scrollbar-padding);
|
width: calc(100% - $scrollbar-size - 2 * $scrollbar-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vertical-scrollbar {
|
.vertical-scrollbar {
|
||||||
|
|
|
@ -131,6 +131,7 @@ const mapStateToProps = state => ({
|
||||||
shouldShowGradientTools: state.scratchPaint.mode === Modes.SELECT ||
|
shouldShowGradientTools: state.scratchPaint.mode === Modes.SELECT ||
|
||||||
state.scratchPaint.mode === Modes.RESHAPE ||
|
state.scratchPaint.mode === Modes.RESHAPE ||
|
||||||
state.scratchPaint.mode === Modes.FILL ||
|
state.scratchPaint.mode === Modes.FILL ||
|
||||||
|
state.scratchPaint.mode === Modes.BIT_SELECT ||
|
||||||
state.scratchPaint.mode === Modes.BIT_FILL,
|
state.scratchPaint.mode === Modes.BIT_FILL,
|
||||||
textEditTarget: state.scratchPaint.textEditTarget
|
textEditTarget: state.scratchPaint.textEditTarget
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {setLayout} from '../reducers/layout';
|
||||||
|
|
||||||
import {getSelectedLeafItems} from '../helper/selection';
|
import {getSelectedLeafItems} from '../helper/selection';
|
||||||
import {convertToBitmap, convertToVector} from '../helper/bitmap';
|
import {convertToBitmap, convertToVector} from '../helper/bitmap';
|
||||||
import {resetZoom, zoomOnSelection} from '../helper/view';
|
import {resetZoom, zoomOnSelection, OUTERMOST_ZOOM_LEVEL} from '../helper/view';
|
||||||
import EyeDropperTool from '../helper/tools/eye-dropper';
|
import EyeDropperTool from '../helper/tools/eye-dropper';
|
||||||
|
|
||||||
import Modes from '../lib/modes';
|
import Modes from '../lib/modes';
|
||||||
|
@ -211,7 +211,13 @@ class PaintEditor extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleZoomIn () {
|
handleZoomIn () {
|
||||||
zoomOnSelection(PaintEditor.ZOOM_INCREMENT);
|
// Make the "next step" after the outermost zoom level be the default
|
||||||
|
// zoom level (0.5)
|
||||||
|
let zoomIncrement = PaintEditor.ZOOM_INCREMENT;
|
||||||
|
if (paper.view.zoom === OUTERMOST_ZOOM_LEVEL) {
|
||||||
|
zoomIncrement = 0.5 - OUTERMOST_ZOOM_LEVEL;
|
||||||
|
}
|
||||||
|
zoomOnSelection(zoomIncrement);
|
||||||
this.props.updateViewBounds(paper.view.matrix);
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
this.handleSetSelectedItems();
|
this.handleSetSelectedItems();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
.paper-canvas {
|
.paper-canvas {
|
||||||
width: 480px;
|
top: 1px; /* leave room for the border */
|
||||||
height: 360px;
|
left: 1px;
|
||||||
|
width: calc(100% - 2px);
|
||||||
|
height: calc(100% - 2px);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #fff;
|
background-color: #D9E3F2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,10 @@ import log from '../log/log';
|
||||||
import {performSnapshot} from '../helper/undo';
|
import {performSnapshot} from '../helper/undo';
|
||||||
import {undoSnapshot, clearUndoState} from '../reducers/undo';
|
import {undoSnapshot, clearUndoState} from '../reducers/undo';
|
||||||
import {isGroup, ungroupItems} from '../helper/group';
|
import {isGroup, ungroupItems} from '../helper/group';
|
||||||
import {clearRaster, getRaster, setupLayers} from '../helper/layer';
|
import {clearRaster, convertBackgroundGuideLayer, getRaster, setupLayers} from '../helper/layer';
|
||||||
import {clearSelectedItems} from '../reducers/selected-items';
|
import {clearSelectedItems} from '../reducers/selected-items';
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, resetZoom, resizeCrosshair, zoomToFit} from '../helper/view';
|
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, CENTER, MAX_WORKSPACE_BOUNDS} from '../helper/view';
|
||||||
|
import {clampViewBounds, resetZoom, setWorkspaceBounds, zoomToFit, resizeCrosshair} from '../helper/view';
|
||||||
import {ensureClockwise, scaleWithStrokes} from '../helper/math';
|
import {ensureClockwise, scaleWithStrokes} from '../helper/math';
|
||||||
import {clearHoveredItem} from '../reducers/hover';
|
import {clearHoveredItem} from '../reducers/hover';
|
||||||
import {clearPasteOffset} from '../reducers/clipboard';
|
import {clearPasteOffset} from '../reducers/clipboard';
|
||||||
|
@ -30,13 +31,15 @@ class PaperCanvas extends React.Component {
|
||||||
'importSvg',
|
'importSvg',
|
||||||
'initializeSvg',
|
'initializeSvg',
|
||||||
'maybeZoomToFit',
|
'maybeZoomToFit',
|
||||||
'switchCostume'
|
'switchCostume',
|
||||||
|
'onViewResize',
|
||||||
|
'recalibrateSize'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
paper.setup(this.canvas);
|
paper.setup(this.canvas);
|
||||||
|
paper.view.on('resize', this.onViewResize);
|
||||||
resetZoom();
|
resetZoom();
|
||||||
this.props.updateViewBounds(paper.view.matrix);
|
|
||||||
if (this.props.zoomLevelId) {
|
if (this.props.zoomLevelId) {
|
||||||
this.props.setZoomLevelId(this.props.zoomLevelId);
|
this.props.setZoomLevelId(this.props.zoomLevelId);
|
||||||
if (this.props.zoomLevels[this.props.zoomLevelId]) {
|
if (this.props.zoomLevels[this.props.zoomLevelId]) {
|
||||||
|
@ -46,6 +49,8 @@ class PaperCanvas extends React.Component {
|
||||||
// Zoom to fit true means find a comfortable zoom level for viewing the costume
|
// Zoom to fit true means find a comfortable zoom level for viewing the costume
|
||||||
this.shouldZoomToFit = true;
|
this.shouldZoomToFit = true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = this.canvas.getContext('2d');
|
const context = this.canvas.getContext('2d');
|
||||||
|
@ -55,7 +60,7 @@ class PaperCanvas extends React.Component {
|
||||||
// Don't show handles by default
|
// Don't show handles by default
|
||||||
paper.settings.handleSize = 0;
|
paper.settings.handleSize = 0;
|
||||||
// Make layers.
|
// Make layers.
|
||||||
setupLayers();
|
setupLayers(this.props.format);
|
||||||
this.importImage(
|
this.importImage(
|
||||||
this.props.imageFormat, this.props.image, this.props.rotationCenterX, this.props.rotationCenterY);
|
this.props.imageFormat, this.props.image, this.props.rotationCenterX, this.props.rotationCenterY);
|
||||||
}
|
}
|
||||||
|
@ -65,10 +70,17 @@ class PaperCanvas extends React.Component {
|
||||||
newProps.rotationCenterX, newProps.rotationCenterY,
|
newProps.rotationCenterX, newProps.rotationCenterY,
|
||||||
this.props.zoomLevelId, newProps.zoomLevelId);
|
this.props.zoomLevelId, newProps.zoomLevelId);
|
||||||
}
|
}
|
||||||
|
if (this.props.format !== newProps.format) {
|
||||||
|
this.recalibrateSize();
|
||||||
|
convertBackgroundGuideLayer(newProps.format);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.clearQueuedImport();
|
this.clearQueuedImport();
|
||||||
this.props.saveZoomLevel();
|
// shouldZoomToFit means the zoom level hasn't been initialized yet
|
||||||
|
if (!this.shouldZoomToFit) {
|
||||||
|
this.props.saveZoomLevel();
|
||||||
|
}
|
||||||
paper.remove();
|
paper.remove();
|
||||||
}
|
}
|
||||||
clearQueuedImport () {
|
clearQueuedImport () {
|
||||||
|
@ -97,7 +109,9 @@ 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 && !layer.data.isDragCrosshairLayer) {
|
} else if (!layer.data.isBackgroundGuideLayer &&
|
||||||
|
!layer.data.isDragCrosshairLayer &&
|
||||||
|
!layer.data.isOutlineLayer) {
|
||||||
layer.removeChildren();
|
layer.removeChildren();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,12 +128,20 @@ class PaperCanvas extends React.Component {
|
||||||
if (!image) {
|
if (!image) {
|
||||||
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
|
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
|
||||||
performSnapshot(this.props.undoSnapshot, Formats.VECTOR_SKIP_CONVERT);
|
performSnapshot(this.props.undoSnapshot, Formats.VECTOR_SKIP_CONVERT);
|
||||||
|
this.recalibrateSize();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (format === 'jpg' || format === 'png') {
|
if (format === 'jpg' || format === 'png') {
|
||||||
// import bitmap
|
// import bitmap
|
||||||
this.props.changeFormat(Formats.BITMAP_SKIP_CONVERT);
|
this.props.changeFormat(Formats.BITMAP_SKIP_CONVERT);
|
||||||
|
|
||||||
|
const mask = new paper.Shape.Rectangle(getRaster().getBounds());
|
||||||
|
mask.guide = true;
|
||||||
|
mask.locked = true;
|
||||||
|
mask.setPosition(CENTER);
|
||||||
|
mask.clipMask = true;
|
||||||
|
|
||||||
const imgElement = new Image();
|
const imgElement = new Image();
|
||||||
this.queuedImageToLoad = imgElement;
|
this.queuedImageToLoad = imgElement;
|
||||||
imgElement.onload = () => {
|
imgElement.onload = () => {
|
||||||
|
@ -134,8 +156,10 @@ class PaperCanvas extends React.Component {
|
||||||
imgElement,
|
imgElement,
|
||||||
(ART_BOARD_WIDTH / 2) - rotationCenterX,
|
(ART_BOARD_WIDTH / 2) - rotationCenterX,
|
||||||
(ART_BOARD_HEIGHT / 2) - rotationCenterY);
|
(ART_BOARD_HEIGHT / 2) - rotationCenterY);
|
||||||
|
|
||||||
this.maybeZoomToFit(true /* isBitmap */);
|
this.maybeZoomToFit(true /* isBitmap */);
|
||||||
performSnapshot(this.props.undoSnapshot, Formats.BITMAP_SKIP_CONVERT);
|
performSnapshot(this.props.undoSnapshot, Formats.BITMAP_SKIP_CONVERT);
|
||||||
|
this.recalibrateSize();
|
||||||
};
|
};
|
||||||
imgElement.src = image;
|
imgElement.src = image;
|
||||||
} else if (format === 'svg') {
|
} else if (format === 'svg') {
|
||||||
|
@ -145,6 +169,7 @@ class PaperCanvas extends React.Component {
|
||||||
log.error(`Didn't recognize format: ${format}. Use 'jpg', 'png' or 'svg'.`);
|
log.error(`Didn't recognize format: ${format}. Use 'jpg', 'png' or 'svg'.`);
|
||||||
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
|
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
|
||||||
performSnapshot(this.props.undoSnapshot, Formats.VECTOR_SKIP_CONVERT);
|
performSnapshot(this.props.undoSnapshot, Formats.VECTOR_SKIP_CONVERT);
|
||||||
|
this.recalibrateSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maybeZoomToFit (isBitmapMode) {
|
maybeZoomToFit (isBitmapMode) {
|
||||||
|
@ -154,9 +179,10 @@ class PaperCanvas extends React.Component {
|
||||||
resizeCrosshair();
|
resizeCrosshair();
|
||||||
} else if (this.shouldZoomToFit === true) {
|
} else if (this.shouldZoomToFit === true) {
|
||||||
zoomToFit(isBitmapMode);
|
zoomToFit(isBitmapMode);
|
||||||
this.props.updateViewBounds(paper.view.matrix);
|
|
||||||
}
|
}
|
||||||
this.shouldZoomToFit = false;
|
this.shouldZoomToFit = false;
|
||||||
|
setWorkspaceBounds();
|
||||||
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
}
|
}
|
||||||
importSvg (svg, rotationCenterX, rotationCenterY) {
|
importSvg (svg, rotationCenterX, rotationCenterY) {
|
||||||
const paperCanvas = this;
|
const paperCanvas = this;
|
||||||
|
@ -200,6 +226,15 @@ class PaperCanvas extends React.Component {
|
||||||
// positioned incorrectly
|
// positioned incorrectly
|
||||||
paperCanvas.queuedImport =
|
paperCanvas.queuedImport =
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
|
// Detached
|
||||||
|
if (!paper.view) return;
|
||||||
|
// Prevent blurriness caused if the "CSS size" of the element is a float--
|
||||||
|
// setting canvas dimensions to floats floors them, but we need to round instead
|
||||||
|
const elemSize = paper.DomElement.getSize(paper.view.element);
|
||||||
|
elemSize.width = Math.round(elemSize.width);
|
||||||
|
elemSize.height = Math.round(elemSize.height);
|
||||||
|
paper.view.setViewSize(elemSize);
|
||||||
|
paperCanvas.props.updateViewBounds(paper.view.matrix);
|
||||||
paperCanvas.initializeSvg(item, rotationCenterX, rotationCenterY, viewBox);
|
paperCanvas.initializeSvg(item, rotationCenterX, rotationCenterY, viewBox);
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
@ -210,18 +245,28 @@ class PaperCanvas extends React.Component {
|
||||||
const itemWidth = item.bounds.width;
|
const itemWidth = item.bounds.width;
|
||||||
const itemHeight = item.bounds.height;
|
const itemHeight = item.bounds.height;
|
||||||
|
|
||||||
// Remove viewbox
|
// Get reference to viewbox
|
||||||
|
let mask;
|
||||||
if (item.clipped) {
|
if (item.clipped) {
|
||||||
let mask;
|
|
||||||
for (const child of item.children) {
|
for (const child of item.children) {
|
||||||
if (child.isClipMask()) {
|
if (child.isClipMask()) {
|
||||||
mask = child;
|
mask = child;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item.clipped = false;
|
mask.clipMask = false;
|
||||||
mask.remove();
|
} else {
|
||||||
|
mask = new paper.Shape.Rectangle(item.bounds);
|
||||||
}
|
}
|
||||||
|
mask.guide = true;
|
||||||
|
mask.locked = true;
|
||||||
|
mask.matrix = new paper.Matrix(); // Identity
|
||||||
|
// Set the artwork to get clipped at the max costume size
|
||||||
|
mask.size.height = MAX_WORKSPACE_BOUNDS.height;
|
||||||
|
mask.size.width = MAX_WORKSPACE_BOUNDS.width;
|
||||||
|
mask.setPosition(CENTER);
|
||||||
|
paper.project.activeLayer.addChild(mask);
|
||||||
|
mask.clipMask = true;
|
||||||
|
|
||||||
// Reduce single item nested in groups
|
// Reduce single item nested in groups
|
||||||
if (item instanceof paper.Group && item.children.length === 1) {
|
if (item instanceof paper.Group && item.children.length === 1) {
|
||||||
|
@ -231,18 +276,18 @@ class PaperCanvas extends React.Component {
|
||||||
ensureClockwise(item);
|
ensureClockwise(item);
|
||||||
scaleWithStrokes(item, 2, new paper.Point()); // Import at 2x
|
scaleWithStrokes(item, 2, new paper.Point()); // Import at 2x
|
||||||
|
|
||||||
|
// Apply rotation center
|
||||||
if (typeof rotationCenterX !== 'undefined' && typeof rotationCenterY !== 'undefined') {
|
if (typeof rotationCenterX !== 'undefined' && typeof rotationCenterY !== 'undefined') {
|
||||||
let rotationPoint = new paper.Point(rotationCenterX, rotationCenterY);
|
let rotationPoint = new paper.Point(rotationCenterX, rotationCenterY);
|
||||||
if (viewBox && viewBox.length >= 2 && !isNaN(viewBox[0]) && !isNaN(viewBox[1])) {
|
if (viewBox && viewBox.length >= 2 && !isNaN(viewBox[0]) && !isNaN(viewBox[1])) {
|
||||||
rotationPoint = rotationPoint.subtract(viewBox[0], viewBox[1]);
|
rotationPoint = rotationPoint.subtract(viewBox[0], viewBox[1]);
|
||||||
}
|
}
|
||||||
item.translate(new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2)
|
item.translate(CENTER.subtract(rotationPoint.multiply(2)));
|
||||||
.subtract(rotationPoint.multiply(2)));
|
|
||||||
} else {
|
} else {
|
||||||
// Center
|
// Center
|
||||||
item.translate(new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2)
|
item.translate(CENTER.subtract(itemWidth, itemHeight));
|
||||||
.subtract(itemWidth, itemHeight));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
paper.project.activeLayer.insertChild(0, item);
|
paper.project.activeLayer.insertChild(0, item);
|
||||||
if (isGroup(item)) {
|
if (isGroup(item)) {
|
||||||
// Fixes an issue where we may export empty groups
|
// Fixes an issue where we may export empty groups
|
||||||
|
@ -253,9 +298,25 @@ class PaperCanvas extends React.Component {
|
||||||
}
|
}
|
||||||
ungroupItems([item]);
|
ungroupItems([item]);
|
||||||
}
|
}
|
||||||
|
|
||||||
performSnapshot(this.props.undoSnapshot, Formats.VECTOR_SKIP_CONVERT);
|
performSnapshot(this.props.undoSnapshot, Formats.VECTOR_SKIP_CONVERT);
|
||||||
this.maybeZoomToFit();
|
this.maybeZoomToFit();
|
||||||
}
|
}
|
||||||
|
onViewResize () {
|
||||||
|
setWorkspaceBounds(true /* clipEmpty */);
|
||||||
|
clampViewBounds();
|
||||||
|
// Fix incorrect paper canvas scale on browser zoom reset
|
||||||
|
this.recalibrateSize();
|
||||||
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
|
}
|
||||||
|
recalibrateSize () {
|
||||||
|
// Sets the size that Paper thinks the canvas is to the size the canvas element actually is.
|
||||||
|
// When these are out of sync, the mouse events in the paint editor don't line up correctly.
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (!paper.view) return;
|
||||||
|
paper.view.setViewSize(paper.DomElement.getSize(paper.view.element));
|
||||||
|
});
|
||||||
|
}
|
||||||
setCanvas (canvas) {
|
setCanvas (canvas) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
if (this.props.canvasRef) {
|
if (this.props.canvasRef) {
|
||||||
|
@ -266,10 +327,9 @@ class PaperCanvas extends React.Component {
|
||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
className={styles.paperCanvas}
|
className={styles.paperCanvas}
|
||||||
height="360px"
|
|
||||||
ref={this.setCanvas}
|
ref={this.setCanvas}
|
||||||
style={{cursor: this.props.cursor}}
|
style={{cursor: this.props.cursor}}
|
||||||
width="480px"
|
resize="true"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -283,6 +343,7 @@ PaperCanvas.propTypes = {
|
||||||
clearSelectedItems: PropTypes.func.isRequired,
|
clearSelectedItems: PropTypes.func.isRequired,
|
||||||
clearUndo: PropTypes.func.isRequired,
|
clearUndo: PropTypes.func.isRequired,
|
||||||
cursor: PropTypes.string,
|
cursor: PropTypes.string,
|
||||||
|
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||||
image: PropTypes.oneOfType([
|
image: PropTypes.oneOfType([
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
PropTypes.instanceOf(HTMLImageElement)
|
PropTypes.instanceOf(HTMLImageElement)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import ScrollableCanvasComponent from '../components/scrollable-canvas/scrollable-canvas.jsx';
|
import ScrollableCanvasComponent from '../components/scrollable-canvas/scrollable-canvas.jsx';
|
||||||
|
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, clampViewBounds, pan, zoomOnFixedPoint} from '../helper/view';
|
import {clampViewBounds, pan, zoomOnFixedPoint, getWorkspaceBounds} from '../helper/view';
|
||||||
import {updateViewBounds} from '../reducers/view-bounds';
|
import {updateViewBounds} from '../reducers/view-bounds';
|
||||||
import {redrawSelectionBox} from '../reducers/selected-items';
|
import {redrawSelectionBox} from '../reducers/selected-items';
|
||||||
|
|
||||||
|
@ -132,11 +132,12 @@ class ScrollableCanvas extends React.Component {
|
||||||
let topPercent = 0;
|
let topPercent = 0;
|
||||||
let leftPercent = 0;
|
let leftPercent = 0;
|
||||||
if (paper.project) {
|
if (paper.project) {
|
||||||
|
const bounds = getWorkspaceBounds();
|
||||||
const {x, y, width, height} = paper.view.bounds;
|
const {x, y, width, height} = paper.view.bounds;
|
||||||
widthPercent = Math.min(100, 100 * width / ART_BOARD_WIDTH);
|
widthPercent = Math.min(100, 100 * width / bounds.width);
|
||||||
heightPercent = Math.min(100, 100 * height / ART_BOARD_HEIGHT);
|
heightPercent = Math.min(100, 100 * height / bounds.height);
|
||||||
const centerX = (x + (width / 2)) / ART_BOARD_WIDTH;
|
const centerX = (x + (width / 2) - bounds.x) / bounds.width;
|
||||||
const centerY = (y + (height / 2)) / ART_BOARD_HEIGHT;
|
const centerY = (y + (height / 2) - bounds.y) / bounds.height;
|
||||||
topPercent = Math.max(0, (100 * centerY) - (heightPercent / 2));
|
topPercent = Math.max(0, (100 * centerY) - (heightPercent / 2));
|
||||||
leftPercent = Math.max(0, (100 * centerX) - (widthPercent / 2));
|
leftPercent = Math.max(0, (100 * centerX) - (widthPercent / 2));
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ class OvalTool extends paper.Tool {
|
||||||
setCursor,
|
setCursor,
|
||||||
onUpdateImage
|
onUpdateImage
|
||||||
);
|
);
|
||||||
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
|
const nudgeTool = new NudgeTool(Modes.BIT_OVAL, this.boundingBoxTool, onUpdateImage);
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
|
@ -32,7 +32,7 @@ class RectTool extends paper.Tool {
|
||||||
setCursor,
|
setCursor,
|
||||||
onUpdateImage
|
onUpdateImage
|
||||||
);
|
);
|
||||||
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
|
const nudgeTool = new NudgeTool(Modes.BIT_RECT, this.boundingBoxTool, onUpdateImage);
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
|
@ -30,14 +30,14 @@ class SelectTool extends paper.Tool {
|
||||||
super();
|
super();
|
||||||
this.onUpdateImage = onUpdateImage;
|
this.onUpdateImage = onUpdateImage;
|
||||||
this.boundingBoxTool = new BoundingBoxTool(
|
this.boundingBoxTool = new BoundingBoxTool(
|
||||||
Modes.SELECT,
|
Modes.BIT_SELECT,
|
||||||
setSelectedItems,
|
setSelectedItems,
|
||||||
clearSelectedItems,
|
clearSelectedItems,
|
||||||
setCursor,
|
setCursor,
|
||||||
onUpdateImage
|
onUpdateImage
|
||||||
);
|
);
|
||||||
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
|
const nudgeTool = new NudgeTool(Modes.BIT_SELECT, this.boundingBoxTool, onUpdateImage);
|
||||||
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems);
|
this.selectionBoxTool = new SelectionBoxTool(Modes.BIT_SELECT, setSelectedItems, clearSelectedItems);
|
||||||
this.selectionBoxMode = false;
|
this.selectionBoxMode = false;
|
||||||
this.selection = null;
|
this.selection = null;
|
||||||
this.active = false;
|
this.active = false;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import paper from '@scratch/paper';
|
||||||
import {createCanvas, clearRaster, getRaster, hideGuideLayers, showGuideLayers} from './layer';
|
import {createCanvas, clearRaster, getRaster, hideGuideLayers, showGuideLayers} from './layer';
|
||||||
import {getGuideColor} from './guides';
|
import {getGuideColor} from './guides';
|
||||||
import {clearSelection} from './selection';
|
import {clearSelection} from './selection';
|
||||||
|
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, CENTER, MAX_WORKSPACE_BOUNDS} from './view';
|
||||||
import {inlineSvgFonts} from 'scratch-svg-renderer';
|
import {inlineSvgFonts} from 'scratch-svg-renderer';
|
||||||
import Formats from '../lib/format';
|
import Formats from '../lib/format';
|
||||||
|
|
||||||
|
@ -399,7 +400,17 @@ const convertToBitmap = function (clearSelectedItems, onUpdateImage) {
|
||||||
img,
|
img,
|
||||||
new paper.Point(Math.floor(bounds.topLeft.x), Math.floor(bounds.topLeft.y)));
|
new paper.Point(Math.floor(bounds.topLeft.x), Math.floor(bounds.topLeft.y)));
|
||||||
}
|
}
|
||||||
paper.project.activeLayer.removeChildren();
|
for (let i = paper.project.activeLayer.children.length - 1; i >= 0; i--) {
|
||||||
|
const item = paper.project.activeLayer.children[i];
|
||||||
|
if (item.clipMask === false) {
|
||||||
|
item.remove();
|
||||||
|
} else {
|
||||||
|
// Resize mask for bitmap bounds
|
||||||
|
item.size.height = ART_BOARD_HEIGHT;
|
||||||
|
item.size.width = ART_BOARD_WIDTH;
|
||||||
|
item.setPosition(CENTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
onUpdateImage(false /* skipSnapshot */, Formats.BITMAP /* formatOverride */);
|
onUpdateImage(false /* skipSnapshot */, Formats.BITMAP /* formatOverride */);
|
||||||
};
|
};
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
|
@ -420,7 +431,16 @@ const convertToBitmap = function (clearSelectedItems, onUpdateImage) {
|
||||||
|
|
||||||
const convertToVector = function (clearSelectedItems, onUpdateImage) {
|
const convertToVector = function (clearSelectedItems, onUpdateImage) {
|
||||||
clearSelection(clearSelectedItems);
|
clearSelection(clearSelectedItems);
|
||||||
|
for (const item of paper.project.activeLayer.children) {
|
||||||
|
if (item.clipMask === true) {
|
||||||
|
// Resize mask for vector bounds
|
||||||
|
item.size.height = MAX_WORKSPACE_BOUNDS.height;
|
||||||
|
item.size.width = MAX_WORKSPACE_BOUNDS.width;
|
||||||
|
item.setPosition(CENTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
getTrimmedRaster(true /* shouldInsert */);
|
getTrimmedRaster(true /* shouldInsert */);
|
||||||
|
|
||||||
clearRaster();
|
clearRaster();
|
||||||
onUpdateImage(false /* skipSnapshot */, Formats.VECTOR /* formatOverride */);
|
onUpdateImage(false /* skipSnapshot */, Formats.VECTOR /* formatOverride */);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import BroadBrushHelper from './broad-brush-helper';
|
||||||
import SegmentBrushHelper from './segment-brush-helper';
|
import SegmentBrushHelper from './segment-brush-helper';
|
||||||
import {MIXED, styleCursorPreview} from '../../helper/style-path';
|
import {MIXED, styleCursorPreview} from '../../helper/style-path';
|
||||||
import {clearSelection, getItems} from '../../helper/selection';
|
import {clearSelection, getItems} from '../../helper/selection';
|
||||||
import {getGuideLayer} from '../../helper/layer';
|
import {getGuideLayer, setGuideItem} from '../../helper/layer';
|
||||||
import {isCompoundPathChild} from '../compound-path';
|
import {isCompoundPathChild} from '../compound-path';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,6 +182,7 @@ class Blobbiness {
|
||||||
});
|
});
|
||||||
this.cursorPreview.parent = getGuideLayer();
|
this.cursorPreview.parent = getGuideLayer();
|
||||||
this.cursorPreview.data.isHelperItem = true;
|
this.cursorPreview.data.isHelperItem = true;
|
||||||
|
setGuideItem(this.cursorPreview);
|
||||||
}
|
}
|
||||||
this.cursorPreview.position = this.cursorPreviewLastPoint;
|
this.cursorPreview.position = this.cursorPreviewLastPoint;
|
||||||
this.cursorPreview.radius = this.options.brushSize / 2;
|
this.cursorPreview.radius = this.options.brushSize / 2;
|
||||||
|
|
|
@ -1,8 +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, CENTER} from './view';
|
import {ART_BOARD_BOUNDS, ART_BOARD_WIDTH, ART_BOARD_HEIGHT, CENTER, MAX_WORKSPACE_BOUNDS} from './view';
|
||||||
import {isGroupItem} from './item';
|
import {isGroupItem} from './item';
|
||||||
|
import {isBitmap, isVector} from '../lib/format';
|
||||||
|
|
||||||
|
const CHECKERBOARD_SIZE = 8;
|
||||||
const CROSSHAIR_SIZE = 16;
|
const CROSSHAIR_SIZE = 16;
|
||||||
const CROSSHAIR_FULL_OPACITY = 0.75;
|
const CROSSHAIR_FULL_OPACITY = 0.75;
|
||||||
|
|
||||||
|
@ -42,7 +44,7 @@ const clearRaster = function () {
|
||||||
raster.parent = layer;
|
raster.parent = layer;
|
||||||
raster.guide = true;
|
raster.guide = true;
|
||||||
raster.locked = true;
|
raster.locked = true;
|
||||||
raster.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
raster.position = CENTER;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRaster = function () {
|
const getRaster = function () {
|
||||||
|
@ -62,6 +64,15 @@ const getBackgroundGuideLayer = function () {
|
||||||
return _getLayer('isBackgroundGuideLayer');
|
return _getLayer('isBackgroundGuideLayer');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _convertLayer = function (layer, format) {
|
||||||
|
layer.bitmapBackground.visible = isBitmap(format);
|
||||||
|
layer.vectorBackground.visible = isVector(format);
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertBackgroundGuideLayer = function (format) {
|
||||||
|
_convertLayer(getBackgroundGuideLayer(), format);
|
||||||
|
};
|
||||||
|
|
||||||
const _makeGuideLayer = function () {
|
const _makeGuideLayer = function () {
|
||||||
const guideLayer = new paper.Layer();
|
const guideLayer = new paper.Layer();
|
||||||
guideLayer.data.isGuideLayer = true;
|
guideLayer.data.isGuideLayer = true;
|
||||||
|
@ -95,8 +106,10 @@ const setGuideItem = function (item) {
|
||||||
const hideGuideLayers = function (includeRaster) {
|
const hideGuideLayers = function (includeRaster) {
|
||||||
const backgroundGuideLayer = getBackgroundGuideLayer();
|
const backgroundGuideLayer = getBackgroundGuideLayer();
|
||||||
const dragCrosshairLayer = getDragCrosshairLayer();
|
const dragCrosshairLayer = getDragCrosshairLayer();
|
||||||
|
const outlineLayer = _getLayer('isOutlineLayer');
|
||||||
const guideLayer = getGuideLayer();
|
const guideLayer = getGuideLayer();
|
||||||
dragCrosshairLayer.remove();
|
dragCrosshairLayer.remove();
|
||||||
|
outlineLayer.remove();
|
||||||
guideLayer.remove();
|
guideLayer.remove();
|
||||||
backgroundGuideLayer.remove();
|
backgroundGuideLayer.remove();
|
||||||
let rasterLayer;
|
let rasterLayer;
|
||||||
|
@ -106,6 +119,7 @@ const hideGuideLayers = function (includeRaster) {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
dragCrosshairLayer: dragCrosshairLayer,
|
dragCrosshairLayer: dragCrosshairLayer,
|
||||||
|
outlineLayer: outlineLayer,
|
||||||
guideLayer: guideLayer,
|
guideLayer: guideLayer,
|
||||||
backgroundGuideLayer: backgroundGuideLayer,
|
backgroundGuideLayer: backgroundGuideLayer,
|
||||||
rasterLayer: rasterLayer
|
rasterLayer: rasterLayer
|
||||||
|
@ -120,6 +134,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 dragCrosshairLayer = guideLayers.dragCrosshairLayer;
|
||||||
|
const outlineLayer = guideLayers.outlineLayer;
|
||||||
const guideLayer = guideLayers.guideLayer;
|
const guideLayer = guideLayers.guideLayer;
|
||||||
const rasterLayer = guideLayers.rasterLayer;
|
const rasterLayer = guideLayers.rasterLayer;
|
||||||
if (rasterLayer && !rasterLayer.index) {
|
if (rasterLayer && !rasterLayer.index) {
|
||||||
|
@ -134,6 +149,10 @@ const showGuideLayers = function (guideLayers) {
|
||||||
paper.project.addLayer(dragCrosshairLayer);
|
paper.project.addLayer(dragCrosshairLayer);
|
||||||
dragCrosshairLayer.bringToFront();
|
dragCrosshairLayer.bringToFront();
|
||||||
}
|
}
|
||||||
|
if (!outlineLayer.index) {
|
||||||
|
paper.project.addLayer(outlineLayer);
|
||||||
|
outlineLayer.bringToFront();
|
||||||
|
}
|
||||||
if (!guideLayer.index) {
|
if (!guideLayer.index) {
|
||||||
paper.project.addLayer(guideLayer);
|
paper.project.addLayer(guideLayer);
|
||||||
guideLayer.bringToFront();
|
guideLayer.bringToFront();
|
||||||
|
@ -157,7 +176,7 @@ const _makeRasterLayer = function () {
|
||||||
return rasterLayer;
|
return rasterLayer;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _makeBackgroundPaper = function (width, height, color) {
|
const _makeBackgroundPaper = function (width, height, color, opacity) {
|
||||||
// creates a checkerboard path of width * height squares in color on white
|
// creates a checkerboard path of width * height squares in color on white
|
||||||
let x = 0;
|
let x = 0;
|
||||||
let y = 0;
|
let y = 0;
|
||||||
|
@ -176,16 +195,27 @@ const _makeBackgroundPaper = function (width, height, color) {
|
||||||
pathPoints.push(new paper.Point(x, y));
|
pathPoints.push(new paper.Point(x, y));
|
||||||
y--;
|
y--;
|
||||||
}
|
}
|
||||||
const vRect = new paper.Shape.Rectangle(new paper.Point(0, 0), new paper.Point(120, 90));
|
const vRect = new paper.Shape.Rectangle(
|
||||||
|
new paper.Point(0, 0),
|
||||||
|
new paper.Point(ART_BOARD_WIDTH / CHECKERBOARD_SIZE, ART_BOARD_HEIGHT / CHECKERBOARD_SIZE));
|
||||||
vRect.fillColor = '#fff';
|
vRect.fillColor = '#fff';
|
||||||
vRect.guide = true;
|
vRect.guide = true;
|
||||||
vRect.locked = true;
|
vRect.locked = true;
|
||||||
|
vRect.position = CENTER;
|
||||||
const vPath = new paper.Path(pathPoints);
|
const vPath = new paper.Path(pathPoints);
|
||||||
vPath.fillRule = 'evenodd';
|
vPath.fillRule = 'evenodd';
|
||||||
vPath.fillColor = color;
|
vPath.fillColor = color;
|
||||||
|
vPath.opacity = opacity;
|
||||||
vPath.guide = true;
|
vPath.guide = true;
|
||||||
vPath.locked = true;
|
vPath.locked = true;
|
||||||
const vGroup = new paper.Group([vRect, vPath]);
|
vPath.position = CENTER;
|
||||||
|
const mask = new paper.Shape.Rectangle(MAX_WORKSPACE_BOUNDS);
|
||||||
|
mask.position = CENTER;
|
||||||
|
mask.guide = true;
|
||||||
|
mask.locked = true;
|
||||||
|
mask.scale(1 / CHECKERBOARD_SIZE);
|
||||||
|
const vGroup = new paper.Group([vRect, vPath, mask]);
|
||||||
|
mask.clipMask = true;
|
||||||
return vGroup;
|
return vGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -230,7 +260,6 @@ const _makeCrosshair = function (opacity, parent) {
|
||||||
crosshair.applyMatrix = false;
|
crosshair.applyMatrix = false;
|
||||||
parent.dragCrosshair = crosshair;
|
parent.dragCrosshair = crosshair;
|
||||||
crosshair.scale(CROSSHAIR_SIZE / crosshair.bounds.width / paper.view.zoom);
|
crosshair.scale(CROSSHAIR_SIZE / crosshair.bounds.width / paper.view.zoom);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const _makeDragCrosshairLayer = function () {
|
const _makeDragCrosshairLayer = function () {
|
||||||
|
@ -241,15 +270,55 @@ const _makeDragCrosshairLayer = function () {
|
||||||
return dragCrosshairLayer;
|
return dragCrosshairLayer;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _makeBackgroundGuideLayer = function () {
|
const _makeOutlineLayer = function () {
|
||||||
|
const outlineLayer = new paper.Layer();
|
||||||
|
const whiteRect = new paper.Shape.Rectangle(ART_BOARD_BOUNDS.expand(1));
|
||||||
|
whiteRect.strokeWidth = 2;
|
||||||
|
whiteRect.strokeColor = 'white';
|
||||||
|
setGuideItem(whiteRect);
|
||||||
|
const blueRect = new paper.Shape.Rectangle(ART_BOARD_BOUNDS.expand(5));
|
||||||
|
blueRect.strokeWidth = 2;
|
||||||
|
blueRect.strokeColor = '#4280D7';
|
||||||
|
blueRect.opacity = 0.25;
|
||||||
|
setGuideItem(blueRect);
|
||||||
|
outlineLayer.data.isOutlineLayer = true;
|
||||||
|
return outlineLayer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _makeBackgroundGuideLayer = function (format) {
|
||||||
const guideLayer = new paper.Layer();
|
const guideLayer = new paper.Layer();
|
||||||
guideLayer.locked = true;
|
guideLayer.locked = true;
|
||||||
|
|
||||||
const vBackground = _makeBackgroundPaper(120, 90, '#E5E5E5');
|
const vWorkspaceBounds = new paper.Shape.Rectangle(MAX_WORKSPACE_BOUNDS);
|
||||||
vBackground.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
vWorkspaceBounds.fillColor = '#ECF1F9';
|
||||||
vBackground.scaling = new paper.Point(8, 8);
|
vWorkspaceBounds.position = CENTER;
|
||||||
vBackground.guide = true;
|
|
||||||
vBackground.locked = true;
|
// Add 1 to the height because it's an odd number otherwise, and we want it to be even
|
||||||
|
// so the corner of the checkerboard to line up with the center crosshair
|
||||||
|
const vBackground = _makeBackgroundPaper(
|
||||||
|
MAX_WORKSPACE_BOUNDS.width / CHECKERBOARD_SIZE,
|
||||||
|
(MAX_WORKSPACE_BOUNDS.height / CHECKERBOARD_SIZE) + 1,
|
||||||
|
'#0062ff', 0.05);
|
||||||
|
vBackground.position = CENTER;
|
||||||
|
vBackground.scaling = new paper.Point(CHECKERBOARD_SIZE, CHECKERBOARD_SIZE);
|
||||||
|
|
||||||
|
const vectorBackground = new paper.Group();
|
||||||
|
vectorBackground.addChild(vWorkspaceBounds);
|
||||||
|
vectorBackground.addChild(vBackground);
|
||||||
|
setGuideItem(vectorBackground);
|
||||||
|
guideLayer.vectorBackground = vectorBackground;
|
||||||
|
|
||||||
|
const bitmapBackground = _makeBackgroundPaper(
|
||||||
|
ART_BOARD_WIDTH / CHECKERBOARD_SIZE,
|
||||||
|
ART_BOARD_HEIGHT / CHECKERBOARD_SIZE,
|
||||||
|
'#0062ff', 0.05);
|
||||||
|
bitmapBackground.position = CENTER;
|
||||||
|
bitmapBackground.scaling = new paper.Point(CHECKERBOARD_SIZE, CHECKERBOARD_SIZE);
|
||||||
|
bitmapBackground.guide = true;
|
||||||
|
bitmapBackground.locked = true;
|
||||||
|
guideLayer.bitmapBackground = bitmapBackground;
|
||||||
|
|
||||||
|
_convertLayer(guideLayer, format);
|
||||||
|
|
||||||
_makeCrosshair(0.16, guideLayer);
|
_makeCrosshair(0.16, guideLayer);
|
||||||
|
|
||||||
|
@ -257,14 +326,16 @@ const _makeBackgroundGuideLayer = function () {
|
||||||
return guideLayer;
|
return guideLayer;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupLayers = function () {
|
const setupLayers = function (format) {
|
||||||
const backgroundGuideLayer = _makeBackgroundGuideLayer();
|
const backgroundGuideLayer = _makeBackgroundGuideLayer(format);
|
||||||
_makeRasterLayer();
|
_makeRasterLayer();
|
||||||
const paintLayer = _makePaintingLayer();
|
const paintLayer = _makePaintingLayer();
|
||||||
const dragCrosshairLayer = _makeDragCrosshairLayer();
|
const dragCrosshairLayer = _makeDragCrosshairLayer();
|
||||||
|
const outlineLayer = _makeOutlineLayer();
|
||||||
const guideLayer = _makeGuideLayer();
|
const guideLayer = _makeGuideLayer();
|
||||||
backgroundGuideLayer.sendToBack();
|
backgroundGuideLayer.sendToBack();
|
||||||
dragCrosshairLayer.bringToFront();
|
dragCrosshairLayer.bringToFront();
|
||||||
|
outlineLayer.bringToFront();
|
||||||
guideLayer.bringToFront();
|
guideLayer.bringToFront();
|
||||||
paintLayer.activate();
|
paintLayer.activate();
|
||||||
};
|
};
|
||||||
|
@ -278,6 +349,7 @@ export {
|
||||||
getDragCrosshairLayer,
|
getDragCrosshairLayer,
|
||||||
getGuideLayer,
|
getGuideLayer,
|
||||||
getBackgroundGuideLayer,
|
getBackgroundGuideLayer,
|
||||||
|
convertBackgroundGuideLayer,
|
||||||
clearRaster,
|
clearRaster,
|
||||||
getRaster,
|
getRaster,
|
||||||
setGuideItem,
|
setGuideItem,
|
||||||
|
|
|
@ -49,7 +49,7 @@ class BoundingBoxTool {
|
||||||
this.boundsScaleHandles = [];
|
this.boundsScaleHandles = [];
|
||||||
this.boundsRotHandles = [];
|
this.boundsRotHandles = [];
|
||||||
this._modeMap = {};
|
this._modeMap = {};
|
||||||
this._modeMap[BoundingBoxModes.SCALE] = new ScaleTool(onUpdateImage);
|
this._modeMap[BoundingBoxModes.SCALE] = new ScaleTool(mode, onUpdateImage);
|
||||||
this._modeMap[BoundingBoxModes.ROTATE] = new RotateTool(onUpdateImage);
|
this._modeMap[BoundingBoxModes.ROTATE] = new RotateTool(onUpdateImage);
|
||||||
this._modeMap[BoundingBoxModes.MOVE] =
|
this._modeMap[BoundingBoxModes.MOVE] =
|
||||||
new MoveTool(mode, setSelectedItems, clearSelectedItems, onUpdateImage, switchToTextTool);
|
new MoveTool(mode, setSelectedItems, clearSelectedItems, onUpdateImage, switchToTextTool);
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import Modes from '../../lib/modes';
|
import Modes from '../../lib/modes';
|
||||||
|
import {BitmapModes} from '../../lib/modes';
|
||||||
import {isGroup} from '../group';
|
import {isGroup} from '../group';
|
||||||
import {isCompoundPathItem, getRootItem} from '../item';
|
import {isCompoundPathItem, getRootItem} from '../item';
|
||||||
import {checkPointsClose, snapDeltaToAngle} from '../math';
|
import {checkPointsClose, snapDeltaToAngle} from '../math';
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, CENTER} from '../view';
|
import {getActionBounds, CENTER} from '../view';
|
||||||
import {clearSelection, cloneSelection, getSelectedLeafItems, getSelectedRootItems, setItemSelection}
|
import {clearSelection, cloneSelection, getSelectedLeafItems, getSelectedRootItems, setItemSelection}
|
||||||
from '../selection';
|
from '../selection';
|
||||||
import {getDragCrosshairLayer, CROSSHAIR_FULL_OPACITY} from '../layer';
|
import {getDragCrosshairLayer, CROSSHAIR_FULL_OPACITY} from '../layer';
|
||||||
|
@ -123,8 +124,10 @@ class MoveTool {
|
||||||
}
|
}
|
||||||
onMouseDrag (event) {
|
onMouseDrag (event) {
|
||||||
const point = event.point;
|
const point = event.point;
|
||||||
point.x = Math.max(0, Math.min(point.x, ART_BOARD_WIDTH));
|
const actionBounds = getActionBounds(this.mode in BitmapModes);
|
||||||
point.y = Math.max(0, Math.min(point.y, ART_BOARD_HEIGHT));
|
|
||||||
|
point.x = Math.max(actionBounds.left, Math.min(point.x, actionBounds.right));
|
||||||
|
point.y = Math.max(actionBounds.top, Math.min(point.y, actionBounds.bottom));
|
||||||
|
|
||||||
const dragVector = point.subtract(event.downPoint);
|
const dragVector = point.subtract(event.downPoint);
|
||||||
let snapVector;
|
let snapVector;
|
||||||
|
@ -180,6 +183,7 @@ class MoveTool {
|
||||||
(CENTER.y > bounds.bottom && CENTER.x < bounds.left) ||
|
(CENTER.y > bounds.bottom && CENTER.x < bounds.left) ||
|
||||||
(CENTER.y < bounds.top && CENTER.x > bounds.right) ||
|
(CENTER.y < bounds.top && CENTER.x > bounds.right) ||
|
||||||
(CENTER.y > bounds.bottom && CENTER.x > bounds.right)) {
|
(CENTER.y > bounds.bottom && CENTER.x > bounds.right)) {
|
||||||
|
|
||||||
// rotation center is to one of the 4 corners of the selection bounding box
|
// rotation center is to one of the 4 corners of the selection bounding box
|
||||||
const distX = Math.max(CENTER.x - bounds.right, bounds.left - CENTER.x);
|
const distX = Math.max(CENTER.x - bounds.right, bounds.left - CENTER.x);
|
||||||
const distY = Math.max(CENTER.y - bounds.bottom, bounds.top - CENTER.y);
|
const distY = Math.max(CENTER.y - bounds.bottom, bounds.top - CENTER.y);
|
||||||
|
@ -196,7 +200,6 @@ class MoveTool {
|
||||||
(1 - ((Math.abs(CENTER.x - newCenter.x) - (bounds.width / 2)) / (FADE_DISTANCE / paper.view.zoom))));
|
(1 - ((Math.abs(CENTER.x - newCenter.x) - (bounds.width / 2)) / (FADE_DISTANCE / paper.view.zoom))));
|
||||||
} // else the rotation center is within selection bounds, always show drag crosshair at full opacity
|
} // else the rotation center is within selection bounds, always show drag crosshair at full opacity
|
||||||
getDragCrosshairLayer().opacity = CROSSHAIR_FULL_OPACITY * opacityMultiplier;
|
getDragCrosshairLayer().opacity = CROSSHAIR_FULL_OPACITY * opacityMultiplier;
|
||||||
|
|
||||||
}
|
}
|
||||||
onMouseUp () {
|
onMouseUp () {
|
||||||
this.firstDrag = false;
|
this.firstDrag = false;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import {getSelectedRootItems} from '../selection';
|
import {getSelectedRootItems} from '../selection';
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from '../view';
|
import {getActionBounds} from '../view';
|
||||||
|
import {BitmapModes} from '../../lib/modes';
|
||||||
|
|
||||||
const NUDGE_MORE_MULTIPLIER = 15;
|
const NUDGE_MORE_MULTIPLIER = 15;
|
||||||
|
|
||||||
|
@ -10,12 +11,14 @@ const NUDGE_MORE_MULTIPLIER = 15;
|
||||||
*/
|
*/
|
||||||
class NudgeTool {
|
class NudgeTool {
|
||||||
/**
|
/**
|
||||||
|
* @param {Mode} mode Paint editor mode
|
||||||
* @param {function} boundingBoxTool to control the bounding box
|
* @param {function} boundingBoxTool to control the bounding box
|
||||||
* @param {!function} onUpdateImage A callback to call when the image visibly changes
|
* @param {!function} onUpdateImage A callback to call when the image visibly changes
|
||||||
*/
|
*/
|
||||||
constructor (boundingBoxTool, onUpdateImage) {
|
constructor (mode, boundingBoxTool, onUpdateImage) {
|
||||||
this.boundingBoxTool = boundingBoxTool;
|
this.boundingBoxTool = boundingBoxTool;
|
||||||
this.onUpdateImage = onUpdateImage;
|
this.onUpdateImage = onUpdateImage;
|
||||||
|
this.boundingBoxTool.isBitmap = mode in BitmapModes;
|
||||||
}
|
}
|
||||||
onKeyDown (event) {
|
onKeyDown (event) {
|
||||||
if (event.event.target instanceof HTMLInputElement) {
|
if (event.event.target instanceof HTMLInputElement) {
|
||||||
|
@ -38,16 +41,21 @@ class NudgeTool {
|
||||||
rect = item.bounds;
|
rect = item.bounds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const bounds = getActionBounds(this.boundingBoxTool.isBitmap);
|
||||||
|
const bottom = bounds.bottom - rect.top - 1;
|
||||||
|
const top = bounds.top - rect.bottom + 1;
|
||||||
|
const left = bounds.left - rect.right + 1;
|
||||||
|
const right = bounds.right - rect.left - 1;
|
||||||
|
|
||||||
let translation;
|
let translation;
|
||||||
if (event.key === 'up') {
|
if (event.key === 'up') {
|
||||||
translation = new paper.Point(0, -Math.min(nudgeAmount, rect.bottom - 1));
|
translation = new paper.Point(0, Math.min(bottom, Math.max(-nudgeAmount, top)));
|
||||||
} else if (event.key === 'down') {
|
} else if (event.key === 'down') {
|
||||||
translation = new paper.Point(0, Math.min(nudgeAmount, ART_BOARD_HEIGHT - rect.top - 1));
|
translation = new paper.Point(0, Math.max(top, Math.min(nudgeAmount, bottom)));
|
||||||
} else if (event.key === 'left') {
|
} else if (event.key === 'left') {
|
||||||
translation = new paper.Point(-Math.min(nudgeAmount, rect.right - 1), 0);
|
translation = new paper.Point(Math.min(right, Math.max(-nudgeAmount, left)), 0);
|
||||||
} else if (event.key === 'right') {
|
} else if (event.key === 'right') {
|
||||||
translation = new paper.Point(Math.min(nudgeAmount, ART_BOARD_WIDTH - rect.left - 1), 0);
|
translation = new paper.Point(Math.max(left, Math.min(nudgeAmount, right)), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (translation) {
|
if (translation) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import {HANDLE_RATIO, snapDeltaToAngle} from '../math';
|
import {HANDLE_RATIO, snapDeltaToAngle} from '../math';
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from '../view';
|
import {getActionBounds} from '../view';
|
||||||
import {clearSelection, getSelectedLeafItems, getSelectedSegments} from '../selection';
|
import {clearSelection, getSelectedLeafItems, getSelectedSegments} from '../selection';
|
||||||
|
|
||||||
/** Subtool of ReshapeTool for moving control points. */
|
/** Subtool of ReshapeTool for moving control points. */
|
||||||
|
@ -146,8 +146,9 @@ class PointTool {
|
||||||
this.deleteOnMouseUp = null;
|
this.deleteOnMouseUp = null;
|
||||||
|
|
||||||
const point = event.point;
|
const point = event.point;
|
||||||
point.x = Math.max(0, Math.min(point.x, ART_BOARD_WIDTH));
|
const bounds = getActionBounds();
|
||||||
point.y = Math.max(0, Math.min(point.y, ART_BOARD_HEIGHT));
|
point.x = Math.max(bounds.left, Math.min(point.x, bounds.right));
|
||||||
|
point.y = Math.max(bounds.top, Math.min(point.y, bounds.bottom));
|
||||||
|
|
||||||
if (!this.lastPoint) this.lastPoint = event.lastPoint;
|
if (!this.lastPoint) this.lastPoint = event.lastPoint;
|
||||||
const dragVector = point.subtract(event.downPoint);
|
const dragVector = point.subtract(event.downPoint);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import {getItems} from '../selection';
|
import {getItems} from '../selection';
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from '../view';
|
import {getActionBounds} from '../view';
|
||||||
|
import {BitmapModes} from '../../lib/modes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool to handle scaling items by pulling on the handles around the edges of the bounding
|
* Tool to handle scaling items by pulling on the handles around the edges of the bounding
|
||||||
|
@ -8,9 +9,11 @@ import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from '../view';
|
||||||
*/
|
*/
|
||||||
class ScaleTool {
|
class ScaleTool {
|
||||||
/**
|
/**
|
||||||
|
* @param {Mode} mode Paint editor mode
|
||||||
* @param {!function} onUpdateImage A callback to call when the image visibly changes
|
* @param {!function} onUpdateImage A callback to call when the image visibly changes
|
||||||
*/
|
*/
|
||||||
constructor (onUpdateImage) {
|
constructor (mode, onUpdateImage) {
|
||||||
|
this.isBitmap = mode in BitmapModes;
|
||||||
this.active = false;
|
this.active = false;
|
||||||
this.boundsPath = null;
|
this.boundsPath = null;
|
||||||
this.pivot = null;
|
this.pivot = null;
|
||||||
|
@ -71,8 +74,9 @@ class ScaleTool {
|
||||||
onMouseDrag (event) {
|
onMouseDrag (event) {
|
||||||
if (!this.active) return;
|
if (!this.active) return;
|
||||||
const point = event.point;
|
const point = event.point;
|
||||||
point.x = Math.max(0, Math.min(point.x, ART_BOARD_WIDTH));
|
const bounds = getActionBounds(this.isBitmap);
|
||||||
point.y = Math.max(0, Math.min(point.y, ART_BOARD_HEIGHT));
|
point.x = Math.max(bounds.left, Math.min(point.x, bounds.right));
|
||||||
|
point.y = Math.max(bounds.top, Math.min(point.y, bounds.bottom));
|
||||||
|
|
||||||
if (!this.lastPoint) this.lastPoint = event.lastPoint;
|
if (!this.lastPoint) this.lastPoint = event.lastPoint;
|
||||||
const delta = point.subtract(this.lastPoint);
|
const delta = point.subtract(this.lastPoint);
|
||||||
|
|
|
@ -46,7 +46,7 @@ class SelectTool extends paper.Tool {
|
||||||
onUpdateImage,
|
onUpdateImage,
|
||||||
switchToTextTool
|
switchToTextTool
|
||||||
);
|
);
|
||||||
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
|
const nudgeTool = new NudgeTool(Modes.SELECT, this.boundingBoxTool, onUpdateImage);
|
||||||
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems);
|
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems);
|
||||||
this.selectionBoxMode = false;
|
this.selectionBoxMode = false;
|
||||||
this.prevHoveredItemId = null;
|
this.prevHoveredItemId = null;
|
||||||
|
|
|
@ -44,7 +44,7 @@ class SelectionBoxTool {
|
||||||
}
|
}
|
||||||
onMouseUpBitmap (event) {
|
onMouseUpBitmap (event) {
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
if (this.selectionRect) {
|
if (this.selectionRect && this.selectionRect.bounds.intersects(getRaster().bounds)) {
|
||||||
const rect = new paper.Rectangle({
|
const rect = new paper.Rectangle({
|
||||||
from: new paper.Point(
|
from: new paper.Point(
|
||||||
Math.max(0, Math.round(this.selectionRect.bounds.topLeft.x)),
|
Math.max(0, Math.round(this.selectionRect.bounds.topLeft.x)),
|
||||||
|
@ -54,10 +54,6 @@ class SelectionBoxTool {
|
||||||
Math.min(ART_BOARD_HEIGHT, Math.round(this.selectionRect.bounds.bottomRight.y)))
|
Math.min(ART_BOARD_HEIGHT, Math.round(this.selectionRect.bounds.bottomRight.y)))
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove dotted rectangle
|
|
||||||
this.selectionRect.remove();
|
|
||||||
this.selectionRect = null;
|
|
||||||
|
|
||||||
if (rect.area) {
|
if (rect.area) {
|
||||||
// Pull selected raster to active layer
|
// Pull selected raster to active layer
|
||||||
const raster = getRaster().getSubRaster(rect);
|
const raster = getRaster().getSubRaster(rect);
|
||||||
|
@ -75,6 +71,11 @@ class SelectionBoxTool {
|
||||||
this.setSelectedItems();
|
this.setSelectedItems();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.selectionRect) {
|
||||||
|
// Remove dotted rectangle
|
||||||
|
this.selectionRect.remove();
|
||||||
|
this.selectionRect = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ class OvalTool extends paper.Tool {
|
||||||
setCursor,
|
setCursor,
|
||||||
onUpdateImage
|
onUpdateImage
|
||||||
);
|
);
|
||||||
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
|
const nudgeTool = new NudgeTool(Modes.OVAL, this.boundingBoxTool, onUpdateImage);
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
|
@ -31,7 +31,7 @@ class RectTool extends paper.Tool {
|
||||||
setCursor,
|
setCursor,
|
||||||
onUpdateImage
|
onUpdateImage
|
||||||
);
|
);
|
||||||
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
|
const nudgeTool = new NudgeTool(Modes.RECT, this.boundingBoxTool, onUpdateImage);
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
|
@ -50,14 +50,15 @@ class TextTool extends paper.Tool {
|
||||||
this.onUpdateImage = onUpdateImage;
|
this.onUpdateImage = onUpdateImage;
|
||||||
this.setTextEditTarget = setTextEditTarget;
|
this.setTextEditTarget = setTextEditTarget;
|
||||||
this.changeFont = changeFont;
|
this.changeFont = changeFont;
|
||||||
|
const paintMode = isBitmap ? Modes.BIT_TEXT : Modes.TEXT;
|
||||||
this.boundingBoxTool = new BoundingBoxTool(
|
this.boundingBoxTool = new BoundingBoxTool(
|
||||||
Modes.TEXT,
|
paintMode,
|
||||||
setSelectedItems,
|
setSelectedItems,
|
||||||
clearSelectedItems,
|
clearSelectedItems,
|
||||||
setCursor,
|
setCursor,
|
||||||
onUpdateImage
|
onUpdateImage
|
||||||
);
|
);
|
||||||
this.nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
|
this.nudgeTool = new NudgeTool(paintMode, this.boundingBoxTool, onUpdateImage);
|
||||||
this.isBitmap = isBitmap;
|
this.isBitmap = isBitmap;
|
||||||
|
|
||||||
// We have to set these functions instead of just declaring them because
|
// We have to set these functions instead of just declaring them because
|
||||||
|
|
|
@ -27,7 +27,9 @@ 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 && !layer.data.isDragCrosshairLayer) {
|
if (!layer.data.isBackgroundGuideLayer &&
|
||||||
|
!layer.data.isDragCrosshairLayer &&
|
||||||
|
!layer.data.isOutlineLayer) {
|
||||||
layer.removeChildren();
|
layer.removeChildren();
|
||||||
layer.remove();
|
layer.remove();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,96 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import {getSelectedRootItems} from './selection';
|
|
||||||
import {CROSSHAIR_SIZE, getBackgroundGuideLayer, getDragCrosshairLayer, getRaster} from './layer';
|
import {CROSSHAIR_SIZE, getBackgroundGuideLayer, getDragCrosshairLayer, getRaster} from './layer';
|
||||||
|
import {getAllRootItems, getSelectedRootItems} from './selection';
|
||||||
import {getHitBounds} from './bitmap';
|
import {getHitBounds} from './bitmap';
|
||||||
|
import log from '../log/log';
|
||||||
|
|
||||||
// Vectors are imported and exported at SVG_ART_BOARD size.
|
// Vectors are imported and exported at SVG_ART_BOARD size.
|
||||||
// Once they are imported however, both SVGs and bitmaps are on
|
// Once they are imported however, both SVGs and bitmaps are on
|
||||||
// canvases of ART_BOARD size.
|
// canvases of ART_BOARD size.
|
||||||
|
// (This is for backwards compatibility, to handle both assets
|
||||||
|
// designed for 480 x 360, and bitmap resolution 2 bitmaps)
|
||||||
const SVG_ART_BOARD_WIDTH = 480;
|
const SVG_ART_BOARD_WIDTH = 480;
|
||||||
const SVG_ART_BOARD_HEIGHT = 360;
|
const SVG_ART_BOARD_HEIGHT = 360;
|
||||||
const ART_BOARD_WIDTH = 480 * 2;
|
const ART_BOARD_WIDTH = SVG_ART_BOARD_WIDTH * 2;
|
||||||
const ART_BOARD_HEIGHT = 360 * 2;
|
const ART_BOARD_HEIGHT = SVG_ART_BOARD_HEIGHT * 2;
|
||||||
const CENTER = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
const CENTER = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
||||||
const PADDING_PERCENT = 25; // Padding as a percent of the max of width/height of the sprite
|
const PADDING_PERCENT = 25; // Padding as a percent of the max of width/height of the sprite
|
||||||
|
const BUFFER = 50; // Number of pixels of allowance around objects at the edges of the workspace
|
||||||
const MIN_RATIO = .125; // Zoom in to at least 1/8 of the screen. This way you don't end up incredibly
|
const MIN_RATIO = .125; // Zoom in to at least 1/8 of the screen. This way you don't end up incredibly
|
||||||
// zoomed in for tiny costumes.
|
// zoomed in for tiny costumes.
|
||||||
|
const OUTERMOST_ZOOM_LEVEL = 0.333;
|
||||||
|
const ART_BOARD_BOUNDS = new paper.Rectangle(0, 0, ART_BOARD_WIDTH, ART_BOARD_HEIGHT);
|
||||||
|
const MAX_WORKSPACE_BOUNDS = new paper.Rectangle(
|
||||||
|
-ART_BOARD_WIDTH / 4,
|
||||||
|
-ART_BOARD_HEIGHT / 4,
|
||||||
|
ART_BOARD_WIDTH * 1.5,
|
||||||
|
ART_BOARD_HEIGHT * 1.5);
|
||||||
|
|
||||||
|
let _workspaceBounds = ART_BOARD_BOUNDS;
|
||||||
|
|
||||||
|
const getWorkspaceBounds = () => _workspaceBounds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The workspace bounds define the areas that the scroll bars can access.
|
||||||
|
* They include at minimum the artboard, and extend to a bit beyond the
|
||||||
|
* farthest item off tne edge in any given direction (so items can't be
|
||||||
|
* "lost" off the edge)
|
||||||
|
*
|
||||||
|
* @param {boolean} clipEmpty Clip empty space from bounds, even if it
|
||||||
|
* means discontinuously jumping the viewport. This should probably be
|
||||||
|
* false unless the viewport is going to move discontinuously anyway
|
||||||
|
* (such as in a zoom button click)
|
||||||
|
*/
|
||||||
|
const setWorkspaceBounds = clipEmpty => {
|
||||||
|
const items = getAllRootItems();
|
||||||
|
// Include the artboard and what's visible in the viewport
|
||||||
|
let bounds = ART_BOARD_BOUNDS;
|
||||||
|
if (!clipEmpty) {
|
||||||
|
bounds = bounds.unite(paper.view.bounds);
|
||||||
|
}
|
||||||
|
// Include everything the user has drawn and a buffer around it
|
||||||
|
for (const item of items) {
|
||||||
|
bounds = bounds.unite(item.bounds.expand(BUFFER));
|
||||||
|
}
|
||||||
|
// Limit to max workspace bounds
|
||||||
|
bounds = bounds.intersect(MAX_WORKSPACE_BOUNDS.expand(BUFFER));
|
||||||
|
let top = bounds.top;
|
||||||
|
let left = bounds.left;
|
||||||
|
let bottom = bounds.bottom;
|
||||||
|
let right = bounds.right;
|
||||||
|
|
||||||
|
// Center in view if viewport is larger than workspace
|
||||||
|
let hDiff = 0;
|
||||||
|
let vDiff = 0;
|
||||||
|
if (bounds.width < paper.view.bounds.width) {
|
||||||
|
hDiff = (paper.view.bounds.width - bounds.width) / 2;
|
||||||
|
left -= hDiff;
|
||||||
|
right += hDiff;
|
||||||
|
}
|
||||||
|
if (bounds.height < paper.view.bounds.height) {
|
||||||
|
vDiff = (paper.view.bounds.height - bounds.height) / 2;
|
||||||
|
top -= vDiff;
|
||||||
|
bottom += vDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
_workspaceBounds = new paper.Rectangle(left, top, right - left, bottom - top);
|
||||||
|
};
|
||||||
|
|
||||||
const clampViewBounds = () => {
|
const clampViewBounds = () => {
|
||||||
const {left, right, top, bottom} = paper.project.view.bounds;
|
const {left, right, top, bottom} = paper.project.view.bounds;
|
||||||
if (left < 0) {
|
if (left < _workspaceBounds.left) {
|
||||||
paper.project.view.scrollBy(new paper.Point(-left, 0));
|
paper.project.view.scrollBy(new paper.Point(_workspaceBounds.left - left, 0));
|
||||||
}
|
}
|
||||||
if (top < 0) {
|
if (top < _workspaceBounds.top) {
|
||||||
paper.project.view.scrollBy(new paper.Point(0, -top));
|
paper.project.view.scrollBy(new paper.Point(0, _workspaceBounds.top - top));
|
||||||
}
|
}
|
||||||
if (bottom > ART_BOARD_HEIGHT) {
|
if (bottom > _workspaceBounds.bottom) {
|
||||||
paper.project.view.scrollBy(new paper.Point(0, ART_BOARD_HEIGHT - bottom));
|
paper.project.view.scrollBy(new paper.Point(0, _workspaceBounds.bottom - bottom));
|
||||||
}
|
}
|
||||||
if (right > ART_BOARD_WIDTH) {
|
if (right > _workspaceBounds.right) {
|
||||||
paper.project.view.scrollBy(new paper.Point(ART_BOARD_WIDTH - right, 0));
|
paper.project.view.scrollBy(new paper.Point(_workspaceBounds.right - right, 0));
|
||||||
}
|
}
|
||||||
|
setWorkspaceBounds();
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizeCrosshair = () => {
|
const resizeCrosshair = () => {
|
||||||
|
@ -47,13 +109,15 @@ const resizeCrosshair = () => {
|
||||||
const zoomOnFixedPoint = (deltaZoom, fixedPoint) => {
|
const zoomOnFixedPoint = (deltaZoom, fixedPoint) => {
|
||||||
const view = paper.view;
|
const view = paper.view;
|
||||||
const preZoomCenter = view.center;
|
const preZoomCenter = view.center;
|
||||||
const newZoom = Math.max(0.5, view.zoom + deltaZoom);
|
const newZoom = Math.max(OUTERMOST_ZOOM_LEVEL, view.zoom + deltaZoom);
|
||||||
const scaling = view.zoom / newZoom;
|
const scaling = view.zoom / newZoom;
|
||||||
const preZoomOffset = fixedPoint.subtract(preZoomCenter);
|
const preZoomOffset = fixedPoint.subtract(preZoomCenter);
|
||||||
const postZoomOffset = fixedPoint.subtract(preZoomOffset.multiply(scaling))
|
const postZoomOffset = fixedPoint.subtract(preZoomOffset.multiply(scaling))
|
||||||
.subtract(preZoomCenter);
|
.subtract(preZoomCenter);
|
||||||
view.zoom = newZoom;
|
view.zoom = newZoom;
|
||||||
view.translate(postZoomOffset.multiply(-1));
|
view.translate(postZoomOffset.multiply(-1));
|
||||||
|
|
||||||
|
setWorkspaceBounds(true /* clipEmpty */);
|
||||||
clampViewBounds();
|
clampViewBounds();
|
||||||
resizeCrosshair();
|
resizeCrosshair();
|
||||||
};
|
};
|
||||||
|
@ -80,6 +144,7 @@ const zoomOnSelection = deltaZoom => {
|
||||||
|
|
||||||
const resetZoom = () => {
|
const resetZoom = () => {
|
||||||
paper.project.view.zoom = .5;
|
paper.project.view.zoom = .5;
|
||||||
|
setWorkspaceBounds(true /* clipEmpty */);
|
||||||
resizeCrosshair();
|
resizeCrosshair();
|
||||||
clampViewBounds();
|
clampViewBounds();
|
||||||
};
|
};
|
||||||
|
@ -89,18 +154,40 @@ const pan = (dx, dy) => {
|
||||||
clampViewBounds();
|
clampViewBounds();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mouse actions are clamped to action bounds
|
||||||
|
* @param {boolean} isBitmap True if the editor is in bitmap mode, false if it is in vector mode
|
||||||
|
* @returns {paper.Rectangle} the bounds within which mouse events should work in the paint editor
|
||||||
|
*/
|
||||||
|
const getActionBounds = isBitmap => {
|
||||||
|
if (isBitmap) {
|
||||||
|
return ART_BOARD_BOUNDS;
|
||||||
|
}
|
||||||
|
return paper.view.bounds.unite(ART_BOARD_BOUNDS).intersect(MAX_WORKSPACE_BOUNDS);
|
||||||
|
};
|
||||||
|
|
||||||
const zoomToFit = isBitmap => {
|
const zoomToFit = isBitmap => {
|
||||||
resetZoom();
|
resetZoom();
|
||||||
let bounds;
|
let bounds;
|
||||||
if (isBitmap) {
|
if (isBitmap) {
|
||||||
bounds = getHitBounds(getRaster());
|
bounds = getHitBounds(getRaster()).expand(BUFFER);
|
||||||
} else {
|
} else {
|
||||||
bounds = paper.project.activeLayer.bounds;
|
const items = getAllRootItems();
|
||||||
|
for (const item of items) {
|
||||||
|
if (bounds) {
|
||||||
|
bounds = bounds.unite(item.bounds);
|
||||||
|
} else {
|
||||||
|
bounds = item.bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (bounds && bounds.width && bounds.height) {
|
if (bounds && bounds.width && bounds.height) {
|
||||||
// Ratio of (sprite length plus padding on all sides) to art board length.
|
const canvas = paper.view.element;
|
||||||
let ratio = Math.max(bounds.width * (1 + (2 * PADDING_PERCENT / 100)) / ART_BOARD_WIDTH,
|
// Ratio of (sprite length plus padding on all sides) to viewport length.
|
||||||
bounds.height * (1 + (2 * PADDING_PERCENT / 100)) / ART_BOARD_HEIGHT);
|
let ratio = paper.view.zoom *
|
||||||
|
Math.max(
|
||||||
|
bounds.width * (1 + (2 * PADDING_PERCENT / 100)) / canvas.clientWidth,
|
||||||
|
bounds.height * (1 + (2 * PADDING_PERCENT / 100)) / canvas.clientHeight);
|
||||||
// Clamp ratio
|
// Clamp ratio
|
||||||
ratio = Math.max(Math.min(1, ratio), MIN_RATIO);
|
ratio = Math.max(Math.min(1, ratio), MIN_RATIO);
|
||||||
if (ratio < 1) {
|
if (ratio < 1) {
|
||||||
|
@ -109,18 +196,26 @@ const zoomToFit = isBitmap => {
|
||||||
resizeCrosshair();
|
resizeCrosshair();
|
||||||
clampViewBounds();
|
clampViewBounds();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.warn('No bounds!');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
ART_BOARD_BOUNDS,
|
||||||
ART_BOARD_HEIGHT,
|
ART_BOARD_HEIGHT,
|
||||||
ART_BOARD_WIDTH,
|
ART_BOARD_WIDTH,
|
||||||
CENTER,
|
CENTER,
|
||||||
|
OUTERMOST_ZOOM_LEVEL,
|
||||||
SVG_ART_BOARD_WIDTH,
|
SVG_ART_BOARD_WIDTH,
|
||||||
SVG_ART_BOARD_HEIGHT,
|
SVG_ART_BOARD_HEIGHT,
|
||||||
|
MAX_WORKSPACE_BOUNDS,
|
||||||
clampViewBounds,
|
clampViewBounds,
|
||||||
|
getActionBounds,
|
||||||
pan,
|
pan,
|
||||||
resetZoom,
|
resetZoom,
|
||||||
|
setWorkspaceBounds,
|
||||||
|
getWorkspaceBounds,
|
||||||
resizeCrosshair,
|
resizeCrosshair,
|
||||||
zoomOnSelection,
|
zoomOnSelection,
|
||||||
zoomOnFixedPoint,
|
zoomOnFixedPoint,
|
||||||
|
|
|
@ -8,13 +8,16 @@ import {connect} from 'react-redux';
|
||||||
|
|
||||||
import {undoSnapshot} from '../reducers/undo';
|
import {undoSnapshot} from '../reducers/undo';
|
||||||
import {setSelectedItems} from '../reducers/selected-items';
|
import {setSelectedItems} from '../reducers/selected-items';
|
||||||
|
import {updateViewBounds} from '../reducers/view-bounds';
|
||||||
|
|
||||||
import {getSelectedLeafItems} from '../helper/selection';
|
import {getSelectedLeafItems} from '../helper/selection';
|
||||||
import {getRaster, hideGuideLayers, showGuideLayers} from '../helper/layer';
|
import {getRaster, hideGuideLayers, showGuideLayers} from '../helper/layer';
|
||||||
import {commitRectToBitmap, commitOvalToBitmap, commitSelectionToBitmap, getHitBounds} from '../helper/bitmap';
|
import {commitRectToBitmap, commitOvalToBitmap, commitSelectionToBitmap, getHitBounds} from '../helper/bitmap';
|
||||||
import {performSnapshot} from '../helper/undo';
|
import {performSnapshot} from '../helper/undo';
|
||||||
import {scaleWithStrokes} from '../helper/math';
|
import {scaleWithStrokes} from '../helper/math';
|
||||||
|
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, SVG_ART_BOARD_WIDTH, SVG_ART_BOARD_HEIGHT} from '../helper/view';
|
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, SVG_ART_BOARD_WIDTH, SVG_ART_BOARD_HEIGHT} from '../helper/view';
|
||||||
|
import {setWorkspaceBounds} from '../helper/view';
|
||||||
|
|
||||||
import Modes from '../lib/modes';
|
import Modes from '../lib/modes';
|
||||||
import {BitmapModes} from '../lib/modes';
|
import {BitmapModes} from '../lib/modes';
|
||||||
|
@ -47,6 +50,9 @@ const UpdateImageHOC = function (WrappedComponent) {
|
||||||
} else if (isVector(actualFormat)) {
|
} else if (isVector(actualFormat)) {
|
||||||
this.handleUpdateVector(skipSnapshot);
|
this.handleUpdateVector(skipSnapshot);
|
||||||
}
|
}
|
||||||
|
// Any time an image update is made, recalculate the bounds of the artwork
|
||||||
|
setWorkspaceBounds();
|
||||||
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
}
|
}
|
||||||
handleUpdateBitmap (skipSnapshot) {
|
handleUpdateBitmap (skipSnapshot) {
|
||||||
if (!getRaster().loaded) {
|
if (!getRaster().loaded) {
|
||||||
|
@ -110,12 +116,24 @@ const UpdateImageHOC = function (WrappedComponent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleUpdateVector (skipSnapshot) {
|
handleUpdateVector (skipSnapshot) {
|
||||||
|
// Remove viewbox (this would make it export at MAX_WORKSPACE_BOUNDS)
|
||||||
|
let workspaceMask;
|
||||||
|
if (paper.project.activeLayer.clipped) {
|
||||||
|
for (const child of paper.project.activeLayer.children) {
|
||||||
|
if (child.isClipMask()) {
|
||||||
|
workspaceMask = child;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paper.project.activeLayer.clipped = false;
|
||||||
|
workspaceMask.remove();
|
||||||
|
}
|
||||||
const guideLayers = hideGuideLayers(true /* includeRaster */);
|
const guideLayers = hideGuideLayers(true /* includeRaster */);
|
||||||
|
|
||||||
// Export at 0.5x
|
// Export at 0.5x
|
||||||
scaleWithStrokes(paper.project.activeLayer, .5, new paper.Point());
|
scaleWithStrokes(paper.project.activeLayer, .5, new paper.Point());
|
||||||
|
|
||||||
const bounds = paper.project.activeLayer.drawnBounds;
|
const bounds = paper.project.activeLayer.drawnBounds;
|
||||||
// @todo (https://github.com/LLK/scratch-paint/issues/445) generate view box
|
|
||||||
this.props.onUpdateImage(
|
this.props.onUpdateImage(
|
||||||
true /* isVector */,
|
true /* isVector */,
|
||||||
paper.project.exportSVG({
|
paper.project.exportSVG({
|
||||||
|
@ -130,6 +148,12 @@ const UpdateImageHOC = function (WrappedComponent) {
|
||||||
|
|
||||||
showGuideLayers(guideLayers);
|
showGuideLayers(guideLayers);
|
||||||
|
|
||||||
|
// Add back viewbox
|
||||||
|
if (workspaceMask) {
|
||||||
|
paper.project.activeLayer.addChild(workspaceMask);
|
||||||
|
workspaceMask.clipMask = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!skipSnapshot) {
|
if (!skipSnapshot) {
|
||||||
performSnapshot(this.props.undoSnapshot, Formats.VECTOR);
|
performSnapshot(this.props.undoSnapshot, Formats.VECTOR);
|
||||||
}
|
}
|
||||||
|
@ -153,7 +177,8 @@ const UpdateImageHOC = function (WrappedComponent) {
|
||||||
format: PropTypes.oneOf(Object.keys(Formats)),
|
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||||
mode: PropTypes.oneOf(Object.keys(Modes)).isRequired,
|
mode: PropTypes.oneOf(Object.keys(Modes)).isRequired,
|
||||||
onUpdateImage: PropTypes.func.isRequired,
|
onUpdateImage: PropTypes.func.isRequired,
|
||||||
undoSnapshot: PropTypes.func.isRequired
|
undoSnapshot: PropTypes.func.isRequired,
|
||||||
|
updateViewBounds: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -167,6 +192,9 @@ const UpdateImageHOC = function (WrappedComponent) {
|
||||||
},
|
},
|
||||||
undoSnapshot: snapshot => {
|
undoSnapshot: snapshot => {
|
||||||
dispatch(undoSnapshot(snapshot));
|
dispatch(undoSnapshot(snapshot));
|
||||||
|
},
|
||||||
|
updateViewBounds: matrix => {
|
||||||
|
dispatch(updateViewBounds(matrix));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,6 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
</body>
|
</body>
|
||||||
|
|
15
src/playground/playground.css
Normal file
15
src/playground/playground.css
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.playgroundContainer{
|
||||||
|
height: 90%;
|
||||||
|
width: 90%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
|
@ -6,8 +6,10 @@ import {Provider} from 'react-redux';
|
||||||
import {createStore} from 'redux';
|
import {createStore} from 'redux';
|
||||||
import reducer from './reducers/combine-reducers';
|
import reducer from './reducers/combine-reducers';
|
||||||
import {intlInitialState, IntlProvider} from './reducers/intl.js';
|
import {intlInitialState, IntlProvider} from './reducers/intl.js';
|
||||||
|
import styles from './playground.css';
|
||||||
|
|
||||||
const appTarget = document.createElement('div');
|
const appTarget = document.createElement('div');
|
||||||
|
appTarget.setAttribute('class', styles.playgroundContainer);
|
||||||
document.body.appendChild(appTarget);
|
document.body.appendChild(appTarget);
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
reducer,
|
reducer,
|
||||||
|
|
Loading…
Reference in a new issue