* Incomplete port of myou-engine-js to nimskull, after many months of work, and a few extra features that weren't exactly necessary for a "first commit" to work. Excuse the lack of commit history up to this point. * Bare bones structure of the documentation and the process to update it. * Restructure of the whole project to have a more sensible organization. * Making submodules of forks of larger libraries. * README, licenses, AUTHORS.md.
965 lines
41 KiB
C
965 lines
41 KiB
C
// GLFM
|
|
// https://github.com/brackeen/glfm
|
|
|
|
#if defined(__EMSCRIPTEN__)
|
|
|
|
#include "glfm.h"
|
|
|
|
#include <EGL/egl.h>
|
|
#include <emscripten/emscripten.h>
|
|
#include <emscripten/html5.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
|
|
#include "glfm_internal.h"
|
|
|
|
#ifdef NDEBUG
|
|
# define GLFM_LOG(...) do { } while (0)
|
|
#else
|
|
# define GLFM_LOG(...) do { printf("%.3f: ", glfmGetTime()); printf(__VA_ARGS__); printf("\n"); } while (0)
|
|
#endif
|
|
|
|
#define GLFM_MAX_ACTIVE_TOUCHES 10
|
|
|
|
// If 1, test if keyboard event arrays are sorted.
|
|
#define GLFM_TEST_KEYBOARD_EVENT_ARRAYS 0
|
|
|
|
#ifdef EM_JS_DEPS
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wmissing-variable-declarations"
|
|
#pragma clang diagnostic ignored "-Wextra-semi"
|
|
EM_JS_DEPS(glfm_deps, "$stringToUTF8,$lengthBytesUTF8,$UTF8ToString");
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
typedef struct {
|
|
long identifier;
|
|
bool active;
|
|
} GLFMActiveTouch;
|
|
|
|
typedef struct {
|
|
bool multitouchEnabled;
|
|
int32_t width;
|
|
int32_t height;
|
|
double scale;
|
|
GLFMRenderingAPI renderingAPI;
|
|
|
|
bool mouseDown;
|
|
GLFMActiveTouch activeTouches[GLFM_MAX_ACTIVE_TOUCHES];
|
|
|
|
bool isVisible;
|
|
bool isFocused;
|
|
bool refreshRequested;
|
|
|
|
GLFMInterfaceOrientation orientation;
|
|
} GLFMPlatformData;
|
|
|
|
// MARK: - GLFM private functions
|
|
|
|
#if GLFM_TEST_KEYBOARD_EVENT_ARRAYS
|
|
|
|
static bool glfm__listIsSorted(const char *list[], size_t size) {
|
|
for (size_t i = 1; i < size; i++) {
|
|
if (strcmp(list[i - 1], list[i]) > 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int glfm__sortedListSearch(const char *list[], size_t size, const char *word) {
|
|
int left = 0;
|
|
int right = (int)size - 1;
|
|
|
|
while (left <= right) {
|
|
int index = (left + right) / 2;
|
|
int result = strcmp(list[index], word);
|
|
if (result > 0) {
|
|
right = index - 1;
|
|
} else if (result < 0) {
|
|
left = index + 1;
|
|
} else {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void glfm__clearActiveTouches(GLFMPlatformData *platformData) {
|
|
for (int i = 0; i < GLFM_MAX_ACTIVE_TOUCHES; i++) {
|
|
platformData->activeTouches[i].active = false;
|
|
}
|
|
}
|
|
|
|
static void glfm__displayChromeUpdated(GLFMDisplay *display) {
|
|
(void)display;
|
|
}
|
|
|
|
void glfm__sensorFuncUpdated(GLFMDisplay *display) {
|
|
(void)display;
|
|
// TODO: Sensors
|
|
}
|
|
|
|
EMSCRIPTEN_KEEPALIVE extern
|
|
void glfm__requestClipboardTextCallback(GLFMDisplay *display,
|
|
GLFMClipboardTextFunc clipboardTextFunc, const char *text);
|
|
|
|
void glfm__requestClipboardTextCallback(GLFMDisplay *display,
|
|
GLFMClipboardTextFunc clipboardTextFunc, const char *text) {
|
|
if (text && text[0] != '\0') {
|
|
clipboardTextFunc(display, text);
|
|
} else {
|
|
clipboardTextFunc(display, NULL);
|
|
}
|
|
}
|
|
|
|
// MARK: - GLFM public functions
|
|
|
|
double glfmGetTime(void) {
|
|
return emscripten_get_now() / 1000.0;
|
|
}
|
|
|
|
void glfmSwapBuffers(GLFMDisplay *display) {
|
|
(void)display;
|
|
// Do nothing; swap is implicit
|
|
}
|
|
|
|
void glfmSetSupportedInterfaceOrientation(GLFMDisplay *display,
|
|
GLFMInterfaceOrientation supportedOrientations) {
|
|
if (display->supportedOrientations != supportedOrientations) {
|
|
display->supportedOrientations = supportedOrientations;
|
|
|
|
bool portraitRequested = (supportedOrientations & (GLFMInterfaceOrientationPortrait | GLFMInterfaceOrientationPortraitUpsideDown));
|
|
bool landscapeRequested = (supportedOrientations & GLFMInterfaceOrientationLandscape);
|
|
if (portraitRequested && landscapeRequested) {
|
|
emscripten_lock_orientation(EMSCRIPTEN_ORIENTATION_PORTRAIT_PRIMARY |
|
|
EMSCRIPTEN_ORIENTATION_PORTRAIT_SECONDARY |
|
|
EMSCRIPTEN_ORIENTATION_LANDSCAPE_PRIMARY |
|
|
EMSCRIPTEN_ORIENTATION_LANDSCAPE_SECONDARY);
|
|
} else if (landscapeRequested) {
|
|
emscripten_lock_orientation(EMSCRIPTEN_ORIENTATION_LANDSCAPE_PRIMARY |
|
|
EMSCRIPTEN_ORIENTATION_LANDSCAPE_SECONDARY);
|
|
} else {
|
|
emscripten_lock_orientation(EMSCRIPTEN_ORIENTATION_PORTRAIT_PRIMARY |
|
|
EMSCRIPTEN_ORIENTATION_PORTRAIT_SECONDARY);
|
|
}
|
|
}
|
|
}
|
|
|
|
GLFMInterfaceOrientation glfmGetInterfaceOrientation(const GLFMDisplay *display) {
|
|
(void)display;
|
|
|
|
EmscriptenOrientationChangeEvent orientationStatus;
|
|
emscripten_get_orientation_status(&orientationStatus);
|
|
int orientation = orientationStatus.orientationIndex;
|
|
int angle = orientationStatus.orientationAngle;
|
|
|
|
GLFMInterfaceOrientation result = GLFMInterfaceOrientationUnknown;
|
|
if (orientation == EMSCRIPTEN_ORIENTATION_PORTRAIT_PRIMARY) {
|
|
result = GLFMInterfaceOrientationPortrait;
|
|
} else if (orientation == EMSCRIPTEN_ORIENTATION_PORTRAIT_SECONDARY) {
|
|
result = GLFMInterfaceOrientationPortraitUpsideDown;
|
|
} else if (orientation == EMSCRIPTEN_ORIENTATION_LANDSCAPE_PRIMARY ||
|
|
orientation == EMSCRIPTEN_ORIENTATION_LANDSCAPE_SECONDARY) {
|
|
if (angle == 0 || angle == 90) {
|
|
result = GLFMInterfaceOrientationLandscapeRight;
|
|
} else if (angle == 180 || angle == 270) {
|
|
result = GLFMInterfaceOrientationLandscapeLeft;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void glfmGetDisplaySize(const GLFMDisplay *display, int *width, int *height) {
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
if (width) *width = platformData->width;
|
|
if (height) *height = platformData->height;
|
|
}
|
|
|
|
double glfmGetDisplayScale(const GLFMDisplay *display) {
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
return platformData->scale;
|
|
}
|
|
|
|
void glfmGetDisplayChromeInsets(const GLFMDisplay *display, double *top, double *right,
|
|
double *bottom, double *left) {
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
if (top) {
|
|
*top = platformData->scale * EM_ASM_DOUBLE_V( {
|
|
var htmlStyles = window.getComputedStyle(document.querySelector("html"));
|
|
return ((parseInt(htmlStyles.getPropertyValue("--glfm-chrome-top-old")) || 0) +
|
|
(parseInt(htmlStyles.getPropertyValue("--glfm-chrome-top")) || 0));
|
|
} );
|
|
}
|
|
if (right) {
|
|
*right = platformData->scale * EM_ASM_DOUBLE_V( {
|
|
var htmlStyles = window.getComputedStyle(document.querySelector("html"));
|
|
return ((parseInt(htmlStyles.getPropertyValue("--glfm-chrome-right-old")) || 0) +
|
|
(parseInt(htmlStyles.getPropertyValue("--glfm-chrome-right")) || 0));
|
|
} );
|
|
}
|
|
if (bottom) {
|
|
*bottom = platformData->scale * EM_ASM_DOUBLE_V( {
|
|
var htmlStyles = window.getComputedStyle(document.querySelector("html"));
|
|
return ((parseInt(htmlStyles.getPropertyValue("--glfm-chrome-bottom-old")) || 0) +
|
|
(parseInt(htmlStyles.getPropertyValue("--glfm-chrome-bottom")) || 0));
|
|
} );
|
|
}
|
|
if (left) {
|
|
*left = platformData->scale * EM_ASM_DOUBLE_V( {
|
|
var htmlStyles = window.getComputedStyle(document.querySelector("html"));
|
|
return ((parseInt(htmlStyles.getPropertyValue("--glfm-chrome-left-old")) || 0) +
|
|
(parseInt(htmlStyles.getPropertyValue("--glfm-chrome-left")) || 0));
|
|
} );
|
|
}
|
|
}
|
|
|
|
GLFMRenderingAPI glfmGetRenderingAPI(const GLFMDisplay *display) {
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
return platformData->renderingAPI;
|
|
}
|
|
|
|
bool glfmHasTouch(const GLFMDisplay *display) {
|
|
(void)display;
|
|
return EM_ASM_INT_V({
|
|
return (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0));
|
|
});
|
|
}
|
|
|
|
void glfmSetMouseCursor(GLFMDisplay *display, GLFMMouseCursor mouseCursor) {
|
|
(void)display;
|
|
// Make sure the javascript array emCursors is referenced properly
|
|
int emCursor = 0;
|
|
switch (mouseCursor) {
|
|
case GLFMMouseCursorAuto: default:
|
|
emCursor = 0;
|
|
break;
|
|
case GLFMMouseCursorNone:
|
|
emCursor = 1;
|
|
break;
|
|
case GLFMMouseCursorDefault:
|
|
emCursor = 2;
|
|
break;
|
|
case GLFMMouseCursorPointer:
|
|
emCursor = 3;
|
|
break;
|
|
case GLFMMouseCursorCrosshair:
|
|
emCursor = 4;
|
|
break;
|
|
case GLFMMouseCursorText:
|
|
emCursor = 5;
|
|
break;
|
|
case GLFMMouseCursorVerticalText:
|
|
emCursor = 6;
|
|
break;
|
|
}
|
|
EM_ASM_({
|
|
var emCursors = new Array('auto', 'none', 'default', 'pointer', 'crosshair', 'text', 'vertical-text');
|
|
Module['canvas'].style.cursor = emCursors[$0];
|
|
}, emCursor);
|
|
}
|
|
|
|
void glfmSetMultitouchEnabled(GLFMDisplay *display, bool multitouchEnabled) {
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
platformData->multitouchEnabled = multitouchEnabled;
|
|
}
|
|
|
|
bool glfmGetMultitouchEnabled(const GLFMDisplay *display) {
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
return platformData->multitouchEnabled;
|
|
}
|
|
|
|
bool glfmHasVirtualKeyboard(const GLFMDisplay *display) {
|
|
(void)display;
|
|
return false;
|
|
}
|
|
|
|
void glfmSetKeyboardVisible(GLFMDisplay *display, bool visible) {
|
|
(void)display;
|
|
(void)visible;
|
|
// Do nothing
|
|
}
|
|
|
|
bool glfmIsKeyboardVisible(const GLFMDisplay *display) {
|
|
(void)display;
|
|
return false;
|
|
}
|
|
|
|
GLFMProc glfmGetProcAddress(const char *functionName) {
|
|
return eglGetProcAddress(functionName);
|
|
}
|
|
|
|
bool glfmIsSensorAvailable(const GLFMDisplay *display, GLFMSensor sensor) {
|
|
(void)display;
|
|
(void)sensor;
|
|
// TODO: Sensors
|
|
return false;
|
|
}
|
|
|
|
bool glfmIsHapticFeedbackSupported(const GLFMDisplay *display) {
|
|
(void)display;
|
|
return false;
|
|
}
|
|
|
|
void glfmPerformHapticFeedback(GLFMDisplay *display, GLFMHapticFeedbackStyle style) {
|
|
(void)display;
|
|
(void)style;
|
|
// Do nothing
|
|
}
|
|
|
|
bool glfmHasClipboardText(const GLFMDisplay *display) {
|
|
(void)display;
|
|
// Currently, chrome supports navigator.userActivation, but Safari and Firefox do not.
|
|
int result = EM_ASM_INT({
|
|
var hasReadText = (navigator && navigator.clipboard && navigator.clipboard.readText);
|
|
var hasUserActivation = (navigator && navigator.userActivation) ? navigator.userActivation.isActive : true;
|
|
return (hasReadText && hasUserActivation) ? 1 : 0;
|
|
});
|
|
return result != 0;
|
|
}
|
|
|
|
void glfmRequestClipboardText(GLFMDisplay *display, GLFMClipboardTextFunc clipboardTextFunc) {
|
|
if (!clipboardTextFunc) {
|
|
return;
|
|
}
|
|
if (!glfmHasClipboardText(display)) {
|
|
clipboardTextFunc(display, NULL);
|
|
return;
|
|
}
|
|
|
|
EM_ASM({
|
|
if (!navigator.clipboard || !navigator.clipboard.readText) {
|
|
_glfm__requestClipboardTextCallback($0, $1, null);
|
|
return;
|
|
}
|
|
var promise = navigator.clipboard.readText();
|
|
if (promise === undefined) {
|
|
_glfm__requestClipboardTextCallback($0, $1, null);
|
|
return;
|
|
}
|
|
var errorCaught = false;
|
|
promise.catch(error => {
|
|
_glfm__requestClipboardTextCallback($0, $1, null);
|
|
errorCaught = true;
|
|
}).then((clipText) => {
|
|
if (clipText === undefined || clipText.length == 0) {
|
|
if (!errorCaught) {
|
|
_glfm__requestClipboardTextCallback($0, $1, null);
|
|
}
|
|
return;
|
|
}
|
|
var len = lengthBytesUTF8(clipText);
|
|
var buffer = _malloc(len + 1);
|
|
if (buffer) {
|
|
stringToUTF8(clipText, buffer, len + 1);
|
|
_glfm__requestClipboardTextCallback($0, $1, buffer);
|
|
_free(buffer);
|
|
} else {
|
|
_glfm__requestClipboardTextCallback($0, $1, null);
|
|
}
|
|
});
|
|
}, display, clipboardTextFunc);
|
|
}
|
|
|
|
bool glfmSetClipboardText(GLFMDisplay *display, const char *string) {
|
|
(void)display;
|
|
if (!string) {
|
|
return false;
|
|
}
|
|
int result = EM_ASM_INT({
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
var text = UTF8ToString($0);
|
|
if (text) {
|
|
navigator.clipboard.writeText(text);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}, string);
|
|
return result == 1;
|
|
}
|
|
|
|
// MARK: - Platform-specific functions
|
|
|
|
bool glfmIsMetalSupported(const GLFMDisplay *display) {
|
|
(void)display;
|
|
return false;
|
|
}
|
|
|
|
// MARK: - Emscripten glue
|
|
|
|
static int glfm__getDisplayWidth(GLFMDisplay *display) {
|
|
(void)display;
|
|
const double width = EM_ASM_DOUBLE_V({
|
|
var canvas = Module['canvas'];
|
|
return canvas.width;
|
|
});
|
|
return (int)(round(width));
|
|
}
|
|
|
|
static int glfm__getDisplayHeight(GLFMDisplay *display) {
|
|
(void)display;
|
|
const double height = EM_ASM_DOUBLE_V({
|
|
var canvas = Module['canvas'];
|
|
return canvas.height;
|
|
});
|
|
return (int)(round(height));
|
|
}
|
|
|
|
static void glfm__setVisibleAndFocused(GLFMDisplay *display, bool visible, bool focused) {
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
bool wasActive = platformData->isVisible && platformData->isFocused;
|
|
platformData->isVisible = visible;
|
|
platformData->isFocused = focused;
|
|
bool isActive = platformData->isVisible && platformData->isFocused;
|
|
if (wasActive != isActive) {
|
|
platformData->refreshRequested = true;
|
|
glfm__clearActiveTouches(platformData);
|
|
if (display->focusFunc) {
|
|
display->focusFunc(display, isActive);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void glfm__mainLoopFunc(void *userData) {
|
|
GLFMDisplay *display = userData;
|
|
if (display) {
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
|
|
// Check if canvas size has changed
|
|
int displayChanged = EM_ASM_INT_V({
|
|
var canvas = Module['canvas'];
|
|
var devicePixelRatio = window.devicePixelRatio || 1;
|
|
var width = canvas.clientWidth * devicePixelRatio;
|
|
var height = canvas.clientHeight * devicePixelRatio;
|
|
if (width != canvas.width || height != canvas.height) {
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
});
|
|
if (displayChanged) {
|
|
platformData->refreshRequested = true;
|
|
platformData->width = glfm__getDisplayWidth(display);
|
|
platformData->height = glfm__getDisplayHeight(display);
|
|
platformData->scale = emscripten_get_device_pixel_ratio();
|
|
if (display->surfaceResizedFunc) {
|
|
display->surfaceResizedFunc(display, platformData->width, platformData->height);
|
|
}
|
|
}
|
|
|
|
// Tick
|
|
if (platformData->refreshRequested) {
|
|
platformData->refreshRequested = false;
|
|
if (display->surfaceRefreshFunc) {
|
|
display->surfaceRefreshFunc(display);
|
|
}
|
|
}
|
|
if (display->renderFunc) {
|
|
display->renderFunc(display);
|
|
}
|
|
}
|
|
}
|
|
|
|
static EM_BOOL glfm__webGLContextCallback(int eventType, const void *reserved, void *userData) {
|
|
(void)reserved;
|
|
GLFMDisplay *display = userData;
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
platformData->refreshRequested = true;
|
|
switch (eventType) {
|
|
case EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST:
|
|
if (display->surfaceDestroyedFunc) {
|
|
display->surfaceDestroyedFunc(display);
|
|
}
|
|
return 1;
|
|
case EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED:
|
|
if (display->surfaceCreatedFunc) {
|
|
display->surfaceCreatedFunc(display, platformData->width, platformData->height);
|
|
}
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static EM_BOOL glfm__focusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData) {
|
|
(void)focusEvent;
|
|
GLFMDisplay *display = userData;
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
bool focused = (eventType == EMSCRIPTEN_EVENT_FOCUS || eventType == EMSCRIPTEN_EVENT_FOCUSIN);
|
|
glfm__setVisibleAndFocused(display, platformData->isVisible, focused);
|
|
return 1;
|
|
}
|
|
|
|
static EM_BOOL glfm__visibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *event, void *userData) {
|
|
(void)eventType;
|
|
GLFMDisplay *display = userData;
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
glfm__setVisibleAndFocused(display, !event->hidden, platformData->isFocused);
|
|
return 1;
|
|
}
|
|
|
|
static const char *glfm__beforeUnloadCallback(int eventType, const void *reserved, void *userData) {
|
|
(void)eventType;
|
|
(void)reserved;
|
|
GLFMDisplay *display = userData;
|
|
glfm__setVisibleAndFocused(display, false, false);
|
|
return NULL;
|
|
}
|
|
|
|
static EM_BOOL glfm__orientationChangeCallback(int eventType,
|
|
const EmscriptenDeviceOrientationEvent *deviceOrientationEvent,
|
|
void *userData) {
|
|
(void)eventType;
|
|
(void)deviceOrientationEvent;
|
|
GLFMDisplay *display = userData;
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
GLFMInterfaceOrientation orientation = glfmGetInterfaceOrientation(display);
|
|
if (platformData->orientation != orientation) {
|
|
platformData->orientation = orientation;
|
|
platformData->refreshRequested = true;
|
|
if (display->orientationChangedFunc) {
|
|
display->orientationChangedFunc(display, orientation);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static EM_BOOL glfm__keyCallback(int eventType, const EmscriptenKeyboardEvent *event, void *userData) {
|
|
GLFMDisplay *display = userData;
|
|
EM_BOOL handled = 0;
|
|
|
|
// Key input
|
|
if (display->keyFunc && (eventType == EMSCRIPTEN_EVENT_KEYDOWN || eventType == EMSCRIPTEN_EVENT_KEYUP)) {
|
|
// This list of code values is from https://www.w3.org/TR/uievents-code/
|
|
// (Added functions keys F13-F24)
|
|
// egrep -o '<code class="code" id="code-.*?</code>' uievents-code.html | sort | awk -F"[><]" '{print $3}' | awk 1 ORS=', '
|
|
// This array must be sorted for binary search. See GLFM_TEST_KEYBOARD_EVENT_ARRAYS.
|
|
// NOTE: event->keyCode is obsolete. Only event->key or event->code should be used.
|
|
static const char *KEYBOARD_EVENT_CODES[] = {
|
|
"AltLeft", "AltRight", "ArrowDown", "ArrowLeft", "ArrowRight", "ArrowUp",
|
|
"Backquote", "Backslash", "Backspace", "BracketLeft", "BracketRight", "BrowserBack",
|
|
"CapsLock", "Comma", "ContextMenu", "ControlLeft", "ControlRight", "Delete", "Digit0",
|
|
"Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8",
|
|
"Digit9", "End", "Enter", "Equal", "Escape", "F1", "F10", "F11", "F12", "F13", "F14",
|
|
"F15", "F16", "F17", "F18", "F19", "F2", "F20", "F21", "F22", "F23", "F24", "F3", "F4",
|
|
"F5", "F6", "F7", "F8", "F9", "Fn", "Help", "Home", "Insert", "KeyA", "KeyB", "KeyC", "KeyD",
|
|
"KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO",
|
|
"KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ",
|
|
"MediaPlayPause", "MetaLeft", "MetaRight", "Minus", "NumLock", "Numpad0", "Numpad1",
|
|
"Numpad2", "Numpad3", "Numpad4", "Numpad5", "Numpad6", "Numpad7", "Numpad8", "Numpad9",
|
|
"NumpadAdd", "NumpadDecimal", "NumpadDivide", "NumpadEnter", "NumpadEqual",
|
|
"NumpadMultiply", "NumpadSubtract", "PageDown", "PageUp", "Pause", "Period",
|
|
"Power", "PrintScreen", "Quote", "ScrollLock", "Semicolon", "ShiftLeft", "ShiftRight",
|
|
"Slash", "Space", "Tab",
|
|
};
|
|
static const size_t KEYBOARD_EVENT_CODES_LENGTH = sizeof(KEYBOARD_EVENT_CODES) / sizeof(*KEYBOARD_EVENT_CODES);
|
|
static const GLFMKeyCode GLFM_KEY_CODES[] = {
|
|
GLFMKeyCodeAltLeft, GLFMKeyCodeAltRight, GLFMKeyCodeArrowDown, GLFMKeyCodeArrowLeft, GLFMKeyCodeArrowRight, GLFMKeyCodeArrowUp,
|
|
GLFMKeyCodeBackquote, GLFMKeyCodeBackslash, GLFMKeyCodeBackspace, GLFMKeyCodeBracketLeft, GLFMKeyCodeBracketRight, GLFMKeyCodeNavigationBack,
|
|
GLFMKeyCodeCapsLock, GLFMKeyCodeComma, GLFMKeyCodeMenu, GLFMKeyCodeControlLeft, GLFMKeyCodeControlRight, GLFMKeyCodeDelete, GLFMKeyCode0,
|
|
GLFMKeyCode1, GLFMKeyCode2, GLFMKeyCode3, GLFMKeyCode4, GLFMKeyCode5, GLFMKeyCode6, GLFMKeyCode7, GLFMKeyCode8,
|
|
GLFMKeyCode9, GLFMKeyCodeEnd, GLFMKeyCodeEnter, GLFMKeyCodeEqual, GLFMKeyCodeEscape, GLFMKeyCodeF1, GLFMKeyCodeF10, GLFMKeyCodeF11, GLFMKeyCodeF12, GLFMKeyCodeF13, GLFMKeyCodeF14,
|
|
GLFMKeyCodeF15, GLFMKeyCodeF16, GLFMKeyCodeF17, GLFMKeyCodeF18, GLFMKeyCodeF19, GLFMKeyCodeF2, GLFMKeyCodeF20, GLFMKeyCodeF21, GLFMKeyCodeF22, GLFMKeyCodeF23, GLFMKeyCodeF24, GLFMKeyCodeF3, GLFMKeyCodeF4,
|
|
GLFMKeyCodeF5, GLFMKeyCodeF6, GLFMKeyCodeF7, GLFMKeyCodeF8, GLFMKeyCodeF9, GLFMKeyCodeFunction, GLFMKeyCodeInsert, GLFMKeyCodeHome, GLFMKeyCodeInsert, GLFMKeyCodeA, GLFMKeyCodeB, GLFMKeyCodeC, GLFMKeyCodeD,
|
|
GLFMKeyCodeE, GLFMKeyCodeF, GLFMKeyCodeG, GLFMKeyCodeH, GLFMKeyCodeI, GLFMKeyCodeJ, GLFMKeyCodeK, GLFMKeyCodeL, GLFMKeyCodeM, GLFMKeyCodeN, GLFMKeyCodeO,
|
|
GLFMKeyCodeP, GLFMKeyCodeQ, GLFMKeyCodeR, GLFMKeyCodeS, GLFMKeyCodeT, GLFMKeyCodeU, GLFMKeyCodeV, GLFMKeyCodeW, GLFMKeyCodeX, GLFMKeyCodeY, GLFMKeyCodeZ,
|
|
GLFMKeyCodeMediaPlayPause, GLFMKeyCodeMetaLeft, GLFMKeyCodeMetaRight, GLFMKeyCodeMinus, GLFMKeyCodeNumLock, GLFMKeyCodeNumpad0, GLFMKeyCodeNumpad1,
|
|
GLFMKeyCodeNumpad2, GLFMKeyCodeNumpad3, GLFMKeyCodeNumpad4, GLFMKeyCodeNumpad5, GLFMKeyCodeNumpad6, GLFMKeyCodeNumpad7, GLFMKeyCodeNumpad8, GLFMKeyCodeNumpad9,
|
|
GLFMKeyCodeNumpadAdd, GLFMKeyCodeNumpadDecimal, GLFMKeyCodeNumpadDivide, GLFMKeyCodeNumpadEnter, GLFMKeyCodeNumpadEqual,
|
|
GLFMKeyCodeNumpadMultiply, GLFMKeyCodeNumpadSubtract, GLFMKeyCodePageDown, GLFMKeyCodePageUp, GLFMKeyCodePause ,GLFMKeyCodePeriod,
|
|
GLFMKeyCodePower, GLFMKeyCodePrintScreen, GLFMKeyCodeQuote, GLFMKeyCodeScrollLock, GLFMKeyCodeSemicolon, GLFMKeyCodeShiftLeft, GLFMKeyCodeShiftRight,
|
|
GLFMKeyCodeSlash, GLFMKeyCodeSpace, GLFMKeyCodeTab,
|
|
};
|
|
|
|
#if GLFM_TEST_KEYBOARD_EVENT_ARRAYS
|
|
static bool KEYBOARD_EVENT_CODES_TESTED = false;
|
|
if (!KEYBOARD_EVENT_CODES_TESTED) {
|
|
KEYBOARD_EVENT_CODES_TESTED = true;
|
|
if (glfm__listIsSorted(KEYBOARD_EVENT_CODES, KEYBOARD_EVENT_CODES_LENGTH)) {
|
|
GLFM_LOG("Success: KEYBOARD_EVENT_CODES is sorted");
|
|
} else {
|
|
GLFM_LOG("Failure: KEYBOARD_EVENT_CODES is not sorted");
|
|
}
|
|
if (KEYBOARD_EVENT_CODES_LENGTH == sizeof(GLFM_KEY_CODES) / sizeof(*GLFM_KEY_CODES)) {
|
|
GLFM_LOG("Success: GLFM_KEYBOARD_EVENT_CODES is the correct length");
|
|
} else {
|
|
GLFM_LOG("Failure: GLFM_KEYBOARD_EVENT_CODES is not the correct length");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
GLFMKeyAction action;
|
|
if (eventType == EMSCRIPTEN_EVENT_KEYDOWN) {
|
|
action = event->repeat ? GLFMKeyActionRepeated : GLFMKeyActionPressed;
|
|
} else {
|
|
action = GLFMKeyActionReleased;
|
|
}
|
|
|
|
// Modifiers
|
|
// Note, Emscripten doesn't provide a way to get extended modifiers like the function key.
|
|
// (See KeyboardEvent's getModifierState() function).
|
|
// Commands like "fn-f" ("Fullscreen" on macOS) will be treated as text input.
|
|
int modifiers = 0;
|
|
if (event->shiftKey) {
|
|
modifiers |= GLFMKeyModifierShift;
|
|
}
|
|
if (event->ctrlKey) {
|
|
modifiers |= GLFMKeyModifierControl;
|
|
}
|
|
if (event->altKey) {
|
|
modifiers |= GLFMKeyModifierAlt;
|
|
}
|
|
if (event->metaKey) {
|
|
modifiers |= GLFMKeyModifierMeta;
|
|
}
|
|
|
|
int codeIndex = glfm__sortedListSearch(KEYBOARD_EVENT_CODES, KEYBOARD_EVENT_CODES_LENGTH, event->code);
|
|
GLFMKeyCode keyCode = codeIndex >= 0 ? GLFM_KEY_CODES[codeIndex] : GLFMKeyCodeUnknown;
|
|
handled = display->keyFunc(display, keyCode, action, modifiers);
|
|
}
|
|
|
|
// Character input
|
|
if (display->charFunc && eventType == EMSCRIPTEN_EVENT_KEYDOWN && !event->ctrlKey && !event->metaKey) {
|
|
// It appears the only way to detect printable character input is to check if the "key" value is
|
|
// not one of the pre-defined key values.
|
|
// This list of pre-defined key values is from https://www.w3.org/TR/uievents-key/
|
|
// (Added functions keys F13-F24 and Soft5-Soft10)
|
|
// This array must be sorted for binary search. See GLFM_TEST_KEYBOARD_EVENT_ARRAYS.
|
|
// egrep -o '<code class="key" id="key-.*?</code>' uievents-key.html | sort | awk -F"[><]" '{print $3}' | awk 1 ORS=', '
|
|
static const char *KEYBOARD_EVENT_KEYS[] = {
|
|
"AVRInput", "AVRPower", "Accept", "Again", "AllCandidates", "Alphanumeric", "Alt", "AltGraph", "AppSwitch",
|
|
"ArrowDown", "ArrowLeft", "ArrowRight", "ArrowUp", "Attn", "AudioBalanceLeft", "AudioBalanceRight",
|
|
"AudioBassBoostDown", "AudioBassBoostToggle", "AudioBassBoostUp", "AudioFaderFront", "AudioFaderRear",
|
|
"AudioSurroundModeNext", "AudioTrebleDown", "AudioTrebleUp", "AudioVolumeDown", "AudioVolumeMute",
|
|
"AudioVolumeUp", "Backspace", "BrightnessDown", "BrightnessUp", "BrowserBack", "BrowserFavorites",
|
|
"BrowserForward", "BrowserHome", "BrowserRefresh", "BrowserSearch", "BrowserStop", "Call", "Camera",
|
|
"CameraFocus", "Cancel", "CapsLock", "ChannelDown", "ChannelUp", "Clear", "Close", "ClosedCaptionToggle",
|
|
"CodeInput", "ColorF0Red", "ColorF1Green", "ColorF2Yellow", "ColorF3Blue", "ColorF4Grey", "ColorF5Brown",
|
|
"Compose", "ContextMenu", "Control", "Convert", "Copy", "CrSel", "Cut", "DVR", "Dead", "Delete", "Dimmer",
|
|
"DisplaySwap", "Eisu", "Eject", "End", "EndCall", "Enter", "EraseEof", "Escape", "ExSel", "Execute", "Exit",
|
|
"F1", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F2",
|
|
"F20", "F21", "F22", "F23", "F24", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FavoriteClear0",
|
|
"FavoriteClear1", "FavoriteClear2", "FavoriteClear3", "FavoriteRecall0", "FavoriteRecall1",
|
|
"FavoriteRecall2", "FavoriteRecall3", "FavoriteStore0", "FavoriteStore1", "FavoriteStore2",
|
|
"FavoriteStore3", "FinalMode", "Find", "Fn", "FnLock", "GoBack", "GoHome", "GroupFirst", "GroupLast",
|
|
"GroupNext", "GroupPrevious", "Guide", "GuideNextDay", "GuidePreviousDay", "HangulMode", "HanjaMode",
|
|
"Hankaku", "HeadsetHook", "Help", "Hibernate", "Hiragana", "HiraganaKatakana", "Home", "Hyper", "Info",
|
|
"Insert", "InstantReplay", "JunjaMode", "KanaMode", "KanjiMode", "Katakana", "Key11", "Key12",
|
|
"LastNumberRedial", "LaunchApplication1", "LaunchApplication2", "LaunchCalendar", "LaunchContacts",
|
|
"LaunchMail", "LaunchMediaPlayer", "LaunchMusicPlayer", "LaunchPhone", "LaunchScreenSaver",
|
|
"LaunchSpreadsheet", "LaunchWebBrowser", "LaunchWebCam", "LaunchWordProcessor", "Link", "ListProgram",
|
|
"LiveContent", "Lock", "LogOff", "MailForward", "MailReply", "MailSend", "MannerMode", "MediaApps",
|
|
"MediaAudioTrack", "MediaClose", "MediaFastForward", "MediaLast", "MediaPause", "MediaPlay",
|
|
"MediaPlayPause", "MediaRecord", "MediaRewind", "MediaSkipBackward", "MediaSkipForward",
|
|
"MediaStepBackward", "MediaStepForward", "MediaStop", "MediaTopMenu", "MediaTrackNext",
|
|
"MediaTrackPrevious", "Meta", "MicrophoneToggle", "MicrophoneVolumeDown", "MicrophoneVolumeMute",
|
|
"MicrophoneVolumeUp", "ModeChange", "NavigateIn", "NavigateNext", "NavigateOut", "NavigatePrevious", "New",
|
|
"NextCandidate", "NextFavoriteChannel", "NextUserProfile", "NonConvert", "Notification", "NumLock",
|
|
"OnDemand", "Open", "PageDown", "PageUp", "Pairing", "Paste", "Pause", "PinPDown", "PinPMove", "PinPToggle",
|
|
"PinPUp", "Play", "PlaySpeedDown", "PlaySpeedReset", "PlaySpeedUp", "Power", "PowerOff",
|
|
"PreviousCandidate", "Print", "PrintScreen", "Process", "Props", "RandomToggle", "RcLowBattery",
|
|
"RecordSpeedNext", "Redo", "RfBypass", "Romaji", "STBInput", "STBPower", "Save", "ScanChannelsToggle",
|
|
"ScreenModeNext", "ScrollLock", "Select", "Settings", "Shift", "SingleCandidate",
|
|
"Soft1", "Soft10", "Soft2", "Soft3", "Soft4", "Soft5", "Soft6", "Soft7", "Soft8", "Soft9",
|
|
"SpeechCorrectionList", "SpeechInputToggle", "SpellCheck", "SplitScreenToggle", "Standby",
|
|
"Subtitle", "Super", "Symbol", "SymbolLock", "TV", "TV3DMode", "TVAntennaCable", "TVAudioDescription",
|
|
"TVAudioDescriptionMixDown", "TVAudioDescriptionMixUp", "TVContentsMenu", "TVDataService", "TVInput",
|
|
"TVInputComponent1", "TVInputComponent2", "TVInputComposite1", "TVInputComposite2", "TVInputHDMI1",
|
|
"TVInputHDMI2", "TVInputHDMI3", "TVInputHDMI4", "TVInputVGA1", "TVMediaContext", "TVNetwork",
|
|
"TVNumberEntry", "TVPower", "TVRadioService", "TVSatellite", "TVSatelliteBS", "TVSatelliteCS",
|
|
"TVSatelliteToggle", "TVTerrestrialAnalog", "TVTerrestrialDigital", "TVTimer", "Tab", "Teletext",
|
|
"Undo", "Unidentified", "VideoModeNext", "VoiceDial", "WakeUp", "Wink", "Zenkaku", "ZenkakuHankaku",
|
|
"ZoomIn", "ZoomOut", "ZoomToggle",
|
|
};
|
|
static const size_t KEYBOARD_EVENT_KEYS_LENGTH = sizeof(KEYBOARD_EVENT_KEYS) / sizeof(*KEYBOARD_EVENT_KEYS);
|
|
|
|
#if GLFM_TEST_KEYBOARD_EVENT_ARRAYS
|
|
static bool KEYBOARD_EVENT_KEYS_TESTED = false;
|
|
if (!KEYBOARD_EVENT_KEYS_TESTED) {
|
|
KEYBOARD_EVENT_KEYS_TESTED = true;
|
|
if (glfm__listIsSorted(KEYBOARD_EVENT_KEYS, KEYBOARD_EVENT_KEYS_LENGTH)) {
|
|
GLFM_LOG("Success: KEYBOARD_EVENT_KEYS is sorted");
|
|
} else {
|
|
GLFM_LOG("Failure: KEYBOARD_EVENT_KEYS is not sorted");
|
|
}
|
|
}
|
|
#endif
|
|
if (event->key[0] != '\0') {
|
|
bool isSingleChar = (event->key[1] == '\0');
|
|
bool isPredefinedKey = false;
|
|
if (!isSingleChar) {
|
|
isPredefinedKey = glfm__sortedListSearch(KEYBOARD_EVENT_KEYS, KEYBOARD_EVENT_KEYS_LENGTH, event->key) >= 0;
|
|
}
|
|
if (isSingleChar || !isPredefinedKey) {
|
|
display->charFunc(display, event->key, 0);
|
|
handled = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
static EM_BOOL glfm__mouseCallback(int eventType, const EmscriptenMouseEvent *event, void *userData) {
|
|
GLFMDisplay *display = userData;
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
if (!display->touchFunc) {
|
|
platformData->mouseDown = false;
|
|
return 0;
|
|
}
|
|
|
|
// The mouse event handler targets EMSCRIPTEN_EVENT_TARGET_WINDOW so that dragging the mouse outside the canvas can be detected.
|
|
// If a mouse drag begins inside the canvas, the mouse release event is sent even if the mouse is released outside the canvas.
|
|
float canvasX, canvasY, canvasW, canvasH;
|
|
EM_ASM({
|
|
var rect = Module['canvas'].getBoundingClientRect();
|
|
setValue($0, rect.x, "float");
|
|
setValue($1, rect.y, "float");
|
|
setValue($2, rect.width, "float");
|
|
setValue($3, rect.height, "float");
|
|
}, &canvasX, &canvasY, &canvasW, &canvasH);
|
|
const float mouseX = (float)event->targetX - canvasX;
|
|
const float mouseY = (float)event->targetY - canvasY;
|
|
const bool mouseInside = mouseX >= 0 && mouseY >= 0 && mouseX < canvasW && mouseY < canvasH;
|
|
if (!mouseInside && eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) {
|
|
// Mouse click outside canvas
|
|
return 0;
|
|
}
|
|
if (!mouseInside && eventType != EMSCRIPTEN_EVENT_MOUSEDOWN && !platformData->mouseDown) {
|
|
// Mouse hover or click outside canvas
|
|
return 0;
|
|
}
|
|
|
|
GLFMTouchPhase touchPhase;
|
|
switch (eventType) {
|
|
case EMSCRIPTEN_EVENT_MOUSEDOWN:
|
|
touchPhase = GLFMTouchPhaseBegan;
|
|
platformData->mouseDown = true;
|
|
break;
|
|
|
|
case EMSCRIPTEN_EVENT_MOUSEMOVE:
|
|
if (platformData->mouseDown) {
|
|
touchPhase = GLFMTouchPhaseMoved;
|
|
} else {
|
|
touchPhase = GLFMTouchPhaseHover;
|
|
}
|
|
break;
|
|
|
|
case EMSCRIPTEN_EVENT_MOUSEUP:
|
|
touchPhase = GLFMTouchPhaseEnded;
|
|
platformData->mouseDown = false;
|
|
break;
|
|
|
|
default:
|
|
touchPhase = GLFMTouchPhaseCancelled;
|
|
platformData->mouseDown = false;
|
|
break;
|
|
}
|
|
bool handled = display->touchFunc(display, event->button, touchPhase,
|
|
platformData->scale * (double)mouseX,
|
|
platformData->scale * (double)mouseY);
|
|
// Always return `false` when the event is `mouseDown` for iframe support.
|
|
// Returning `true` invokes `preventDefault`, and invoking `preventDefault` on
|
|
// `mouseDown` events prevents `mouseMove` events outside the iframe.
|
|
return handled && eventType != EMSCRIPTEN_EVENT_MOUSEDOWN;
|
|
}
|
|
|
|
static EM_BOOL glfm__mouseWheelCallback(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData) {
|
|
(void)eventType;
|
|
GLFMDisplay *display = userData;
|
|
if (!display->mouseWheelFunc) {
|
|
return 0;
|
|
}
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
GLFMMouseWheelDeltaType deltaType;
|
|
switch (wheelEvent->deltaMode) {
|
|
case DOM_DELTA_PIXEL: default:
|
|
deltaType = GLFMMouseWheelDeltaPixel;
|
|
break;
|
|
case DOM_DELTA_LINE:
|
|
deltaType = GLFMMouseWheelDeltaLine;
|
|
break;
|
|
case DOM_DELTA_PAGE:
|
|
deltaType = GLFMMouseWheelDeltaPage;
|
|
break;
|
|
}
|
|
return display->mouseWheelFunc(display,
|
|
platformData->scale * (double)wheelEvent->mouse.targetX,
|
|
platformData->scale * (double)wheelEvent->mouse.targetY,
|
|
deltaType, wheelEvent->deltaX, wheelEvent->deltaY, wheelEvent->deltaZ);
|
|
}
|
|
|
|
static int glfm__getTouchIdentifier(GLFMPlatformData *platformData, const EmscriptenTouchPoint *touch) {
|
|
int firstNullIndex = -1;
|
|
int index = -1;
|
|
for (int i = 0; i < GLFM_MAX_ACTIVE_TOUCHES; i++) {
|
|
if (platformData->activeTouches[i].identifier == touch->identifier &&
|
|
platformData->activeTouches[i].active) {
|
|
index = i;
|
|
break;
|
|
}
|
|
if (firstNullIndex == -1 && !platformData->activeTouches[i].active) {
|
|
firstNullIndex = i;
|
|
}
|
|
}
|
|
if (index == -1) {
|
|
if (firstNullIndex == -1) {
|
|
// Shouldn't happen
|
|
return -1;
|
|
}
|
|
index = firstNullIndex;
|
|
platformData->activeTouches[index].identifier = touch->identifier;
|
|
platformData->activeTouches[index].active = true;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
static EM_BOOL glfm__touchCallback(int eventType, const EmscriptenTouchEvent *event, void *userData) {
|
|
GLFMDisplay *display = userData;
|
|
if (!display->touchFunc) {
|
|
return 0;
|
|
}
|
|
GLFMPlatformData *platformData = display->platformData;
|
|
GLFMTouchPhase touchPhase;
|
|
switch (eventType) {
|
|
case EMSCRIPTEN_EVENT_TOUCHSTART:
|
|
touchPhase = GLFMTouchPhaseBegan;
|
|
break;
|
|
|
|
case EMSCRIPTEN_EVENT_TOUCHMOVE:
|
|
touchPhase = GLFMTouchPhaseMoved;
|
|
break;
|
|
|
|
case EMSCRIPTEN_EVENT_TOUCHEND:
|
|
touchPhase = GLFMTouchPhaseEnded;
|
|
break;
|
|
|
|
case EMSCRIPTEN_EVENT_TOUCHCANCEL:
|
|
default:
|
|
touchPhase = GLFMTouchPhaseCancelled;
|
|
break;
|
|
}
|
|
|
|
int handled = 0;
|
|
for (int i = 0; i < event->numTouches; i++) {
|
|
const EmscriptenTouchPoint *touch = &event->touches[i];
|
|
if (touch->isChanged) {
|
|
int identifier = glfm__getTouchIdentifier(platformData, touch);
|
|
if (identifier >= 0) {
|
|
if ((platformData->multitouchEnabled || identifier == 0)) {
|
|
handled |= display->touchFunc(display, identifier, touchPhase,
|
|
platformData->scale * (double)touch->targetX,
|
|
platformData->scale * (double)touch->targetY);
|
|
}
|
|
|
|
if (touchPhase == GLFMTouchPhaseEnded || touchPhase == GLFMTouchPhaseCancelled) {
|
|
platformData->activeTouches[identifier].active = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
// MARK: - main
|
|
|
|
int main(void) {
|
|
GLFMDisplay *glfmDisplay = calloc(1, sizeof(GLFMDisplay));
|
|
GLFMPlatformData *platformData = calloc(1, sizeof(GLFMPlatformData));
|
|
glfmDisplay->platformData = platformData;
|
|
glfmDisplay->supportedOrientations = GLFMInterfaceOrientationAll;
|
|
platformData->orientation = glfmGetInterfaceOrientation(glfmDisplay);
|
|
|
|
// Main entry
|
|
glfmMain(glfmDisplay);
|
|
|
|
// Init resizable canvas
|
|
EM_ASM({
|
|
var canvas = Module['canvas'];
|
|
var devicePixelRatio = window.devicePixelRatio || 1;
|
|
canvas.width = canvas.clientWidth * devicePixelRatio;
|
|
canvas.height = canvas.clientHeight * devicePixelRatio;
|
|
});
|
|
platformData->width = glfm__getDisplayWidth(glfmDisplay);
|
|
platformData->height = glfm__getDisplayHeight(glfmDisplay);
|
|
platformData->scale = emscripten_get_device_pixel_ratio();
|
|
|
|
// Create WebGL context
|
|
EmscriptenWebGLContextAttributes attribs;
|
|
emscripten_webgl_init_context_attributes(&attribs);
|
|
attribs.alpha = glfmDisplay->colorFormat == GLFMColorFormatRGBA8888;
|
|
attribs.depth = glfmDisplay->depthFormat != GLFMDepthFormatNone;
|
|
attribs.stencil = glfmDisplay->stencilFormat != GLFMStencilFormatNone;
|
|
attribs.antialias = glfmDisplay->multisample != GLFMMultisampleNone;
|
|
attribs.premultipliedAlpha = 1;
|
|
attribs.preserveDrawingBuffer = 0;
|
|
attribs.powerPreference = EM_WEBGL_POWER_PREFERENCE_HIGH_PERFORMANCE;
|
|
attribs.failIfMajorPerformanceCaveat = 0;
|
|
attribs.enableExtensionsByDefault = 1;
|
|
|
|
const char *webGLTarget = "#canvas";
|
|
int contextHandle = 0;
|
|
if (glfmDisplay->preferredAPI >= GLFMRenderingAPIOpenGLES3) {
|
|
// OpenGL ES 3.0 / WebGL 2.0
|
|
attribs.majorVersion = 2;
|
|
attribs.minorVersion = 0;
|
|
contextHandle = emscripten_webgl_create_context(webGLTarget, &attribs);
|
|
if (contextHandle) {
|
|
platformData->renderingAPI = GLFMRenderingAPIOpenGLES3;
|
|
}
|
|
}
|
|
if (!contextHandle) {
|
|
// OpenGL ES 2.0 / WebGL 1.0
|
|
attribs.majorVersion = 1;
|
|
attribs.minorVersion = 0;
|
|
contextHandle = emscripten_webgl_create_context(webGLTarget, &attribs);
|
|
if (contextHandle) {
|
|
platformData->renderingAPI = GLFMRenderingAPIOpenGLES2;
|
|
}
|
|
}
|
|
if (!contextHandle) {
|
|
GLFM_LOG("Couldn't create GL context");
|
|
glfm__reportSurfaceError(glfmDisplay, "Couldn't create GL context");
|
|
return 0;
|
|
}
|
|
|
|
emscripten_webgl_make_context_current(contextHandle);
|
|
|
|
if (glfmDisplay->surfaceCreatedFunc) {
|
|
glfmDisplay->surfaceCreatedFunc(glfmDisplay, platformData->width, platformData->height);
|
|
}
|
|
glfm__setVisibleAndFocused(glfmDisplay, true, true);
|
|
|
|
// Setup callbacks
|
|
emscripten_set_main_loop_arg(glfm__mainLoopFunc, glfmDisplay, 0, 0);
|
|
emscripten_set_touchstart_callback(webGLTarget, glfmDisplay, 1, glfm__touchCallback);
|
|
emscripten_set_touchend_callback(webGLTarget, glfmDisplay, 1, glfm__touchCallback);
|
|
emscripten_set_touchmove_callback(webGLTarget, glfmDisplay, 1, glfm__touchCallback);
|
|
emscripten_set_touchcancel_callback(webGLTarget, glfmDisplay, 1, glfm__touchCallback);
|
|
emscripten_set_mousedown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, glfmDisplay, 1, glfm__mouseCallback);
|
|
emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, glfmDisplay, 1, glfm__mouseCallback);
|
|
emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, glfmDisplay, 1, glfm__mouseCallback);
|
|
emscripten_set_wheel_callback(webGLTarget, glfmDisplay, 1, glfm__mouseWheelCallback);
|
|
//emscripten_set_click_callback(webGLTarget, glfmDisplay, 1, glfm__mouseCallback);
|
|
//emscripten_set_dblclick_callback(webGLTarget, glfmDisplay, 1, glfm__mouseCallback);
|
|
emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, glfmDisplay, 1, glfm__keyCallback);
|
|
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, glfmDisplay, 1, glfm__keyCallback);
|
|
emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, glfmDisplay, 1, glfm__keyCallback);
|
|
emscripten_set_webglcontextlost_callback(webGLTarget, glfmDisplay, 1, glfm__webGLContextCallback);
|
|
emscripten_set_webglcontextrestored_callback(webGLTarget, glfmDisplay, 1, glfm__webGLContextCallback);
|
|
emscripten_set_visibilitychange_callback(glfmDisplay, 1, glfm__visibilityChangeCallback);
|
|
emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, glfmDisplay, 1, glfm__focusCallback);
|
|
emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, glfmDisplay, 1, glfm__focusCallback);
|
|
emscripten_set_beforeunload_callback(glfmDisplay, glfm__beforeUnloadCallback);
|
|
emscripten_set_deviceorientation_callback(glfmDisplay, 1, glfm__orientationChangeCallback);
|
|
return 0;
|
|
}
|
|
|
|
#endif // __EMSCRIPTEN__
|