Loading packages/client-next/src/components/CanvasWrapper.tsx +4 −9 Original line number Diff line number Diff line 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 Loading @@ -24,6 +18,7 @@ export const CanvasWrapper = () => { const CanvasInner = () => { const canvasRef = createRef<HTMLCanvasElement>(); const { config } = useAppContext(); const PanZoom = useContext(RendererContext); // const { centerView } = useControls(); // useTransformEffect( Loading @@ -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 () => { Loading packages/client-next/src/lib/canvas.ts +54 −53 Original line number Diff line number Diff line 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; Loading @@ -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 }; Loading @@ -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(); Loading @@ -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) { 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; Loading @@ -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] = { Loading Loading @@ -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(); 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, ]; return [x - rect.left, y - rect.top] .map((v, i) => v * scale[i]) .map((v) => v >> 0); 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() { Loading packages/lib/src/renderer/PanZoom.ts +104 −11 Original line number Diff line number Diff line Loading @@ -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> { Loading Loading @@ -68,7 +131,9 @@ export class PanZoom extends EventEmitter<PanZoomEvents> { pinchMidpoint: null, }; this.mouse = {}; this.mouse = { mouseDown: null, }; this.panning = new Panning(this); Loading Loading @@ -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 } Loading @@ -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(); this.panning.move(e.clientX, e.clientY); } else { // not panning this.emit("hover", { clientX: e.clientX, clientY: e.clientY, }); } }, { passive: false } ); Loading @@ -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 const delta = [ Math.abs(this.panning.x - e.clientX), Math.abs(this.panning.y - 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 } ); Loading Loading
packages/client-next/src/components/CanvasWrapper.tsx +4 −9 Original line number Diff line number Diff line 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 Loading @@ -24,6 +18,7 @@ export const CanvasWrapper = () => { const CanvasInner = () => { const canvasRef = createRef<HTMLCanvasElement>(); const { config } = useAppContext(); const PanZoom = useContext(RendererContext); // const { centerView } = useControls(); // useTransformEffect( Loading @@ -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 () => { Loading
packages/client-next/src/lib/canvas.ts +54 −53 Original line number Diff line number Diff line 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; Loading @@ -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 }; Loading @@ -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(); Loading @@ -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) { 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; Loading @@ -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] = { Loading Loading @@ -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(); 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, ]; return [x - rect.left, y - rect.top] .map((v, i) => v * scale[i]) .map((v) => v >> 0); 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() { Loading
packages/lib/src/renderer/PanZoom.ts +104 −11 Original line number Diff line number Diff line Loading @@ -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> { Loading Loading @@ -68,7 +131,9 @@ export class PanZoom extends EventEmitter<PanZoomEvents> { pinchMidpoint: null, }; this.mouse = {}; this.mouse = { mouseDown: null, }; this.panning = new Panning(this); Loading Loading @@ -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 } Loading @@ -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(); this.panning.move(e.clientX, e.clientY); } else { // not panning this.emit("hover", { clientX: e.clientX, clientY: e.clientY, }); } }, { passive: false } ); Loading @@ -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 const delta = [ Math.abs(this.panning.x - e.clientX), Math.abs(this.panning.y - 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 } ); Loading