comparison DPF-Prymula-audioplugins/dpf/dgl/src/pugl-extra/wasm.c @ 3:84e66ea83026

DPF-Prymula-audioplugins-0.231015-2
author prymula <prymula76@outlook.com>
date Mon, 16 Oct 2023 21:53:34 +0200
parents
children
comparison
equal deleted inserted replaced
2:cf2cb71d31dd 3:84e66ea83026
1 // Copyright 2012-2022 David Robillard <d@drobilla.net>
2 // Copyright 2021-2022 Filipe Coelho <falktx@falktx.com>
3 // SPDX-License-Identifier: ISC
4
5 #include "wasm.h"
6
7 #include "../pugl-upstream/src/internal.h"
8
9 #include <stdio.h>
10
11 #include <emscripten/html5.h>
12
13 #ifdef __cplusplus
14 # define PUGL_INIT_STRUCT \
15 {}
16 #else
17 # define PUGL_INIT_STRUCT \
18 { \
19 0 \
20 }
21 #endif
22
23 #ifdef __MOD_DEVICES__
24 # define MOD_SCALE_FACTOR_MULT 1
25 #endif
26
27 // #define PUGL_WASM_AUTO_POINTER_LOCK
28 // #define PUGL_WASM_NO_KEYBOARD_INPUT
29 // #define PUGL_WASM_NO_MOUSEWHEEL_INPUT
30
31 PuglWorldInternals*
32 puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags)
33 {
34 PuglWorldInternals* impl =
35 (PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals));
36
37 impl->scaleFactor = emscripten_get_device_pixel_ratio();
38 #ifdef __MOD_DEVICES__
39 impl->scaleFactor *= MOD_SCALE_FACTOR_MULT;
40 #endif
41
42 printf("DONE: %s %d | -> %f\n", __func__, __LINE__, impl->scaleFactor);
43
44 return impl;
45 }
46
47 void*
48 puglGetNativeWorld(PuglWorld*)
49 {
50 printf("DONE: %s %d\n", __func__, __LINE__);
51 return NULL;
52 }
53
54 PuglInternals*
55 puglInitViewInternals(PuglWorld* const world)
56 {
57 printf("DONE: %s %d\n", __func__, __LINE__);
58 PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
59
60 impl->buttonPressTimeout = -1;
61 impl->supportsTouch = PUGL_DONT_CARE; // not yet known
62
63 #ifdef PUGL_WASM_ASYNC_CLIPBOARD
64 impl->supportsClipboardRead = (PuglViewHintValue)EM_ASM_INT({
65 if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.readText) === 'function' && window.isSecureContext) {
66 return 1; // PUGL_TRUE
67 }
68 return 0; // PUGL_FALSE
69 });
70
71 impl->supportsClipboardWrite = (PuglViewHintValue)EM_ASM_INT({
72 if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.writeText) === 'function' && window.isSecureContext) {
73 return 1; // PUGL_TRUE
74 }
75 if (typeof(document.queryCommandSupported) !== 'undefined' && document.queryCommandSupported("copy")) {
76 return 1; // PUGL_TRUE
77 }
78 return 0; // PUGL_FALSE
79 });
80 #endif
81
82 return impl;
83 }
84
85 static PuglStatus
86 puglDispatchEventWithContext(PuglView* const view, const PuglEvent* event)
87 {
88 PuglStatus st0 = PUGL_SUCCESS;
89 PuglStatus st1 = PUGL_SUCCESS;
90
91 if (!(st0 = view->backend->enter(view, NULL))) {
92 st0 = view->eventFunc(view, event);
93 st1 = view->backend->leave(view, NULL);
94 }
95
96 return st0 ? st0 : st1;
97 }
98
99 static PuglMods
100 translateModifiers(const EM_BOOL ctrlKey,
101 const EM_BOOL shiftKey,
102 const EM_BOOL altKey,
103 const EM_BOOL metaKey)
104 {
105 return (ctrlKey ? PUGL_MOD_CTRL : 0u) |
106 (shiftKey ? PUGL_MOD_SHIFT : 0u) |
107 (altKey ? PUGL_MOD_ALT : 0u) |
108 (metaKey ? PUGL_MOD_SUPER : 0u);
109 }
110
111 #ifndef PUGL_WASM_NO_KEYBOARD_INPUT
112 static PuglKey
113 keyCodeToSpecial(const unsigned long code, const unsigned long location)
114 {
115 switch (code) {
116 case 0x08: return PUGL_KEY_BACKSPACE;
117 case 0x1B: return PUGL_KEY_ESCAPE;
118 case 0x2E: return PUGL_KEY_DELETE;
119 case 0x70: return PUGL_KEY_F1;
120 case 0x71: return PUGL_KEY_F2;
121 case 0x72: return PUGL_KEY_F3;
122 case 0x73: return PUGL_KEY_F4;
123 case 0x74: return PUGL_KEY_F5;
124 case 0x75: return PUGL_KEY_F6;
125 case 0x76: return PUGL_KEY_F7;
126 case 0x77: return PUGL_KEY_F8;
127 case 0x78: return PUGL_KEY_F9;
128 case 0x79: return PUGL_KEY_F10;
129 case 0x7A: return PUGL_KEY_F11;
130 case 0x7B: return PUGL_KEY_F12;
131 case 0x25: return PUGL_KEY_LEFT;
132 case 0x26: return PUGL_KEY_UP;
133 case 0x27: return PUGL_KEY_RIGHT;
134 case 0x28: return PUGL_KEY_DOWN;
135 case 0x21: return PUGL_KEY_PAGE_UP;
136 case 0x22: return PUGL_KEY_PAGE_DOWN;
137 case 0x24: return PUGL_KEY_HOME;
138 case 0x23: return PUGL_KEY_END;
139 case 0x2D: return PUGL_KEY_INSERT;
140 case 0x10: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_SHIFT_R : PUGL_KEY_SHIFT_L;
141 case 0x11: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_CTRL_R : PUGL_KEY_CTRL_L;
142 case 0x12: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_ALT_R : PUGL_KEY_ALT_L;
143 case 0xE0: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_SUPER_R : PUGL_KEY_SUPER_L;
144 case 0x5D: return PUGL_KEY_MENU;
145 case 0x14: return PUGL_KEY_CAPS_LOCK;
146 case 0x91: return PUGL_KEY_SCROLL_LOCK;
147 case 0x90: return PUGL_KEY_NUM_LOCK;
148 case 0x2C: return PUGL_KEY_PRINT_SCREEN;
149 case 0x13: return PUGL_KEY_PAUSE;
150 case '\r': return (PuglKey)'\r';
151 default: break;
152 }
153
154 return (PuglKey)0;
155 }
156
157 static bool
158 decodeCharacterString(const unsigned long keyCode,
159 const EM_UTF8 key[EM_HTML5_SHORT_STRING_LEN_BYTES],
160 char str[8])
161 {
162 if (key[1] == 0)
163 {
164 str[0] = key[0];
165 return true;
166 }
167
168 return false;
169 }
170
171 static EM_BOOL
172 puglKeyCallback(const int eventType, const EmscriptenKeyboardEvent* const keyEvent, void* const userData)
173 {
174 PuglView* const view = (PuglView*)userData;
175
176 if (!view->visible) {
177 return EM_FALSE;
178 }
179
180 if (keyEvent->repeat && view->hints[PUGL_IGNORE_KEY_REPEAT])
181 return EM_TRUE;
182
183 PuglStatus st0 = PUGL_SUCCESS;
184 PuglStatus st1 = PUGL_SUCCESS;
185
186 const uint state = translateModifiers(keyEvent->ctrlKey,
187 keyEvent->shiftKey,
188 keyEvent->altKey,
189 keyEvent->metaKey);
190
191 const PuglKey special = keyCodeToSpecial(keyEvent->keyCode, keyEvent->location);
192
193 uint key = keyEvent->key[0] >= ' ' && keyEvent->key[0] <= '~' && keyEvent->key[1] == '\0'
194 ? keyEvent->key[0]
195 : keyEvent->keyCode;
196
197 if (key >= 'A' && key <= 'Z' && !keyEvent->shiftKey)
198 key += 'a' - 'A';
199
200 PuglEvent event = {{PUGL_NOTHING, 0}};
201 event.key.type = eventType == EMSCRIPTEN_EVENT_KEYDOWN ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
202 event.key.time = keyEvent->timestamp / 1e3;
203 // event.key.x = xevent.xkey.x;
204 // event.key.y = xevent.xkey.y;
205 // event.key.xRoot = xevent.xkey.x_root;
206 // event.key.yRoot = xevent.xkey.y_root;
207 event.key.key = special ? special : key;
208 event.key.keycode = keyEvent->keyCode;
209 event.key.state = state;
210 st0 = puglDispatchEventWithContext(view, &event);
211
212 d_debug("key event \n"
213 "\tdown: %d\n"
214 "\trepeat: %d\n"
215 "\tlocation: %d\n"
216 "\tstate: 0x%x\n"
217 "\tkey[]: '%s'\n"
218 "\tcode[]: '%s'\n"
219 "\tlocale[]: '%s'\n"
220 "\tkeyCode: 0x%lx:'%c' [deprecated, use key]\n"
221 "\twhich: 0x%lx:'%c' [deprecated, use key, same as keycode?]\n"
222 "\tspecial: 0x%x",
223 eventType == EMSCRIPTEN_EVENT_KEYDOWN,
224 keyEvent->repeat,
225 keyEvent->location,
226 state,
227 keyEvent->key,
228 keyEvent->code,
229 keyEvent->locale,
230 keyEvent->keyCode, keyEvent->keyCode >= ' ' && keyEvent->keyCode <= '~' ? keyEvent->keyCode : 0,
231 keyEvent->which, keyEvent->which >= ' ' && keyEvent->which <= '~' ? keyEvent->which : 0,
232 special);
233
234 if (event.type == PUGL_KEY_PRESS && !special && !(keyEvent->ctrlKey|keyEvent->altKey|keyEvent->metaKey)) {
235 char str[8] = PUGL_INIT_STRUCT;
236
237 if (decodeCharacterString(keyEvent->keyCode, keyEvent->key, str)) {
238 d_debug("resulting string is '%s'", str);
239
240 event.text.type = PUGL_TEXT;
241 event.text.character = event.key.key;
242 memcpy(event.text.string, str, sizeof(event.text.string));
243 st1 = puglDispatchEventWithContext(view, &event);
244 }
245 }
246
247 return (st0 ? st0 : st1) == PUGL_SUCCESS ? EM_TRUE : EM_FALSE;
248 }
249 #endif
250
251 static EM_BOOL
252 puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEvent, void* const userData)
253 {
254 PuglView* const view = (PuglView*)userData;
255
256 if (!view->visible) {
257 return EM_FALSE;
258 }
259
260 PuglEvent event = {{PUGL_NOTHING, 0}};
261
262 const double time = mouseEvent->timestamp / 1e3;
263 const PuglMods state = translateModifiers(mouseEvent->ctrlKey,
264 mouseEvent->shiftKey,
265 mouseEvent->altKey,
266 mouseEvent->metaKey);
267
268 double scaleFactor = view->world->impl->scaleFactor;
269 #ifdef __MOD_DEVICES__
270 if (!view->impl->isFullscreen) {
271 scaleFactor /= EM_ASM_DOUBLE({
272 return parseFloat(
273 RegExp('^scale\\\((.*)\\\)$')
274 .exec(document.getElementById("pedalboard-dashboard").style.transform)[1]
275 );
276 }) * MOD_SCALE_FACTOR_MULT;
277 }
278 #endif
279
280 // workaround missing pointer lock callback, see https://github.com/emscripten-core/emscripten/issues/9681
281 EmscriptenPointerlockChangeEvent e;
282 if (emscripten_get_pointerlock_status(&e) == EMSCRIPTEN_RESULT_SUCCESS)
283 view->impl->pointerLocked = e.isActive;
284
285 #ifdef __MOD_DEVICES__
286 const long canvasX = mouseEvent->canvasX;
287 const long canvasY = mouseEvent->canvasY;
288 #else
289 const char* const className = view->world->className;
290 const double canvasX = mouseEvent->clientX - EM_ASM_DOUBLE({
291 var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
292 return canvasWrapper.getBoundingClientRect().x;
293 }, className);
294 const double canvasY = mouseEvent->clientY - EM_ASM_DOUBLE({
295 var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
296 return canvasWrapper.getBoundingClientRect().y;
297 }, className);
298 #endif
299
300 switch (eventType) {
301 case EMSCRIPTEN_EVENT_MOUSEDOWN:
302 case EMSCRIPTEN_EVENT_MOUSEUP:
303 event.button.type = eventType == EMSCRIPTEN_EVENT_MOUSEDOWN ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE;
304 event.button.time = time;
305 event.button.x = canvasX * scaleFactor;
306 event.button.y = canvasY * scaleFactor;
307 event.button.xRoot = mouseEvent->screenX * scaleFactor;
308 event.button.yRoot = mouseEvent->screenY * scaleFactor;
309 event.button.state = state;
310 switch (mouseEvent->button) {
311 case 1:
312 event.button.button = 2;
313 break;
314 case 2:
315 event.button.button = 1;
316 break;
317 default:
318 event.button.button = mouseEvent->button;
319 break;
320 }
321 break;
322 case EMSCRIPTEN_EVENT_MOUSEMOVE:
323 event.motion.type = PUGL_MOTION;
324 event.motion.time = time;
325 if (view->impl->pointerLocked) {
326 // adjust local values for delta
327 const double movementX = mouseEvent->movementX * scaleFactor;
328 const double movementY = mouseEvent->movementY * scaleFactor;
329 view->impl->lastMotion.x += movementX;
330 view->impl->lastMotion.y += movementY;
331 view->impl->lastMotion.xRoot += movementX;
332 view->impl->lastMotion.yRoot += movementY;
333 // now set x, y, xRoot and yRoot
334 event.motion.x = view->impl->lastMotion.x;
335 event.motion.y = view->impl->lastMotion.y;
336 event.motion.xRoot = view->impl->lastMotion.xRoot;
337 event.motion.yRoot = view->impl->lastMotion.yRoot;
338 } else {
339 // cache values for possible pointer lock movement later
340 view->impl->lastMotion.x = event.motion.x = canvasX * scaleFactor;
341 view->impl->lastMotion.y = event.motion.y = canvasY * scaleFactor;
342 view->impl->lastMotion.xRoot = event.motion.xRoot = mouseEvent->screenX * scaleFactor;
343 view->impl->lastMotion.yRoot = event.motion.yRoot = mouseEvent->screenY * scaleFactor;
344 }
345 event.motion.state = state;
346 break;
347 case EMSCRIPTEN_EVENT_MOUSEENTER:
348 case EMSCRIPTEN_EVENT_MOUSELEAVE:
349 event.crossing.type = eventType == EMSCRIPTEN_EVENT_MOUSEENTER ? PUGL_POINTER_IN : PUGL_POINTER_OUT;
350 event.crossing.time = time;
351 event.crossing.x = canvasX * scaleFactor;
352 event.crossing.y = canvasY * scaleFactor;
353 event.crossing.xRoot = mouseEvent->screenX * scaleFactor;
354 event.crossing.yRoot = mouseEvent->screenY * scaleFactor;
355 event.crossing.state = state;
356 event.crossing.mode = PUGL_CROSSING_NORMAL;
357 break;
358 }
359
360 if (event.type == PUGL_NOTHING)
361 return EM_FALSE;
362
363 puglDispatchEventWithContext(view, &event);
364
365 #ifdef PUGL_WASM_AUTO_POINTER_LOCK
366 switch (eventType) {
367 case EMSCRIPTEN_EVENT_MOUSEDOWN:
368 emscripten_request_pointerlock(view->world->className, false);
369 break;
370 case EMSCRIPTEN_EVENT_MOUSEUP:
371 emscripten_exit_pointerlock();
372 break;
373 }
374 #endif
375
376 // note: we must always return false, otherwise canvas never gets keyboard input
377 return EM_FALSE;
378 }
379
380 static void
381 puglTouchStartDelay(void* const userData)
382 {
383 PuglView* const view = (PuglView*)userData;
384 PuglInternals* const impl = view->impl;
385
386 impl->buttonPressTimeout = -1;
387 impl->nextButtonEvent.button.time += 2000;
388 puglDispatchEventWithContext(view, &impl->nextButtonEvent);
389 }
390
391 static EM_BOOL
392 puglTouchCallback(const int eventType, const EmscriptenTouchEvent* const touchEvent, void* const userData)
393 {
394 if (touchEvent->numTouches <= 0) {
395 return EM_FALSE;
396 }
397
398 PuglView* const view = (PuglView*)userData;
399 PuglInternals* const impl = view->impl;
400 const char* const className = view->world->className;
401
402 if (impl->supportsTouch == PUGL_DONT_CARE) {
403 impl->supportsTouch = PUGL_TRUE;
404
405 // stop using mouse press events which conflict with touch
406 emscripten_set_mousedown_callback(className, view, false, NULL);
407 emscripten_set_mouseup_callback(className, view, false, NULL);
408 }
409
410 if (!view->visible) {
411 return EM_FALSE;
412 }
413
414 PuglEvent event = {{PUGL_NOTHING, 0}};
415
416 const double time = touchEvent->timestamp / 1e3;
417 const PuglMods state = translateModifiers(touchEvent->ctrlKey,
418 touchEvent->shiftKey,
419 touchEvent->altKey,
420 touchEvent->metaKey);
421
422 double scaleFactor = view->world->impl->scaleFactor;
423 #ifdef __MOD_DEVICES__
424 if (!view->impl->isFullscreen) {
425 scaleFactor /= EM_ASM_DOUBLE({
426 return parseFloat(
427 RegExp('^scale\\\((.*)\\\)$')
428 .exec(document.getElementById("pedalboard-dashboard").style.transform)[1]
429 );
430 }) * MOD_SCALE_FACTOR_MULT;
431 }
432 #endif
433
434 d_debug("touch %d|%s %d || %ld",
435 eventType,
436 eventType == EMSCRIPTEN_EVENT_TOUCHSTART ? "start" :
437 eventType == EMSCRIPTEN_EVENT_TOUCHEND ? "end" : "cancel",
438 touchEvent->numTouches,
439 impl->buttonPressTimeout);
440
441 const EmscriptenTouchPoint* point = &touchEvent->touches[0];
442
443 if (impl->buttonPressTimeout != -1 || eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) {
444 // if we received an event while touch is active, trigger initial click now
445 if (impl->buttonPressTimeout != -1) {
446 emscripten_clear_timeout(impl->buttonPressTimeout);
447 impl->buttonPressTimeout = -1;
448 if (eventType != EMSCRIPTEN_EVENT_TOUCHCANCEL) {
449 impl->nextButtonEvent.button.button = 0;
450 }
451 }
452 impl->nextButtonEvent.button.time = time;
453 puglDispatchEventWithContext(view, &impl->nextButtonEvent);
454 }
455
456 #ifdef __MOD_DEVICES__
457 const long canvasX = point->canvasX;
458 const long canvasY = point->canvasY;
459 #else
460 const double canvasX = point->clientX - EM_ASM_DOUBLE({
461 var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
462 return canvasWrapper.getBoundingClientRect().x;
463 }, className);
464 const double canvasY = point->clientY - EM_ASM_DOUBLE({
465 var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
466 return canvasWrapper.getBoundingClientRect().y;
467 }, className);
468 #endif
469
470 switch (eventType) {
471 case EMSCRIPTEN_EVENT_TOUCHEND:
472 case EMSCRIPTEN_EVENT_TOUCHCANCEL:
473 event.button.type = PUGL_BUTTON_RELEASE;
474 event.button.time = time;
475 event.button.button = eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL ? 1 : 0;
476 event.button.x = canvasX * scaleFactor;
477 event.button.y = canvasY * scaleFactor;
478 event.button.xRoot = point->screenX * scaleFactor;
479 event.button.yRoot = point->screenY * scaleFactor;
480 event.button.state = state;
481 break;
482
483 case EMSCRIPTEN_EVENT_TOUCHSTART:
484 // this event can be used for a couple of things, store it until we know more
485 event.button.type = PUGL_BUTTON_PRESS;
486 event.button.time = time;
487 event.button.button = 1; // if no other event occurs soon, treat it as right-click
488 event.button.x = canvasX * scaleFactor;
489 event.button.y = canvasY * scaleFactor;
490 event.button.xRoot = point->screenX * scaleFactor;
491 event.button.yRoot = point->screenY * scaleFactor;
492 event.button.state = state;
493 memcpy(&impl->nextButtonEvent, &event, sizeof(PuglEvent));
494 impl->buttonPressTimeout = emscripten_set_timeout(puglTouchStartDelay, 2000, view);
495 // fall through, moving "mouse" to touch position
496
497 case EMSCRIPTEN_EVENT_TOUCHMOVE:
498 event.motion.type = PUGL_MOTION;
499 event.motion.time = time;
500 event.motion.x = canvasX * scaleFactor;
501 event.motion.y = canvasY * scaleFactor;
502 event.motion.xRoot = point->screenX * scaleFactor;
503 event.motion.yRoot = point->screenY * scaleFactor;
504 event.motion.state = state;
505 break;
506 }
507
508 if (event.type == PUGL_NOTHING)
509 return EM_FALSE;
510
511 puglDispatchEventWithContext(view, &event);
512
513 // FIXME we must always return false??
514 return EM_FALSE;
515 }
516
517 static EM_BOOL
518 puglFocusCallback(const int eventType, const EmscriptenFocusEvent* /*const focusEvent*/, void* const userData)
519 {
520 PuglView* const view = (PuglView*)userData;
521
522 if (!view->visible) {
523 return EM_FALSE;
524 }
525
526 d_debug("focus %d|%s", eventType, eventType == EMSCRIPTEN_EVENT_FOCUSIN ? "focus-in" : "focus-out");
527
528 PuglEvent event = {{eventType == EMSCRIPTEN_EVENT_FOCUSIN ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT, 0}};
529 event.focus.mode = PUGL_CROSSING_NORMAL;
530
531 puglDispatchEventWithContext(view, &event);
532
533 // note: we must always return false, otherwise canvas never gets proper focus
534 return EM_FALSE;
535 }
536
537 static EM_BOOL
538 puglPointerLockChangeCallback(const int eventType, const EmscriptenPointerlockChangeEvent* event, void* const userData)
539 {
540 PuglView* const view = (PuglView*)userData;
541
542 view->impl->pointerLocked = event->isActive;
543 return EM_TRUE;
544 }
545
546 #ifndef PUGL_WASM_NO_MOUSEWHEEL_INPUT
547 static EM_BOOL
548 puglWheelCallback(const int eventType, const EmscriptenWheelEvent* const wheelEvent, void* const userData)
549 {
550 PuglView* const view = (PuglView*)userData;
551
552 if (!view->visible) {
553 return EM_FALSE;
554 }
555
556 double scaleFactor = view->world->impl->scaleFactor;
557 #ifdef __MOD_DEVICES__
558 if (!view->impl->isFullscreen) {
559 scaleFactor /= EM_ASM_DOUBLE({
560 return parseFloat(
561 RegExp('^scale\\\((.*)\\\)$')
562 .exec(document.getElementById("pedalboard-dashboard").style.transform)[1]
563 );
564 }) * MOD_SCALE_FACTOR_MULT;
565 }
566 #endif
567
568 #ifdef __MOD_DEVICES__
569 const long canvasX = wheelEvent->mouse.canvasX;
570 const long canvasY = wheelEvent->mouse.canvasY;
571 #else
572 const char* const className = view->world->className;
573 const double canvasX = wheelEvent->mouse.canvasX - EM_ASM_INT({
574 var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
575 return canvasWrapper.getBoundingClientRect().x;
576 }, className);
577 const double canvasY = wheelEvent->mouse.canvasY - EM_ASM_INT({
578 var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
579 return canvasWrapper.getBoundingClientRect().y;
580 }, className);
581 #endif
582
583 PuglEvent event = {{PUGL_SCROLL, 0}};
584 event.scroll.time = wheelEvent->mouse.timestamp / 1e3;
585 event.scroll.x = canvasX;
586 event.scroll.y = canvasY;
587 event.scroll.xRoot = wheelEvent->mouse.screenX;
588 event.scroll.yRoot = wheelEvent->mouse.screenY;
589 event.scroll.state = translateModifiers(wheelEvent->mouse.ctrlKey,
590 wheelEvent->mouse.shiftKey,
591 wheelEvent->mouse.altKey,
592 wheelEvent->mouse.metaKey);
593 event.scroll.direction = PUGL_SCROLL_SMOOTH;
594 // FIXME handle wheelEvent->deltaMode
595 event.scroll.dx = wheelEvent->deltaX * 0.01 * scaleFactor;
596 event.scroll.dy = -wheelEvent->deltaY * 0.01 * scaleFactor;
597
598 return puglDispatchEventWithContext(view, &event) == PUGL_SUCCESS ? EM_TRUE : EM_FALSE;
599 }
600 #endif
601
602 static EM_BOOL
603 puglUiCallback(const int eventType, const EmscriptenUiEvent* const uiEvent, void* const userData)
604 {
605 PuglView* const view = (PuglView*)userData;
606 const char* const className = view->world->className;
607
608 // FIXME
609 const int width = EM_ASM_INT({
610 var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
611 canvasWrapper.style.setProperty("--device-pixel-ratio", window.devicePixelRatio);
612 return canvasWrapper.clientWidth;
613 }, className);
614
615 const int height = EM_ASM_INT({
616 var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
617 return canvasWrapper.clientHeight;
618 }, className);
619
620 if (!width || !height)
621 return EM_FALSE;
622
623 double scaleFactor = emscripten_get_device_pixel_ratio();
624 #ifdef __MOD_DEVICES__
625 scaleFactor *= MOD_SCALE_FACTOR_MULT;
626 #endif
627 view->world->impl->scaleFactor = scaleFactor;
628
629 PuglEvent event = {{PUGL_CONFIGURE, 0}};
630 event.configure.x = view->frame.x;
631 event.configure.y = view->frame.y;
632 event.configure.width = width * scaleFactor;
633 event.configure.height = height * scaleFactor;
634 puglDispatchEvent(view, &event);
635
636 emscripten_set_canvas_element_size(view->world->className, width * scaleFactor, height * scaleFactor);
637 return EM_TRUE;
638 }
639
640 static EM_BOOL
641 puglFullscreenChangeCallback(const int eventType, const EmscriptenFullscreenChangeEvent* const fscEvent, void* const userData)
642 {
643 PuglView* const view = (PuglView*)userData;
644
645 view->impl->isFullscreen = fscEvent->isFullscreen;
646
647 double scaleFactor = emscripten_get_device_pixel_ratio();
648 #ifdef __MOD_DEVICES__
649 scaleFactor *= MOD_SCALE_FACTOR_MULT;
650 #endif
651 view->world->impl->scaleFactor = scaleFactor;
652
653 if (fscEvent->isFullscreen) {
654 PuglEvent event = {{PUGL_CONFIGURE, 0}};
655 event.configure.x = 0;
656 event.configure.y = 0;
657 event.configure.width = fscEvent->elementWidth * scaleFactor;
658 event.configure.height = fscEvent->elementHeight * scaleFactor;
659 puglDispatchEvent(view, &event);
660
661 emscripten_set_canvas_element_size(view->world->className,
662 fscEvent->elementWidth * scaleFactor,
663 fscEvent->elementHeight * scaleFactor);
664
665 #ifdef __MOD_DEVICES__
666 EM_ASM({
667 document.getElementById("pedalboard-dashboard").style.transform = "scale(1.0)";
668 });
669 #endif
670 return EM_TRUE;
671 }
672
673 return puglUiCallback(0, NULL, userData);
674 }
675
676 static EM_BOOL
677 puglVisibilityChangeCallback(const int eventType, const EmscriptenVisibilityChangeEvent* const visibilityChangeEvent, void* const userData)
678 {
679 PuglView* const view = (PuglView*)userData;
680
681 view->visible = visibilityChangeEvent->hidden == EM_FALSE;
682 PuglEvent event = {{ view->visible ? PUGL_MAP : PUGL_UNMAP, 0}};
683 puglDispatchEvent(view, &event);
684 return EM_FALSE;
685 }
686
687 PuglStatus
688 puglRealize(PuglView* const view)
689 {
690 printf("TODO: %s %d\n", __func__, __LINE__);
691 PuglStatus st = PUGL_SUCCESS;
692
693 // Ensure that we do not have a parent
694 if (view->parent) {
695 printf("TODO: %s %d\n", __func__, __LINE__);
696 return PUGL_FAILURE;
697 }
698
699 if (!view->backend || !view->backend->configure) {
700 printf("TODO: %s %d\n", __func__, __LINE__);
701 return PUGL_BAD_BACKEND;
702 }
703
704 const char* const className = view->world->className;
705 d_stdout("className is %s", className);
706
707 // Set the size to the default if it has not already been set
708 if (view->frame.width <= 0.0 && view->frame.height <= 0.0) {
709 PuglViewSize defaultSize = view->sizeHints[PUGL_DEFAULT_SIZE];
710 if (!defaultSize.width || !defaultSize.height) {
711 return PUGL_BAD_CONFIGURATION;
712 }
713
714 view->frame.width = defaultSize.width;
715 view->frame.height = defaultSize.height;
716 }
717
718 // Configure and create the backend
719 if ((st = view->backend->configure(view)) || (st = view->backend->create(view))) {
720 view->backend->destroy(view);
721 return st;
722 }
723
724 if (view->title) {
725 puglSetWindowTitle(view, view->title);
726 }
727
728 puglDispatchSimpleEvent(view, PUGL_CREATE);
729
730 PuglEvent event = {{PUGL_CONFIGURE, 0}};
731 event.configure.x = view->frame.x;
732 event.configure.y = view->frame.y;
733 event.configure.width = view->frame.width;
734 event.configure.height = view->frame.height;
735 puglDispatchEvent(view, &event);
736
737 EM_ASM({
738 var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
739 canvasWrapper.style.setProperty("--device-pixel-ratio", window.devicePixelRatio);
740 }, className);
741
742 emscripten_set_canvas_element_size(className, view->frame.width, view->frame.height);
743 #ifndef PUGL_WASM_NO_KEYBOARD_INPUT
744 // emscripten_set_keypress_callback(className, view, false, puglKeyCallback);
745 emscripten_set_keydown_callback(className, view, false, puglKeyCallback);
746 emscripten_set_keyup_callback(className, view, false, puglKeyCallback);
747 #endif
748 emscripten_set_touchstart_callback(className, view, false, puglTouchCallback);
749 emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglTouchCallback);
750 emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglTouchCallback);
751 emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglTouchCallback);
752 emscripten_set_mousedown_callback(className, view, false, puglMouseCallback);
753 emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglMouseCallback);
754 emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglMouseCallback);
755 emscripten_set_mouseenter_callback(className, view, false, puglMouseCallback);
756 emscripten_set_mouseleave_callback(className, view, false, puglMouseCallback);
757 emscripten_set_focusin_callback(className, view, false, puglFocusCallback);
758 emscripten_set_focusout_callback(className, view, false, puglFocusCallback);
759 #ifndef PUGL_WASM_NO_MOUSEWHEEL_INPUT
760 emscripten_set_wheel_callback(className, view, false, puglWheelCallback);
761 #endif
762 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglPointerLockChangeCallback);
763 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglUiCallback);
764 emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglFullscreenChangeCallback);
765 emscripten_set_visibilitychange_callback(view, false, puglVisibilityChangeCallback);
766
767 printf("TODO: %s %d\n", __func__, __LINE__);
768 return PUGL_SUCCESS;
769 }
770
771 PuglStatus
772 puglShow(PuglView* const view)
773 {
774 view->visible = true;
775 view->impl->needsRepaint = true;
776 return puglPostRedisplay(view);
777 }
778
779 PuglStatus
780 puglHide(PuglView* const view)
781 {
782 view->visible = false;
783 return PUGL_FAILURE;
784 }
785
786 void
787 puglFreeViewInternals(PuglView* const view)
788 {
789 printf("DONE: %s %d\n", __func__, __LINE__);
790 if (view && view->impl) {
791 if (view->backend) {
792 // unregister the window events, to make sure no callbacks to old views are triggered
793 emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
794 emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
795 emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
796 emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
797 emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
798 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
799 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
800 emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
801 emscripten_set_visibilitychange_callback(NULL, false, NULL);
802 view->backend->destroy(view);
803 }
804 free(view->impl->clipboardData);
805 free(view->impl->timers);
806 free(view->impl);
807 }
808 }
809
810 void
811 puglFreeWorldInternals(PuglWorld* const world)
812 {
813 printf("DONE: %s %d\n", __func__, __LINE__);
814 free(world->impl);
815 }
816
817 PuglStatus
818 puglGrabFocus(PuglView*)
819 {
820 return PUGL_FAILURE;
821 }
822
823 double
824 puglGetScaleFactor(const PuglView* const view)
825 {
826 printf("DONE: %s %d\n", __func__, __LINE__);
827 return view->world->impl->scaleFactor;
828 }
829
830 double
831 puglGetTime(const PuglWorld*)
832 {
833 return emscripten_get_now() / 1e3;
834 }
835
836 PuglStatus
837 puglUpdate(PuglWorld* const world, const double timeout)
838 {
839 for (size_t i = 0; i < world->numViews; ++i) {
840 PuglView* const view = world->views[i];
841
842 if (!view->visible) {
843 continue;
844 }
845
846 puglDispatchSimpleEvent(view, PUGL_UPDATE);
847
848 if (!view->impl->needsRepaint) {
849 continue;
850 }
851
852 view->impl->needsRepaint = false;
853
854 PuglEvent event = {{PUGL_EXPOSE, 0}};
855 event.expose.x = view->frame.x;
856 event.expose.y = view->frame.y;
857 event.expose.width = view->frame.width;
858 event.expose.height = view->frame.height;
859 puglDispatchEvent(view, &event);
860 }
861
862 return PUGL_SUCCESS;
863 }
864
865 PuglStatus
866 puglPostRedisplay(PuglView* const view)
867 {
868 view->impl->needsRepaint = true;
869 return PUGL_SUCCESS;
870 }
871
872 PuglStatus
873 puglPostRedisplayRect(PuglView* const view, const PuglRect rect)
874 {
875 view->impl->needsRepaint = true;
876 return PUGL_FAILURE;
877 }
878
879 PuglNativeView
880 puglGetNativeView(PuglView* const view)
881 {
882 return 0;
883 }
884
885 PuglStatus
886 puglSetWindowTitle(PuglView* const view, const char* const title)
887 {
888 puglSetString(&view->title, title);
889 emscripten_set_window_title(title);
890 return PUGL_SUCCESS;
891 }
892
893 PuglStatus
894 puglSetSizeHint(PuglView* const view,
895 const PuglSizeHint hint,
896 const PuglSpan width,
897 const PuglSpan height)
898 {
899 view->sizeHints[hint].width = width;
900 view->sizeHints[hint].height = height;
901 return PUGL_SUCCESS;
902 }
903
904 static EM_BOOL
905 puglTimerLoopCallback(double timeout, void* const arg)
906 {
907 PuglTimer* const timer = (PuglTimer*)arg;
908 PuglInternals* const impl = timer->view->impl;
909
910 // only handle active timers
911 for (uint32_t i=0; i<impl->numTimers; ++i)
912 {
913 if (impl->timers[i].id == timer->id)
914 {
915 PuglEvent event = {{PUGL_TIMER, 0}};
916 event.timer.id = timer->id;
917 puglDispatchEventWithContext(timer->view, &event);
918 return EM_TRUE;
919 }
920 }
921
922 return EM_FALSE;
923
924 // unused
925 (void)timeout;
926 }
927
928 PuglStatus
929 puglStartTimer(PuglView* const view, const uintptr_t id, const double timeout)
930 {
931 printf("DONE: %s %d\n", __func__, __LINE__);
932 PuglInternals* const impl = view->impl;
933 const uint32_t timerIndex = impl->numTimers++;
934
935 if (impl->timers == NULL)
936 impl->timers = (PuglTimer*)malloc(sizeof(PuglTimer));
937 else
938 impl->timers = (PuglTimer*)realloc(impl->timers, sizeof(PuglTimer) * timerIndex);
939
940 PuglTimer* const timer = &impl->timers[timerIndex];
941 timer->view = view;
942 timer->id = id;
943
944 emscripten_set_timeout_loop(puglTimerLoopCallback, timeout * 1e3, timer);
945 return PUGL_SUCCESS;
946 }
947
948 PuglStatus
949 puglStopTimer(PuglView* const view, const uintptr_t id)
950 {
951 printf("DONE: %s %d\n", __func__, __LINE__);
952 PuglInternals* const impl = view->impl;
953
954 if (impl->timers == NULL || impl->numTimers == 0)
955 return PUGL_FAILURE;
956
957 for (uint32_t i=0; i<impl->numTimers; ++i)
958 {
959 if (impl->timers[i].id == id)
960 {
961 memmove(impl->timers + i, impl->timers + (i + 1), sizeof(PuglTimer) * (impl->numTimers - 1));
962 --impl->numTimers;
963 return PUGL_SUCCESS;
964 }
965 }
966
967 return PUGL_FAILURE;
968 }
969
970 #ifdef PUGL_WASM_ASYNC_CLIPBOARD
971 EM_JS(char*, puglGetAsyncClipboardData, (), {
972 var text = Asyncify.handleSleep(function(wakeUp) {
973 navigator.clipboard.readText()
974 .then(function(text) {
975 wakeUp(text);
976 })
977 .catch(function() {
978 wakeUp("");
979 });
980 });
981 if (!text.length) {
982 return null;
983 }
984 var length = lengthBytesUTF8(text) + 1;
985 var str = _malloc(length);
986 stringToUTF8(text, str, length);
987 return str;
988 });
989 #endif
990
991 PuglStatus
992 puglPaste(PuglView* const view)
993 {
994 #ifdef PUGL_WASM_ASYNC_CLIPBOARD
995 // abort early if we already know it is not supported
996 if (view->impl->supportsClipboardRead == PUGL_FALSE) {
997 return PUGL_UNSUPPORTED;
998 }
999
1000 free(view->impl->clipboardData);
1001 view->impl->clipboardData = puglGetAsyncClipboardData();
1002 #endif
1003
1004 if (view->impl->clipboardData == NULL) {
1005 return PUGL_FAILURE;
1006 }
1007
1008 const PuglDataOfferEvent offer = {
1009 PUGL_DATA_OFFER,
1010 0,
1011 emscripten_get_now() / 1e3,
1012 };
1013
1014 PuglEvent offerEvent;
1015 offerEvent.offer = offer;
1016 puglDispatchEvent(view, &offerEvent);
1017 return PUGL_SUCCESS;
1018 }
1019
1020 PuglStatus
1021 puglAcceptOffer(PuglView* const view,
1022 const PuglDataOfferEvent* const offer,
1023 const uint32_t typeIndex)
1024 {
1025 if (typeIndex != 0) {
1026 return PUGL_UNSUPPORTED;
1027 }
1028
1029 const PuglDataEvent data = {
1030 PUGL_DATA,
1031 0,
1032 emscripten_get_now() / 1e3,
1033 0,
1034 };
1035
1036 PuglEvent dataEvent;
1037 dataEvent.data = data;
1038 puglDispatchEvent(view, &dataEvent);
1039 return PUGL_SUCCESS;
1040 }
1041
1042 uint32_t
1043 puglGetNumClipboardTypes(const PuglView* const view)
1044 {
1045 return view->impl->clipboardData != NULL ? 1u : 0u;
1046 }
1047
1048 const char*
1049 puglGetClipboardType(const PuglView* const view, const uint32_t typeIndex)
1050 {
1051 return (typeIndex == 0 && view->impl->clipboardData != NULL)
1052 ? "text/plain"
1053 : NULL;
1054 }
1055
1056 const void*
1057 puglGetClipboard(PuglView* const view,
1058 const uint32_t typeIndex,
1059 size_t* const len)
1060 {
1061 return view->impl->clipboardData;
1062 }
1063
1064 PuglStatus
1065 puglSetClipboard(PuglView* const view,
1066 const char* const type,
1067 const void* const data,
1068 const size_t len)
1069 {
1070 // only utf8 text supported for now
1071 if (type != NULL && strcmp(type, "text/plain") != 0) {
1072 return PUGL_UNSUPPORTED;
1073 }
1074
1075 const char* const className = view->world->className;
1076 const char* const text = (const char*)data;
1077
1078 #ifdef PUGL_WASM_ASYNC_CLIPBOARD
1079 // abort early if we already know it is not supported
1080 if (view->impl->supportsClipboardWrite == PUGL_FALSE) {
1081 return PUGL_UNSUPPORTED;
1082 }
1083 #else
1084 puglSetString(&view->impl->clipboardData, text);
1085 #endif
1086
1087 EM_ASM({
1088 if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.writeText) === 'function' && window.isSecureContext) {
1089 navigator.clipboard.writeText(UTF8ToString($1));
1090 } else {
1091 var canvasClipboardObjName = UTF8ToString($0) + "_clipboard";
1092 var canvasClipboardElem = document.getElementById(canvasClipboardObjName);
1093
1094 if (!canvasClipboardElem) {
1095 canvasClipboardElem = document.createElement('textarea');
1096 canvasClipboardElem.id = canvasClipboardObjName;
1097 canvasClipboardElem.style.position = 'fixed';
1098 canvasClipboardElem.style.whiteSpace = 'pre';
1099 canvasClipboardElem.style.zIndex = '-1';
1100 canvasClipboardElem.setAttribute('readonly', true);
1101 document.body.appendChild(canvasClipboardElem);
1102 }
1103
1104 canvasClipboardElem.textContent = UTF8ToString($1);
1105 canvasClipboardElem.select();
1106 document.execCommand("copy");
1107 }
1108 }, className, text);
1109
1110 // FIXME proper return status
1111 return PUGL_SUCCESS;
1112 }
1113
1114 PuglStatus
1115 puglSetCursor(PuglView* const view, const PuglCursor cursor)
1116 {
1117 printf("TODO: %s %d\n", __func__, __LINE__);
1118 return PUGL_FAILURE;
1119 }
1120
1121 PuglStatus
1122 puglSetTransientParent(PuglView* const view, const PuglNativeView parent)
1123 {
1124 printf("TODO: %s %d\n", __func__, __LINE__);
1125 view->transientParent = parent;
1126 return PUGL_FAILURE;
1127 }
1128
1129 PuglStatus
1130 puglSetPosition(PuglView* const view, const int x, const int y)
1131 {
1132 printf("TODO: %s %d\n", __func__, __LINE__);
1133
1134 if (x > INT16_MAX || y > INT16_MAX) {
1135 return PUGL_BAD_PARAMETER;
1136 }
1137
1138 view->frame.x = (PuglCoord)x;
1139 view->frame.y = (PuglCoord)y;
1140 return PUGL_FAILURE;
1141 }