Skip to content
81 changes: 81 additions & 0 deletions src/glfw/main.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include "data_win.h"
#include "glfw/gl_legacy_renderer.h"
#include "runner_mouse.h"
#include "vm.h"

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/runner.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
5 changes: 5 additions & 0 deletions src/runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand Down
73 changes: 73 additions & 0 deletions src/runner_mouse.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "runner_mouse.h"
#include "utils.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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;
}
31 changes: 31 additions & 0 deletions src/runner_mouse.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include "common.h"
#include <stdint.h>

// 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);
36 changes: 35 additions & 1 deletion src/vm_builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions src/vm_builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,

Expand Down
Loading