Graphite/frontend/src/utilities/input.ts

129 lines
4.0 KiB
TypeScript

import { toggleFullscreen } from "@/utilities/fullscreen";
import { dialogIsVisible, dismissDialog, submitDialog } from "@/utilities/dialog";
const wasm = import("@/../wasm/pkg");
let viewportMouseInteractionOngoing = false;
// Keyboard events
function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): boolean {
// Don't redirect user input from text entry into HTML elements
const target = e.target as HTMLElement;
if (target.nodeName === "INPUT" || target.nodeName === "TEXTAREA" || target.isContentEditable) return false;
// Don't redirect when a modal is covering the workspace
if (dialogIsVisible()) return false;
// Don't redirect a fullscreen request
if (e.key.toLowerCase() === "f11" && e.type === "keydown" && !e.repeat) {
e.preventDefault();
toggleFullscreen();
return false;
}
// Don't redirect a reload request
if (e.key.toLowerCase() === "f5") return false;
// Don't redirect debugging tools
if (e.key.toLowerCase() === "f12") return false;
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "c") return false;
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "i") return false;
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "j") return false;
// Redirect to the backend
return true;
}
export async function onKeyDown(e: KeyboardEvent) {
if (shouldRedirectKeyboardEventToBackend(e)) {
e.preventDefault();
const modifiers = makeModifiersBitfield(e);
(await wasm).on_key_down(e.key, modifiers);
return;
}
if (dialogIsVisible()) {
if (e.key === "Escape") dismissDialog();
if (e.key === "Enter") submitDialog();
// Prevent the Enter key from acting like a click on the last clicked button, which might reopen the dialog
e.preventDefault();
}
}
export async function onKeyUp(e: KeyboardEvent) {
if (shouldRedirectKeyboardEventToBackend(e)) {
e.preventDefault();
const modifiers = makeModifiersBitfield(e);
(await wasm).on_key_up(e.key, modifiers);
}
}
// Mouse events
export async function onMouseMove(e: MouseEvent) {
if (!e.buttons) viewportMouseInteractionOngoing = false;
const modifiers = makeModifiersBitfield(e);
(await wasm).on_mouse_move(e.clientX, e.clientY, e.buttons, modifiers);
}
export async function onMouseDown(e: MouseEvent) {
const target = e.target && (e.target as HTMLElement);
const inCanvas = target && target.closest(".canvas");
const inDialog = target && target.closest(".dialog-modal .floating-menu-content");
// Block middle mouse button auto-scroll mode
if (e.button === 1) e.preventDefault();
if (dialogIsVisible() && !inDialog) {
dismissDialog();
e.preventDefault();
e.stopPropagation();
}
if (inCanvas) viewportMouseInteractionOngoing = true;
if (viewportMouseInteractionOngoing) {
const modifiers = makeModifiersBitfield(e);
(await wasm).on_mouse_down(e.clientX, e.clientY, e.buttons, modifiers);
}
}
export async function onMouseUp(e: MouseEvent) {
if (!e.buttons) viewportMouseInteractionOngoing = false;
const modifiers = makeModifiersBitfield(e);
(await wasm).on_mouse_up(e.clientX, e.clientY, e.buttons, modifiers);
}
export async function onMouseScroll(e: WheelEvent) {
const target = e.target && (e.target as HTMLElement);
const inCanvas = target && target.closest(".canvas");
if (inCanvas) {
e.preventDefault();
const modifiers = makeModifiersBitfield(e);
(await wasm).on_mouse_scroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers);
}
}
export async function onWindowResize() {
const viewports = Array.from(document.querySelectorAll(".canvas"));
const boundsOfViewports = viewports.map((canvas) => {
const bounds = canvas.getBoundingClientRect();
return [bounds.left, bounds.top, bounds.right, bounds.bottom];
});
const flattened = boundsOfViewports.flat();
const data = Float64Array.from(flattened);
if (boundsOfViewports.length > 0) (await wasm).bounds_of_viewports(data);
}
export function makeModifiersBitfield(e: MouseEvent | KeyboardEvent): number {
// eslint-disable-next-line no-bitwise
return Number(e.ctrlKey) | (Number(e.shiftKey) << 1) | (Number(e.altKey) << 2);
}