Zoom on cursor | Issue #940 (#2319)

This commit is contained in:
João Forja 2020-11-04 17:49:15 +00:00 committed by GitHub
parent facde7ace0
commit 566e6a5ede
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 912 additions and 357 deletions

View file

@ -1,5 +1,5 @@
import React from "react";
import { AppState } from "../types";
import { AppState, Zoom } from "../types";
import { ExcalidrawElement } from "../element/types";
import { ActionManager } from "../actions/manager";
import {
@ -183,14 +183,16 @@ export const ZoomActions = ({
zoom,
}: {
renderAction: ActionManager["renderAction"];
zoom: number;
zoom: Zoom;
}) => (
<Stack.Col gap={1}>
<Stack.Row gap={1} align="center">
{renderAction("zoomIn")}
{renderAction("zoomOut")}
{renderAction("resetZoom")}
<div style={{ marginInlineStart: 4 }}>{(zoom * 100).toFixed(0)}%</div>
<div style={{ marginInlineStart: 4 }}>
{(zoom.value * 100).toFixed(0)}%
</div>
</Stack.Row>
</Stack.Col>
);

View file

@ -180,6 +180,7 @@ import {
saveToFirebase,
isSavedToFirebase,
} from "../data/firebase";
import { getNewZoom } from "../scene/zoom";
/**
* @param func handler taking at most single parameter (event).
@ -935,8 +936,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
sceneY: user.pointer.y,
},
this.state,
this.canvas,
window.devicePixelRatio,
);
cursorButton[socketId] = user.button;
});
@ -1146,8 +1145,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const { x, y } = viewportCoordsToSceneCoords(
{ clientX, clientY },
this.state,
this.canvas,
window.devicePixelRatio,
);
const dx = x - elementsCenterX;
@ -1205,8 +1202,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const { x, y } = viewportCoordsToSceneCoords(
{ clientX: cursorX, clientY: cursorY },
this.state,
this.canvas,
window.devicePixelRatio,
);
const element = newTextElement({
@ -1719,15 +1714,19 @@ class App extends React.Component<ExcalidrawProps, AppState> {
this.setState({
selectedElementIds: {},
});
gesture.initialScale = this.state.zoom;
gesture.initialScale = this.state.zoom.value;
});
private onGestureChange = withBatchedUpdates((event: GestureEvent) => {
event.preventDefault();
this.setState({
zoom: getNormalizedZoom(gesture.initialScale! * event.scale),
});
const gestureCenter = getCenter(gesture.pointers);
this.setState(({ zoom }) => ({
zoom: getNewZoom(
getNormalizedZoom(gesture.initialScale! * event.scale),
zoom,
gestureCenter,
),
}));
});
private onGestureEnd = withBatchedUpdates((event: GestureEvent) => {
@ -1771,8 +1770,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
sceneY: y,
},
this.state,
this.canvas,
window.devicePixelRatio,
);
return [viewportX, viewportY];
},
@ -1990,8 +1987,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
event,
this.state,
this.canvas,
window.devicePixelRatio,
);
const selectedGroupIds = getSelectedGroupIds(this.state);
@ -2051,12 +2046,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const distance = getDistance(Array.from(gesture.pointers.values()));
const scaleFactor = distance / gesture.initialDistance!;
this.setState({
scrollX: normalizeScroll(this.state.scrollX + deltaX / this.state.zoom),
scrollY: normalizeScroll(this.state.scrollY + deltaY / this.state.zoom),
zoom: getNormalizedZoom(gesture.initialScale! * scaleFactor),
this.setState(({ zoom, scrollX, scrollY }) => ({
scrollX: normalizeScroll(scrollX + deltaX / zoom.value),
scrollY: normalizeScroll(scrollY + deltaY / zoom.value),
zoom: getNewZoom(
getNormalizedZoom(gesture.initialScale! * scaleFactor),
zoom,
center,
),
shouldCacheIgnoreZoom: true,
});
}));
this.resetShouldCacheIgnoreZoomDebounced();
} else {
gesture.lastCenter = gesture.initialDistance = gesture.initialScale = null;
@ -2079,12 +2078,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
}
const scenePointer = viewportCoordsToSceneCoords(
event,
this.state,
this.canvas,
window.devicePixelRatio,
);
const scenePointer = viewportCoordsToSceneCoords(event, this.state);
const { x: scenePointerX, y: scenePointerY } = scenePointer;
if (
@ -2453,8 +2447,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
this.setState({
scrollX: normalizeScroll(this.state.scrollX - deltaX / this.state.zoom),
scrollY: normalizeScroll(this.state.scrollY - deltaY / this.state.zoom),
scrollX: normalizeScroll(
this.state.scrollX - deltaX / this.state.zoom.value,
),
scrollY: normalizeScroll(
this.state.scrollY - deltaY / this.state.zoom.value,
),
});
});
const teardown = withBatchedUpdates(
@ -2491,7 +2489,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
if (gesture.pointers.size === 2) {
gesture.lastCenter = getCenter(gesture.pointers);
gesture.initialScale = this.state.zoom;
gesture.initialScale = this.state.zoom.value;
gesture.initialDistance = getDistance(
Array.from(gesture.pointers.values()),
);
@ -2501,12 +2499,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
private initialPointerDownState(
event: React.PointerEvent<HTMLCanvasElement>,
): PointerDownState {
const origin = viewportCoordsToSceneCoords(
event,
this.state,
this.canvas,
window.devicePixelRatio,
);
const origin = viewportCoordsToSceneCoords(event, this.state);
const selectedElements = getSelectedElements(
this.scene.getElements(),
this.state,
@ -2790,7 +2783,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
// How many pixels off the shape boundary we still consider a hit
const threshold = 10 / this.state.zoom;
const threshold = 10 / this.state.zoom.value;
const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
return (
point.x > x1 - threshold &&
@ -2985,12 +2978,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
return;
}
const pointerCoords = viewportCoordsToSceneCoords(
event,
this.state,
this.canvas,
window.devicePixelRatio,
);
const pointerCoords = viewportCoordsToSceneCoords(event, this.state);
const [gridX, gridY] = getGridPoint(
pointerCoords.x,
pointerCoords.y,
@ -3212,7 +3200,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
mutateElement(draggingElement, {
points: simplify(
[...(points as Point[]), [dx, dy]],
0.7 / this.state.zoom,
0.7 / this.state.zoom.value,
),
});
} else {
@ -3300,7 +3288,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const x = event.clientX;
const dx = x - pointerDownState.lastCoords.x;
this.setState({
scrollX: normalizeScroll(this.state.scrollX - dx / this.state.zoom),
scrollX: normalizeScroll(
this.state.scrollX - dx / this.state.zoom.value,
),
});
pointerDownState.lastCoords.x = x;
return true;
@ -3310,7 +3300,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const y = event.clientY;
const dy = y - pointerDownState.lastCoords.y;
this.setState({
scrollY: normalizeScroll(this.state.scrollY - dy / this.state.zoom),
scrollY: normalizeScroll(
this.state.scrollY - dy / this.state.zoom.value,
),
});
pointerDownState.lastCoords.y = y;
return true;
@ -3387,8 +3379,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const pointerCoords = viewportCoordsToSceneCoords(
childEvent,
this.state,
this.canvas,
window.devicePixelRatio,
);
if (
@ -3808,8 +3798,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const { x, y } = viewportCoordsToSceneCoords(
{ clientX, clientY },
this.state,
this.canvas,
window.devicePixelRatio,
);
const elements = this.scene.getElements();
@ -3885,7 +3873,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const { deltaX, deltaY } = event;
const { selectedElementIds, previousSelectedElementIds } = this.state;
// note that event.ctrlKey is necessary to handle pinch zooming
if (event.metaKey || event.ctrlKey) {
const sign = Math.sign(deltaY);
@ -3903,8 +3890,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
});
}, 1000);
}
this.setState(({ zoom }) => ({
zoom: getNormalizedZoom(zoom - delta / 100),
zoom: getNewZoom(getNormalizedZoom(zoom.value - delta / 100), zoom, {
x: cursorX,
y: cursorY,
}),
selectedElementIds: {},
previousSelectedElementIds:
Object.keys(selectedElementIds).length !== 0
@ -3920,14 +3911,14 @@ class App extends React.Component<ExcalidrawProps, AppState> {
if (event.shiftKey) {
this.setState(({ zoom, scrollX }) => ({
// on Mac, shift+wheel tends to result in deltaX
scrollX: normalizeScroll(scrollX - (deltaY || deltaX) / zoom),
scrollX: normalizeScroll(scrollX - (deltaY || deltaX) / zoom.value),
}));
return;
}
this.setState(({ zoom, scrollX, scrollY }) => ({
scrollX: normalizeScroll(scrollX - deltaX / zoom),
scrollY: normalizeScroll(scrollY - deltaY / zoom),
scrollX: normalizeScroll(scrollX - deltaX / zoom.value),
scrollY: normalizeScroll(scrollY - deltaY / zoom.value),
}));
});
@ -3960,8 +3951,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
{ sceneX: elementCenterX, sceneY: elementCenterY },
appState,
canvas,
scale,
);
return { viewportX, viewportY, elementCenterX, elementCenterY };
}
@ -3975,8 +3964,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const pointer = viewportCoordsToSceneCoords(
{ clientX: x, clientY: y },
this.state,
this.canvas,
window.devicePixelRatio,
);
if (isNaN(pointer.x) || isNaN(pointer.y)) {