From e69f5bf6183bf1c78fa55d0925aba8d76038eb52 Mon Sep 17 00:00:00 2001 From: Grant Date: Tue, 4 Jun 2024 17:28:19 -0600 Subject: [PATCH] allow templates to be moved via cursor + alt (related #28) --- packages/client/src/components/Template.tsx | 62 ++++++++++++++++++- packages/client/src/lib/canvas.ts | 10 +++ packages/client/src/lib/keybinds.ts | 23 +++++++ packages/lib/src/renderer/PanZoom.ts | 30 +++------ .../lib/src/renderer/lib/panning.utils.ts | 14 ++++- 5 files changed, 114 insertions(+), 25 deletions(-) diff --git a/packages/client/src/components/Template.tsx b/packages/client/src/components/Template.tsx index f5f6241..4223446 100644 --- a/packages/client/src/components/Template.tsx +++ b/packages/client/src/components/Template.tsx @@ -2,10 +2,12 @@ import { useEffect, useRef } from "react"; import { Template as TemplateCl } from "../lib/template"; import { useAppContext } from "../contexts/AppContext"; import { useTemplateContext } from "../contexts/TemplateContext"; +import { Canvas } from "../lib/canvas"; export const Template = () => { const { config } = useAppContext(); - const { enable, url, width, setWidth, x, y, opacity } = useTemplateContext(); + const { enable, url, width, setWidth, x, y, opacity, setX, setY } = + useTemplateContext(); const templateHolder = useRef(null); const instance = useRef(); @@ -15,15 +17,71 @@ export const Template = () => { return; } + const templateHolderRef = templateHolder.current; + instance.current = new TemplateCl(config!, templateHolder.current); instance.current.on("autoDetectWidth", (width) => { - console.log("autodetectwidth", width); setWidth(width); }); + let startLocation: { clientX: number; clientY: number } | undefined; + let offset: [x: number, y: number] = [0, 0]; + + const handleMouseDown = (e: MouseEvent) => { + if (!e.altKey) return; + + startLocation = { clientX: e.clientX, clientY: e.clientY }; + offset = [e.offsetX, e.offsetY]; + Canvas.instance?.getPanZoom().panning.setEnabled(false); + }; + + const handleMouseMove = (e: MouseEvent) => { + if (!startLocation) return; + if (!Canvas.instance) { + console.warn( + "[Template#handleMouseMove] Canvas.instance is not defined" + ); + return; + } + + const deltaX = e.clientX - startLocation.clientX; + const deltaY = e.clientY - startLocation.clientY; + const newX = startLocation.clientX + deltaX; + const newY = startLocation.clientY + deltaY; + + const [canvasX, canvasY] = Canvas.instance.screenToPos(newX, newY); + + templateHolderRef.style.setProperty("left", canvasX - offset[0] + "px"); + templateHolderRef.style.setProperty("top", canvasY - offset[1] + "px"); + }; + + const handleMouseUp = (e: MouseEvent) => { + startLocation = undefined; + Canvas.instance?.getPanZoom().panning.setEnabled(true); + + const x = parseInt( + templateHolderRef.style.getPropertyValue("left").replace("px", "") || + "0" + ); + const y = parseInt( + templateHolderRef.style.getPropertyValue("top").replace("px", "") || "0" + ); + + setX(x); + setY(y); + }; + + templateHolder.current.addEventListener("mousedown", handleMouseDown); + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + return () => { instance.current?.destroy(); + + templateHolderRef?.removeEventListener("mousedown", handleMouseDown); + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); }; }, []); diff --git a/packages/client/src/lib/canvas.ts b/packages/client/src/lib/canvas.ts index 17bc05d..30bab06 100644 --- a/packages/client/src/lib/canvas.ts +++ b/packages/client/src/lib/canvas.ts @@ -95,6 +95,10 @@ export class Canvas extends EventEmitter { return this.config; } + getPanZoom() { + return this.PanZoom; + } + /** * Get nearby pixels * @param x @@ -325,6 +329,12 @@ export class Canvas extends EventEmitter { document.body.appendChild(el); } + /** + * Screen (clientX, clientY) to Canvas position + * @param x + * @param y + * @returns + */ screenToPos(x: number, y: number) { // the rendered dimentions in the browser const rect = this.canvas.getBoundingClientRect(); diff --git a/packages/client/src/lib/keybinds.ts b/packages/client/src/lib/keybinds.ts index b4d22e9..4857306 100644 --- a/packages/client/src/lib/keybinds.ts +++ b/packages/client/src/lib/keybinds.ts @@ -25,6 +25,12 @@ const KEYBINDS = enforceObjectType({ }, { key: "LONG_PRESS" }, ], + TEMPLATE_MOVE: [ + { + key: "LCLICK", + alt: true, + }, + ], }); class KeybindManager_ extends EventEmitter<{ @@ -34,6 +40,9 @@ class KeybindManager_ extends EventEmitter<{ super(); // setup listeners + document.addEventListener("keydown", this.handleKeydown, { + passive: false, + }); document.addEventListener("keyup", this.handleKeyup); document.addEventListener("click", this.handleClick); } @@ -43,6 +52,20 @@ class KeybindManager_ extends EventEmitter<{ // this is global and doesn't depend on any elements, so this shouldn't need to be called } + handleKeydown = (e: KeyboardEvent) => { + const blacklistedElements = ["INPUT"]; + + if (e.target instanceof HTMLElement) { + if (blacklistedElements.indexOf(e.target.tagName) > -1) { + return; + } + } + + if (e.key === "Alt") e.preventDefault(); + if (e.key === "Control") e.preventDefault(); + if (e.key === "Shift") e.preventDefault(); + }; + handleKeyup = (e: KeyboardEvent) => { // discard if in an input element diff --git a/packages/lib/src/renderer/PanZoom.ts b/packages/lib/src/renderer/PanZoom.ts index 6c93372..d90157a 100644 --- a/packages/lib/src/renderer/PanZoom.ts +++ b/packages/lib/src/renderer/PanZoom.ts @@ -343,7 +343,7 @@ export class PanZoom extends EventEmitter { * @param e */ private _touch_touchmove = (event: TouchEvent) => { - if (this.panning.enabled && event.touches.length === 1) { + if (this.panning.active && event.touches.length === 1) { event.preventDefault(); event.stopPropagation(); @@ -362,7 +362,7 @@ export class PanZoom extends EventEmitter { * @param e */ private _touch_touchend = (event: TouchEvent) => { - if (this.touch.lastTouch && this.panning.enabled) { + if (this.touch.lastTouch && this.panning.active) { const touch = event.changedTouches[0]; const dx = Math.abs(this.panning.x - touch.clientX); const dy = Math.abs(this.panning.y - touch.clientY); @@ -372,8 +372,8 @@ export class PanZoom extends EventEmitter { } } - if (this.panning.enabled) { - this.panning.enabled = false; + if (this.panning.active) { + this.panning.active = false; const touch = event.changedTouches[0]; @@ -391,7 +391,7 @@ export class PanZoom extends EventEmitter { this.touch.pinchStartDistance = distance; this.touch.lastDistance = distance; this.touch.pinchStartScale = this.transform.scale; - this.panning.enabled = false; + this.panning.active = false; } onPinch(event: TouchEvent) { @@ -560,7 +560,9 @@ export class PanZoom extends EventEmitter { this.mouse.mouseDown = Date.now(); - this.panning.start(e.clientX, e.clientY); + if (this.panning.enabled) { + this.panning.start(e.clientX, e.clientY); + } }; /** @@ -570,19 +572,7 @@ export class PanZoom extends EventEmitter { * @param e */ private _mouse_mousemove = (e: MouseEvent) => { - if (this.panning.enabled) { - e.preventDefault(); - e.stopPropagation(); - - this.panning.move(e.clientX, e.clientY); - } else { - // not panning - this.emit("hover", { - clientX: e.clientX, - clientY: e.clientY, - }); - } - if (this.panning.enabled) { + if (this.panning.active) { e.preventDefault(); e.stopPropagation(); @@ -629,7 +619,7 @@ export class PanZoom extends EventEmitter { } } - if (this.panning.enabled) { + if (this.panning.active) { // currently panning e.preventDefault(); e.stopPropagation(); diff --git a/packages/lib/src/renderer/lib/panning.utils.ts b/packages/lib/src/renderer/lib/panning.utils.ts index 9430bc9..bc11f4a 100644 --- a/packages/lib/src/renderer/lib/panning.utils.ts +++ b/packages/lib/src/renderer/lib/panning.utils.ts @@ -3,7 +3,8 @@ import { PanZoom } from "../PanZoom"; export class Panning { private instance: PanZoom; - public enabled: boolean = false; + public active: boolean = false; + public enabled: boolean = true; public x: number = 0; public y: number = 0; @@ -11,13 +12,20 @@ export class Panning { this.instance = instance; } + public setEnabled(enabled: boolean) { + this.enabled = enabled; + + this.active = false; + this.instance.update(); + } + /** * trigger panning start * @param x clientX * @param y clientY */ public start(x: number, y: number) { - this.enabled = true; + this.active = true; this.x = x; this.y = y; } @@ -45,7 +53,7 @@ export class Panning { * @param y clientY */ public end(x: number, y: number) { - this.enabled = false; + this.active = false; const deltaX = (x - this.x) / this.instance.transform.scale; const deltaY = (y - this.y) / this.instance.transform.scale;