Commit f396b4a1 authored by Grant's avatar Grant
Browse files

move mouse events to PanZoom

parent 93abdf6b
Loading
Loading
Loading
Loading
+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
@@ -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 () => {
+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;
@@ -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) {
  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();

    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() {
+104 −11
Original line number Diff line number Diff line
@@ -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();

          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

          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 }
    );