viewport coordinate system

This commit is contained in:
Grant 2024-02-13 21:44:25 -07:00
parent f396b4a16a
commit 45defd0a5b
8 changed files with 130 additions and 15 deletions

View File

@ -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 { Canvas } from "../lib/canvas";
import { useAppContext } from "../contexts/AppContext"; import { useAppContext } from "../contexts/AppContext";
import { PanZoomWrapper } from "@sc07-canvas/lib/src/renderer"; import { PanZoomWrapper } from "@sc07-canvas/lib/src/renderer";
import { RendererContext } from "@sc07-canvas/lib/src/renderer/RendererContext"; 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 = () => { export const CanvasWrapper = () => {
// to prevent safari from blurring things, use the zoom css property // to prevent safari from blurring things, use the zoom css property
@ -17,30 +21,37 @@ export const CanvasWrapper = () => {
const CanvasInner = () => { const CanvasInner = () => {
const canvasRef = createRef<HTMLCanvasElement>(); const canvasRef = createRef<HTMLCanvasElement>();
const { config } = useAppContext(); const { config, setCanvasPosition } = useAppContext();
const PanZoom = useContext(RendererContext); const PanZoom = useContext(RendererContext);
// const { centerView } = useControls(); // 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(() => { useEffect(() => {
if (!config.canvas || !canvasRef.current) return; if (!config.canvas || !canvasRef.current) return;
const canvas = canvasRef.current!; const canvas = canvasRef.current!;
const canvasInstance = new Canvas(config, canvas, PanZoom); const canvasInstance = new Canvas(config, canvas, PanZoom);
// centerView(); // 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 () => { return () => {
canvasInstance.destroy(); canvasInstance.destroy();
PanZoom.removeListener("viewportMove", handleViewportMove);
}; };
}, [canvasRef, config]); }, [PanZoom, canvasRef, config, setCanvasPosition]);
return ( return (
<canvas <canvas

View File

@ -68,8 +68,17 @@ export const Pallete = () => {
}; };
export const CanvasMeta = () => { export const CanvasMeta = () => {
const { canvasPosition } = useAppContext();
return ( return (
<div id="canvas-meta"> <div id="canvas-meta">
{canvasPosition && (
<span>
<button className="btn-link">
({canvasPosition.x}, {canvasPosition.y})
</button>
</span>
)}
<span> <span>
Pixels: <span>123</span> Pixels: <span>123</span>
</span> </span>

View File

@ -6,7 +6,12 @@ import React, {
useState, useState,
} from "react"; } from "react";
import { Socket } from "socket.io-client"; 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 { AuthSession } from "@sc07-canvas/lib/src/net";
import { number } from "prop-types"; import { number } from "prop-types";
import Network from "../lib/network"; import Network from "../lib/network";
@ -18,6 +23,7 @@ export const useAppContext = () => useContext(appContext);
export const AppContext = ({ children }: PropsWithChildren) => { export const AppContext = ({ children }: PropsWithChildren) => {
const [config, setConfig] = useState<ClientConfig>(undefined as any); const [config, setConfig] = useState<ClientConfig>(undefined as any);
const [auth, setAuth] = useState<AuthSession>(); const [auth, setAuth] = useState<AuthSession>();
const [canvasPosition, setCanvasPosition] = useState<ICanvasPosition>();
useEffect(() => { useEffect(() => {
function handleConfig(config: ClientConfig) { function handleConfig(config: ClientConfig) {
@ -41,7 +47,9 @@ export const AppContext = ({ children }: PropsWithChildren) => {
}, []); }, []);
return ( return (
<appContext.Provider value={{ config, user: auth }}> <appContext.Provider
value={{ config, user: auth, canvasPosition, setCanvasPosition }}
>
{config ? children : "Loading..."} {config ? children : "Loading..."}
</appContext.Provider> </appContext.Provider>
); );

View File

@ -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) { screenToPos(x: number, y: number) {
// the rendered dimentions in the browser // the rendered dimentions in the browser
const rect = this.canvas.getBoundingClientRect(); const rect = this.canvas.getBoundingClientRect();

View File

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

View File

@ -122,5 +122,19 @@ main {
image-rendering: crisp-edges; 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 "./components/Pallete.scss";
@import "./board.scss"; @import "./board.scss";

View File

@ -18,12 +18,20 @@ export interface ClientToServerEvents {
export interface IAppContext { export interface IAppContext {
config: ClientConfig; config: ClientConfig;
user?: AuthSession; user?: AuthSession;
canvasPosition?: ICanvasPosition;
setCanvasPosition: (v: ICanvasPosition) => void;
} }
export interface IPalleteContext { export interface IPalleteContext {
color?: number; color?: number;
} }
export interface ICanvasPosition {
x: number;
y: number;
zoom: number;
}
// other // other
export type Pixel = { export type Pixel = {

View File

@ -95,10 +95,17 @@ export interface HoverEvent {
clientY: number; clientY: number;
} }
export interface ViewportMoveEvent {
scale: number;
x: number;
y: number;
}
interface PanZoomEvents { interface PanZoomEvents {
doubleTap: (e: TouchEvent) => void; doubleTap: (e: TouchEvent) => void;
click: (e: ClickEvent) => void; click: (e: ClickEvent) => void;
hover: (e: HoverEvent) => void; hover: (e: HoverEvent) => void;
viewportMove: (e: ViewportMoveEvent) => void;
} }
export class PanZoom extends EventEmitter<PanZoomEvents> { export class PanZoom extends EventEmitter<PanZoomEvents> {
@ -446,6 +453,12 @@ export class PanZoom extends EventEmitter<PanZoomEvents> {
} }
update() { update() {
this.emit("viewportMove", {
scale: this.transform.scale,
x: this.transform.x,
y: this.transform.y,
});
if (this.flags.useZoom) { if (this.flags.useZoom) {
this.$zoom.style.setProperty("zoom", this.transform.scale * 100 + "%"); this.$zoom.style.setProperty("zoom", this.transform.scale * 100 + "%");
} else { } else {