diff --git a/src/glfw/main.c b/src/glfw/main.c index c5c8cdf9..1e34cfc3 100644 --- a/src/glfw/main.c +++ b/src/glfw/main.c @@ -1,10 +1,12 @@ #include "data_win.h" #include "glfw/gl_legacy_renderer.h" +#include "runner_mouse.h" #include "vm.h" #include #include #include +#include #include #include #include @@ -486,6 +488,77 @@ void saveInputRecording() { } } +// ===[ MOUSE INPUT ]=== + +static int32_t glfwMouseButtonToGml(int glfwButton) { + switch (glfwButton) { + case GLFW_MOUSE_BUTTON_LEFT: return MB_LEFT; + case GLFW_MOUSE_BUTTON_RIGHT: return MB_RIGHT; + case GLFW_MOUSE_BUTTON_MIDDLE: return MB_MIDDLE; + default: return INT32_MIN; // Unknown + } +} + +static void cursorPositionCallback(GLFWwindow* window, double xpos, double ypos) { + Runner* runner = (Runner*) glfwGetWindowUserPointer(window); + int winWidth, winHeight; + glfwGetWindowSize(window, &winWidth, &winHeight); + + if (winWidth <= 0 || winHeight <= 0 || runner->currentRoom == nullptr) return; + + // Map window pixel -> FBO pixel. The FBO is blit-stretched to fill the window. + int32_t gameW = runner->renderGameW > 0 ? runner->renderGameW : runner->currentRoom->width; + int32_t gameH = runner->renderGameH > 0 ? runner->renderGameH : runner->currentRoom->height; + double fboX = (xpos / winWidth) * gameW; + double fboY = (ypos / winHeight) * gameH; + + // Find the view whose port rect contains the cursor; fall back to the first enabled view, then to a default (0,0,roomW,roomH) mapping when no views are enabled. + // Native runner rule (GR_Window_Views_Convert): count enabled views that render directly to screen (view_surface_id == -1). + // If any exist, map via the one whose port contains the cursor (or fall through to the last one tried). + // If ALL enabled views have a surface bound, use room-space mapping scaled by the window, since the game is manually compositing those surfaces onto the window. + bool viewsEnabled = (runner->currentRoom->flags & 1) != 0; + int32_t screenViewCount = 0; + RoomView* pickedView = nullptr; + RoomView* lastScreenView = nullptr; + if (viewsEnabled) { + repeat(8, vi) { + RoomView* v = &runner->currentRoom->views[vi]; + if (!v->enabled || runner->viewSurfaceIds[vi] != -1) continue; + screenViewCount++; + lastScreenView = v; + if (fboX >= v->portX && fboX < v->portX + v->portWidth && fboY >= v->portY && fboY < v->portY + v->portHeight) { + pickedView = v; + break; + } + } + if (pickedView == nullptr) pickedView = lastScreenView; + } + + if (pickedView != nullptr && pickedView->portWidth > 0 && pickedView->portHeight > 0) { + runner->mouse->mouseX = pickedView->viewX + (fboX - pickedView->portX) * ((double) pickedView->viewWidth / pickedView->portWidth); + runner->mouse->mouseY = pickedView->viewY + (fboY - pickedView->portY) * ((double) pickedView->viewHeight / pickedView->portHeight); + } else if (viewsEnabled && screenViewCount == 0) { + // No enabled view renders to screen (all redirect to surfaces). Mouse is in room space. + int32_t roomW = runner->currentRoom->width; + int32_t roomH = runner->currentRoom->height; + runner->mouse->mouseX = (xpos / winWidth) * roomW; + runner->mouse->mouseY = (ypos / winHeight) * roomH; + } else { + runner->mouse->mouseX = fboX; + runner->mouse->mouseY = fboY; + } +} + +static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { + (void)mods; + Runner* runner = (Runner*) glfwGetWindowUserPointer(window); + int32_t gmlButton = glfwMouseButtonToGml(button); + if (0 > gmlButton) return; + if (action == GLFW_PRESS) RunnerMouse_onButtonDown(runner->mouse, gmlButton); + else if (action == GLFW_RELEASE) RunnerMouse_onButtonUp(runner->mouse, gmlButton); +} + + #ifndef _WIN32 typedef struct { int key; struct sigaction value; } PreviousSignalActionEntry; static PreviousSignalActionEntry* previousSignalActions = nullptr; @@ -724,6 +797,10 @@ int main(int argc, char* argv[]) { glfwSetKeyCallback(window, keyCallback); glfwSetCharCallback(window, characterCallback); + // Set up mouse input + glfwSetCursorPosCallback(window, cursorPositionCallback); + glfwSetMouseButtonCallback(window, mouseButtonCallback); + #ifndef _WIN32 struct sigaction sa = { .sa_handler = onCrashSignal }; sigemptyset(&sa.sa_mask); @@ -744,6 +821,7 @@ int main(int argc, char* argv[]) { while (!glfwWindowShouldClose(window) && !runner->shouldExit) { // Clear last frame's pressed/released state, then poll new input events RunnerKeyboard_beginFrame(runner->keyboard); + RunnerMouse_beginFrame(runner->mouse); glfwPollEvents(); // Process input recording/playback (must happen after glfwPollEvents, before Runner_step) @@ -917,6 +995,9 @@ int main(int argc, char* argv[]) { } } + runner->renderGameW = gameW; + runner->renderGameH = gameH; + renderer->vtable->beginFrame(renderer, gameW, gameH, fbWidth, fbHeight); // Clear FBO with room background color diff --git a/src/runner.c b/src/runner.c index 30e9cd1c..632e9462 100644 --- a/src/runner.c +++ b/src/runner.c @@ -2,6 +2,7 @@ #include "data_win.h" #include "instance.h" #include "renderer.h" +#include "runner_mouse.h" #include "vm.h" #include "utils.h" #include "json_writer.h" @@ -1319,6 +1320,10 @@ Runner* Runner_create(DataWin* dataWin, VMContext* vm, Renderer* renderer, FileS runner->frameCount = 0; runner->osType = OS_WINDOWS; runner->keyboard = RunnerKeyboard_create(); + runner->mouse = RunnerMouse_create(); + repeat(8, i) { + runner->viewSurfaceIds[i] = -1; + } Runner_reset(runner); @@ -2406,6 +2411,7 @@ void Runner_free(Runner* runner) { runner->instanceSnapshots = nullptr; RunnerKeyboard_free(runner->keyboard); + RunnerMouse_free(runner->mouse); Instance_free(runner->globalScopeInstance); free(runner); } diff --git a/src/runner.h b/src/runner.h index 27c61746..2267ca79 100644 --- a/src/runner.h +++ b/src/runner.h @@ -8,6 +8,7 @@ #include "instance.h" #include "renderer.h" #include "runner_keyboard.h" +#include "runner_mouse.h" #include "spatial_grid.h" #include "vm.h" @@ -298,6 +299,7 @@ typedef struct Runner { int frameCount; uint32_t nextInstanceId; RunnerKeyboardState* keyboard; + RunnerMouseState* mouse; RuntimeView views[MAX_VIEWS]; RuntimeBackground backgrounds[8]; uint32_t backgroundColor; // runtime-mutable (BGR format) @@ -311,6 +313,9 @@ typedef struct Runner { uint32_t nextLayerId; // counter for IDs of layers/elements created at runtime SavedRoomState* savedRoomStates; // array of size dataWin->room.count, for persistent room support int32_t viewCurrent; // index of the view currently being drawn (for view_current) + int32_t renderGameW; // FBO width used by the last frame (= max port bound), 0 if not yet rendered + int32_t renderGameH; // FBO height used by the last frame (= max port bound), 0 if not yet rendered + int32_t viewSurfaceIds[8]; // view_surface_id per view, -1 = default (render to screen), else surface index struct { char* key; int value; }* disabledObjects; // stb_ds string hashmap, nullptr = no filtering struct { int key; Instance* value; }* instancesToId; bool forceDrawDepth; diff --git a/src/runner_mouse.c b/src/runner_mouse.c new file mode 100644 index 00000000..d6d5c004 --- /dev/null +++ b/src/runner_mouse.c @@ -0,0 +1,73 @@ +#include "runner_mouse.h" +#include "utils.h" +#include +#include +#include +#include +#include + +static bool isValidButtonVirtual(int_fast8_t button) { + return button >= -1 && GML_MOUSE_BUTTONS > button; +} + +static bool isValidButton(int_fast8_t button) { + return button >= 1 && GML_MOUSE_BUTTONS > button; +} + +RunnerMouseState* RunnerMouse_create(void) { + RunnerMouseState* m = safeCalloc(1, sizeof(RunnerMouseState)); + return m; +} + +void RunnerMouse_free(RunnerMouseState* m) { + free(m); +} + +void RunnerMouse_beginFrame(RunnerMouseState* m) { + memset(m->buttonPressed, 0, sizeof(m->buttonPressed)); +} + +void RunnerMouse_onButtonDown(RunnerMouseState* m, int32_t button) { + if (!isValidButton(button)) return; + m->buttonDown[button-1] = true; + m->buttonPressed[button-1] = true; +} + +void RunnerMouse_onButtonUp(RunnerMouseState* m, int32_t button) { + if (!isValidButton(button)) return; + m->buttonDown[button-1] = false; +} + +bool RunnerMouse_checkButton(RunnerMouseState* m, int32_t button) { + if (!isValidButtonVirtual(button)) return false; + + if (isValidButton(button)) { + return m->buttonDown[button-1]; + } + + bool any = false; + repeat(GML_MOUSE_BUTTON_COUNT, i) { + any &= m->buttonDown[i]; + } + + if (button == MB_ANY) return any; + else if (button == MB_NONE) return !any; + return false; +} + +bool RunnerMouse_checkButtonPressed(RunnerMouseState* m, int32_t button) { + if (!isValidButtonVirtual(button)) return false; + + if (isValidButton(button)) { + return m->buttonPressed[button-1]; + } + + bool any = false; + repeat(GML_MOUSE_BUTTON_COUNT, i) { + any &= m->buttonPressed[i]; + } + + if (button == MB_ANY) return any; + else if (button == MB_NONE) return !any; + return false; +} \ No newline at end of file diff --git a/src/runner_mouse.h b/src/runner_mouse.h new file mode 100644 index 00000000..047edd69 --- /dev/null +++ b/src/runner_mouse.h @@ -0,0 +1,31 @@ +#pragma once + +#include "common.h" +#include + +// Mouse buttons +// TODO verify side buttons are correct +#define MB_ANY -1 +#define MB_NONE 0 +#define MB_LEFT 1 +#define MB_RIGHT 2 +#define MB_MIDDLE 3 +#define MB_SIDE1 4 +#define MB_SIDE2 5 +#define GML_MOUSE_BUTTONS 6 + +#define GML_MOUSE_BUTTON_COUNT 5 + +typedef struct RunnerMouseState { + double mouseX, mouseY; + bool buttonDown[GML_MOUSE_BUTTON_COUNT]; + bool buttonPressed[GML_MOUSE_BUTTON_COUNT]; +} RunnerMouseState; + +RunnerMouseState* RunnerMouse_create(void); +void RunnerMouse_free(RunnerMouseState* m); +void RunnerMouse_beginFrame(RunnerMouseState* m); +void RunnerMouse_onButtonDown(RunnerMouseState* m, int32_t button); +void RunnerMouse_onButtonUp(RunnerMouseState* m, int32_t button); +bool RunnerMouse_checkButton(RunnerMouseState* m, int32_t button); +bool RunnerMouse_checkButtonPressed(RunnerMouseState* m, int32_t button); \ No newline at end of file diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 5796dc78..7d94ab76 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -191,6 +191,8 @@ static const BuiltinVarEntry BUILTIN_VAR_TABLE[] = { { "keyboard_lastchar", BUILTIN_VAR_KEYBOARD_LASTCHAR }, { "keyboard_lastkey", BUILTIN_VAR_KEYBOARD_LASTKEY }, { "mask_index", BUILTIN_VAR_MASK_INDEX }, + { "mouse_x", BUILTIN_VAR_MOUSE_X }, + { "mouse_y", BUILTIN_VAR_MOUSE_Y }, { "object_index", BUILTIN_VAR_OBJECT_INDEX }, { "os_3ds", BUILTIN_VAR_OS_3DS }, { "os_amazon", BUILTIN_VAR_OS_AMAZON }, @@ -259,6 +261,7 @@ static const BuiltinVarEntry BUILTIN_VAR_TABLE[] = { { "view_hspeed", BUILTIN_VAR_VIEW_HSPEED }, { "view_hview", BUILTIN_VAR_VIEW_HVIEW }, { "view_object", BUILTIN_VAR_VIEW_OBJECT }, + { "view_surface_id", BUILTIN_VAR_VIEW_SURFACE_ID }, { "view_vborder", BUILTIN_VAR_VIEW_VBORDER }, { "view_visible", BUILTIN_VAR_VIEW_VISIBLE }, { "view_vspeed", BUILTIN_VAR_VIEW_VSPEED }, @@ -613,6 +616,9 @@ RValue VMBuiltins_getVariable(VMContext* ctx, int16_t builtinVarId, const char* case BUILTIN_VAR_VIEW_VSPEED: if (arrayIndex >= 0 && MAX_VIEWS > arrayIndex) return RValue_makeReal((GMLReal) runner->views[arrayIndex].speedY); return RValue_makeReal(0.0); + case BUILTIN_VAR_VIEW_SURFACE_ID: + if (arrayIndex >= 0 && MAX_VIEWS > arrayIndex) return RValue_makeReal((GMLReal) runner->viewSurfaceIds[arrayIndex]); + return RValue_makeReal(-1.0); // Background properties case BUILTIN_VAR_BACKGROUND_VISIBLE: @@ -697,7 +703,13 @@ RValue VMBuiltins_getVariable(VMContext* ctx, int16_t builtinVarId, const char* return RValue_makeString(runner->keyboard->lastChar); case BUILTIN_VAR_KEYBOARD_LASTKEY: return RValue_makeReal((GMLReal) runner->keyboard->lastKey); - + + // Mouse + case BUILTIN_VAR_MOUSE_X: + return RValue_makeReal((GMLReal) runner->mouse->mouseX); + case BUILTIN_VAR_MOUSE_Y: + return RValue_makeReal((GMLReal) runner->mouse->mouseY); + // Surfaces case BUILTIN_VAR_APPLICATION_SURFACE: return RValue_makeReal(-1.0); // sentinel ID for the application surface @@ -1007,6 +1019,9 @@ void VMBuiltins_setVariable(VMContext* ctx, int16_t builtinVarId, const char* na case BUILTIN_VAR_VIEW_VSPEED: if (arrayIndex >= 0 && MAX_VIEWS > arrayIndex) runner->views[arrayIndex].speedY = RValue_toInt32(val); return; + case BUILTIN_VAR_VIEW_SURFACE_ID: + if (arrayIndex >= 0 && MAX_VIEWS > arrayIndex) runner->viewSurfaceIds[arrayIndex] = RValue_toInt32(val); + return; // Background properties case BUILTIN_VAR_BACKGROUND_VISIBLE: @@ -3577,6 +3592,21 @@ static RValue builtinKeyboardClear(VMContext* ctx, RValue* args, int32_t argCoun return RValue_makeUndefined(); } +// Mouse functions +static RValue builtinMouseCheckButton(VMContext* ctx, RValue* args, int32_t argCount) { + if (1 > argCount) return RValue_makeBool(false); + Runner* runner = (Runner*) ctx->runner; + int32_t button = RValue_toInt32(args[0]); + return RValue_makeBool(RunnerMouse_checkButton(runner->mouse, button)); +} + +static RValue builtinMouseCheckButtonPressed(VMContext* ctx, RValue* args, int32_t argCount) { + if (1 > argCount) return RValue_makeBool(false); + Runner* runner = (Runner*) ctx->runner; + int32_t button = RValue_toInt32(args[0]); + return RValue_makeBool(RunnerMouse_checkButtonPressed(runner->mouse, button)); +} + // Joystick stubs STUB_RETURN_ZERO(joystick_exists) STUB_RETURN_ZERO(joystick_xpos) @@ -7510,6 +7540,10 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "keyboard_key_release", builtinKeyboardKeyRelease); VM_registerBuiltin(ctx, "keyboard_clear", builtinKeyboardClear); + // Mouse + VM_registerBuiltin(ctx, "mouse_check_button", builtinMouseCheckButton); + VM_registerBuiltin(ctx, "mouse_check_button_pressed", builtinMouseCheckButtonPressed); + // Joystick VM_registerBuiltin(ctx, "joystick_exists", builtin_joystick_exists); VM_registerBuiltin(ctx, "joystick_xpos", builtin_joystick_xpos); diff --git a/src/vm_builtins.h b/src/vm_builtins.h index 13392a2e..1c5502a8 100644 --- a/src/vm_builtins.h +++ b/src/vm_builtins.h @@ -81,6 +81,7 @@ typedef enum { BUILTIN_VAR_VIEW_OBJECT, BUILTIN_VAR_VIEW_HSPEED, BUILTIN_VAR_VIEW_VSPEED, + BUILTIN_VAR_VIEW_SURFACE_ID, // Background properties BUILTIN_VAR_BACKGROUND_VISIBLE, @@ -160,6 +161,10 @@ typedef enum { BUILTIN_VAR_KEYBOARD_LASTCHAR, BUILTIN_VAR_KEYBOARD_LASTKEY, + // Mouse + BUILTIN_VAR_MOUSE_X, + BUILTIN_VAR_MOUSE_Y, + // Surfaces BUILTIN_VAR_APPLICATION_SURFACE,