allow templates to be moved via cursor + alt (related #28)

This commit is contained in:
Grant 2024-06-04 17:28:19 -06:00
parent 5d2ef8989e
commit e69f5bf618
5 changed files with 114 additions and 25 deletions

View File

@ -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<HTMLDivElement>(null);
const instance = useRef<TemplateCl>();
@ -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);
};
}, []);

View File

@ -95,6 +95,10 @@ export class Canvas extends EventEmitter<CanvasEvents> {
return this.config;
}
getPanZoom() {
return this.PanZoom;
}
/**
* Get nearby pixels
* @param x
@ -325,6 +329,12 @@ export class Canvas extends EventEmitter<CanvasEvents> {
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();

View File

@ -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

View File

@ -343,7 +343,7 @@ export class PanZoom extends EventEmitter<PanZoomEvents> {
* @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<PanZoomEvents> {
* @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<PanZoomEvents> {
}
}
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<PanZoomEvents> {
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<PanZoomEvents> {
this.mouse.mouseDown = Date.now();
if (this.panning.enabled) {
this.panning.start(e.clientX, e.clientY);
}
};
/**
@ -570,19 +572,7 @@ export class PanZoom extends EventEmitter<PanZoomEvents> {
* @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<PanZoomEvents> {
}
}
if (this.panning.enabled) {
if (this.panning.active) {
// currently panning
e.preventDefault();
e.stopPropagation();

View File

@ -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;