|
- // Copyright 2012-2022 David Robillard <d@drobilla.net>
- // Copyright 2021-2025 Filipe Coelho <falktx@falktx.com>
- // SPDX-License-Identifier: ISC
-
- #include "wasm.h"
-
- #include "../pugl-upstream/src/internal.h"
- #include "../pugl-upstream/src/platform.h"
-
- #include <stdio.h>
-
- #include <emscripten/html5.h>
-
- #ifdef __cplusplus
- # define PUGL_INIT_STRUCT \
- {}
- #else
- # define PUGL_INIT_STRUCT \
- { \
- 0 \
- }
- #endif
-
- #ifdef __MOD_DEVICES__
- # define MOD_SCALE_FACTOR_MULT 1
- #endif
-
- // #define PUGL_WASM_AUTO_POINTER_LOCK
- // #define PUGL_WASM_NO_KEYBOARD_INPUT
- // #define PUGL_WASM_NO_MOUSEWHEEL_INPUT
-
- PuglWorldInternals*
- puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags)
- {
- PuglWorldInternals* impl =
- (PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals));
-
- impl->scaleFactor = emscripten_get_device_pixel_ratio();
- #ifdef __MOD_DEVICES__
- impl->scaleFactor *= MOD_SCALE_FACTOR_MULT;
- #endif
-
- printf("DONE: %s %d | -> %f\n", __func__, __LINE__, impl->scaleFactor);
-
- return impl;
- }
-
- void*
- puglGetNativeWorld(PuglWorld*)
- {
- printf("DONE: %s %d\n", __func__, __LINE__);
- return NULL;
- }
-
- PuglInternals*
- puglInitViewInternals(PuglWorld* const world)
- {
- printf("DONE: %s %d\n", __func__, __LINE__);
- PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
-
- impl->buttonPressTimeout = -1;
- impl->supportsTouch = PUGL_DONT_CARE; // not yet known
-
- #ifdef PUGL_WASM_ASYNC_CLIPBOARD
- impl->supportsClipboardRead = (PuglViewHintValue)EM_ASM_INT({
- if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.readText) === 'function' && window.isSecureContext) {
- return 1; // PUGL_TRUE
- }
- return 0; // PUGL_FALSE
- });
-
- impl->supportsClipboardWrite = (PuglViewHintValue)EM_ASM_INT({
- if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.writeText) === 'function' && window.isSecureContext) {
- return 1; // PUGL_TRUE
- }
- if (typeof(document.queryCommandSupported) !== 'undefined' && document.queryCommandSupported("copy")) {
- return 1; // PUGL_TRUE
- }
- return 0; // PUGL_FALSE
- });
- #endif
-
- return impl;
- }
-
- PuglStatus
- puglApplySizeHint(PuglView* const view, const PuglSizeHint PUGL_UNUSED(hint))
- {
- // No fine-grained updates, hints are always recalculated together
- return puglUpdateSizeHints(view);
- }
-
- PuglStatus
- puglUpdateSizeHints(PuglView* const view)
- {
- const char* const className = view->world->strings[PUGL_CLASS_NAME];
-
- if (!view->hints[PUGL_RESIZABLE]) {
- PuglArea size = puglGetSizeHint(view, PUGL_CURRENT_SIZE);
- if (!puglIsValidSize(size.width, size.height)) {
- size = puglGetSizeHint(view, PUGL_DEFAULT_SIZE);
- }
- EM_ASM({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- var width = parseInt($1 / window.devicePixelRatio);
- var height = parseInt($2 / window.devicePixelRatio);
- canvasWrapper.style.setProperty("min-width", width + 'px');
- canvasWrapper.style.setProperty("max-width", width + 'px');
- canvasWrapper.style.setProperty("min-height", height + 'px');
- canvasWrapper.style.setProperty("max-height", height + 'px');
- }, className, size.width, size.height);
- } else {
- const PuglArea minSize = view->sizeHints[PUGL_MIN_SIZE];
- if (puglIsValidArea(minSize)) {
- EM_ASM({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- canvasWrapper.style.setProperty("min-width", parseInt($1 / window.devicePixelRatio) + 'px');
- canvasWrapper.style.setProperty("min-height", parseInt($2 / window.devicePixelRatio) + 'px');
- }, className, minSize.width, minSize.height);
- } else {
- EM_ASM({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- canvasWrapper.style.removeProperty("min-width");
- canvasWrapper.style.removeProperty("min-height");
- }, className);
- }
-
- const PuglArea maxSize = view->sizeHints[PUGL_MAX_SIZE];
- if (puglIsValidArea(maxSize)) {
- EM_ASM({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- canvasWrapper.style.setProperty("max-width", parseInt($1 / window.devicePixelRatio) + 'px');
- canvasWrapper.style.setProperty("max-height", parseInt($2 / window.devicePixelRatio) + 'px');
- }, className, maxSize.width, maxSize.height);
- } else {
- EM_ASM({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- canvasWrapper.style.removeProperty("max-width");
- canvasWrapper.style.removeProperty("max-height");
- }, className);
- }
-
- /* TODO
- const PuglArea minAspect = view->sizeHints[PUGL_MIN_ASPECT];
- const PuglArea maxAspect = view->sizeHints[PUGL_MAX_ASPECT];
- const PuglArea fixedAspect = view->sizeHints[PUGL_FIXED_ASPECT];
- */
- }
-
- return PUGL_SUCCESS;
- }
-
- static PuglStatus
- puglDispatchEventWithContext(PuglView* const view, const PuglEvent* event)
- {
- PuglStatus st0 = PUGL_SUCCESS;
- PuglStatus st1 = PUGL_SUCCESS;
-
- if (!(st0 = view->backend->enter(view, NULL))) {
- st0 = view->eventFunc(view, event);
- st1 = view->backend->leave(view, NULL);
- }
-
- return st0 ? st0 : st1;
- }
-
- static PuglMods
- translateModifiers(const EM_BOOL ctrlKey,
- const EM_BOOL shiftKey,
- const EM_BOOL altKey,
- const EM_BOOL metaKey)
- {
- return (ctrlKey ? PUGL_MOD_CTRL : 0u) |
- (shiftKey ? PUGL_MOD_SHIFT : 0u) |
- (altKey ? PUGL_MOD_ALT : 0u) |
- (metaKey ? PUGL_MOD_SUPER : 0u);
- }
-
- #ifndef PUGL_WASM_NO_KEYBOARD_INPUT
- static PuglKey
- keyCodeToSpecial(const unsigned long code, const unsigned long location)
- {
- switch (code) {
- case 0x08: return PUGL_KEY_BACKSPACE;
- case 0x1B: return PUGL_KEY_ESCAPE;
- case 0x2E: return PUGL_KEY_DELETE;
- case 0x70: return PUGL_KEY_F1;
- case 0x71: return PUGL_KEY_F2;
- case 0x72: return PUGL_KEY_F3;
- case 0x73: return PUGL_KEY_F4;
- case 0x74: return PUGL_KEY_F5;
- case 0x75: return PUGL_KEY_F6;
- case 0x76: return PUGL_KEY_F7;
- case 0x77: return PUGL_KEY_F8;
- case 0x78: return PUGL_KEY_F9;
- case 0x79: return PUGL_KEY_F10;
- case 0x7A: return PUGL_KEY_F11;
- case 0x7B: return PUGL_KEY_F12;
- case 0x25: return PUGL_KEY_LEFT;
- case 0x26: return PUGL_KEY_UP;
- case 0x27: return PUGL_KEY_RIGHT;
- case 0x28: return PUGL_KEY_DOWN;
- case 0x21: return PUGL_KEY_PAGE_UP;
- case 0x22: return PUGL_KEY_PAGE_DOWN;
- case 0x24: return PUGL_KEY_HOME;
- case 0x23: return PUGL_KEY_END;
- case 0x2D: return PUGL_KEY_INSERT;
- case 0x10: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_SHIFT_R : PUGL_KEY_SHIFT_L;
- case 0x11: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_CTRL_R : PUGL_KEY_CTRL_L;
- case 0x12: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_ALT_R : PUGL_KEY_ALT_L;
- case 0xE0: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_SUPER_R : PUGL_KEY_SUPER_L;
- case 0x5D: return PUGL_KEY_MENU;
- case 0x14: return PUGL_KEY_CAPS_LOCK;
- case 0x91: return PUGL_KEY_SCROLL_LOCK;
- case 0x90: return PUGL_KEY_NUM_LOCK;
- case 0x2C: return PUGL_KEY_PRINT_SCREEN;
- case 0x13: return PUGL_KEY_PAUSE;
- case '\r': return (PuglKey)'\r';
- default: break;
- }
-
- return (PuglKey)0;
- }
-
- static bool
- decodeCharacterString(const unsigned long keyCode,
- const EM_UTF8 key[EM_HTML5_SHORT_STRING_LEN_BYTES],
- char str[8])
- {
- if (key[1] == 0)
- {
- str[0] = key[0];
- return true;
- }
-
- return false;
- }
-
- static EM_BOOL
- puglKeyCallback(const int eventType, const EmscriptenKeyboardEvent* const keyEvent, void* const userData)
- {
- PuglView* const view = (PuglView*)userData;
-
- if (!view->impl->visible) {
- return EM_FALSE;
- }
-
- if (keyEvent->repeat && view->hints[PUGL_IGNORE_KEY_REPEAT])
- return EM_TRUE;
-
- PuglStatus st0 = PUGL_SUCCESS;
- PuglStatus st1 = PUGL_SUCCESS;
-
- const uint state = translateModifiers(keyEvent->ctrlKey,
- keyEvent->shiftKey,
- keyEvent->altKey,
- keyEvent->metaKey);
-
- const PuglKey special = keyCodeToSpecial(keyEvent->keyCode, keyEvent->location);
-
- uint key = keyEvent->key[0] >= ' ' && keyEvent->key[0] <= '~' && keyEvent->key[1] == '\0'
- ? keyEvent->key[0]
- : keyEvent->keyCode;
-
- if (key >= 'A' && key <= 'Z' && !keyEvent->shiftKey)
- key += 'a' - 'A';
-
- PuglEvent event = {{PUGL_NOTHING, 0}};
- event.key.type = eventType == EMSCRIPTEN_EVENT_KEYDOWN ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
- event.key.time = keyEvent->timestamp / 1e3;
- // event.key.x = xevent.xkey.x;
- // event.key.y = xevent.xkey.y;
- // event.key.xRoot = xevent.xkey.x_root;
- // event.key.yRoot = xevent.xkey.y_root;
- event.key.key = special ? special : key;
- event.key.keycode = keyEvent->keyCode;
- event.key.state = state;
- st0 = puglDispatchEventWithContext(view, &event);
-
- d_debug("key event \n"
- "\tdown: %d\n"
- "\trepeat: %d\n"
- "\tlocation: %d\n"
- "\tstate: 0x%x\n"
- "\tkey[]: '%s'\n"
- "\tcode[]: '%s'\n"
- "\tlocale[]: '%s'\n"
- "\tkeyCode: 0x%lx:'%c' [deprecated, use key]\n"
- "\twhich: 0x%lx:'%c' [deprecated, use key, same as keycode?]\n"
- "\tspecial: 0x%x",
- eventType == EMSCRIPTEN_EVENT_KEYDOWN,
- keyEvent->repeat,
- keyEvent->location,
- state,
- keyEvent->key,
- keyEvent->code,
- keyEvent->locale,
- keyEvent->keyCode, keyEvent->keyCode >= ' ' && keyEvent->keyCode <= '~' ? keyEvent->keyCode : 0,
- keyEvent->which, keyEvent->which >= ' ' && keyEvent->which <= '~' ? keyEvent->which : 0,
- special);
-
- if (event.type == PUGL_KEY_PRESS && !special && !(keyEvent->ctrlKey|keyEvent->altKey|keyEvent->metaKey)) {
- char str[8] = PUGL_INIT_STRUCT;
-
- if (decodeCharacterString(keyEvent->keyCode, keyEvent->key, str)) {
- d_debug("resulting string is '%s'", str);
-
- event.text.type = PUGL_TEXT;
- event.text.character = event.key.key;
- memcpy(event.text.string, str, sizeof(event.text.string));
- st1 = puglDispatchEventWithContext(view, &event);
- }
- }
-
- return (st0 ? st0 : st1) == PUGL_SUCCESS ? EM_TRUE : EM_FALSE;
- }
- #endif
-
- static EM_BOOL
- puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEvent, void* const userData)
- {
- PuglView* const view = (PuglView*)userData;
-
- if (!view->impl->visible) {
- return EM_FALSE;
- }
-
- PuglEvent event = {{PUGL_NOTHING, 0}};
-
- const double time = mouseEvent->timestamp / 1e3;
- const PuglMods state = translateModifiers(mouseEvent->ctrlKey,
- mouseEvent->shiftKey,
- mouseEvent->altKey,
- mouseEvent->metaKey);
-
- double scaleFactor = view->world->impl->scaleFactor;
- #ifdef __MOD_DEVICES__
- if (!view->impl->isFullscreen) {
- scaleFactor /= EM_ASM_DOUBLE({
- return parseFloat(
- RegExp('^scale\\\((.*)\\\)$')
- .exec(document.getElementById("pedalboard-dashboard").style.transform)[1]
- );
- }) * MOD_SCALE_FACTOR_MULT;
- }
- #endif
-
- // workaround missing pointer lock callback, see https://github.com/emscripten-core/emscripten/issues/9681
- EmscriptenPointerlockChangeEvent e;
- if (emscripten_get_pointerlock_status(&e) == EMSCRIPTEN_RESULT_SUCCESS)
- view->impl->pointerLocked = e.isActive;
-
- #ifdef __MOD_DEVICES__
- const long canvasX = mouseEvent->canvasX;
- const long canvasY = mouseEvent->canvasY;
- #else
- const char* const className = view->world->strings[PUGL_CLASS_NAME];
- const double canvasX = mouseEvent->clientX - EM_ASM_DOUBLE({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- return canvasWrapper.getBoundingClientRect().x;
- }, className);
- const double canvasY = mouseEvent->clientY - EM_ASM_DOUBLE({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- return canvasWrapper.getBoundingClientRect().y;
- }, className);
- #endif
-
- switch (eventType) {
- case EMSCRIPTEN_EVENT_MOUSEDOWN:
- case EMSCRIPTEN_EVENT_MOUSEUP:
- event.button.type = eventType == EMSCRIPTEN_EVENT_MOUSEDOWN ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE;
- event.button.time = time;
- event.button.x = canvasX * scaleFactor;
- event.button.y = canvasY * scaleFactor;
- event.button.xRoot = mouseEvent->screenX * scaleFactor;
- event.button.yRoot = mouseEvent->screenY * scaleFactor;
- event.button.state = state;
- switch (mouseEvent->button) {
- case 1:
- event.button.button = 2;
- break;
- case 2:
- event.button.button = 1;
- break;
- default:
- event.button.button = mouseEvent->button;
- break;
- }
- break;
- case EMSCRIPTEN_EVENT_MOUSEMOVE:
- event.motion.type = PUGL_MOTION;
- event.motion.time = time;
- if (view->impl->pointerLocked) {
- // adjust local values for delta
- const double movementX = mouseEvent->movementX * scaleFactor;
- const double movementY = mouseEvent->movementY * scaleFactor;
- view->impl->lastMotion.x += movementX;
- view->impl->lastMotion.y += movementY;
- view->impl->lastMotion.xRoot += movementX;
- view->impl->lastMotion.yRoot += movementY;
- // now set x, y, xRoot and yRoot
- event.motion.x = view->impl->lastMotion.x;
- event.motion.y = view->impl->lastMotion.y;
- event.motion.xRoot = view->impl->lastMotion.xRoot;
- event.motion.yRoot = view->impl->lastMotion.yRoot;
- } else {
- // cache values for possible pointer lock movement later
- view->impl->lastMotion.x = event.motion.x = canvasX * scaleFactor;
- view->impl->lastMotion.y = event.motion.y = canvasY * scaleFactor;
- view->impl->lastMotion.xRoot = event.motion.xRoot = mouseEvent->screenX * scaleFactor;
- view->impl->lastMotion.yRoot = event.motion.yRoot = mouseEvent->screenY * scaleFactor;
- }
- event.motion.state = state;
- break;
- case EMSCRIPTEN_EVENT_MOUSEENTER:
- case EMSCRIPTEN_EVENT_MOUSELEAVE:
- event.crossing.type = eventType == EMSCRIPTEN_EVENT_MOUSEENTER ? PUGL_POINTER_IN : PUGL_POINTER_OUT;
- event.crossing.time = time;
- event.crossing.x = canvasX * scaleFactor;
- event.crossing.y = canvasY * scaleFactor;
- event.crossing.xRoot = mouseEvent->screenX * scaleFactor;
- event.crossing.yRoot = mouseEvent->screenY * scaleFactor;
- event.crossing.state = state;
- event.crossing.mode = PUGL_CROSSING_NORMAL;
- break;
- }
-
- if (event.type == PUGL_NOTHING)
- return EM_FALSE;
-
- puglDispatchEventWithContext(view, &event);
-
- #ifdef PUGL_WASM_AUTO_POINTER_LOCK
- switch (eventType) {
- case EMSCRIPTEN_EVENT_MOUSEDOWN:
- emscripten_request_pointerlock(view->world->strings[PUGL_CLASS_NAME], false);
- break;
- case EMSCRIPTEN_EVENT_MOUSEUP:
- emscripten_exit_pointerlock();
- break;
- }
- #endif
-
- // note: we must always return false, otherwise canvas never gets keyboard input
- return EM_FALSE;
- }
-
- static void
- puglTouchStartDelay(void* const userData)
- {
- PuglView* const view = (PuglView*)userData;
- PuglInternals* const impl = view->impl;
-
- impl->buttonPressTimeout = -1;
- impl->nextButtonEvent.button.time += 2000;
- puglDispatchEventWithContext(view, &impl->nextButtonEvent);
- }
-
- static EM_BOOL
- puglTouchCallback(const int eventType, const EmscriptenTouchEvent* const touchEvent, void* const userData)
- {
- if (touchEvent->numTouches <= 0) {
- return EM_FALSE;
- }
-
- PuglView* const view = (PuglView*)userData;
- PuglInternals* const impl = view->impl;
- const char* const className = view->world->strings[PUGL_CLASS_NAME];
-
- if (impl->supportsTouch == PUGL_DONT_CARE) {
- impl->supportsTouch = PUGL_TRUE;
-
- // stop using mouse press events which conflict with touch
- emscripten_set_mousedown_callback(className, view, false, NULL);
- emscripten_set_mouseup_callback(className, view, false, NULL);
- }
-
- if (!view->impl->visible) {
- return EM_FALSE;
- }
-
- PuglEvent event = {{PUGL_NOTHING, 0}};
-
- const double time = touchEvent->timestamp / 1e3;
- const PuglMods state = translateModifiers(touchEvent->ctrlKey,
- touchEvent->shiftKey,
- touchEvent->altKey,
- touchEvent->metaKey);
-
- double scaleFactor = view->world->impl->scaleFactor;
- #ifdef __MOD_DEVICES__
- if (!view->impl->isFullscreen) {
- scaleFactor /= EM_ASM_DOUBLE({
- return parseFloat(
- RegExp('^scale\\\((.*)\\\)$')
- .exec(document.getElementById("pedalboard-dashboard").style.transform)[1]
- );
- }) * MOD_SCALE_FACTOR_MULT;
- }
- #endif
-
- d_debug("touch %d|%s %d || %ld",
- eventType,
- eventType == EMSCRIPTEN_EVENT_TOUCHSTART ? "start" :
- eventType == EMSCRIPTEN_EVENT_TOUCHEND ? "end" : "cancel",
- touchEvent->numTouches,
- impl->buttonPressTimeout);
-
- const EmscriptenTouchPoint* point = &touchEvent->touches[0];
-
- if (impl->buttonPressTimeout != -1 || eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) {
- // if we received an event while touch is active, trigger initial click now
- if (impl->buttonPressTimeout != -1) {
- emscripten_clear_timeout(impl->buttonPressTimeout);
- impl->buttonPressTimeout = -1;
- if (eventType != EMSCRIPTEN_EVENT_TOUCHCANCEL) {
- impl->nextButtonEvent.button.button = 0;
- }
- }
- impl->nextButtonEvent.button.time = time;
- puglDispatchEventWithContext(view, &impl->nextButtonEvent);
- }
-
- #ifdef __MOD_DEVICES__
- const long canvasX = point->canvasX;
- const long canvasY = point->canvasY;
- #else
- const double canvasX = point->clientX - EM_ASM_DOUBLE({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- return canvasWrapper.getBoundingClientRect().x;
- }, className);
- const double canvasY = point->clientY - EM_ASM_DOUBLE({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- return canvasWrapper.getBoundingClientRect().y;
- }, className);
- #endif
-
- switch (eventType) {
- case EMSCRIPTEN_EVENT_TOUCHEND:
- case EMSCRIPTEN_EVENT_TOUCHCANCEL:
- event.button.type = PUGL_BUTTON_RELEASE;
- event.button.time = time;
- event.button.button = eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL ? 1 : 0;
- event.button.x = canvasX * scaleFactor;
- event.button.y = canvasY * scaleFactor;
- event.button.xRoot = point->screenX * scaleFactor;
- event.button.yRoot = point->screenY * scaleFactor;
- event.button.state = state;
- break;
-
- case EMSCRIPTEN_EVENT_TOUCHSTART:
- // this event can be used for a couple of things, store it until we know more
- event.button.type = PUGL_BUTTON_PRESS;
- event.button.time = time;
- event.button.button = 1; // if no other event occurs soon, treat it as right-click
- event.button.x = canvasX * scaleFactor;
- event.button.y = canvasY * scaleFactor;
- event.button.xRoot = point->screenX * scaleFactor;
- event.button.yRoot = point->screenY * scaleFactor;
- event.button.state = state;
- memcpy(&impl->nextButtonEvent, &event, sizeof(PuglEvent));
- impl->buttonPressTimeout = emscripten_set_timeout(puglTouchStartDelay, 2000, view);
- // fall through, moving "mouse" to touch position
-
- case EMSCRIPTEN_EVENT_TOUCHMOVE:
- event.motion.type = PUGL_MOTION;
- event.motion.time = time;
- event.motion.x = canvasX * scaleFactor;
- event.motion.y = canvasY * scaleFactor;
- event.motion.xRoot = point->screenX * scaleFactor;
- event.motion.yRoot = point->screenY * scaleFactor;
- event.motion.state = state;
- break;
- }
-
- if (event.type == PUGL_NOTHING)
- return EM_FALSE;
-
- puglDispatchEventWithContext(view, &event);
-
- // FIXME we must always return false??
- return EM_FALSE;
- }
-
- static EM_BOOL
- puglFocusCallback(const int eventType, const EmscriptenFocusEvent* /*const focusEvent*/, void* const userData)
- {
- PuglView* const view = (PuglView*)userData;
-
- if (!view->impl->visible) {
- return EM_FALSE;
- }
-
- d_debug("focus %d|%s", eventType, eventType == EMSCRIPTEN_EVENT_FOCUSIN ? "focus-in" : "focus-out");
-
- PuglEvent event = {{eventType == EMSCRIPTEN_EVENT_FOCUSIN ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT, 0}};
- event.focus.mode = PUGL_CROSSING_NORMAL;
-
- puglDispatchEventWithContext(view, &event);
-
- // note: we must always return false, otherwise canvas never gets proper focus
- return EM_FALSE;
- }
-
- static EM_BOOL
- puglPointerLockChangeCallback(const int eventType, const EmscriptenPointerlockChangeEvent* event, void* const userData)
- {
- PuglView* const view = (PuglView*)userData;
-
- view->impl->pointerLocked = event->isActive;
- return EM_TRUE;
- }
-
- #ifndef PUGL_WASM_NO_MOUSEWHEEL_INPUT
- static EM_BOOL
- puglWheelCallback(const int eventType, const EmscriptenWheelEvent* const wheelEvent, void* const userData)
- {
- PuglView* const view = (PuglView*)userData;
-
- if (!view->impl->visible) {
- return EM_FALSE;
- }
-
- double scaleFactor = view->world->impl->scaleFactor;
- #ifdef __MOD_DEVICES__
- if (!view->impl->isFullscreen) {
- scaleFactor /= EM_ASM_DOUBLE({
- return parseFloat(
- RegExp('^scale\\\((.*)\\\)$')
- .exec(document.getElementById("pedalboard-dashboard").style.transform)[1]
- );
- }) * MOD_SCALE_FACTOR_MULT;
- }
- #endif
-
- #ifdef __MOD_DEVICES__
- const long canvasX = wheelEvent->mouse.canvasX;
- const long canvasY = wheelEvent->mouse.canvasY;
- #else
- const char* const className = view->world->strings[PUGL_CLASS_NAME];
- const double canvasX = wheelEvent->mouse.canvasX - EM_ASM_INT({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- return canvasWrapper.getBoundingClientRect().x;
- }, className);
- const double canvasY = wheelEvent->mouse.canvasY - EM_ASM_INT({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- return canvasWrapper.getBoundingClientRect().y;
- }, className);
- #endif
-
- PuglEvent event = {{PUGL_SCROLL, 0}};
- event.scroll.time = wheelEvent->mouse.timestamp / 1e3;
- event.scroll.x = canvasX;
- event.scroll.y = canvasY;
- event.scroll.xRoot = wheelEvent->mouse.screenX;
- event.scroll.yRoot = wheelEvent->mouse.screenY;
- event.scroll.state = translateModifiers(wheelEvent->mouse.ctrlKey,
- wheelEvent->mouse.shiftKey,
- wheelEvent->mouse.altKey,
- wheelEvent->mouse.metaKey);
- event.scroll.direction = PUGL_SCROLL_SMOOTH;
- // FIXME handle wheelEvent->deltaMode
- event.scroll.dx = wheelEvent->deltaX * 0.01 * scaleFactor;
- event.scroll.dy = -wheelEvent->deltaY * 0.01 * scaleFactor;
-
- return puglDispatchEventWithContext(view, &event) == PUGL_SUCCESS ? EM_TRUE : EM_FALSE;
- }
- #endif
-
- static EM_BOOL
- puglUiCallback(const int eventType, const EmscriptenUiEvent* const uiEvent, void* const userData)
- {
- PuglView* const view = (PuglView*)userData;
- const char* const className = view->world->strings[PUGL_CLASS_NAME];
- const bool isFullscreen = view->impl->isFullscreen;
-
- // FIXME
- const int width = EM_ASM_INT({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- canvasWrapper.style.setProperty("--device-pixel-ratio", window.devicePixelRatio);
- if ($1) {
- return window.innerWidth;
- } else {
- return canvasWrapper.clientWidth;
- }
- }, className, isFullscreen);
-
- const int height = EM_ASM_INT({
- if ($1) {
- return window.innerHeight;
- } else {
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- return canvasWrapper.clientHeight;
- }
- }, className, isFullscreen);
-
- if (!width || !height)
- return EM_FALSE;
-
- double scaleFactor = emscripten_get_device_pixel_ratio();
- #ifdef __MOD_DEVICES__
- scaleFactor *= MOD_SCALE_FACTOR_MULT;
- #endif
- view->world->impl->scaleFactor = scaleFactor;
-
- PuglEvent event = {{PUGL_CONFIGURE, 0}};
- event.configure.x = view->lastConfigure.x;
- event.configure.y = view->lastConfigure.y;
- event.configure.width = width * scaleFactor;
- event.configure.height = height * scaleFactor;
- puglDispatchEvent(view, &event);
-
- emscripten_set_canvas_element_size(view->world->strings[PUGL_CLASS_NAME],
- width * scaleFactor,
- height * scaleFactor);
-
- #ifdef __MOD_DEVICES__
- if (isFullscreen) {
- EM_ASM({
- document.getElementById("pedalboard-dashboard").style.transform = "scale(1.0)";
- });
- }
- #endif
-
- return EM_TRUE;
- }
-
- static EM_BOOL
- puglFullscreenChangeCallback(const int eventType, const EmscriptenFullscreenChangeEvent* const fscEvent, void* const userData)
- {
- PuglView* const view = (PuglView*)userData;
-
- view->impl->isFullscreen = fscEvent->isFullscreen;
-
- double scaleFactor = emscripten_get_device_pixel_ratio();
- #ifdef __MOD_DEVICES__
- scaleFactor *= MOD_SCALE_FACTOR_MULT;
- #endif
-
- view->world->impl->scaleFactor = scaleFactor;
-
- if (!fscEvent->isFullscreen)
- return puglUiCallback(0, NULL, userData);
-
- const int width = EM_ASM_INT({
- return window.innerWidth;
- });
-
- const int height = EM_ASM_INT({
- return window.innerHeight;
- });
-
- PuglEvent event = {{PUGL_CONFIGURE, 0}};
- event.configure.x = 0;
- event.configure.y = 0;
- event.configure.width = width;
- event.configure.height = height;
- puglDispatchEvent(view, &event);
-
- emscripten_set_canvas_element_size(view->world->strings[PUGL_CLASS_NAME], width, height);
-
- #ifdef __MOD_DEVICES__
- EM_ASM({
- document.getElementById("pedalboard-dashboard").style.transform = "scale(1.0)";
- });
- #endif
-
- return EM_TRUE;
- }
-
- static EM_BOOL
- puglVisibilityChangeCallback(const int eventType, const EmscriptenVisibilityChangeEvent* const visibilityChangeEvent, void* const userData)
- {
- PuglView* const view = (PuglView*)userData;
-
- view->impl->visible = visibilityChangeEvent->hidden == EM_FALSE;
- return EM_FALSE;
- }
-
- PuglStatus
- puglRealize(PuglView* const view)
- {
- printf("TODO: %s %d\n", __func__, __LINE__);
- PuglStatus st = PUGL_SUCCESS;
-
- // Ensure that we do not have a parent
- if (view->parent) {
- printf("TODO: %s %d\n", __func__, __LINE__);
- return PUGL_FAILURE;
- }
-
- if (!view->backend || !view->backend->configure) {
- printf("TODO: %s %d\n", __func__, __LINE__);
- return PUGL_BAD_BACKEND;
- }
-
- const char* const className = view->world->strings[PUGL_CLASS_NAME];
- d_stdout("className is %s", className);
-
- const PuglPoint defaultPos = view->positionHints[PUGL_DEFAULT_POSITION];
- const PuglArea defaultSize = view->sizeHints[PUGL_DEFAULT_SIZE];
- if (!defaultSize.width || !defaultSize.height) {
- return PUGL_BAD_CONFIGURATION;
- }
-
- // Configure and create the backend
- if ((st = view->backend->configure(view)) || (st = view->backend->create(view))) {
- view->backend->destroy(view);
- return st;
- }
-
- if (view->strings[PUGL_WINDOW_TITLE]) {
- emscripten_set_window_title(view->strings[PUGL_WINDOW_TITLE]);
- }
-
- puglDispatchSimpleEvent(view, PUGL_REALIZE);
-
- PuglEvent event = {{PUGL_CONFIGURE, 0}};
- event.configure.x = defaultPos.x;
- event.configure.y = defaultPos.y;
- event.configure.width = defaultSize.width;
- event.configure.height = defaultSize.height;
- puglDispatchEvent(view, &event);
-
- view->impl->created = true;
-
- EM_ASM({
- var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
- canvasWrapper.style.setProperty("--device-pixel-ratio", window.devicePixelRatio);
- }, className);
-
- puglUpdateSizeHints(view);
-
- emscripten_set_canvas_element_size(className, defaultSize.width, defaultSize.height);
- #ifndef PUGL_WASM_NO_KEYBOARD_INPUT
- // emscripten_set_keypress_callback(className, view, false, puglKeyCallback);
- emscripten_set_keydown_callback(className, view, false, puglKeyCallback);
- emscripten_set_keyup_callback(className, view, false, puglKeyCallback);
- #endif
- emscripten_set_touchstart_callback(className, view, false, puglTouchCallback);
- emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglTouchCallback);
- emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglTouchCallback);
- emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglTouchCallback);
- emscripten_set_mousedown_callback(className, view, false, puglMouseCallback);
- emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglMouseCallback);
- emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglMouseCallback);
- emscripten_set_mouseenter_callback(className, view, false, puglMouseCallback);
- emscripten_set_mouseleave_callback(className, view, false, puglMouseCallback);
- emscripten_set_focusin_callback(className, view, false, puglFocusCallback);
- emscripten_set_focusout_callback(className, view, false, puglFocusCallback);
- #ifndef PUGL_WASM_NO_MOUSEWHEEL_INPUT
- emscripten_set_wheel_callback(className, view, false, puglWheelCallback);
- #endif
- emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglPointerLockChangeCallback);
- emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglUiCallback);
- emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglFullscreenChangeCallback);
- emscripten_set_visibilitychange_callback(view, false, puglVisibilityChangeCallback);
-
- printf("TODO: %s %d\n", __func__, __LINE__);
- return PUGL_SUCCESS;
- }
-
- PuglStatus
- puglShow(PuglView* const view, PuglShowCommand)
- {
- view->impl->visible = true;
- view->impl->needsRepaint = true;
- return puglObscureView(view);
- }
-
- PuglStatus
- puglHide(PuglView* const view)
- {
- view->impl->visible = false;
- return PUGL_FAILURE;
- }
-
- void
- puglFreeViewInternals(PuglView* const view)
- {
- printf("DONE: %s %d\n", __func__, __LINE__);
- if (view && view->impl) {
- if (view->backend) {
- // unregister the window events, to make sure no callbacks to old views are triggered
- emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
- emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
- emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
- emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
- emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
- emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
- emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
- emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
- emscripten_set_visibilitychange_callback(NULL, false, NULL);
- view->backend->destroy(view);
- }
- free(view->impl->clipboardData);
- free(view->impl->timers);
- free(view->impl);
- }
- }
-
- void
- puglFreeWorldInternals(PuglWorld* const world)
- {
- printf("DONE: %s %d\n", __func__, __LINE__);
- free(world->impl);
- }
-
- PuglStatus
- puglGrabFocus(PuglView*)
- {
- return PUGL_FAILURE;
- }
-
- double
- puglGetScaleFactor(const PuglView* const view)
- {
- printf("DONE: %s %d\n", __func__, __LINE__);
- return view->world->impl->scaleFactor;
- }
-
- double
- puglGetTime(const PuglWorld*)
- {
- return emscripten_get_now() / 1e3;
- }
-
- PuglStatus
- puglUpdate(PuglWorld* const world, const double timeout)
- {
- for (size_t i = 0; i < world->numViews; ++i) {
- PuglView* const view = world->views[i];
-
- if (!view->impl->visible) {
- continue;
- }
-
- puglDispatchSimpleEvent(view, PUGL_UPDATE);
-
- if (!view->impl->needsRepaint) {
- continue;
- }
-
- view->impl->needsRepaint = false;
-
- PuglEvent event = {{PUGL_EXPOSE, 0}};
- event.expose.x = view->lastConfigure.x;
- event.expose.y = view->lastConfigure.y;
- event.expose.width = view->lastConfigure.width;
- event.expose.height = view->lastConfigure.height;
- puglDispatchEvent(view, &event);
- }
-
- return PUGL_SUCCESS;
- }
-
- PuglStatus
- puglObscureView(PuglView* const view)
- {
- view->impl->needsRepaint = true;
- return PUGL_SUCCESS;
- }
-
- PuglStatus
- puglObscureRegion(PuglView* view,
- const int x,
- const int y,
- const unsigned width,
- const unsigned height)
- {
- if (!puglIsValidPosition(x, y) || !puglIsValidSize(width, height)) {
- return PUGL_BAD_PARAMETER;
- }
-
- view->impl->needsRepaint = true;
- return PUGL_FAILURE;
- }
-
- PuglNativeView
- puglGetNativeView(const PuglView* const view)
- {
- return 0;
- }
-
- PuglStatus
- puglViewStringChanged(PuglView*, const PuglStringHint key, const char* const value)
- {
- switch (key) {
- case PUGL_CLASS_NAME:
- break;
- case PUGL_WINDOW_TITLE:
- emscripten_set_window_title(value);
- break;
- }
-
- return PUGL_SUCCESS;
- }
-
- PuglStatus
- puglSetWindowSize(PuglView* const view, const unsigned width, const unsigned height)
- {
- if (view->impl->created) {
- const char* const className = view->world->strings[PUGL_CLASS_NAME];
- emscripten_set_canvas_element_size(className, width, height);
- }
-
- return PUGL_SUCCESS;
- }
-
- static EM_BOOL
- puglTimerLoopCallback(double timeout, void* const arg)
- {
- PuglTimer* const timer = (PuglTimer*)arg;
- PuglInternals* const impl = timer->view->impl;
-
- // only handle active timers
- for (uint32_t i=0; i<impl->numTimers; ++i)
- {
- if (impl->timers[i].id == timer->id)
- {
- PuglEvent event = {{PUGL_TIMER, 0}};
- event.timer.id = timer->id;
- puglDispatchEventWithContext(timer->view, &event);
- return EM_TRUE;
- }
- }
-
- return EM_FALSE;
-
- // unused
- (void)timeout;
- }
-
- PuglStatus
- puglStartTimer(PuglView* const view, const uintptr_t id, const double timeout)
- {
- printf("DONE: %s %d\n", __func__, __LINE__);
- PuglInternals* const impl = view->impl;
- const uint32_t timerIndex = impl->numTimers++;
-
- if (impl->timers == NULL)
- impl->timers = (PuglTimer*)malloc(sizeof(PuglTimer));
- else
- impl->timers = (PuglTimer*)realloc(impl->timers, sizeof(PuglTimer) * timerIndex);
-
- PuglTimer* const timer = &impl->timers[timerIndex];
- timer->view = view;
- timer->id = id;
-
- emscripten_set_timeout_loop(puglTimerLoopCallback, timeout * 1e3, timer);
- return PUGL_SUCCESS;
- }
-
- PuglStatus
- puglStopTimer(PuglView* const view, const uintptr_t id)
- {
- printf("DONE: %s %d\n", __func__, __LINE__);
- PuglInternals* const impl = view->impl;
-
- if (impl->timers == NULL || impl->numTimers == 0)
- return PUGL_FAILURE;
-
- for (uint32_t i=0; i<impl->numTimers; ++i)
- {
- if (impl->timers[i].id == id)
- {
- memmove(impl->timers + i, impl->timers + (i + 1), sizeof(PuglTimer) * (impl->numTimers - 1));
- --impl->numTimers;
- return PUGL_SUCCESS;
- }
- }
-
- return PUGL_FAILURE;
- }
-
- #ifdef PUGL_WASM_ASYNC_CLIPBOARD
- EM_JS(char*, puglGetAsyncClipboardData, (), {
- var text = Asyncify.handleSleep(function(wakeUp) {
- navigator.clipboard.readText()
- .then(function(text) {
- wakeUp(text);
- })
- .catch(function() {
- wakeUp("");
- });
- });
- if (!text.length) {
- return null;
- }
- var length = lengthBytesUTF8(text) + 1;
- var str = _malloc(length);
- stringToUTF8(text, str, length);
- return str;
- });
- #endif
-
- PuglStatus
- puglPaste(PuglView* const view)
- {
- #ifdef PUGL_WASM_ASYNC_CLIPBOARD
- // abort early if we already know it is not supported
- if (view->impl->supportsClipboardRead == PUGL_FALSE) {
- return PUGL_UNSUPPORTED;
- }
-
- free(view->impl->clipboardData);
- view->impl->clipboardData = puglGetAsyncClipboardData();
- #endif
-
- if (view->impl->clipboardData == NULL) {
- return PUGL_FAILURE;
- }
-
- const PuglDataOfferEvent offer = {
- PUGL_DATA_OFFER,
- 0,
- emscripten_get_now() / 1e3,
- };
-
- PuglEvent offerEvent;
- offerEvent.offer = offer;
- puglDispatchEvent(view, &offerEvent);
- return PUGL_SUCCESS;
- }
-
- PuglStatus
- puglAcceptOffer(PuglView* const view,
- const PuglDataOfferEvent* const offer,
- const uint32_t typeIndex)
- {
- if (typeIndex != 0) {
- return PUGL_UNSUPPORTED;
- }
-
- const PuglDataEvent data = {
- PUGL_DATA,
- 0,
- emscripten_get_now() / 1e3,
- 0,
- };
-
- PuglEvent dataEvent;
- dataEvent.data = data;
- puglDispatchEvent(view, &dataEvent);
- return PUGL_SUCCESS;
- }
-
- uint32_t
- puglGetNumClipboardTypes(const PuglView* const view)
- {
- return view->impl->clipboardData != NULL ? 1u : 0u;
- }
-
- const char*
- puglGetClipboardType(const PuglView* const view, const uint32_t typeIndex)
- {
- return (typeIndex == 0 && view->impl->clipboardData != NULL)
- ? "text/plain"
- : NULL;
- }
-
- const void*
- puglGetClipboard(PuglView* const view,
- const uint32_t typeIndex,
- size_t* const len)
- {
- return view->impl->clipboardData;
- }
-
- PuglStatus
- puglSetClipboard(PuglView* const view,
- const char* const type,
- const void* const data,
- const size_t len)
- {
- // only utf8 text supported for now
- if (type != NULL && strcmp(type, "text/plain") != 0) {
- return PUGL_UNSUPPORTED;
- }
-
- const char* const className = view->world->strings[PUGL_CLASS_NAME];
- const char* const text = (const char*)data;
-
- #ifdef PUGL_WASM_ASYNC_CLIPBOARD
- // abort early if we already know it is not supported
- if (view->impl->supportsClipboardWrite == PUGL_FALSE) {
- return PUGL_UNSUPPORTED;
- }
- #else
- puglSetString(&view->impl->clipboardData, text);
- #endif
-
- EM_ASM({
- if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.writeText) === 'function' && window.isSecureContext) {
- navigator.clipboard.writeText(UTF8ToString($1));
- } else {
- var canvasClipboardObjName = UTF8ToString($0) + "_clipboard";
- var canvasClipboardElem = document.getElementById(canvasClipboardObjName);
-
- if (!canvasClipboardElem) {
- canvasClipboardElem = document.createElement('textarea');
- canvasClipboardElem.id = canvasClipboardObjName;
- canvasClipboardElem.style.position = 'fixed';
- canvasClipboardElem.style.whiteSpace = 'pre';
- canvasClipboardElem.style.zIndex = '-1';
- canvasClipboardElem.setAttribute('readonly', true);
- document.body.appendChild(canvasClipboardElem);
- }
-
- canvasClipboardElem.textContent = UTF8ToString($1);
- canvasClipboardElem.select();
- document.execCommand("copy");
- }
- }, className, text);
-
- // FIXME proper return status
- return PUGL_SUCCESS;
- }
-
- PuglStatus
- puglSetCursor(PuglView* const view, const PuglCursor cursor)
- {
- printf("TODO: %s %d\n", __func__, __LINE__);
- return PUGL_FAILURE;
- }
-
- PuglStatus
- puglSetTransientParent(PuglView* const view, const PuglNativeView parent)
- {
- printf("TODO: %s %d\n", __func__, __LINE__);
- view->transientParent = parent;
- return PUGL_FAILURE;
- }
|