mirror of
https://github.com/whoahq/whoa.git
synced 2026-03-18 13:41:06 +03:00
Adds the platform layer for building whoa as a WebAssembly application: Working: - CMake configuration for WHOA_SYSTEM_WEB with pthreads and ASYNCIFY - Web entry point and HTML shell template - Event loop adapted for emscripten_set_main_loop callback model - WebSocket-based networking (WowConnection over JS WebSocket API) - Sound system stubs (audio not yet implemented) - FetchFS for async file loading from web server - Freetype fixes for WASM compatibility (type mismatches) - Input handling for web canvas Missing (in separate commits): - WebGPU graphics backend (CGxDeviceWebGPU) - WGSL shaders - API selection in Device.cpp
289 lines
11 KiB
C++
289 lines
11 KiB
C++
#include "event/Input.hpp"
|
|
#include <emscripten/html5.h>
|
|
#include <cstring>
|
|
|
|
// Key code translation from DOM key codes to engine KEY values
|
|
static KEY TranslateKeyCode(const char* code) {
|
|
// Letters
|
|
if (strcmp(code, "KeyA") == 0) return KEY_A;
|
|
if (strcmp(code, "KeyB") == 0) return KEY_B;
|
|
if (strcmp(code, "KeyC") == 0) return KEY_C;
|
|
if (strcmp(code, "KeyD") == 0) return KEY_D;
|
|
if (strcmp(code, "KeyE") == 0) return KEY_E;
|
|
if (strcmp(code, "KeyF") == 0) return KEY_F;
|
|
if (strcmp(code, "KeyG") == 0) return KEY_G;
|
|
if (strcmp(code, "KeyH") == 0) return KEY_H;
|
|
if (strcmp(code, "KeyI") == 0) return KEY_I;
|
|
if (strcmp(code, "KeyJ") == 0) return KEY_J;
|
|
if (strcmp(code, "KeyK") == 0) return KEY_K;
|
|
if (strcmp(code, "KeyL") == 0) return KEY_L;
|
|
if (strcmp(code, "KeyM") == 0) return KEY_M;
|
|
if (strcmp(code, "KeyN") == 0) return KEY_N;
|
|
if (strcmp(code, "KeyO") == 0) return KEY_O;
|
|
if (strcmp(code, "KeyP") == 0) return KEY_P;
|
|
if (strcmp(code, "KeyQ") == 0) return KEY_Q;
|
|
if (strcmp(code, "KeyR") == 0) return KEY_R;
|
|
if (strcmp(code, "KeyS") == 0) return KEY_S;
|
|
if (strcmp(code, "KeyT") == 0) return KEY_T;
|
|
if (strcmp(code, "KeyU") == 0) return KEY_U;
|
|
if (strcmp(code, "KeyV") == 0) return KEY_V;
|
|
if (strcmp(code, "KeyW") == 0) return KEY_W;
|
|
if (strcmp(code, "KeyX") == 0) return KEY_X;
|
|
if (strcmp(code, "KeyY") == 0) return KEY_Y;
|
|
if (strcmp(code, "KeyZ") == 0) return KEY_Z;
|
|
|
|
// Numbers
|
|
if (strcmp(code, "Digit0") == 0) return KEY_0;
|
|
if (strcmp(code, "Digit1") == 0) return KEY_1;
|
|
if (strcmp(code, "Digit2") == 0) return KEY_2;
|
|
if (strcmp(code, "Digit3") == 0) return KEY_3;
|
|
if (strcmp(code, "Digit4") == 0) return KEY_4;
|
|
if (strcmp(code, "Digit5") == 0) return KEY_5;
|
|
if (strcmp(code, "Digit6") == 0) return KEY_6;
|
|
if (strcmp(code, "Digit7") == 0) return KEY_7;
|
|
if (strcmp(code, "Digit8") == 0) return KEY_8;
|
|
if (strcmp(code, "Digit9") == 0) return KEY_9;
|
|
|
|
// Function keys
|
|
if (strcmp(code, "F1") == 0) return KEY_F1;
|
|
if (strcmp(code, "F2") == 0) return KEY_F2;
|
|
if (strcmp(code, "F3") == 0) return KEY_F3;
|
|
if (strcmp(code, "F4") == 0) return KEY_F4;
|
|
if (strcmp(code, "F5") == 0) return KEY_F5;
|
|
if (strcmp(code, "F6") == 0) return KEY_F6;
|
|
if (strcmp(code, "F7") == 0) return KEY_F7;
|
|
if (strcmp(code, "F8") == 0) return KEY_F8;
|
|
if (strcmp(code, "F9") == 0) return KEY_F9;
|
|
if (strcmp(code, "F10") == 0) return KEY_F10;
|
|
if (strcmp(code, "F11") == 0) return KEY_F11;
|
|
if (strcmp(code, "F12") == 0) return KEY_F12;
|
|
|
|
// Special keys
|
|
if (strcmp(code, "Escape") == 0) return KEY_ESCAPE;
|
|
if (strcmp(code, "Enter") == 0) return KEY_ENTER;
|
|
if (strcmp(code, "NumpadEnter") == 0) return KEY_ENTER;
|
|
if (strcmp(code, "Backspace") == 0) return KEY_BACKSPACE;
|
|
if (strcmp(code, "Tab") == 0) return KEY_TAB;
|
|
if (strcmp(code, "Space") == 0) return KEY_SPACE;
|
|
|
|
// Arrow keys
|
|
if (strcmp(code, "ArrowLeft") == 0) return KEY_LEFT;
|
|
if (strcmp(code, "ArrowUp") == 0) return KEY_UP;
|
|
if (strcmp(code, "ArrowRight") == 0) return KEY_RIGHT;
|
|
if (strcmp(code, "ArrowDown") == 0) return KEY_DOWN;
|
|
|
|
// Navigation keys
|
|
if (strcmp(code, "Insert") == 0) return KEY_INSERT;
|
|
if (strcmp(code, "Delete") == 0) return KEY_DELETE;
|
|
if (strcmp(code, "Home") == 0) return KEY_HOME;
|
|
if (strcmp(code, "End") == 0) return KEY_END;
|
|
if (strcmp(code, "PageUp") == 0) return KEY_PAGEUP;
|
|
if (strcmp(code, "PageDown") == 0) return KEY_PAGEDOWN;
|
|
|
|
// Modifier keys
|
|
if (strcmp(code, "ShiftLeft") == 0) return KEY_LSHIFT;
|
|
if (strcmp(code, "ShiftRight") == 0) return KEY_RSHIFT;
|
|
if (strcmp(code, "ControlLeft") == 0) return KEY_LCONTROL;
|
|
if (strcmp(code, "ControlRight") == 0) return KEY_RCONTROL;
|
|
if (strcmp(code, "AltLeft") == 0) return KEY_LALT;
|
|
if (strcmp(code, "AltRight") == 0) return KEY_RALT;
|
|
|
|
// Lock keys
|
|
if (strcmp(code, "CapsLock") == 0) return KEY_CAPSLOCK;
|
|
if (strcmp(code, "NumLock") == 0) return KEY_NUMLOCK;
|
|
if (strcmp(code, "ScrollLock") == 0) return KEY_SCROLLLOCK;
|
|
|
|
// Numpad
|
|
if (strcmp(code, "Numpad0") == 0) return KEY_NUMPAD0;
|
|
if (strcmp(code, "Numpad1") == 0) return KEY_NUMPAD1;
|
|
if (strcmp(code, "Numpad2") == 0) return KEY_NUMPAD2;
|
|
if (strcmp(code, "Numpad3") == 0) return KEY_NUMPAD3;
|
|
if (strcmp(code, "Numpad4") == 0) return KEY_NUMPAD4;
|
|
if (strcmp(code, "Numpad5") == 0) return KEY_NUMPAD5;
|
|
if (strcmp(code, "Numpad6") == 0) return KEY_NUMPAD6;
|
|
if (strcmp(code, "Numpad7") == 0) return KEY_NUMPAD7;
|
|
if (strcmp(code, "Numpad8") == 0) return KEY_NUMPAD8;
|
|
if (strcmp(code, "Numpad9") == 0) return KEY_NUMPAD9;
|
|
if (strcmp(code, "NumpadAdd") == 0) return KEY_NUMPAD_PLUS;
|
|
if (strcmp(code, "NumpadSubtract") == 0) return KEY_NUMPAD_MINUS;
|
|
if (strcmp(code, "NumpadMultiply") == 0) return KEY_NUMPAD_MULTIPLY;
|
|
if (strcmp(code, "NumpadDivide") == 0) return KEY_NUMPAD_DIVIDE;
|
|
if (strcmp(code, "NumpadDecimal") == 0) return KEY_NUMPAD_DECIMAL;
|
|
|
|
// Punctuation
|
|
if (strcmp(code, "Minus") == 0) return KEY_MINUS;
|
|
if (strcmp(code, "Equal") == 0) return KEY_PLUS;
|
|
if (strcmp(code, "BracketLeft") == 0) return KEY_BRACKET_OPEN;
|
|
if (strcmp(code, "BracketRight") == 0) return KEY_BRACKET_CLOSE;
|
|
if (strcmp(code, "Semicolon") == 0) return KEY_SEMICOLON;
|
|
if (strcmp(code, "Quote") == 0) return KEY_APOSTROPHE;
|
|
if (strcmp(code, "Backquote") == 0) return KEY_TILDE;
|
|
if (strcmp(code, "Backslash") == 0) return KEY_BACKSLASH;
|
|
if (strcmp(code, "Comma") == 0) return KEY_COMMA;
|
|
if (strcmp(code, "Period") == 0) return KEY_PERIOD;
|
|
if (strcmp(code, "Slash") == 0) return KEY_SLASH;
|
|
|
|
return KEY_NONE;
|
|
}
|
|
|
|
static uint32_t GetMetaKeyState(const EmscriptenKeyboardEvent* e) {
|
|
uint32_t state = 0;
|
|
if (e->shiftKey) {
|
|
state |= (1 << KEY_LSHIFT);
|
|
}
|
|
if (e->ctrlKey) {
|
|
state |= (1 << KEY_LCONTROL);
|
|
}
|
|
if (e->altKey) {
|
|
state |= (1 << KEY_LALT);
|
|
}
|
|
return state;
|
|
}
|
|
|
|
static MOUSEBUTTON TranslateMouseButton(int button) {
|
|
switch (button) {
|
|
case 0: return MOUSE_BUTTON_LEFT;
|
|
case 1: return MOUSE_BUTTON_MIDDLE;
|
|
case 2: return MOUSE_BUTTON_RIGHT;
|
|
case 3: return MOUSE_BUTTON_XBUTTON1;
|
|
case 4: return MOUSE_BUTTON_XBUTTON2;
|
|
default: return MOUSE_BUTTON_NONE;
|
|
}
|
|
}
|
|
|
|
// Keyboard event callback
|
|
static EM_BOOL OnKeyDown(int eventType, const EmscriptenKeyboardEvent* e, void* userData) {
|
|
KEY key = TranslateKeyCode(e->code);
|
|
if (key != KEY_NONE) {
|
|
uint32_t metaState = GetMetaKeyState(e);
|
|
OsQueuePut(OS_INPUT_KEY_DOWN, static_cast<int32_t>(key), metaState, e->repeat ? 1 : 0, 0);
|
|
}
|
|
|
|
// Also send char event for printable characters
|
|
if (e->key[0] != '\0' && e->key[1] == '\0') {
|
|
uint32_t metaState = GetMetaKeyState(e);
|
|
OsQueuePut(OS_INPUT_CHAR, static_cast<int32_t>(e->key[0]), metaState, e->repeat ? 1 : 0, 0);
|
|
}
|
|
|
|
return EM_TRUE;
|
|
}
|
|
|
|
static EM_BOOL OnKeyUp(int eventType, const EmscriptenKeyboardEvent* e, void* userData) {
|
|
KEY key = TranslateKeyCode(e->code);
|
|
if (key != KEY_NONE) {
|
|
uint32_t metaState = GetMetaKeyState(e);
|
|
OsQueuePut(OS_INPUT_KEY_UP, static_cast<int32_t>(key), metaState, 0, 0);
|
|
}
|
|
return EM_TRUE;
|
|
}
|
|
|
|
// Mouse event callbacks
|
|
static EM_BOOL OnMouseDown(int eventType, const EmscriptenMouseEvent* e, void* userData) {
|
|
MOUSEBUTTON button = TranslateMouseButton(e->button);
|
|
int32_t x = e->targetX;
|
|
int32_t y = e->targetY;
|
|
OsQueuePut(OS_INPUT_MOUSE_DOWN, static_cast<int32_t>(button), x, y, 0);
|
|
return EM_TRUE;
|
|
}
|
|
|
|
static EM_BOOL OnMouseUp(int eventType, const EmscriptenMouseEvent* e, void* userData) {
|
|
MOUSEBUTTON button = TranslateMouseButton(e->button);
|
|
int32_t x = e->targetX;
|
|
int32_t y = e->targetY;
|
|
OsQueuePut(OS_INPUT_MOUSE_UP, static_cast<int32_t>(button), x, y, 0);
|
|
return EM_TRUE;
|
|
}
|
|
|
|
static EM_BOOL OnMouseMove(int eventType, const EmscriptenMouseEvent* e, void* userData) {
|
|
if (Input::s_osMouseMode == OS_MOUSE_MODE_RELATIVE) {
|
|
// Relative mouse movement (pointer locked)
|
|
int32_t dx = e->movementX;
|
|
int32_t dy = e->movementY;
|
|
OsQueuePut(OS_INPUT_MOUSE_MOVE_RELATIVE, dx, dy, 0, 0);
|
|
} else {
|
|
// Absolute mouse position
|
|
int32_t x = e->targetX;
|
|
int32_t y = e->targetY;
|
|
OsQueuePut(OS_INPUT_MOUSE_MOVE, 0, x, y, 0);
|
|
}
|
|
return EM_TRUE;
|
|
}
|
|
|
|
static EM_BOOL OnWheel(int eventType, const EmscriptenWheelEvent* e, void* userData) {
|
|
// Normalize wheel delta
|
|
int32_t delta = static_cast<int32_t>(-e->deltaY);
|
|
if (e->deltaMode == DOM_DELTA_LINE) {
|
|
delta *= 40; // Approximate line height
|
|
} else if (e->deltaMode == DOM_DELTA_PAGE) {
|
|
delta *= 400; // Approximate page height
|
|
}
|
|
OsQueuePut(OS_INPUT_MOUSE_WHEEL, delta, 0, 0, 0);
|
|
return EM_TRUE;
|
|
}
|
|
|
|
static EM_BOOL OnFocus(int eventType, const EmscriptenFocusEvent* e, void* userData) {
|
|
OsQueuePut(OS_INPUT_FOCUS, 1, 0, 0, 0);
|
|
return EM_TRUE;
|
|
}
|
|
|
|
static EM_BOOL OnBlur(int eventType, const EmscriptenFocusEvent* e, void* userData) {
|
|
OsQueuePut(OS_INPUT_FOCUS, 0, 0, 0, 0);
|
|
return EM_TRUE;
|
|
}
|
|
|
|
static EM_BOOL OnPointerLockChange(int eventType, const EmscriptenPointerlockChangeEvent* e, void* userData) {
|
|
if (!e->isActive && Input::s_osMouseMode == OS_MOUSE_MODE_RELATIVE) {
|
|
// Pointer lock was released externally
|
|
Input::s_osMouseMode = OS_MOUSE_MODE_NORMAL;
|
|
}
|
|
return EM_TRUE;
|
|
}
|
|
|
|
void OsInputInitialize() {
|
|
// Register keyboard events on document
|
|
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, OnKeyDown);
|
|
emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, OnKeyUp);
|
|
|
|
// Register mouse events on canvas
|
|
emscripten_set_mousedown_callback("#canvas", nullptr, EM_TRUE, OnMouseDown);
|
|
emscripten_set_mouseup_callback("#canvas", nullptr, EM_TRUE, OnMouseUp);
|
|
emscripten_set_mousemove_callback("#canvas", nullptr, EM_TRUE, OnMouseMove);
|
|
emscripten_set_wheel_callback("#canvas", nullptr, EM_TRUE, OnWheel);
|
|
|
|
// Register focus events
|
|
emscripten_set_focus_callback("#canvas", nullptr, EM_TRUE, OnFocus);
|
|
emscripten_set_blur_callback("#canvas", nullptr, EM_TRUE, OnBlur);
|
|
|
|
// Register pointer lock change event
|
|
emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, OnPointerLockChange);
|
|
}
|
|
|
|
int32_t OsInputGet(OSINPUT* id, int32_t* param0, int32_t* param1, int32_t* param2, int32_t* param3) {
|
|
return OsQueueGet(id, param0, param1, param2, param3);
|
|
}
|
|
|
|
void OsInputSetMouseMode(OS_MOUSE_MODE mode) {
|
|
if (mode == Input::s_osMouseMode) {
|
|
return;
|
|
}
|
|
|
|
Input::s_osMouseMode = mode;
|
|
|
|
if (mode == OS_MOUSE_MODE_RELATIVE) {
|
|
// Request pointer lock
|
|
emscripten_request_pointerlock("#canvas", EM_TRUE);
|
|
} else {
|
|
// Exit pointer lock
|
|
emscripten_exit_pointerlock();
|
|
}
|
|
}
|
|
|
|
int32_t OsWindowProc(void* window, uint32_t message, uintptr_t wparam, intptr_t lparam) {
|
|
// Not used on web - events come through Emscripten callbacks
|
|
return 0;
|
|
}
|
|
|
|
bool OsInputIsUsingCocoaEventLoop() {
|
|
return false;
|
|
}
|