Panning with space key (#579)

* Panning with space key

* prevent panning when selecting/dragging & add more checks

* Fix changing current tool via shortcut while panning

* Fix order of statements

* teardown on blur event

* Refactor cursor setting

Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
Guillermo Peralta Scura 2020-01-30 17:08:59 -03:00 committed by GitHub
parent 4ad38e317e
commit 35750d8d09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 12 deletions

View File

@ -100,12 +100,27 @@ function resetCursor() {
document.documentElement.style.cursor = "";
}
function setCursorForShape(shape: string) {
if (shape === "selection") {
resetCursor();
} else {
document.documentElement.style.cursor =
shape === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR;
}
}
const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
const ELEMENT_TRANSLATE_AMOUNT = 1;
const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
const CURSOR_TYPE = {
TEXT: "text",
CROSSHAIR: "crosshair",
GRABBING: "grabbing",
};
const MOUSE_BUTTON = {
MAIN: 0,
WHEEL: 1,
SECONDARY: 2,
};
let lastCanvasWidth = -1;
@ -141,6 +156,9 @@ function pickAppStatePropertiesForHistory(
let cursorX = 0;
let cursorY = 0;
let isHoldingSpace: boolean = false;
let isPanning: boolean = false;
let isHoldingMouseButton: boolean = false;
export class App extends React.Component<any, AppState> {
canvas: HTMLCanvasElement | null = null;
@ -225,6 +243,7 @@ export class App extends React.Component<any, AppState> {
};
private onUnload = () => {
isHoldingSpace = false;
this.saveDebounced();
this.saveDebounced.flush();
};
@ -269,9 +288,11 @@ export class App extends React.Component<any, AppState> {
document.addEventListener("cut", this.onCut);
document.addEventListener("keydown", this.onKeyDown, false);
document.addEventListener("keyup", this.onKeyUp, { passive: true });
document.addEventListener("mousemove", this.updateCurrentCursorPosition);
window.addEventListener("resize", this.onResize, false);
window.addEventListener("unload", this.onUnload, false);
window.addEventListener("blur", this.onUnload, false);
const searchParams = new URLSearchParams(window.location.search);
const id = searchParams.get("id");
@ -292,6 +313,7 @@ export class App extends React.Component<any, AppState> {
);
window.removeEventListener("resize", this.onResize, false);
window.removeEventListener("unload", this.onUnload, false);
window.removeEventListener("blur", this.onUnload, false);
}
public state: AppState = getDefaultAppState();
@ -356,11 +378,11 @@ export class App extends React.Component<any, AppState> {
!event.metaKey &&
this.state.draggingElement === null
) {
if (shape === "text") {
document.documentElement.style.cursor = CURSOR_TYPE.TEXT;
} else {
document.documentElement.style.cursor = CURSOR_TYPE.CROSSHAIR;
if (!isHoldingSpace) {
document.documentElement.style.cursor =
shape === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR;
}
elements = clearSelection(elements);
this.setState({ elementType: shape });
} else if (event[KEYS.META] && event.code === "KeyZ") {
event.preventDefault();
@ -380,6 +402,25 @@ export class App extends React.Component<any, AppState> {
this.setState(data.appState);
}
}
} else if (event.key === KEYS.SPACE && !isHoldingMouseButton) {
isHoldingSpace = true;
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING;
}
};
private onKeyUp = (event: KeyboardEvent) => {
if (event.key === KEYS.SPACE) {
if (this.state.elementType === "selection") {
resetCursor();
} else {
elements = clearSelection(elements);
document.documentElement.style.cursor =
this.state.elementType === "text"
? CURSOR_TYPE.TEXT
: CURSOR_TYPE.CROSSHAIR;
this.setState({});
}
isHoldingSpace = false;
}
};
@ -783,11 +824,19 @@ export class App extends React.Component<any, AppState> {
lastMouseUp(e);
}
// pan canvas on wheel button drag
if (e.button === 1) {
if (isPanning) return;
// pan canvas on wheel button drag or space+drag
if (
!isHoldingMouseButton &&
(e.button === MOUSE_BUTTON.WHEEL ||
(e.button === MOUSE_BUTTON.MAIN && isHoldingSpace))
) {
isHoldingMouseButton = true;
isPanning = true;
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING;
let { clientX: lastX, clientY: lastY } = e;
const onMouseMove = (e: MouseEvent) => {
document.documentElement.style.cursor = `grabbing`;
let deltaX = lastX - e.clientX;
let deltaY = lastY - e.clientY;
lastX = e.clientX;
@ -799,22 +848,28 @@ export class App extends React.Component<any, AppState> {
scrollY: state.scrollY - deltaY,
}));
};
const onMouseUp = (lastMouseUp = (e: MouseEvent) => {
const teardown = (lastMouseUp = () => {
lastMouseUp = null;
resetCursor();
isPanning = false;
isHoldingMouseButton = false;
if (!isHoldingSpace) {
setCursorForShape(this.state.elementType);
}
history.resumeRecording();
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
window.removeEventListener("mouseup", teardown);
window.removeEventListener("blur", teardown);
});
window.addEventListener("blur", teardown);
window.addEventListener("mousemove", onMouseMove, {
passive: true,
});
window.addEventListener("mouseup", onMouseUp);
window.addEventListener("mouseup", teardown);
return;
}
// only handle left mouse button
if (e.button !== 0) return;
if (e.button !== MOUSE_BUTTON.MAIN) return;
// fixes mousemove causing selection of UI texts #32
e.preventDefault();
// Preventing the event above disables default behavior
@ -1208,6 +1263,7 @@ export class App extends React.Component<any, AppState> {
} = this.state;
lastMouseUp = null;
isHoldingMouseButton = false;
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
@ -1393,6 +1449,7 @@ export class App extends React.Component<any, AppState> {
});
}}
onMouseMove={e => {
if (isHoldingSpace || isPanning) return;
const hasDeselectedButton = Boolean(e.buttons);
if (
hasDeselectedButton ||

View File

@ -13,6 +13,7 @@ export const KEYS = {
: "ctrlKey";
},
TAB: "Tab",
SPACE: " ",
};
export function isArrowKey(keyCode: string) {