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 c4d162a commit 1e0ae0b
Showing 1 changed file with 81 additions and 35 deletions.
116 changes: 81 additions & 35 deletions backends/imgui_impl_sdl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,36 @@
#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))
{
g_GameControllers.push_back(SDL_GameControllerOpen(i));
}
}
}

static const char* ImGui_ImplSDL2_GetClipboardText(void*)
{
Expand Down Expand Up @@ -124,6 +148,11 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
#endif
return true;
}
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
{
ImGui_ImplSDL2_EnumerateGameControllers();
}
}
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 @@ -301,7 +332,15 @@ static void ImGui_ImplSDL2_UpdateMouseCursor()

static void ImGui_ImplSDL2_MapButton(float* nav_input, Uint8 btn_value)
{
*nav_input = (btn_value != 0) ? 1.0f : 0.0f;
*nav_input += (btn_value != 0) ? 1.0f : 0.0f;
if (*nav_input > 1.0f)
{
*nav_input = 1.0f;
}
if (*nav_input < 0.0f)
{
*nav_input = 0.0f;
}
}

static void ImGui_ImplSDL2_MapAnalog(float* nav_input, Sint16 axis_value, float v0, float v1)
Expand All @@ -314,7 +353,7 @@ static void ImGui_ImplSDL2_MapAnalog(float* nav_input, Sint16 axis_value, float

if (vn > 0.0f && *nav_input < vn)
{
*nav_input = vn;
*nav_input += vn;
}
}

Expand All @@ -325,38 +364,45 @@ 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 gamepad inputs
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) { ImGui_ImplSDL2_MapButton(&io.NavInputs[NAV_NO], SDL_GameControllerGetButton(game_controller, BUTTON_NO)); }
#define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { ImGui_ImplSDL2_MapAnalog(&io.NavInputs[NAV_NO], SDL_GameControllerGetAxis(game_controller, AXIS_NO), V0, V1); }
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) { ImGui_ImplSDL2_MapButton(&io.NavInputs[NAV_NO], SDL_GameControllerGetButton(game_controller, BUTTON_NO)); }
#define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { ImGui_ImplSDL2_MapAnalog(&io.NavInputs[NAV_NO], SDL_GameControllerGetAxis(game_controller, AXIS_NO), V0, V1); }
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
}
}

void ImGui_ImplSDL2_NewFrame(SDL_Window* window)
Expand Down

0 comments on commit 1e0ae0b

Please sign in to comment.