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

View File

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