Mercurial > hg > pub > prymula > com
view 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 |
line wrap: on
line source
// Copyright 2012-2022 David Robillard <d@drobilla.net> // Copyright 2021-2022 Filipe Coelho <falktx@falktx.com> // SPDX-License-Identifier: ISC #include "wasm.h" #include "../pugl-upstream/src/internal.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; } 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->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->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->className; 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->className, 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->className; 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->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->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->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->className; 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->className; // FIXME const int width = EM_ASM_INT({ var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement; canvasWrapper.style.setProperty("--device-pixel-ratio", window.devicePixelRatio); return canvasWrapper.clientWidth; }, className); const int height = EM_ASM_INT({ var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement; return canvasWrapper.clientHeight; }, className); 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->frame.x; event.configure.y = view->frame.y; event.configure.width = width * scaleFactor; event.configure.height = height * scaleFactor; puglDispatchEvent(view, &event); emscripten_set_canvas_element_size(view->world->className, width * scaleFactor, height * scaleFactor); 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) { PuglEvent event = {{PUGL_CONFIGURE, 0}}; event.configure.x = 0; event.configure.y = 0; event.configure.width = fscEvent->elementWidth * scaleFactor; event.configure.height = fscEvent->elementHeight * scaleFactor; puglDispatchEvent(view, &event); emscripten_set_canvas_element_size(view->world->className, fscEvent->elementWidth * scaleFactor, fscEvent->elementHeight * scaleFactor); #ifdef __MOD_DEVICES__ EM_ASM({ document.getElementById("pedalboard-dashboard").style.transform = "scale(1.0)"; }); #endif return EM_TRUE; } return puglUiCallback(0, NULL, userData); } static EM_BOOL puglVisibilityChangeCallback(const int eventType, const EmscriptenVisibilityChangeEvent* const visibilityChangeEvent, void* const userData) { PuglView* const view = (PuglView*)userData; view->visible = visibilityChangeEvent->hidden == EM_FALSE; PuglEvent event = {{ view->visible ? PUGL_MAP : PUGL_UNMAP, 0}}; puglDispatchEvent(view, &event); 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->className; d_stdout("className is %s", className); // Set the size to the default if it has not already been set if (view->frame.width <= 0.0 && view->frame.height <= 0.0) { PuglViewSize defaultSize = view->sizeHints[PUGL_DEFAULT_SIZE]; if (!defaultSize.width || !defaultSize.height) { return PUGL_BAD_CONFIGURATION; } view->frame.width = defaultSize.width; view->frame.height = defaultSize.height; } // Configure and create the backend if ((st = view->backend->configure(view)) || (st = view->backend->create(view))) { view->backend->destroy(view); return st; } if (view->title) { puglSetWindowTitle(view, view->title); } puglDispatchSimpleEvent(view, PUGL_CREATE); PuglEvent event = {{PUGL_CONFIGURE, 0}}; event.configure.x = view->frame.x; event.configure.y = view->frame.y; event.configure.width = view->frame.width; event.configure.height = view->frame.height; puglDispatchEvent(view, &event); EM_ASM({ var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement; canvasWrapper.style.setProperty("--device-pixel-ratio", window.devicePixelRatio); }, className); emscripten_set_canvas_element_size(className, view->frame.width, view->frame.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) { view->visible = true; view->impl->needsRepaint = true; return puglPostRedisplay(view); } PuglStatus puglHide(PuglView* const view) { view->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->visible) { continue; } puglDispatchSimpleEvent(view, PUGL_UPDATE); if (!view->impl->needsRepaint) { continue; } view->impl->needsRepaint = false; PuglEvent event = {{PUGL_EXPOSE, 0}}; event.expose.x = view->frame.x; event.expose.y = view->frame.y; event.expose.width = view->frame.width; event.expose.height = view->frame.height; puglDispatchEvent(view, &event); } return PUGL_SUCCESS; } PuglStatus puglPostRedisplay(PuglView* const view) { view->impl->needsRepaint = true; return PUGL_SUCCESS; } PuglStatus puglPostRedisplayRect(PuglView* const view, const PuglRect rect) { view->impl->needsRepaint = true; return PUGL_FAILURE; } PuglNativeView puglGetNativeView(PuglView* const view) { return 0; } PuglStatus puglSetWindowTitle(PuglView* const view, const char* const title) { puglSetString(&view->title, title); emscripten_set_window_title(title); return PUGL_SUCCESS; } PuglStatus puglSetSizeHint(PuglView* const view, const PuglSizeHint hint, const PuglSpan width, const PuglSpan height) { view->sizeHints[hint].width = width; view->sizeHints[hint].height = 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->className; 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; } PuglStatus puglSetPosition(PuglView* const view, const int x, const int y) { printf("TODO: %s %d\n", __func__, __LINE__); if (x > INT16_MAX || y > INT16_MAX) { return PUGL_BAD_PARAMETER; } view->frame.x = (PuglCoord)x; view->frame.y = (PuglCoord)y; return PUGL_FAILURE; }