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:
parent
4ad38e317e
commit
35750d8d09
@ -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 ||
|
||||||
|
@ -13,6 +13,7 @@ export const KEYS = {
|
|||||||
: "ctrlKey";
|
: "ctrlKey";
|
||||||
},
|
},
|
||||||
TAB: "Tab",
|
TAB: "Tab",
|
||||||
|
SPACE: " ",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isArrowKey(keyCode: string) {
|
export function isArrowKey(keyCode: string) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user