Skip to content

Commit

Permalink
Add gamepad support for Windows (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
skejeton committed Mar 23, 2024
1 parent fb15a09 commit 1d36d69
Show file tree
Hide file tree
Showing 8 changed files with 503 additions and 2 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ if(MSVC)
string(STRIP "${gitver}" gitver)
target_compile_definitions(tophat PUBLIC -DTH_VERSION=\"${ver}\" -DTH_GITVER=\"${gitver}\")

target_link_libraries(tophat gdi32 opengl32 user32 ${umka_lib_path})
target_link_libraries(tophat xinput gdi32 opengl32 user32 ${umka_lib_path})
else()
message("TODO(skejeton): This script only works with MSVC. If you are on Linux or WSL/MSYS2 run `make` instead.")
endif()
91 changes: 91 additions & 0 deletions src/bindings.c
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,89 @@ umth_input_get_mouse_scroll(UmkaStackSlot *p, UmkaStackSlot *r)
*(fu *)(p[1].ptrVal) = thg->mouse_wheel.x;
}

void
umth_input_gamepad_get_players(UmkaStackSlot *p, UmkaStackSlot *r)
{
((int64_t *)p[0].ptrVal)[0] = thg->gamepad[0].connected ? 0 : -1;
((int64_t *)p[0].ptrVal)[1] = thg->gamepad[1].connected ? 1 : -1;
((int64_t *)p[0].ptrVal)[2] = thg->gamepad[2].connected ? 2 : -1;
((int64_t *)p[0].ptrVal)[3] = thg->gamepad[3].connected ? 3 : -1;
}

void
umth_input_gamepad_is_pressed(UmkaStackSlot *p, UmkaStackSlot *r)
{
int button = p[0].intVal;
int gamepad = p[1].intVal;

if (gamepad < 0 || gamepad >= 4) {
r->intVal = 0;
return;
}

r->intVal = thg->gamepad[gamepad].buttons[button].pressed;
}

void
umth_input_gamepad_is_just_pressed(UmkaStackSlot *p, UmkaStackSlot *r)
{
int button = p[0].intVal;
int gamepad = p[1].intVal;

if (gamepad < 0 || gamepad >= 4) {
r->intVal = 0;
return;
}

r->intVal = thg->gamepad[gamepad].buttons[button].just_pressed;
}

void
umth_input_gamepad_is_just_released(UmkaStackSlot *p, UmkaStackSlot *r)
{
int button = p[0].intVal;
int gamepad = p[1].intVal;

if (gamepad < 0 || gamepad >= 4) {
r->intVal = 0;
return;
}

r->intVal = thg->gamepad[gamepad].buttons[button].just_released;
}

void
umth_input_gamepad_pressure(UmkaStackSlot *p, UmkaStackSlot *r)
{
int button = p[0].intVal;
int gamepad = p[1].intVal;

if (gamepad < 0 || gamepad >= 4) {
r->realVal = 0;
return;
}

r->realVal = thg->gamepad[gamepad].buttons[button].pressure;
}

void
umth_input_gamepad_stick(UmkaStackSlot *p, UmkaStackSlot *r)
{
th_vf2 *out = p[0].ptrVal;
int stick = p[1].intVal;
int gamepad = p[2].intVal;

if (gamepad < 0 || gamepad >= 4) {
*out = (th_vf2){0};
return;
}

switch (stick) {
case 0: *out = thg->gamepad[gamepad].left_stick; break;
case 1: *out = thg->gamepad[gamepad].right_stick; break;
}
}

///////////////////////
// entities
// draws an entity
Expand Down Expand Up @@ -1204,6 +1287,14 @@ _th_umka_bind(void *umka)
umkaAddFunc(umka, "umth_input_get_str", &umth_input_get_str);
umkaAddFunc(umka, "umth_input_get_mouse_delta", &umth_input_get_mouse_delta);
umkaAddFunc(umka, "umth_input_get_mouse_scroll", &umth_input_get_mouse_scroll);
umkaAddFunc(umka, "umth_input_gamepad_get_players", &umth_input_gamepad_get_players);
umkaAddFunc(umka, "umth_input_gamepad_is_pressed", &umth_input_gamepad_is_pressed);
umkaAddFunc(
umka, "umth_input_gamepad_is_just_pressed", &umth_input_gamepad_is_just_pressed);
umkaAddFunc(
umka, "umth_input_gamepad_is_just_released", &umth_input_gamepad_is_just_released);
umkaAddFunc(umka, "umth_input_gamepad_pressure", &umth_input_gamepad_pressure);
umkaAddFunc(umka, "umth_input_gamepad_stick", &umth_input_gamepad_stick);

// entities
umkaAddFunc(umka, "umth_ent_draw", &umth_ent_draw);
Expand Down
76 changes: 76 additions & 0 deletions src/input.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "tophat.h"
#include <string.h>
#ifdef _WIN32
#include <Xinput.h>
#endif

extern th_global *thg;

Expand Down Expand Up @@ -63,3 +66,76 @@ th_input_cycle()
memset(thg->just_released, 0, 512 * sizeof(uu));
memset(thg->press_repeat, 0, 512 * sizeof(uu));
}

// Deadzone and drift fix
static float
i_fix(float value)
{
if (fabs(value) < 0.08)
return 0;
return value;
}

static void
i_update_button(th_gamepad_button *btn, float value)
{
btn->just_pressed = !btn->pressed && value > 0.5f;
btn->just_released = btn->pressed && value <= 0.5f;
btn->pressed = value > 0.5f;
btn->pressure = value;
}

void
th_input_update_gamepads()
{
#ifdef _WIN32
XINPUT_STATE state;
for (int i = 0; i < 4; i++) {
state = (XINPUT_STATE){0};

th_generic_gamepad *gp = &thg->gamepad[i];

if (XInputGetState(i, &state) == ERROR_SUCCESS) {
gp->connected = true;

XINPUT_GAMEPAD xgp = state.Gamepad;

i_update_button(&gp->dpad_up, (xgp.wButtons & XINPUT_GAMEPAD_DPAD_UP) > 0);
i_update_button(
&gp->dpad_down, (xgp.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) > 0);
i_update_button(
&gp->dpad_left, (xgp.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) > 0);
i_update_button(
&gp->dpad_right, (xgp.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) > 0);

i_update_button(&gp->start, (xgp.wButtons & XINPUT_GAMEPAD_START) > 0);
i_update_button(&gp->select, (xgp.wButtons & XINPUT_GAMEPAD_BACK) > 0);

i_update_button(
&gp->left_stick_press, (xgp.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) > 0);
i_update_button(&gp->right_stick_press,
(xgp.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) > 0);

i_update_button(&gp->LB, (xgp.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) > 0);
i_update_button(
&gp->RB, (xgp.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) > 0);

i_update_button(&gp->A, (xgp.wButtons & XINPUT_GAMEPAD_A) > 0);
i_update_button(&gp->B, (xgp.wButtons & XINPUT_GAMEPAD_B) > 0);
i_update_button(&gp->X, (xgp.wButtons & XINPUT_GAMEPAD_X) > 0);
i_update_button(&gp->Y, (xgp.wButtons & XINPUT_GAMEPAD_Y) > 0);

i_update_button(&gp->LT, (float)xgp.bLeftTrigger / 255.0f);
i_update_button(&gp->RT, (float)xgp.bRightTrigger / 255.0f);

gp->left_stick.x = i_fix((float)xgp.sThumbLX / 32768.0f);
gp->left_stick.y = i_fix((float)xgp.sThumbLY / 32768.0f);

gp->right_stick.x = i_fix((float)xgp.sThumbRX / 32768.0f);
gp->right_stick.y = i_fix((float)xgp.sThumbRY / 32768.0f);
} else {
gp->connected = false;
}
}
#endif
}
87 changes: 87 additions & 0 deletions src/staembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,34 @@ const char *th_em_modulesrc[] = {
"}\n"
"//~~\n"
"\n"
"type GenericGamepadButton* = enum {\n"
"\ta\n"
"\tb\n"
"\tx\n"
"\ty\n"
"\tlt\n"
"\trt\n"
"\tlb\n"
"\trb\n"
"\tselect\n"
"\tstart\n"
"\tup\n"
"\tdown\n"
"\tleft\n"
"\tright\n"
"\tlstick\n"
"\trstick\n"
"\t_count\n"
"}\n"
"\n"
"type GamepadStick* = enum {\n"
"\tleft\n"
"\tright\n"
"}\n"
"\n"
"type Player* = struct {\n"
"\tid: int\n"
"}\n"
"\n"
"fn toupper(c: char): int {\n"
"\tif c >= \'a\' && c <= \'z\' {\n"
Expand Down Expand Up @@ -1075,6 +1103,65 @@ const char *th_em_modulesrc[] = {
"\tumth_input_get_mouse_scroll(&x, &y)\n"
"\treturn th.Vf2{x, y}\n"
"}\n"
"\n"
"fn umth_input_gamepad_get_players*(out: ^[4]int)\n"
"\n"
"fn players*(): []Player {\n"
"\tvar players: [4]int\n"
"\tumth_input_gamepad_get_players(&players)\n"
"\n"
"\tresult := []Player{}\n"
"\n"
"\tfor i, p in players {\n"
"\t\tif p >= 0 {\n"
"\t\t\tresult = append(result, Player{p})\n"
"\t\t}\n"
"\t}\n"
"\n"
"\treturn result\n"
"}\n"
"\n"
"fn player*(): Player {\n"
"\tplayers := players()\n"
"\n"
"\tif len(players) == 0 {\n"
"\t\treturn Player{-1}\n"
"\t}\n"
"\n"
"\treturn players[0]\n"
"}\n"
"\n"
"fn umth_input_gamepad_is_pressed(player: int, button: GenericGamepadButton): bool\n"
"\n"
"fn (p: ^Player) isPressed*(button: GenericGamepadButton): bool {\n"
"\treturn umth_input_gamepad_is_pressed(p.id, button)\n"
"}\n"
"\n"
"fn umth_input_gamepad_is_just_pressed(player: int, button: GenericGamepadButton): bool\n"
"\n"
"fn (p: ^Player) isJustPressed*(button: GenericGamepadButton): bool {\n"
"\treturn umth_input_gamepad_is_just_pressed(p.id, button)\n"
"}\n"
"\n"
"fn umth_input_gamepad_is_just_released(player: int, button: GenericGamepadButton): bool\n"
"\n"
"fn (p: ^Player) isJustReleased*(button: GenericGamepadButton): bool {\n"
"\treturn umth_input_gamepad_is_just_released(p.id, button)\n"
"}\n"
"\n"
"fn umth_input_gamepad_pressure(player: int, button: GenericGamepadButton): th.fu\n"
"\n"
"fn (p: ^Player) pressure*(button: GenericGamepadButton): th.fu {\n"
"\treturn umth_input_gamepad_pressure(p.id, button)\n"
"}\n"
"\n"
"fn umth_input_gamepad_stick(player: int, stick: GamepadStick, out: ^th.Vf2)\n"
"\n"
"fn (p: ^Player) stick*(stick: GamepadStick): th.Vf2 {\n"
"\tvar out: th.Vf2\n"
"\tumth_input_gamepad_stick(p.id, stick, &out)\n"
"\treturn out\n"
"}\n"
"",
"//~~\n"
"// Misc functions.\n"
Expand Down
42 changes: 42 additions & 0 deletions src/tophat.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,44 @@ typedef struct

typedef uint32_t th_shader;

typedef struct
{
uu pressed;
uu just_pressed;
uu just_released;
fu pressure;
} th_gamepad_button;

typedef struct
{
bool connected;

// Modelled after XBox controller
union
{
struct
{
th_gamepad_button A, B, X, Y;
th_gamepad_button LT, RT, LB, RB;

th_gamepad_button select; // AKA view, back
th_gamepad_button start; // AKA menu

th_gamepad_button dpad_up;
th_gamepad_button dpad_down;
th_gamepad_button dpad_left;
th_gamepad_button dpad_right;

th_gamepad_button left_stick_press;
th_gamepad_button right_stick_press;
};
th_gamepad_button buttons[16];
};

th_vf2 left_stick; // -1 to 1
th_vf2 right_stick; // -1 to 1
} th_generic_gamepad;

// struct holding all tophat's global variables.
typedef struct
{
Expand All @@ -251,6 +289,8 @@ typedef struct
th_vf2 mouse_delta;
th_vf2 mouse_wheel;

th_generic_gamepad gamepad[4];

th_shader *shaders;
uu shader_count;

Expand Down Expand Up @@ -474,6 +514,8 @@ void
th_input_sync_fake_keys();
void
th_input_cycle();
void
th_input_update_gamepads();

// misc
void
Expand Down
3 changes: 2 additions & 1 deletion src/window.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ static void
frame()
{
thg->dpi_scale_factor = sapp_dpi_scale();
th_input_update_gamepads();
th_input_sync_fake_keys();

thg->pass_action = (sg_pass_action){
.colors[0] =
Expand All @@ -90,7 +92,6 @@ frame()
.swapchain = sglue_swapchain(),
});
sg_apply_pipeline(thg->canvas_pip);
th_input_sync_fake_keys();
int window_width, window_height;
th_window_get_dimensions(&window_width, &window_height);
thg->target_size = (th_vf2){.w = window_width, .h = window_height};
Expand Down
Loading

0 comments on commit 1d36d69

Please sign in to comment.