From 45defd0a5bf8d4365e378ae4a7886697e5a17559 Mon Sep 17 00:00:00 2001 From: Grant Date: Tue, 13 Feb 2024 21:44:25 -0700 Subject: [PATCH] viewport coordinate system --- .../src/components/CanvasWrapper.tsx | 37 +++++++++++------ .../client-next/src/components/Pallete.tsx | 9 +++++ .../client-next/src/contexts/AppContext.tsx | 12 +++++- packages/client-next/src/lib/canvas.ts | 40 +++++++++++++++++++ packages/client-next/src/lib/routes.ts | 12 ++++++ packages/client-next/src/style.scss | 14 +++++++ packages/client-next/src/types.ts | 8 ++++ packages/lib/src/renderer/PanZoom.ts | 13 ++++++ 8 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 packages/client-next/src/lib/routes.ts diff --git a/packages/client-next/src/components/CanvasWrapper.tsx b/packages/client-next/src/components/CanvasWrapper.tsx index 376afca..9cfc326 100644 --- a/packages/client-next/src/components/CanvasWrapper.tsx +++ b/packages/client-next/src/components/CanvasWrapper.tsx @@ -1,8 +1,12 @@ -import React, { createRef, useContext, useEffect } from "react"; +import React, { createRef, useCallback, useContext, useEffect } from "react"; import { Canvas } from "../lib/canvas"; import { useAppContext } from "../contexts/AppContext"; import { PanZoomWrapper } from "@sc07-canvas/lib/src/renderer"; import { RendererContext } from "@sc07-canvas/lib/src/renderer/RendererContext"; +import { ViewportMoveEvent } from "@sc07-canvas/lib/src/renderer/PanZoom"; +import throttle from "lodash.throttle"; +import { ICanvasPosition } from "../types"; +import { Routes } from "../lib/routes"; export const CanvasWrapper = () => { // to prevent safari from blurring things, use the zoom css property @@ -17,30 +21,37 @@ export const CanvasWrapper = () => { const CanvasInner = () => { const canvasRef = createRef(); - const { config } = useAppContext(); + const { config, setCanvasPosition } = useAppContext(); const PanZoom = useContext(RendererContext); // const { centerView } = useControls(); - // useTransformEffect( - // throttle(({ state, instance }) => { - // const params = new URLSearchParams(); - // params.set("x", state.positionX + ""); - // params.set("y", state.positionY + ""); - // params.set("zoom", state.scale + ""); - // window.location.hash = params.toString(); - // }, 1000) - // ); - useEffect(() => { if (!config.canvas || !canvasRef.current) return; const canvas = canvasRef.current!; const canvasInstance = new Canvas(config, canvas, PanZoom); // centerView(); + const handleViewportMove = throttle((state: ViewportMoveEvent) => { + const pos = canvasInstance.panZoomTransformToCanvas(); + + const canvasPosition: ICanvasPosition = { + x: pos.canvasX, + y: pos.canvasY, + zoom: state.scale >> 0, + }; + + setCanvasPosition(canvasPosition); + + window.location.replace(Routes.canvas(canvasPosition)); + }, 1000); + + PanZoom.addListener("viewportMove", handleViewportMove); + return () => { canvasInstance.destroy(); + PanZoom.removeListener("viewportMove", handleViewportMove); }; - }, [canvasRef, config]); + }, [PanZoom, canvasRef, config, setCanvasPosition]); return ( { }; export const CanvasMeta = () => { + const { canvasPosition } = useAppContext(); + return (
+ {canvasPosition && ( + + + + )} Pixels: 123 diff --git a/packages/client-next/src/contexts/AppContext.tsx b/packages/client-next/src/contexts/AppContext.tsx index 4f4e035..be2e837 100644 --- a/packages/client-next/src/contexts/AppContext.tsx +++ b/packages/client-next/src/contexts/AppContext.tsx @@ -6,7 +6,12 @@ import React, { useState, } from "react"; import { Socket } from "socket.io-client"; -import { ClientConfig, IAppContext, IPalleteContext } from "../types"; +import { + ClientConfig, + IAppContext, + ICanvasPosition, + IPalleteContext, +} from "../types"; import { AuthSession } from "@sc07-canvas/lib/src/net"; import { number } from "prop-types"; import Network from "../lib/network"; @@ -18,6 +23,7 @@ export const useAppContext = () => useContext(appContext); export const AppContext = ({ children }: PropsWithChildren) => { const [config, setConfig] = useState(undefined as any); const [auth, setAuth] = useState(); + const [canvasPosition, setCanvasPosition] = useState(); useEffect(() => { function handleConfig(config: ClientConfig) { @@ -41,7 +47,9 @@ export const AppContext = ({ children }: PropsWithChildren) => { }, []); return ( - + {config ? children : "Loading..."} ); diff --git a/packages/client-next/src/lib/canvas.ts b/packages/client-next/src/lib/canvas.ts index 284a351..6acc916 100644 --- a/packages/client-next/src/lib/canvas.ts +++ b/packages/client-next/src/lib/canvas.ts @@ -148,6 +148,46 @@ export class Canvas extends EventEmitter { }); } + panZoomTransformToCanvas() { + const { x, y, scale: zoom } = this.PanZoom.transform; + const rect = this.canvas.getBoundingClientRect(); + + let canvasX = 0; + let canvasY = 0; + + if (this.PanZoom.flags.useZoom) { + // css zoom doesn't change the bounding client rect + // therefore dividing by zoom doesn't return the correct output + canvasX = this.canvas.width - (x + rect.width / 2); + canvasY = this.canvas.height - (y + rect.height / 2); + } else { + canvasX = this.canvas.width / 2 - (x + rect.width / zoom); + canvasY = this.canvas.height / 2 - (y + rect.height / zoom); + + canvasX += this.canvas.width; + canvasY += this.canvas.height; + } + + canvasX >>= 0; + canvasY >>= 0; + + return { canvasX, canvasY }; + } + + debug(x: number, y: number, id?: string) { + if (document.getElementById("debug-" + id)) { + document.getElementById("debug-" + id)!.style.top = y + "px"; + document.getElementById("debug-" + id)!.style.left = x + "px"; + return; + } + let el = document.createElement("div"); + if (id) el.id = "debug-" + id; + el.classList.add("debug-point"); + el.style.setProperty("top", y + "px"); + el.style.setProperty("left", x + "px"); + document.body.appendChild(el); + } + screenToPos(x: number, y: number) { // the rendered dimentions in the browser const rect = this.canvas.getBoundingClientRect(); diff --git a/packages/client-next/src/lib/routes.ts b/packages/client-next/src/lib/routes.ts new file mode 100644 index 0000000..a0698bf --- /dev/null +++ b/packages/client-next/src/lib/routes.ts @@ -0,0 +1,12 @@ +import { ICanvasPosition } from "../types"; + +export const Routes = { + canvas: (pos: ICanvasPosition) => { + const params = new URLSearchParams(); + params.set("x", pos.x + ""); + params.set("y", pos.y + ""); + params.set("zoom", pos.zoom + ""); + + return "/#" + params; + }, +}; diff --git a/packages/client-next/src/style.scss b/packages/client-next/src/style.scss index c9a566f..a243680 100644 --- a/packages/client-next/src/style.scss +++ b/packages/client-next/src/style.scss @@ -122,5 +122,19 @@ main { image-rendering: crisp-edges; } +.btn-link { + background-color: transparent; + border: 0; + padding: 0; + margin: 0; + color: inherit; + font-size: inherit; + text-decoration: underline; + + &:active { + opacity: 0.5; + } +} + @import "./components/Pallete.scss"; @import "./board.scss"; diff --git a/packages/client-next/src/types.ts b/packages/client-next/src/types.ts index a670c4b..dc27411 100644 --- a/packages/client-next/src/types.ts +++ b/packages/client-next/src/types.ts @@ -18,12 +18,20 @@ export interface ClientToServerEvents { export interface IAppContext { config: ClientConfig; user?: AuthSession; + canvasPosition?: ICanvasPosition; + setCanvasPosition: (v: ICanvasPosition) => void; } export interface IPalleteContext { color?: number; } +export interface ICanvasPosition { + x: number; + y: number; + zoom: number; +} + // other export type Pixel = { diff --git a/packages/lib/src/renderer/PanZoom.ts b/packages/lib/src/renderer/PanZoom.ts index 97d035b..2c58198 100644 --- a/packages/lib/src/renderer/PanZoom.ts +++ b/packages/lib/src/renderer/PanZoom.ts @@ -95,10 +95,17 @@ export interface HoverEvent { clientY: number; } +export interface ViewportMoveEvent { + scale: number; + x: number; + y: number; +} + interface PanZoomEvents { doubleTap: (e: TouchEvent) => void; click: (e: ClickEvent) => void; hover: (e: HoverEvent) => void; + viewportMove: (e: ViewportMoveEvent) => void; } export class PanZoom extends EventEmitter { @@ -446,6 +453,12 @@ export class PanZoom extends EventEmitter { } update() { + this.emit("viewportMove", { + scale: this.transform.scale, + x: this.transform.x, + y: this.transform.y, + }); + if (this.flags.useZoom) { this.$zoom.style.setProperty("zoom", this.transform.scale * 100 + "%"); } else {