Skip to content
Snippets Groups Projects
canvas.js 3.06 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Local canvas rendering.
    // Emit input events and receive draw calls seperately - these must be piped
    // together externally if desired.
    
    import { line, curveLinear } from "d3-shape"
    
    import { canvas } from "./elements.js"
    
    // TODO: switch to curve interpolation that respects mouse points based on velocity
    const lineFn = line()
      .x((d) => d[0])
      .y((d) => d[1])
      .curve(curveLinear)
    
    const pathElems = new Map()
    
    export const STROKE_COLOUR = "blue"
    export const STROKE_RADIUS = 2
    
    export const input = new EventTarget()
    
    export const renderPath = (id, points) => {
      let pathElem = pathElems.get(id)
    
      if (pathElem == null) {
        pathElem = document.createElementNS("http://www.w3.org/2000/svg", "g")
    
        pathElem.setAttribute("stroke", STROKE_COLOUR)
        pathElem.setAttribute("stroke-width", STROKE_RADIUS * 2)
        pathElem.setAttribute("fill", "none")
    
        pathElem.setAttribute("stroke-linecap", "round")
    
        pathElem.setAttribute("pointer-events", "none")
    
        canvas.appendChild(pathElem)
        pathElems.set(id, pathElem)
      }
    
      pathElem.innerHTML = ""
    
      if (points.length == 0) {
        return pathElem
      }
    
      // Push a fake path split to generate the last path
      points.push([-1, -1, false])
    
      let subpath = []
    
      for (const point of points) {
        if (point[0] === undefined) {
          continue
        }
    
        if (point[2] === false) {
          if (subpath.length == 1) {
            const subpathElem = document.createElementNS(
              "http://www.w3.org/2000/svg",
              "circle",
            )
    
            subpathElem.setAttribute("stroke", "none")
            subpathElem.setAttribute("fill", STROKE_COLOUR)
            subpathElem.setAttribute("cx", subpath[0][0])
            subpathElem.setAttribute("cy", subpath[0][1])
            subpathElem.setAttribute("r", STROKE_RADIUS)
    
            pathElem.appendChild(subpathElem)
          } else if (subpath.length > 0) {
            const subpathElem = document.createElementNS(
              "http://www.w3.org/2000/svg",
              "path",
            )
    
            subpathElem.setAttribute("d", lineFn(subpath))
    
            pathElem.appendChild(subpathElem)
          }
    
          subpath = []
    
          continue
        }
    
        subpath.push(point)
      }
    
      return pathElem
    }
    
    export const clear = () => {
    
      pathElems.clear()
    
    }
    
    // Note that the PointerEvent is passed as the detail in these 'stroke events'.
    canvas.addEventListener("pointerdown", (e) => {
      if (e.buttons & 1) {
        input.dispatchEvent(new CustomEvent("strokestart", { detail: e }))
      }
    })
    canvas.addEventListener("pointerenter", (e) => {
      if (e.buttons & 1) {
        input.dispatchEvent(new CustomEvent("strokestart", { detail: e }))
      }
    })
    canvas.addEventListener("pointerup", (e) => {
      if (e.buttons & 1) {
        input.dispatchEvent(new CustomEvent("strokeend", { detail: e }))
      }
    })
    canvas.addEventListener("pointerleave", (e) => {
      if (e.buttons & 1) {
        input.dispatchEvent(new CustomEvent("strokeend", { detail: e }))
      }
    })
    canvas.addEventListener("pointermove", (e) => {
      if (e.buttons & 1) {
        input.dispatchEvent(new CustomEvent("strokemove", { detail: e }))
      }
    })
    canvas.addEventListener("touchmove", (e) => e.preventDefault())