From 49a4c07e23d2d608162d9d333eb4b5889701713a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 2 Jun 2026 19:50:05 -0400 Subject: [PATCH] examples/input/05-gamepad-rumble: add some visual feedback. --- .../input/05-gamepad-rumble/gamepad-rumble.c | 72 +++++++++++++++++- .../input/05-gamepad-rumble/onmouseover.webp | Bin 1862 -> 11672 bytes .../input/05-gamepad-rumble/thumbnail.png | Bin 362 -> 1145 bytes 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/examples/input/05-gamepad-rumble/gamepad-rumble.c b/examples/input/05-gamepad-rumble/gamepad-rumble.c index e49037a3d8..b0a504480a 100644 --- a/examples/input/05-gamepad-rumble/gamepad-rumble.c +++ b/examples/input/05-gamepad-rumble/gamepad-rumble.c @@ -12,6 +12,25 @@ static SDL_Window *window = NULL; static SDL_Renderer *renderer = NULL; +typedef struct GamepadInfo +{ + SDL_JoystickID gamepad_id; + const char *action; +} GamepadInfo; + +static GamepadInfo gamepads_info[16]; /* if you have more than this, we'll ignore it. */ + +static GamepadInfo *FindGamepadInfo(SDL_JoystickID which) +{ + int i; + for (i = 0; i < SDL_arraysize(gamepads_info); i++) { + if (gamepads_info[i].gamepad_id == which) { + return &gamepads_info[i]; + } + } + return NULL; /* not found */ +} + /* This function runs once at startup. */ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { @@ -33,22 +52,39 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) /* This function runs when a new event (mouse input, keypresses, etc) occurs. */ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { + GamepadInfo *info; if (event->type == SDL_EVENT_QUIT) { return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ } else if (event->type == SDL_EVENT_GAMEPAD_ADDED) { /* this event is sent for each hotplugged stick, but also each already-connected gamepad during SDL_Init(). */ SDL_OpenGamepad(event->gdevice.which); + info = FindGamepadInfo(0); /* find an empty space */ + if (info) { + info->gamepad_id = event->gdevice.which; + info->action = "idle"; + } } else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) { SDL_Gamepad *gamepad = SDL_GetGamepadFromID(event->gdevice.which); SDL_CloseGamepad(gamepad); + info = FindGamepadInfo(event->gdevice.which); + if (info) { + info->gamepad_id = 0; + } } else if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { SDL_Gamepad *gamepad = SDL_GetGamepadFromID(event->gbutton.which); + info = FindGamepadInfo(event->gbutton.which); switch (event->gbutton.button) { case SDL_GAMEPAD_BUTTON_SOUTH: SDL_RumbleGamepad(gamepad, 0xFFFF, 0x0000, 5000); + if (info) { + info->action = "rumble high frequency"; + } break; case SDL_GAMEPAD_BUTTON_EAST: SDL_RumbleGamepad(gamepad, 0x0000, 0xFFFF, 5000); + if (info) { + info->action = "rumble low frequency"; + } break; default: break; @@ -56,19 +92,49 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) } else if (event->type == SDL_EVENT_GAMEPAD_BUTTON_UP) { SDL_Gamepad *gamepad = SDL_GetGamepadFromID(event->gbutton.which); SDL_RumbleGamepad(gamepad, 0x0000, 0x0000, 0); + info = FindGamepadInfo(event->gbutton.which); + if (info) { + info->action = "idle"; + } } return SDL_APP_CONTINUE; /* carry on with the program! */ } +static void draw_centered_text(const int rw, int *y, const char *str) +{ + const int x = (rw - (((int) SDL_strlen(str)) * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE)) / 2; + if (*str) { + SDL_RenderDebugText(renderer, (float) x, (float) *y, str); + } + *y += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 2; +} + /* This function runs once per frame, and is the heart of the program. */ SDL_AppResult SDL_AppIterate(void *appstate) { - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + int rw, rh, y, i; + SDL_GetCurrentRenderOutputSize(renderer, &rw, &rh); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); /* clear to black */ SDL_RenderClear(renderer); - SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - SDL_RenderDebugText(renderer, 8, 8, "Connect gamepad and press buttons to rumble"); + y = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 8; + SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); /* yellow text */ + draw_centered_text(rw, &y, "Connect gamepads and press buttons to rumble."); + y += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * 3; + + /* report all the visible joysticks and what they are doing at the moment. */ + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); /* white text */ + for (i = 0; i < SDL_arraysize(gamepads_info); i++) { + const SDL_JoystickID which = gamepads_info[i].gamepad_id; + if (which == 0) { + draw_centered_text(rw, &y, ""); /* just leave a blank line. */ + } else { + char str[128]; + SDL_snprintf(str, sizeof (str), "%s: %s", SDL_GetGamepadNameForID(which), gamepads_info[i].action); + draw_centered_text(rw, &y, str); + } + } SDL_RenderPresent(renderer); diff --git a/examples/input/05-gamepad-rumble/onmouseover.webp b/examples/input/05-gamepad-rumble/onmouseover.webp index 10d0eeffa47df48398c2864f55fea07993865494..2e8c8ee1268cdf573cd396eabd7c7c96b4248211 100644 GIT binary patch literal 11672 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 bckd6$FZP`^=?NbL0|SGntDnm{r-UW|jW(Hs