mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
Merge pull request #116 from paulkaplan/wheel-pan-zoom
Add basic zooming and panning
This commit is contained in:
commit
5bae62e7d1
11 changed files with 226 additions and 51 deletions
|
@ -19,7 +19,7 @@
|
|||
}
|
||||
|
||||
.top-align-row {
|
||||
display: flex;
|
||||
display: flex;
|
||||
padding-top: calc(5 * $grid-unit);
|
||||
flex-direction: row;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
|
||||
.mod-dashed-border {
|
||||
border-right: 1px dashed $ui-pane-border;
|
||||
border-right: 1px dashed $ui-pane-border;
|
||||
padding-right: calc(3 * $grid-unit);
|
||||
}
|
||||
|
||||
|
@ -92,3 +92,8 @@ $border-radius: 0.25rem;
|
|||
align-content: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
|
|
@ -39,6 +39,9 @@ import sendForwardIcon from './send-forward.svg';
|
|||
import sendFrontIcon from './send-front.svg';
|
||||
import undoIcon from './undo.svg';
|
||||
import ungroupIcon from './ungroup.svg';
|
||||
import zoomInIcon from './zoom-in.svg';
|
||||
import zoomOutIcon from './zoom-out.svg';
|
||||
import zoomResetIcon from './zoom-reset.svg';
|
||||
|
||||
const BufferedInput = BufferedInputHOC(Input);
|
||||
const messages = defineMessages({
|
||||
|
@ -97,7 +100,7 @@ class PaintEditorComponent extends React.Component {
|
|||
onClick={this.props.onUndo}
|
||||
>
|
||||
<img
|
||||
alt="Undo Icon"
|
||||
alt="Undo"
|
||||
className={styles.buttonGroupButtonIcon}
|
||||
src={undoIcon}
|
||||
/>
|
||||
|
@ -115,7 +118,7 @@ class PaintEditorComponent extends React.Component {
|
|||
onClick={this.props.onRedo}
|
||||
>
|
||||
<img
|
||||
alt="Redo Icon"
|
||||
alt="Redo"
|
||||
className={styles.buttonGroupButtonIcon}
|
||||
src={redoIcon}
|
||||
/>
|
||||
|
@ -127,14 +130,14 @@ class PaintEditorComponent extends React.Component {
|
|||
<InputGroup className={styles.modDashedBorder}>
|
||||
<LabeledIconButton
|
||||
disabled={!shouldShowGroup()}
|
||||
imgAlt="Group Icon"
|
||||
imgAlt="Group"
|
||||
imgSrc={groupIcon}
|
||||
title="Group"
|
||||
onClick={this.props.onGroup}
|
||||
/>
|
||||
<LabeledIconButton
|
||||
disabled={!shouldShowUngroup()}
|
||||
imgAlt="Ungroup Icon"
|
||||
imgAlt="Ungroup"
|
||||
imgSrc={ungroupIcon}
|
||||
title="Ungroup"
|
||||
onClick={this.props.onUngroup}
|
||||
|
@ -145,14 +148,14 @@ class PaintEditorComponent extends React.Component {
|
|||
<InputGroup className={styles.modDashedBorder}>
|
||||
<LabeledIconButton
|
||||
disabled={!shouldShowBringForward()}
|
||||
imgAlt="Send Forward Icon"
|
||||
imgAlt="Send Forward"
|
||||
imgSrc={sendForwardIcon}
|
||||
title="Forward"
|
||||
onClick={this.props.onSendForward}
|
||||
/>
|
||||
<LabeledIconButton
|
||||
disabled={!shouldShowSendBackward()}
|
||||
imgAlt="Send Backward Icon"
|
||||
imgAlt="Send Backward"
|
||||
imgSrc={sendBackwardIcon}
|
||||
title="Backward"
|
||||
onClick={this.props.onSendBackward}
|
||||
|
@ -163,14 +166,14 @@ class PaintEditorComponent extends React.Component {
|
|||
<InputGroup>
|
||||
<LabeledIconButton
|
||||
disabled={!shouldShowBringForward()}
|
||||
imgAlt="Send to Front Icon"
|
||||
imgAlt="Send to Front"
|
||||
imgSrc={sendFrontIcon}
|
||||
title="Front"
|
||||
onClick={this.props.onSendToFront}
|
||||
/>
|
||||
<LabeledIconButton
|
||||
disabled={!shouldShowSendBackward()}
|
||||
imgAlt="Send to Back Icon"
|
||||
imgAlt="Send to Back"
|
||||
imgSrc={sendBackIcon}
|
||||
title="Back"
|
||||
onClick={this.props.onSendToBack}
|
||||
|
@ -180,7 +183,7 @@ class PaintEditorComponent extends React.Component {
|
|||
{/* To be rotation point */}
|
||||
{/* <InputGroup>
|
||||
<LabeledIconButton
|
||||
imgAlt="Rotation Point Icon"
|
||||
imgAlt="Rotation Point"
|
||||
imgSrc={rotationPointIcon}
|
||||
title="Rotation Point"
|
||||
onClick={function () {}}
|
||||
|
@ -222,20 +225,16 @@ class PaintEditorComponent extends React.Component {
|
|||
onUpdateSvg={this.props.onUpdateSvg}
|
||||
/>
|
||||
<BrushMode
|
||||
canvas={this.state.canvas}
|
||||
onUpdateSvg={this.props.onUpdateSvg}
|
||||
/>
|
||||
<EraserMode
|
||||
canvas={this.state.canvas}
|
||||
onUpdateSvg={this.props.onUpdateSvg}
|
||||
/>
|
||||
<PenMode
|
||||
canvas={this.state.canvas}
|
||||
onUpdateSvg={this.props.onUpdateSvg}
|
||||
/>
|
||||
{/* Text mode will go here */}
|
||||
<LineMode
|
||||
canvas={this.state.canvas}
|
||||
onUpdateSvg={this.props.onUpdateSvg}
|
||||
/>
|
||||
<OvalMode
|
||||
|
@ -257,6 +256,41 @@ class PaintEditorComponent extends React.Component {
|
|||
svgId={this.props.svgId}
|
||||
onUpdateSvg={this.props.onUpdateSvg}
|
||||
/>
|
||||
{/* Zoom controls */}
|
||||
<InputGroup className={styles.zoomControls}>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
className={styles.buttonGroupButton}
|
||||
onClick={this.props.onZoomIn}
|
||||
>
|
||||
<img
|
||||
alt="Zoom In"
|
||||
className={styles.buttonGroupButtonIcon}
|
||||
src={zoomInIcon}
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
className={styles.buttonGroupButton}
|
||||
onClick={this.props.onZoomReset}
|
||||
>
|
||||
<img
|
||||
alt="Zoom Reset"
|
||||
className={styles.buttonGroupButtonIcon}
|
||||
src={zoomResetIcon}
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
className={styles.buttonGroupButton}
|
||||
onClick={this.props.onZoomOut}
|
||||
>
|
||||
<img
|
||||
alt="Zoom Out"
|
||||
className={styles.buttonGroupButtonIcon}
|
||||
src={zoomOutIcon}
|
||||
/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -279,6 +313,9 @@ PaintEditorComponent.propTypes = {
|
|||
onUngroup: PropTypes.func.isRequired,
|
||||
onUpdateName: PropTypes.func.isRequired,
|
||||
onUpdateSvg: PropTypes.func.isRequired,
|
||||
onZoomIn: PropTypes.func.isRequired,
|
||||
onZoomOut: PropTypes.func.isRequired,
|
||||
onZoomReset: PropTypes.func.isRequired,
|
||||
rotationCenterX: PropTypes.number,
|
||||
rotationCenterY: PropTypes.number,
|
||||
svg: PropTypes.string,
|
||||
|
|
15
src/components/paint-editor/zoom-in.svg
Normal file
15
src/components/paint-editor/zoom-in.svg
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="6 6 24 24">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-4{fill:none;stroke:#575e75;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}
|
||||
</style>
|
||||
</defs>
|
||||
<title>zoom-in</title>
|
||||
<g class="cls-3">
|
||||
<circle class="cls-4" cx="18" cy="18" r="7"/>
|
||||
<line class="cls-4" x1="23" y1="23" x2="26" y2="26"/>
|
||||
<line class="cls-4" x1="16" y1="18" x2="20" y2="18"/>
|
||||
<line class="cls-4" x1="18" y1="16" x2="18" y2="20"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 537 B |
14
src/components/paint-editor/zoom-out.svg
Normal file
14
src/components/paint-editor/zoom-out.svg
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="6 6 24 24">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-4{fill:none;stroke:#575e75;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}
|
||||
</style>
|
||||
</defs>
|
||||
<title>zoom-out</title>
|
||||
<g class="cls-3">
|
||||
<circle class="cls-4" cx="18" cy="18" r="7"/>
|
||||
<line class="cls-4" x1="23" y1="23" x2="26" y2="26"/>
|
||||
<line class="cls-4" x1="16" y1="18" x2="20" y2="18"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 480 B |
13
src/components/paint-editor/zoom-reset.svg
Normal file
13
src/components/paint-editor/zoom-reset.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="6 6 24 24">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-4{fill:#575e75;}
|
||||
</style>
|
||||
</defs>
|
||||
<title>zoom-reset</title>
|
||||
<g class="cls-3">
|
||||
<rect class="cls-4" x="13" y="14" width="10" height="2" rx="1" ry="1"/>
|
||||
<rect class="cls-4" x="13" y="20" width="10" height="2" rx="1" ry="1"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 394 B |
|
@ -22,8 +22,7 @@ class BrushMode extends React.Component {
|
|||
super(props);
|
||||
bindAll(this, [
|
||||
'activateTool',
|
||||
'deactivateTool',
|
||||
'onScroll'
|
||||
'deactivateTool'
|
||||
]);
|
||||
this.blob = new Blobbiness(
|
||||
this.props.onUpdateSvg, this.props.clearSelectedItems);
|
||||
|
@ -53,15 +52,11 @@ class BrushMode extends React.Component {
|
|||
// TODO: Instead of clearing selection, consider a kind of "draw inside"
|
||||
// analogous to how selection works with eraser
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
|
||||
// Force the default brush color if fill is MIXED or transparent
|
||||
const {fillColor} = this.props.colorState;
|
||||
if (fillColor === MIXED || fillColor === null) {
|
||||
this.props.onChangeFillColor(BrushMode.DEFAULT_COLOR);
|
||||
}
|
||||
|
||||
// TODO: This is temporary until a component that provides the brush size is hooked up
|
||||
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
||||
this.blob.activateTool({
|
||||
isEraser: false,
|
||||
...this.props.colorState,
|
||||
|
@ -69,17 +64,8 @@ class BrushMode extends React.Component {
|
|||
});
|
||||
}
|
||||
deactivateTool () {
|
||||
this.props.canvas.removeEventListener('mousewheel', this.onScroll);
|
||||
this.blob.deactivateTool();
|
||||
}
|
||||
onScroll (event) {
|
||||
if (event.deltaY < 0) {
|
||||
this.props.changeBrushSize(this.props.brushModeState.brushSize + 1);
|
||||
} else if (event.deltaY > 0 && this.props.brushModeState.brushSize > 1) {
|
||||
this.props.changeBrushSize(this.props.brushModeState.brushSize - 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<BrushModeComponent
|
||||
|
@ -94,8 +80,6 @@ BrushMode.propTypes = {
|
|||
brushModeState: PropTypes.shape({
|
||||
brushSize: PropTypes.number.isRequired
|
||||
}),
|
||||
canvas: PropTypes.instanceOf(Element).isRequired,
|
||||
changeBrushSize: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
colorState: PropTypes.shape({
|
||||
fillColor: PropTypes.string,
|
||||
|
|
|
@ -14,8 +14,7 @@ class EraserMode extends React.Component {
|
|||
super(props);
|
||||
bindAll(this, [
|
||||
'activateTool',
|
||||
'deactivateTool',
|
||||
'onScroll'
|
||||
'deactivateTool'
|
||||
]);
|
||||
this.blob = new Blobbiness(
|
||||
this.props.onUpdateSvg, this.props.clearSelectedItems);
|
||||
|
@ -41,22 +40,11 @@ class EraserMode extends React.Component {
|
|||
return nextProps.isEraserModeActive !== this.props.isEraserModeActive;
|
||||
}
|
||||
activateTool () {
|
||||
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
||||
|
||||
this.blob.activateTool({isEraser: true, ...this.props.eraserModeState});
|
||||
}
|
||||
deactivateTool () {
|
||||
this.props.canvas.removeEventListener('mousewheel', this.onScroll);
|
||||
this.blob.deactivateTool();
|
||||
}
|
||||
onScroll (event) {
|
||||
event.preventDefault();
|
||||
if (event.deltaY < 0) {
|
||||
this.props.changeBrushSize(this.props.eraserModeState.brushSize + 1);
|
||||
} else if (event.deltaY > 0 && this.props.eraserModeState.brushSize > 1) {
|
||||
this.props.changeBrushSize(this.props.eraserModeState.brushSize - 1);
|
||||
}
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<EraserModeComponent
|
||||
|
@ -68,8 +56,6 @@ class EraserMode extends React.Component {
|
|||
}
|
||||
|
||||
EraserMode.propTypes = {
|
||||
canvas: PropTypes.instanceOf(Element).isRequired,
|
||||
changeBrushSize: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
eraserModeState: PropTypes.shape({
|
||||
brushSize: PropTypes.number.isRequired
|
||||
|
|
|
@ -205,7 +205,6 @@ class LineMode extends React.Component {
|
|||
}
|
||||
}
|
||||
deactivateTool () {
|
||||
this.props.canvas.removeEventListener('mousewheel', this.onScroll);
|
||||
this.tool.remove();
|
||||
this.tool = null;
|
||||
if (this.hitResult) {
|
||||
|
@ -227,7 +226,6 @@ class LineMode extends React.Component {
|
|||
}
|
||||
|
||||
LineMode.propTypes = {
|
||||
canvas: PropTypes.instanceOf(Element).isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
colorState: PropTypes.shape({
|
||||
fillColor: PropTypes.string,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {performUndo, performRedo, performSnapshot, shouldShowUndo, shouldShowRed
|
|||
import {bringToFront, sendBackward, sendToBack, bringForward} from '../helper/order';
|
||||
import {groupSelection, ungroupSelection} from '../helper/group';
|
||||
import {getSelectedLeafItems} from '../helper/selection';
|
||||
import {resetZoom, zoomOnSelection} from '../helper/view';
|
||||
|
||||
import Modes from '../modes/modes';
|
||||
import {connect} from 'react-redux';
|
||||
|
@ -18,6 +19,9 @@ import bindAll from 'lodash.bindall';
|
|||
import paper from '@scratch/paper';
|
||||
|
||||
class PaintEditor extends React.Component {
|
||||
static get ZOOM_INCREMENT () {
|
||||
return 0.5;
|
||||
}
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
|
@ -41,6 +45,11 @@ class PaintEditor extends React.Component {
|
|||
document.removeEventListener('keydown', this.props.onKeyPress);
|
||||
}
|
||||
handleUpdateSvg (skipSnapshot) {
|
||||
// Store the zoom/pan and restore it after snapshotting
|
||||
// TODO Only doing this because snapshotting at zoom/pan makes export wrong
|
||||
const oldZoom = paper.project.view.zoom;
|
||||
const oldCenter = paper.project.view.center.clone();
|
||||
resetZoom();
|
||||
// Hide guide layer
|
||||
const guideLayer = getGuideLayer();
|
||||
const backgroundGuideLayer = getBackgroundGuideLayer();
|
||||
|
@ -60,6 +69,10 @@ class PaintEditor extends React.Component {
|
|||
paper.project.addLayer(backgroundGuideLayer);
|
||||
backgroundGuideLayer.sendToBack();
|
||||
paper.project.addLayer(guideLayer);
|
||||
// Restore old zoom
|
||||
paper.project.view.zoom = oldZoom;
|
||||
paper.project.view.center = oldCenter;
|
||||
paper.project.view.update();
|
||||
}
|
||||
handleUndo () {
|
||||
performUndo(this.props.undoState, this.props.onUndo, this.props.setSelectedItems, this.handleUpdateSvg);
|
||||
|
@ -91,6 +104,15 @@ class PaintEditor extends React.Component {
|
|||
canRedo () {
|
||||
return shouldShowRedo(this.props.undoState);
|
||||
}
|
||||
handleZoomIn () {
|
||||
zoomOnSelection(PaintEditor.ZOOM_INCREMENT);
|
||||
}
|
||||
handleZoomOut () {
|
||||
zoomOnSelection(-PaintEditor.ZOOM_INCREMENT);
|
||||
}
|
||||
handleZoomReset () {
|
||||
resetZoom();
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<PaintEditorComponent
|
||||
|
@ -111,6 +133,9 @@ class PaintEditor extends React.Component {
|
|||
onUngroup={this.handleUngroup}
|
||||
onUpdateName={this.props.onUpdateName}
|
||||
onUpdateSvg={this.handleUpdateSvg}
|
||||
onZoomIn={this.handleZoomIn}
|
||||
onZoomOut={this.handleZoomOut}
|
||||
onZoomReset={this.handleZoomReset}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import {undoSnapshot, clearUndoState} from '../reducers/undo';
|
|||
import {isGroup, ungroupItems} from '../helper/group';
|
||||
import {setupLayers} from '../helper/layer';
|
||||
import {deleteSelection, getSelectedLeafItems} from '../helper/selection';
|
||||
import {pan, resetZoom, zoomOnFixedPoint} from '../helper/view';
|
||||
|
||||
import {setSelectedItems} from '../reducers/selected-items';
|
||||
|
||||
import styles from './paper-canvas.css';
|
||||
|
@ -20,7 +22,8 @@ class PaperCanvas extends React.Component {
|
|||
bindAll(this, [
|
||||
'setCanvas',
|
||||
'importSvg',
|
||||
'handleKeyDown'
|
||||
'handleKeyDown',
|
||||
'handleWheel'
|
||||
]);
|
||||
}
|
||||
componentDidMount () {
|
||||
|
@ -45,7 +48,14 @@ class PaperCanvas extends React.Component {
|
|||
}
|
||||
this.props.clearUndo();
|
||||
if (newProps.svg) {
|
||||
// Store the zoom/pan and restore it after importing a new SVG
|
||||
const oldZoom = paper.project.view.zoom;
|
||||
const oldCenter = paper.project.view.center.clone();
|
||||
resetZoom();
|
||||
this.importSvg(newProps.svg, newProps.rotationCenterX, newProps.rotationCenterY);
|
||||
paper.project.view.zoom = oldZoom;
|
||||
paper.project.view.center = oldCenter;
|
||||
paper.project.view.update();
|
||||
}
|
||||
}
|
||||
componentWillUnmount () {
|
||||
|
@ -84,7 +94,7 @@ class PaperCanvas extends React.Component {
|
|||
item.clipped = false;
|
||||
mask.remove();
|
||||
}
|
||||
|
||||
|
||||
// Reduce single item nested in groups
|
||||
if (item.children && item.children.length === 1) {
|
||||
item = item.reduce();
|
||||
|
@ -114,6 +124,23 @@ class PaperCanvas extends React.Component {
|
|||
this.props.canvasRef(canvas);
|
||||
}
|
||||
}
|
||||
handleWheel (event) {
|
||||
if (event.metaKey) {
|
||||
// Zoom keeping mouse location fixed
|
||||
const canvasRect = this.canvas.getBoundingClientRect();
|
||||
const offsetX = event.clientX - canvasRect.left;
|
||||
const offsetY = event.clientY - canvasRect.top;
|
||||
const fixedPoint = paper.project.view.viewToProject(
|
||||
new paper.Point(offsetX, offsetY)
|
||||
);
|
||||
zoomOnFixedPoint(-event.deltaY / 100, fixedPoint);
|
||||
} else {
|
||||
const dx = event.deltaX / paper.project.view.zoom;
|
||||
const dy = event.deltaY / paper.project.view.zoom;
|
||||
pan(dx, dy);
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<canvas
|
||||
|
@ -121,6 +148,7 @@ class PaperCanvas extends React.Component {
|
|||
height="400px"
|
||||
ref={this.setCanvas}
|
||||
width="500px"
|
||||
onWheel={this.handleWheel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
70
src/helper/view.js
Normal file
70
src/helper/view.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import paper from '@scratch/paper';
|
||||
import {getSelectedRootItems} from './selection';
|
||||
|
||||
const clampViewBounds = () => {
|
||||
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));
|
||||
}
|
||||
if (bottom > 400) {
|
||||
paper.project.view.scrollBy(new paper.Point(0, 400 - bottom));
|
||||
}
|
||||
if (right > 500) {
|
||||
paper.project.view.scrollBy(new paper.Point(500 - right, 0));
|
||||
}
|
||||
};
|
||||
|
||||
// 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) => {
|
||||
const {view} = paper.project;
|
||||
const preZoomCenter = view.center;
|
||||
const newZoom = Math.max(1, view.zoom + deltaZoom);
|
||||
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));
|
||||
clampViewBounds();
|
||||
};
|
||||
|
||||
// Zoom keeping the selection center (if any) fixed.
|
||||
const zoomOnSelection = deltaZoom => {
|
||||
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 = () => {
|
||||
paper.project.view.zoom = 1;
|
||||
clampViewBounds();
|
||||
};
|
||||
|
||||
const pan = (dx, dy) => {
|
||||
paper.project.view.scrollBy(new paper.Point(dx, dy));
|
||||
clampViewBounds();
|
||||
};
|
||||
|
||||
export {
|
||||
pan,
|
||||
resetZoom,
|
||||
zoomOnSelection,
|
||||
zoomOnFixedPoint
|
||||
};
|
Loading…
Reference in a new issue