viewport coordinate system
This commit is contained in:
parent
f396b4a16a
commit
45defd0a5b
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
},
|
||||||
|
};
|
|
@ -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";
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue