2017-10-26 20:28:17 -04:00
|
|
|
import paper from '@scratch/paper';
|
|
|
|
import {getSelectedRootItems} from './selection';
|
2020-02-06 18:11:10 -05:00
|
|
|
import {CROSSHAIR_SIZE, getBackgroundGuideLayer, getDragCrosshairLayer, getRaster} from './layer';
|
2018-08-31 12:07:17 -04:00
|
|
|
import {getHitBounds} from './bitmap';
|
2017-10-26 20:28:17 -04:00
|
|
|
|
2018-05-01 16:18:24 -04:00
|
|
|
// Vectors are imported and exported at SVG_ART_BOARD size.
|
|
|
|
// Once they are imported however, both SVGs and bitmaps are on
|
|
|
|
// canvases of ART_BOARD size.
|
|
|
|
const SVG_ART_BOARD_WIDTH = 480;
|
|
|
|
const SVG_ART_BOARD_HEIGHT = 360;
|
2018-04-16 18:08:17 -04:00
|
|
|
const ART_BOARD_WIDTH = 480 * 2;
|
|
|
|
const ART_BOARD_HEIGHT = 360 * 2;
|
2018-08-31 12:07:17 -04:00
|
|
|
const PADDING_PERCENT = 25; // Padding as a percent of the max of width/height of the sprite
|
|
|
|
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.
|
2018-04-16 18:08:17 -04:00
|
|
|
|
2018-08-16 16:49:43 -04:00
|
|
|
const clampViewBounds = () => {
|
2017-10-27 09:33:06 -04:00
|
|
|
const {left, right, top, bottom} = paper.project.view.bounds;
|
|
|
|
if (left < 0) {
|
|
|
|
paper.project.view.scrollBy(new paper.Point(-left, 0));
|
|
|
|
}
|
|
|
|
if (top < 0) {
|
|
|
|
paper.project.view.scrollBy(new paper.Point(0, -top));
|
|
|
|
}
|
2018-04-16 18:08:17 -04:00
|
|
|
if (bottom > ART_BOARD_HEIGHT) {
|
|
|
|
paper.project.view.scrollBy(new paper.Point(0, ART_BOARD_HEIGHT - bottom));
|
2017-10-27 09:33:06 -04:00
|
|
|
}
|
2018-04-16 18:08:17 -04:00
|
|
|
if (right > ART_BOARD_WIDTH) {
|
|
|
|
paper.project.view.scrollBy(new paper.Point(ART_BOARD_WIDTH - right, 0));
|
2017-10-27 09:33:06 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-02-06 18:11:10 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-10-27 10:06:01 -04:00
|
|
|
// Zoom keeping a project-space point fixed.
|
|
|
|
// This article was helpful http://matthiasberth.com/tech/stable-zoom-and-pan-in-paperjs
|
|
|
|
const zoomOnFixedPoint = (deltaZoom, fixedPoint) => {
|
2018-08-31 12:07:17 -04:00
|
|
|
const view = paper.view;
|
2017-10-27 10:06:01 -04:00
|
|
|
const preZoomCenter = view.center;
|
2018-04-20 11:20:38 -04:00
|
|
|
const newZoom = Math.max(0.5, view.zoom + deltaZoom);
|
2017-10-27 10:06:01 -04:00
|
|
|
const scaling = view.zoom / newZoom;
|
|
|
|
const preZoomOffset = fixedPoint.subtract(preZoomCenter);
|
|
|
|
const postZoomOffset = fixedPoint.subtract(preZoomOffset.multiply(scaling))
|
|
|
|
.subtract(preZoomCenter);
|
|
|
|
view.zoom = newZoom;
|
|
|
|
view.translate(postZoomOffset.multiply(-1));
|
2018-08-16 16:49:43 -04:00
|
|
|
clampViewBounds();
|
2020-02-06 18:11:10 -05:00
|
|
|
_resizeCrosshair();
|
2017-10-27 10:06:01 -04:00
|
|
|
};
|
|
|
|
|
2017-10-26 20:28:17 -04:00
|
|
|
// Zoom keeping the selection center (if any) fixed.
|
2017-10-27 09:33:06 -04:00
|
|
|
const zoomOnSelection = deltaZoom => {
|
2017-10-26 20:28:17 -04:00
|
|
|
let fixedPoint;
|
|
|
|
const items = getSelectedRootItems();
|
|
|
|
if (items.length > 0) {
|
|
|
|
let rect = null;
|
|
|
|
for (const item of items) {
|
|
|
|
if (rect) {
|
|
|
|
rect = rect.unite(item.bounds);
|
|
|
|
} else {
|
|
|
|
rect = item.bounds;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fixedPoint = rect.center;
|
|
|
|
} else {
|
|
|
|
fixedPoint = paper.project.view.center;
|
|
|
|
}
|
|
|
|
zoomOnFixedPoint(deltaZoom, fixedPoint);
|
|
|
|
};
|
|
|
|
|
|
|
|
const resetZoom = () => {
|
2018-04-16 18:08:17 -04:00
|
|
|
paper.project.view.zoom = .5;
|
2020-02-06 18:11:10 -05:00
|
|
|
_resizeCrosshair();
|
2018-08-16 16:49:43 -04:00
|
|
|
clampViewBounds();
|
2017-10-26 20:28:17 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
const pan = (dx, dy) => {
|
|
|
|
paper.project.view.scrollBy(new paper.Point(dx, dy));
|
2018-08-16 16:49:43 -04:00
|
|
|
clampViewBounds();
|
2017-10-26 20:28:17 -04:00
|
|
|
};
|
|
|
|
|
2018-08-31 12:07:17 -04:00
|
|
|
const zoomToFit = isBitmap => {
|
|
|
|
resetZoom();
|
|
|
|
let bounds;
|
|
|
|
if (isBitmap) {
|
|
|
|
bounds = getHitBounds(getRaster());
|
|
|
|
} else {
|
|
|
|
bounds = paper.project.activeLayer.bounds;
|
|
|
|
}
|
|
|
|
if (bounds && bounds.width && bounds.height) {
|
|
|
|
// Ratio of (sprite length plus padding on all sides) to art board length.
|
|
|
|
let ratio = Math.max(bounds.width * (1 + (2 * PADDING_PERCENT / 100)) / ART_BOARD_WIDTH,
|
|
|
|
bounds.height * (1 + (2 * PADDING_PERCENT / 100)) / ART_BOARD_HEIGHT);
|
|
|
|
// Clamp ratio
|
|
|
|
ratio = Math.max(Math.min(1, ratio), MIN_RATIO);
|
|
|
|
if (ratio < 1) {
|
|
|
|
paper.view.center = bounds.center;
|
|
|
|
paper.view.zoom = paper.view.zoom / ratio;
|
2020-02-06 18:11:10 -05:00
|
|
|
_resizeCrosshair();
|
2018-08-31 12:07:17 -04:00
|
|
|
clampViewBounds();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-10-26 20:28:17 -04:00
|
|
|
export {
|
2018-04-16 18:08:17 -04:00
|
|
|
ART_BOARD_HEIGHT,
|
|
|
|
ART_BOARD_WIDTH,
|
2018-05-01 16:18:24 -04:00
|
|
|
SVG_ART_BOARD_WIDTH,
|
|
|
|
SVG_ART_BOARD_HEIGHT,
|
2018-08-16 16:49:43 -04:00
|
|
|
clampViewBounds,
|
2017-10-26 20:28:17 -04:00
|
|
|
pan,
|
|
|
|
resetZoom,
|
|
|
|
zoomOnSelection,
|
2018-08-31 12:07:17 -04:00
|
|
|
zoomOnFixedPoint,
|
|
|
|
zoomToFit
|
2017-10-27 09:33:06 -04:00
|
|
|
};
|