move mouse events to PanZoom

This commit is contained in:
Grant 2024-02-13 16:30:49 -07:00
parent 93abdf6bcb
commit f396b4a16a
3 changed files with 162 additions and 73 deletions

View File

@ -1,14 +1,8 @@
import React, { createRef, useEffect } from "react";
import {
TransformComponent,
TransformWrapper,
useControls,
useTransformEffect,
} from "react-zoom-pan-pinch";
import React, { createRef, useContext, useEffect } from "react";
import { Canvas } from "../lib/canvas";
import { useAppContext } from "../contexts/AppContext";
import throttle from "lodash.throttle";
import { PanZoomWrapper } from "@sc07-canvas/lib/src/renderer";
import { RendererContext } from "@sc07-canvas/lib/src/renderer/RendererContext";
export const CanvasWrapper = () => {
// to prevent safari from blurring things, use the zoom css property
@ -24,6 +18,7 @@ export const CanvasWrapper = () => {
const CanvasInner = () => {
const canvasRef = createRef<HTMLCanvasElement>();
const { config } = useAppContext();
const PanZoom = useContext(RendererContext);
// const { centerView } = useControls();
// useTransformEffect(
@ -39,7 +34,7 @@ const CanvasInner = () => {
useEffect(() => {
if (!config.canvas || !canvasRef.current) return;
const canvas = canvasRef.current!;
const canvasInstance = new Canvas(config, canvas);
const canvasInstance = new Canvas(config, canvas, PanZoom);
// centerView();
return () => {

View File

@ -1,6 +1,11 @@
import EventEmitter from "eventemitter3";
import { ClientConfig, IPalleteContext, Pixel } from "../types";
import Network from "./network";
import {
ClickEvent,
HoverEvent,
PanZoom,
} from "@sc07-canvas/lib/src/renderer/PanZoom";
export class Canvas extends EventEmitter {
static instance: Canvas | undefined;
@ -8,6 +13,7 @@ export class Canvas extends EventEmitter {
private _destroy = false;
private config: ClientConfig;
private canvas: HTMLCanvasElement;
private PanZoom: PanZoom;
private ctx: CanvasRenderingContext2D;
private cursor = { x: -1, y: -1 };
@ -16,24 +22,27 @@ export class Canvas extends EventEmitter {
} = {};
private lastPlace: number | undefined;
constructor(config: ClientConfig, canvas: HTMLCanvasElement) {
constructor(
config: ClientConfig,
canvas: HTMLCanvasElement,
PanZoom: PanZoom
) {
super();
Canvas.instance = this;
this.config = config;
this.canvas = canvas;
this.PanZoom = PanZoom;
this.ctx = canvas.getContext("2d")!;
canvas.width = config.canvas.size[0];
canvas.height = config.canvas.size[1];
canvas.addEventListener("mousemove", this.handleMouseMove.bind(this));
canvas.addEventListener("mouseup", this.handleMouseClick.bind(this));
canvas.addEventListener("mousedown", this.handleMouseDown.bind(this));
this.PanZoom.addListener("hover", this.handleMouseMove.bind(this));
this.PanZoom.addListener("click", this.handleMouseDown.bind(this));
this.on("pallete", this.updatePallete.bind(this));
// Network.on("canvas", this.handleBatch.bind(this));
Network.waitFor("canvas").then(([pixels]) => this.handleBatch(pixels));
this.draw();
@ -42,53 +51,24 @@ export class Canvas extends EventEmitter {
destroy() {
this._destroy = true;
this.canvas.removeEventListener(
"mousemove",
this.handleMouseMove.bind(this)
);
this.canvas.removeEventListener(
"mouseup",
this.handleMouseClick.bind(this)
);
this.canvas.removeEventListener(
"mousedown",
this.handleMouseDown.bind(this)
);
this.PanZoom.removeListener("hover", this.handleMouseMove.bind(this));
this.PanZoom.removeListener("click", this.handleMouseDown.bind(this));
Network.off("canvas", this.handleBatch.bind(this));
}
private downTime: number | undefined;
private dragOrigin: { x: number; y: number } = { x: 0, y: 0 };
handleMouseClick(e: MouseEvent) {
const downDelta = Date.now() - this.downTime!;
const delta = [
Math.abs(this.dragOrigin.x - e.clientX),
Math.abs(this.dragOrigin.y - e.clientY),
];
if (downDelta < 500) {
// mouse was down for less than 500ms
if (delta[0] < 5 && delta[1] < 5) {
const [x, y] = this.screenToPos(e.clientX, e.clientY);
this.place(x, y);
}
}
handleMouseDown(e: ClickEvent) {
const [x, y] = this.screenToPos(e.clientX, e.clientY);
this.place(x, y);
}
handleMouseDown(e: MouseEvent) {
this.downTime = Date.now();
this.dragOrigin = { x: e.pageX, y: e.pageY };
}
handleMouseMove(e: MouseEvent) {
handleMouseMove(e: HoverEvent) {
const canvasRect = this.canvas.getBoundingClientRect();
if (
canvasRect.left <= e.pageX &&
canvasRect.right >= e.pageX &&
canvasRect.top <= e.pageY &&
canvasRect.bottom >= e.pageY
canvasRect.left <= e.clientX &&
canvasRect.right >= e.clientX &&
canvasRect.top <= e.clientY &&
canvasRect.bottom >= e.clientY
) {
const [x, y] = this.screenToPos(e.clientX, e.clientY);
this.cursor.x = x;
@ -101,8 +81,8 @@ export class Canvas extends EventEmitter {
handleBatch(pixels: string[]) {
pixels.forEach((hex, index) => {
const x = index / this.config.canvas.size[0];
const y = index % this.config.canvas.size[0];
const x = index % this.config.canvas.size[0];
const y = index / this.config.canvas.size[1];
const color = this.Pallete.getColorFromHex(hex);
this.pixels[x + "_" + y] = {
@ -169,14 +149,35 @@ export class Canvas extends EventEmitter {
}
screenToPos(x: number, y: number) {
// the rendered dimentions in the browser
const rect = this.canvas.getBoundingClientRect();
const scale = [
this.canvas.width / rect.width,
this.canvas.height / rect.height,
];
return [x - rect.left, y - rect.top]
.map((v, i) => v * scale[i])
.map((v) => v >> 0);
let output = {
x: 0,
y: 0,
};
if (this.PanZoom.flags.useZoom) {
const scale = this.PanZoom.transform.scale;
output.x = x / scale - rect.left;
output.y = y / scale - rect.top;
} else {
// get the ratio
const scale = [
this.canvas.width / rect.width,
this.canvas.height / rect.height,
];
output.x = (x - rect.left) * scale[0];
output.y = (y - rect.top) * scale[1];
}
// floor it, we're getting canvas coords, which can't have decimals
output.x >>= 0;
output.y >>= 0;
return [output.x, output.y];
}
draw() {

View File

@ -11,31 +11,94 @@ import {
import { Panning } from "./lib/panning.utils";
interface TransformState {
/**
* Zoom scale
*
* < 0 : zoomed out
* > 0 : zoomed in
*/
scale: number;
/**
* X position of canvas
*/
x: number;
/**
* Y position of canvas
*/
y: number;
}
interface Flags {
/**
* If CSS Zoom is used
*
* CSS Zoom is not supported on Firefox, as it's not a standard
* But on iOS, <canvas> is fuzzy (ignoring other css rules) when transform: scale()'d up
*
* @see https://caniuse.com/css-zoom
*/
useZoom: boolean;
}
interface TouchState {
/**
* Timestamp of last touch
*/
lastTouch: number | null;
/**
* Distance between each finger when pinch starts
*/
pinchStartDistance: number | null;
/**
* previous distance between each finger
*/
lastDistance: number | null;
/**
* scale when pinch starts
*/
pinchStartScale: number | null;
/**
* middle coord of pinch
*/
pinchMidpoint: { x: number; y: number } | null;
}
interface MouseState {}
interface MouseState {
/**
* timestamp of mouse down
*/
mouseDown: number | null;
}
interface ISetup {
/**
* Scale limits
* [minimum scale, maximum scale]
*/
scale: [number, number];
}
// TODO: move these event interfaces out
export interface ClickEvent {
clientX: number;
clientY: number;
}
export interface HoverEvent {
clientX: number;
clientY: number;
}
interface PanZoomEvents {
doubleTap: (e: TouchEvent) => void;
click: (e: ClickEvent) => void;
hover: (e: HoverEvent) => void;
}
export class PanZoom extends EventEmitter<PanZoomEvents> {
@ -68,7 +131,9 @@ export class PanZoom extends EventEmitter<PanZoomEvents> {
pinchMidpoint: null,
};
this.mouse = {};
this.mouse = {
mouseDown: null,
};
this.panning = new Panning(this);
@ -318,6 +383,8 @@ export class PanZoom extends EventEmitter<PanZoomEvents> {
e.preventDefault();
e.stopPropagation();
this.mouse.mouseDown = Date.now();
this.panning.start(e.clientX, e.clientY);
},
{ passive: false }
@ -327,12 +394,18 @@ export class PanZoom extends EventEmitter<PanZoomEvents> {
document.addEventListener(
"mousemove",
(e) => {
if (!this.panning.enabled) return;
if (this.panning.enabled) {
e.preventDefault();
e.stopPropagation();
e.preventDefault();
e.stopPropagation();
this.panning.move(e.clientX, e.clientY);
this.panning.move(e.clientX, e.clientY);
} else {
// not panning
this.emit("hover", {
clientX: e.clientX,
clientY: e.clientY,
});
}
},
{ passive: false }
);
@ -341,12 +414,32 @@ export class PanZoom extends EventEmitter<PanZoomEvents> {
document.addEventListener(
"mouseup",
(e) => {
if (!this.panning.enabled) return;
if (this.mouse.mouseDown && Date.now() - this.mouse.mouseDown <= 500) {
// if the mouse was down for less than a half a second, it's a click
// this can't depend on this.panning.enabled because that'll always be true when mouse is down
e.preventDefault();
e.stopPropagation();
const delta = [
Math.abs(this.panning.x - e.clientX),
Math.abs(this.panning.y - e.clientY),
];
this.panning.end(e.clientX, e.clientY);
if (delta[0] < 5 && delta[1] < 5) {
// difference from the start position to the up position is very very slow,
// so it's most likely intended to be a click
this.emit("click", {
clientX: e.clientX,
clientY: e.clientY,
});
}
}
if (this.panning.enabled) {
// currently panning
e.preventDefault();
e.stopPropagation();
this.panning.end(e.clientX, e.clientY);
}
},
{ passive: false }
);