Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// 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("pointer-events", "none")
pathElem.setAttribute("marker-start", "url(#dot)")
pathElem.setAttribute("marker-end", "url(#dot)")
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 = () => {
while (canvas.children[1]) {
canvas.removeChild(canvas.children[1])
}
}
// 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())