|
1 | 1 | (function () { |
| 2 | + const canvas = document.getElementById("bevy-canvas"); |
2 | 3 | const loader = document.getElementById("app-loader"); |
3 | 4 | const status = document.getElementById("loader-status"); |
4 | | - const canvas = document.getElementById("bevy-canvas"); |
5 | | - const pickerInput = document.getElementById("picker-keyboard-input"); |
6 | 5 | if (!loader) return; |
7 | 6 |
|
8 | | - const emitPickerQuery = (action) => { |
9 | | - if (!pickerInput) return; |
10 | | - const query = pickerInput.value || ""; |
11 | | - window.dispatchEvent( |
12 | | - new CustomEvent("vizmat-picker-query", { |
13 | | - detail: { |
14 | | - query, |
15 | | - action, |
16 | | - }, |
17 | | - }), |
18 | | - ); |
19 | | - }; |
20 | | - |
21 | | - if (pickerInput) { |
22 | | - window.addEventListener("vizmat-structure-picker-open", () => { |
23 | | - pickerInput.value = ""; |
24 | | - try { |
25 | | - pickerInput.focus({ preventScroll: true }); |
26 | | - } catch (error) { |
27 | | - pickerInput.focus(); |
28 | | - } |
29 | | - emitPickerQuery("change"); |
30 | | - }); |
31 | | - |
32 | | - window.addEventListener("vizmat-structure-picker-close", () => { |
33 | | - pickerInput.blur(); |
34 | | - pickerInput.value = ""; |
35 | | - }); |
36 | | - |
37 | | - pickerInput.addEventListener("input", () => emitPickerQuery("change")); |
38 | | - pickerInput.addEventListener("keydown", (event) => { |
39 | | - if (event.key === "Enter") { |
40 | | - event.preventDefault(); |
41 | | - emitPickerQuery("submit"); |
42 | | - event.stopPropagation(); |
43 | | - } |
44 | | - }); |
45 | | - } |
46 | | - |
47 | | - const emitTouchGesture = (gesture) => { |
48 | | - window.dispatchEvent( |
49 | | - new CustomEvent("vizmat-touch-gesture", { |
50 | | - detail: gesture, |
51 | | - }), |
52 | | - ); |
53 | | - }; |
54 | | - |
55 | | - const toVec = (touch) => ({ x: touch.clientX, y: touch.clientY }); |
56 | | - |
57 | | - const touchPanState = { |
58 | | - one: null, |
59 | | - two: { |
60 | | - active: false, |
61 | | - midpoint: null, |
62 | | - distance: null, |
63 | | - }, |
64 | | - }; |
65 | | - |
66 | | - const getMidpoint = (a, b) => ({ |
67 | | - x: (a.x + b.x) * 0.5, |
68 | | - y: (a.y + b.y) * 0.5, |
69 | | - }); |
70 | | - |
71 | | - const getDistance = (a, b) => { |
72 | | - const dx = a.x - b.x; |
73 | | - const dy = a.y - b.y; |
74 | | - return Math.hypot(dx, dy); |
75 | | - }; |
76 | | - |
77 | | - const clearTouchState = () => { |
78 | | - touchPanState.one = null; |
79 | | - touchPanState.two = { |
80 | | - active: false, |
81 | | - midpoint: null, |
82 | | - distance: null, |
83 | | - }; |
84 | | - }; |
85 | | - |
86 | | - const emitIf = (condition, payload) => { |
87 | | - if (!condition) return false; |
88 | | - emitTouchGesture(payload); |
89 | | - return true; |
90 | | - }; |
91 | | - |
92 | | - if (canvas) { |
93 | | - canvas.style.touchAction = "none"; |
94 | | - |
95 | | - if (window.PointerEvent) { |
96 | | - const pointerState = new Map(); |
97 | | - |
98 | | - const removePointer = (event) => { |
99 | | - if (event.pointerType !== "touch") return; |
100 | | - if (!event.currentTarget || !pointerState.has(event.pointerId)) return; |
101 | | - pointerState.delete(event.pointerId); |
102 | | - |
103 | | - if (pointerState.size === 0) { |
104 | | - clearTouchState(); |
105 | | - return; |
106 | | - } |
107 | | - |
108 | | - if (pointerState.size === 1) { |
109 | | - touchPanState.one = [...pointerState.values()][0]; |
110 | | - touchPanState.two = { |
111 | | - active: false, |
112 | | - midpoint: null, |
113 | | - distance: null, |
114 | | - }; |
115 | | - return; |
116 | | - } |
117 | | - |
118 | | - const points = [...pointerState.values()]; |
119 | | - touchPanState.two = { |
120 | | - active: true, |
121 | | - midpoint: getMidpoint(points[0], points[1]), |
122 | | - distance: getDistance(points[0], points[1]), |
123 | | - }; |
124 | | - }; |
125 | | - |
126 | | - const updateSinglePointer = (point) => { |
127 | | - if (!touchPanState.one) { |
128 | | - touchPanState.one = point; |
129 | | - return; |
130 | | - } |
131 | | - |
132 | | - const dx = point.x - touchPanState.one.x; |
133 | | - const dy = point.y - touchPanState.one.y; |
134 | | - const emitted = emitIf(dx !== 0 || dy !== 0, { |
135 | | - gesture: "Rotate", |
136 | | - dx, |
137 | | - dy, |
138 | | - scale_delta: 0, |
139 | | - }); |
140 | | - if (emitted) { |
141 | | - touchPanState.one = point; |
142 | | - } |
143 | | - }; |
144 | | - |
145 | | - const updateTwoPointer = (a, b) => { |
146 | | - if (!touchPanState.two.active) { |
147 | | - touchPanState.two = { |
148 | | - active: true, |
149 | | - midpoint: getMidpoint(a, b), |
150 | | - distance: getDistance(a, b), |
151 | | - }; |
152 | | - return; |
153 | | - } |
154 | | - |
155 | | - const midpoint = getMidpoint(a, b); |
156 | | - const distance = getDistance(a, b); |
157 | | - const panDx = midpoint.x - touchPanState.two.midpoint.x; |
158 | | - const panDy = midpoint.y - touchPanState.two.midpoint.y; |
159 | | - const scaleDelta = |
160 | | - touchPanState.two.distance > 0 |
161 | | - ? (distance - touchPanState.two.distance) / touchPanState.two.distance |
162 | | - : 0; |
163 | | - |
164 | | - const emitted = emitIf(panDx !== 0 || panDy !== 0 || scaleDelta !== 0, { |
165 | | - gesture: "TwoFinger", |
166 | | - dx: panDx, |
167 | | - dy: panDy, |
168 | | - scale_delta: scaleDelta, |
169 | | - }); |
170 | | - |
171 | | - if (emitted) { |
172 | | - touchPanState.two.midpoint = midpoint; |
173 | | - touchPanState.two.distance = distance; |
174 | | - } |
175 | | - }; |
176 | | - |
177 | | - canvas.addEventListener( |
178 | | - "pointerdown", |
179 | | - (event) => { |
180 | | - if (event.pointerType !== "touch") return; |
181 | | - if (!event.target) return; |
182 | | - const point = toVec(event); |
183 | | - pointerState.set(event.pointerId, point); |
184 | | - event.currentTarget.setPointerCapture(event.pointerId); |
185 | | - touchPanState.one = point; |
186 | | - touchPanState.two = { |
187 | | - active: false, |
188 | | - midpoint: null, |
189 | | - distance: null, |
190 | | - }; |
191 | | - event.preventDefault(); |
192 | | - }, |
193 | | - { passive: false }, |
194 | | - ); |
195 | | - |
196 | | - canvas.addEventListener( |
197 | | - "pointermove", |
198 | | - (event) => { |
199 | | - if (event.pointerType !== "touch") return; |
200 | | - if (!pointerState.has(event.pointerId)) return; |
201 | | - |
202 | | - const point = toVec(event); |
203 | | - pointerState.set(event.pointerId, point); |
204 | | - |
205 | | - if (pointerState.size === 1) { |
206 | | - updateSinglePointer(point); |
207 | | - } else if (pointerState.size >= 2) { |
208 | | - const points = [...pointerState.values()]; |
209 | | - const a = points[0]; |
210 | | - const b = points[1]; |
211 | | - updateTwoPointer(a, b); |
212 | | - } |
213 | | - |
214 | | - event.preventDefault(); |
215 | | - }, |
216 | | - { passive: false }, |
217 | | - ); |
218 | | - |
219 | | - canvas.addEventListener( |
220 | | - "pointerup", |
221 | | - removePointer, |
222 | | - { passive: false }, |
223 | | - ); |
224 | | - canvas.addEventListener("pointercancel", removePointer, { |
225 | | - passive: false, |
226 | | - }); |
227 | | - canvas.addEventListener("pointerleave", removePointer, { |
228 | | - passive: false, |
229 | | - }); |
230 | | - canvas.addEventListener("pointerout", removePointer, { passive: false }); |
231 | | - canvas.addEventListener("pointerlostcapture", removePointer); |
232 | | - } else { |
233 | | - canvas.addEventListener( |
234 | | - "touchstart", |
235 | | - (event) => { |
236 | | - if (!event.target) return; |
237 | | - if (event.touches.length === 1) { |
238 | | - touchPanState.one = toVec(event.touches[0]); |
239 | | - touchPanState.two.active = false; |
240 | | - } else if (event.touches.length === 2) { |
241 | | - const a = toVec(event.touches[0]); |
242 | | - const b = toVec(event.touches[1]); |
243 | | - touchPanState.two = { |
244 | | - active: true, |
245 | | - midpoint: getMidpoint(a, b), |
246 | | - distance: getDistance(a, b), |
247 | | - }; |
248 | | - touchPanState.one = null; |
249 | | - } |
250 | | - event.preventDefault(); |
251 | | - }, |
252 | | - { passive: false }, |
253 | | - ); |
254 | | - |
255 | | - canvas.addEventListener( |
256 | | - "touchmove", |
257 | | - (event) => { |
258 | | - if (event.touches.length === 1 && touchPanState.one) { |
259 | | - const current = toVec(event.touches[0]); |
260 | | - const dx = current.x - touchPanState.one.x; |
261 | | - const dy = current.y - touchPanState.one.y; |
262 | | - |
263 | | - if (dx !== 0 || dy !== 0) { |
264 | | - emitTouchGesture({ |
265 | | - gesture: "Rotate", |
266 | | - dx, |
267 | | - dy, |
268 | | - scale_delta: 0, |
269 | | - }); |
270 | | - touchPanState.one = current; |
271 | | - } |
272 | | - event.preventDefault(); |
273 | | - return; |
274 | | - } |
275 | | - |
276 | | - if (event.touches.length === 2 && touchPanState.two.active) { |
277 | | - const a = toVec(event.touches[0]); |
278 | | - const b = toVec(event.touches[1]); |
279 | | - const midpoint = getMidpoint(a, b); |
280 | | - const distance = getDistance(a, b); |
281 | | - |
282 | | - const panDx = midpoint.x - touchPanState.two.midpoint.x; |
283 | | - const panDy = midpoint.y - touchPanState.two.midpoint.y; |
284 | | - const scaleDelta = |
285 | | - touchPanState.two.distance > 0 |
286 | | - ? (distance - touchPanState.two.distance) / touchPanState.two.distance |
287 | | - : 0; |
288 | | - |
289 | | - if (panDx !== 0 || panDy !== 0 || scaleDelta !== 0) { |
290 | | - emitTouchGesture({ |
291 | | - gesture: "TwoFinger", |
292 | | - dx: panDx, |
293 | | - dy: panDy, |
294 | | - scale_delta: scaleDelta, |
295 | | - }); |
296 | | - touchPanState.two.midpoint = midpoint; |
297 | | - touchPanState.two.distance = distance; |
298 | | - } |
299 | | - |
300 | | - event.preventDefault(); |
301 | | - } |
302 | | - }, |
303 | | - { passive: false }, |
304 | | - ); |
305 | | - } |
306 | | - |
307 | | - const clearTouch = () => clearTouchState(); |
308 | | - canvas.addEventListener("touchend", clearTouch, { passive: false }); |
309 | | - canvas.addEventListener("touchcancel", clearTouch, { passive: false }); |
310 | | - canvas.addEventListener("touchleave", clearTouch, { passive: false }); |
311 | | - } |
312 | | - |
313 | 7 | if (canvas) { |
314 | 8 | // Keep RMB free for in-app panning instead of opening browser context menu. |
315 | 9 | canvas.addEventListener("contextmenu", (event) => event.preventDefault()); |
|
0 commit comments