Skip to content

Commit

Permalink
Backends: SDL2: Implement multiple gamepad support
Browse files Browse the repository at this point in the history
This makes the SDL2 backend work correctly with multiple gamepads. Instead of
hard-coding gamepad index 0, we keep a list of all currently available
gamepads. When gamepads are added or removed, we update the list accordingly.
When processing gamepad input, we then iterate over this list and combine the
button and axis state of all gamepads.

Note that it's not necessary to enumerate gamepads during initialization
of the backend, because SDL automatically sends a
`SDL_CONTROLLERDEVICEADDED` event for every gamepad that was already
present during initialization of SDL.

The main motivation for this change is that I can have multiple gamepads
connected to my system and grab any one of them at random, and have ImGui
recognize my inputs. Previously, this was not guaranteed, because the gamepad I
grabbed might not have index 0. It's also possible now to (for example) use the
D-pad on one gamepad and and buttons on another one at the same time, although
that seems less useful in practice :)

Additionally, we now properly close all previously opened gamepads when
shutting down the backend.
  • Loading branch information
lethal-guitar committed Mar 5, 2021
1 parent 2b3e3b5 commit f8a814e
Showing 1 changed file with 67 additions and 35 deletions.
102 changes: 67 additions & 35 deletions backends/imgui_impl_sdl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2021-03-05: Inputs: Added support for handling multiple game controllers simultaneously
// 2020-05-25: Misc: Report a zero display-size when window is minimized, to be consistent with other backends.
// 2020-02-20: Inputs: Fixed mapping for ImGuiKey_KeyPadEnter (using SDL_SCANCODE_KP_ENTER instead of SDL_SCANCODE_RETURN2).
// 2019-12-17: Inputs: On Wayland, use SDL_GetMouseState (because there is no global mouse state).
Expand Down Expand Up @@ -58,12 +59,34 @@
#define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6)

// Data
static SDL_Window* g_Window = NULL;
static Uint64 g_Time = 0;
static bool g_MousePressed[3] = { false, false, false };
static SDL_Cursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {};
static char* g_ClipboardTextData = NULL;
static bool g_MouseCanUseGlobalState = true;
static SDL_Window* g_Window = NULL;
static Uint64 g_Time = 0;
static bool g_MousePressed[3] = { false, false, false };
static SDL_Cursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {};
static char* g_ClipboardTextData = NULL;
static bool g_MouseCanUseGlobalState = true;
static ImVector<SDL_GameController*> g_GameControllers;

static void ImGui_ImplSDL2_CloseGameControllers()
{
for (int i = 0; i < g_GameControllers.Size; ++i)
SDL_GameControllerClose(g_GameControllers[i]);

g_GameControllers.clear();
}

static void ImGui_ImplSDL2_EnumerateGameControllers()
{
ImGui_ImplSDL2_CloseGameControllers();

for (Uint8 i = 0; i < SDL_NumJoysticks(); ++i)
{
if (!SDL_IsGameController(i))
continue;
if (SDL_GameController* game_controller = SDL_GameControllerOpen(i))
g_GameControllers.push_back(game_controller);
}
}

static const char* ImGui_ImplSDL2_GetClipboardText(void*)
{
Expand Down Expand Up @@ -124,6 +147,12 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
#endif
return true;
}
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
{
ImGui_ImplSDL2_EnumerateGameControllers();
break;
}
}
return false;
}
Expand Down Expand Up @@ -232,6 +261,8 @@ void ImGui_ImplSDL2_Shutdown()
for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
SDL_FreeCursor(g_MouseCursors[cursor_n]);
memset(g_MouseCursors, 0, sizeof(g_MouseCursors));

ImGui_ImplSDL2_CloseGameControllers();
}

static void ImGui_ImplSDL2_UpdateMousePosAndButtons()
Expand Down Expand Up @@ -306,38 +337,39 @@ static void ImGui_ImplSDL2_UpdateGamepads()
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
return;

// Get gamepad
SDL_GameController* game_controller = SDL_GameControllerOpen(0);
if (!game_controller)
{
// Update "has gamepad" flag
if (g_GameControllers.Size > 0)
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
else
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
return;
}

// Update gamepad inputs
#define MAP_BUTTON(NAV_NO, BUTTON_NO) { io.NavInputs[NAV_NO] = (SDL_GameControllerGetButton(game_controller, BUTTON_NO) != 0) ? 1.0f : 0.0f; }
#define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float vn = (float)(SDL_GameControllerGetAxis(game_controller, AXIS_NO) - V0) / (float)(V1 - V0); if (vn > 1.0f) vn = 1.0f; if (vn > 0.0f && io.NavInputs[NAV_NO] < vn) io.NavInputs[NAV_NO] = vn; }
const int thumb_dead_zone = 8000; // SDL_gamecontroller.h suggests using this value.
MAP_BUTTON(ImGuiNavInput_Activate, SDL_CONTROLLER_BUTTON_A); // Cross / A
MAP_BUTTON(ImGuiNavInput_Cancel, SDL_CONTROLLER_BUTTON_B); // Circle / B
MAP_BUTTON(ImGuiNavInput_Menu, SDL_CONTROLLER_BUTTON_X); // Square / X
MAP_BUTTON(ImGuiNavInput_Input, SDL_CONTROLLER_BUTTON_Y); // Triangle / Y
MAP_BUTTON(ImGuiNavInput_DpadLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT); // D-Pad Left
MAP_BUTTON(ImGuiNavInput_DpadRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); // D-Pad Right
MAP_BUTTON(ImGuiNavInput_DpadUp, SDL_CONTROLLER_BUTTON_DPAD_UP); // D-Pad Up
MAP_BUTTON(ImGuiNavInput_DpadDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN); // D-Pad Down
MAP_BUTTON(ImGuiNavInput_FocusPrev, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); // L1 / LB
MAP_BUTTON(ImGuiNavInput_FocusNext, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); // R1 / RB
MAP_BUTTON(ImGuiNavInput_TweakSlow, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); // L1 / LB
MAP_BUTTON(ImGuiNavInput_TweakFast, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); // R1 / RB
MAP_ANALOG(ImGuiNavInput_LStickLeft, SDL_CONTROLLER_AXIS_LEFTX, -thumb_dead_zone, -32768);
MAP_ANALOG(ImGuiNavInput_LStickRight, SDL_CONTROLLER_AXIS_LEFTX, +thumb_dead_zone, +32767);
MAP_ANALOG(ImGuiNavInput_LStickUp, SDL_CONTROLLER_AXIS_LEFTY, -thumb_dead_zone, -32767);
MAP_ANALOG(ImGuiNavInput_LStickDown, SDL_CONTROLLER_AXIS_LEFTY, +thumb_dead_zone, +32767);

io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
#undef MAP_BUTTON
#undef MAP_ANALOG
for (int i = 0; i < g_GameControllers.Size; ++i)
{
SDL_GameController* game_controller = g_GameControllers[i];

#define MAP_BUTTON(NAV_NO, BUTTON_NO) { io.NavInputs[NAV_NO] += (SDL_GameControllerGetButton(game_controller, BUTTON_NO) != 0) ? 1.0f : 0.0f; if (io.NavInputs[NAV_NO] > 1.0f) io.NavInputs[NAV_NO] = 1.0f; }
#define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float vn = (float)(SDL_GameControllerGetAxis(game_controller, AXIS_NO) - V0) / (float)(V1 - V0); if (vn > 1.0f) vn = 1.0f; if (vn > 0.0f && io.NavInputs[NAV_NO] < vn) io.NavInputs[NAV_NO] += vn; }
const int thumb_dead_zone = 8000; // SDL_gamecontroller.h suggests using this value.
MAP_BUTTON(ImGuiNavInput_Activate, SDL_CONTROLLER_BUTTON_A); // Cross / A
MAP_BUTTON(ImGuiNavInput_Cancel, SDL_CONTROLLER_BUTTON_B); // Circle / B
MAP_BUTTON(ImGuiNavInput_Menu, SDL_CONTROLLER_BUTTON_X); // Square / X
MAP_BUTTON(ImGuiNavInput_Input, SDL_CONTROLLER_BUTTON_Y); // Triangle / Y
MAP_BUTTON(ImGuiNavInput_DpadLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT); // D-Pad Left
MAP_BUTTON(ImGuiNavInput_DpadRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); // D-Pad Right
MAP_BUTTON(ImGuiNavInput_DpadUp, SDL_CONTROLLER_BUTTON_DPAD_UP); // D-Pad Up
MAP_BUTTON(ImGuiNavInput_DpadDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN); // D-Pad Down
MAP_BUTTON(ImGuiNavInput_FocusPrev, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); // L1 / LB
MAP_BUTTON(ImGuiNavInput_FocusNext, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); // R1 / RB
MAP_BUTTON(ImGuiNavInput_TweakSlow, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); // L1 / LB
MAP_BUTTON(ImGuiNavInput_TweakFast, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); // R1 / RB
MAP_ANALOG(ImGuiNavInput_LStickLeft, SDL_CONTROLLER_AXIS_LEFTX, -thumb_dead_zone, -32768);
MAP_ANALOG(ImGuiNavInput_LStickRight, SDL_CONTROLLER_AXIS_LEFTX, +thumb_dead_zone, +32767);
MAP_ANALOG(ImGuiNavInput_LStickUp, SDL_CONTROLLER_AXIS_LEFTY, -thumb_dead_zone, -32767);
MAP_ANALOG(ImGuiNavInput_LStickDown, SDL_CONTROLLER_AXIS_LEFTY, +thumb_dead_zone, +32767);
#undef MAP_BUTTON
#undef MAP_ANALOG
}
}

void ImGui_ImplSDL2_NewFrame(SDL_Window* window)
Expand Down

0 comments on commit f8a814e

Please sign in to comment.