Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remapping keyboard input to gamepad for navigation #7115

Closed
luke-moll-itdev opened this issue Dec 11, 2023 · 3 comments
Closed

Remapping keyboard input to gamepad for navigation #7115

luke-moll-itdev opened this issue Dec 11, 2023 · 3 comments
Labels
inputs nav keyboard/gamepad navigation

Comments

@luke-moll-itdev
Copy link

Dear ImGui 1.90.0 (19000)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=199711
define: _WIN32
define: _WIN64
define: _MSC_VER=1936
define: _MSVC_LANG=202002
--------------------------------
io.BackendPlatformName: imgui_impl_win32
io.BackendRendererName: imgui_impl_dx11
io.ConfigFlags: 0x00000003
 NavEnableKeyboard
 NavEnableGamepad
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x0000000E
 HasMouseCursors
 HasSetMousePos
 RendererHasVtxOffset
--------------------------------

Version/Branch of Dear ImGui:

Version: 1.90
Branch: Checked-out tag v1.90

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_win32.cpp + imgui_impl_dx11.cpp
Compiler: MSVC, C++20
Operating System: Windows 10

My Issue/Question:

I'm accepting input from a USB controller that presents as a keyboard but has directional keys. For example, pressing up,down,left,right on the controller sends the w,a,s,d keypresses. It also has a select key. I'd like to navigate ImGUI using only this controller (no standard USB keyboard, and no mouse).
I had understood from the examples in #787 that I could detect which keys are pressed (w,a,s,d), and generate a navigation event to pass to ImGui. I have done my best to implement that using the new APIs in #4921. I don't believe I need to use io.SetKeyEventNativeData(). Config flags include NavEnableKeyboard and NavEnableGamepad. I also added:

// example_win32_directx11/main.cpp
// Main code
int main(int, char**)
{
// ...
    // Setup Platform/Renderer backends
    ImGui_ImplWin32_Init(hwnd);
    ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
    
    ///////////////////////////////////////////////
    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
    ///////////////////////////////////////////////

to main.cpp, but the flag shows as disabled in Configuration > Backend flags. I understand that this may be required to use AddKeyEvent, but I'm not sure how to ensure it is enabled.

Using the code below, I can press W,A,S,D on my standard USB keyboard (to emulate the controller) and see the down/up events being printed from std::cout. I can use the normal directional arrows to navigate ImGUI as expected, but when I press W,A,S,D, nothing happens.

Compiled in debug mode, confirmed that assertions are enabled.

Standalone, minimal, complete and verifiable example:

// example_win32_directx11/main.cpp

#include <set>
#include <map>
#include <iostream>

void handleKeyboard()
{
    auto& io = ImGui::GetIO();

    // Mapping between keys pressed and key event generated
    // When W is pressed, generate GamePadDpadUp
    std::map<ImGuiKey, ImGuiKey> keyMapping {
        {ImGuiKey_W, ImGuiKey_GamepadDpadUp},
        { ImGuiKey_A, ImGuiKey_GamepadDpadLeft },
        { ImGuiKey_S, ImGuiKey_GamepadDpadDown },
        { ImGuiKey_D, ImGuiKey_GamepadDpadRight }
    };

    // Input keys that are currently held
    // As per Q&A in #4921 , this isn't technically needed
    // but it means AddKeyEvent is only called once when the key goes {down, up}.
    static std::set<ImGuiKey> downKeys{};

    // May require compiling with C++20 to support this unpacking syntax
    for (const auto& [key, val] : keyMapping)
    {
        if (ImGui::IsKeyDown(key))
        {
            if (!downKeys.contains(key))
            {
                // Key is down and wasn't down last frame
                std::cout << ImGui::GetKeyName(key) << " down, AddKeyEvent(" << ImGui::GetKeyName(val) << ", true)" << std::endl;
                io.AddKeyEvent(val, true);
                downKeys.insert(key);
            }
        }
        else
        {
            if (downKeys.contains(key))
            {
                // Key is up, and was down last frame
                std::cout << ImGui::GetKeyName(key) << " up, AddKeyEvent(" << ImGui::GetKeyName(val) << ", false)" << std::endl;
                io.AddKeyEvent(val, false);
                downKeys.erase(key);
            }
        }
    }
}

// Main code
int main(int, char**)
{
// ...
        // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
        if (show_demo_window)
            ImGui::ShowDemoWindow(&show_demo_window);

        //////////////////////////////////////////
        handleKeyboard();
        //////////////////////////////////////////

        // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
        {
@ocornut ocornut changed the title [HELP] Remapping keyboard input to gamepad for navigation Remapping keyboard input to gamepad for navigation Dec 11, 2023
@ocornut ocornut added nav keyboard/gamepad navigation inputs labels Dec 11, 2023
@ocornut
Copy link
Owner

ocornut commented Dec 11, 2023

I had understood from the examples in #787 that I could detect which keys are pressed (w,a,s,d), and generate a navigation event to pass to ImGui.

(For completeness, please note that ImGuiNavInput and io.NavInputs[] are obsoleted since 1.89, since we have transitioned to full ImGuiKey API for both keyboard and gamepad.)

In principle you can use this logic to remap, so you would e.g. transform ImGuiKey_W events to ImGuiKey_UpArrow events:
#5723 (comment)

But there's a slight issue in your case: you can transform your events into corresponding keyboard nav events. But handling gamepad nav events is currently conditioned by ImGuiBackendFlags_HasGamepad being set. And gamepad nav is slightly different from keyboard nav (both will work but gamepad nav is better tuned, well, for gamepad).
As ImGuiBackendFlags_HasGamepad is automatically updated by the backend (in your case imgui_impl_win32) every frame, you would need to set that flag again every frame:

// Start the Dear ImGui frame
const int input_queue_size_before = ImGui::GetCurrentContext()->InputEventsQueue.Size; // record event count before backend submit them
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();

// Call between backend and main lib NewFrame():
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
YourFunctionToRemapInput(input_queue_size_before);

ImGui::NewFrame();

However, doing so would only make your app work with you specific gamepad. If you only need your app your work with that setup it may be fine, but otherwise I would instead be looking for a third-party app to do remapping on selected apps.

@luke-moll-itdev
Copy link
Author

Thanks for the quick response - I wasn't aware that AddKeyEvent could be used with other values than ImGuiKey_GamePadXXX.

Using _UpArrow etc. seems to work fine for now, but it is useful to know how to emulate a gamepad input as well.

@ocornut
Copy link
Owner

ocornut commented Dec 11, 2023

Your code is essentially copying keys but it will do so with an extra frame of lag so that's not ideal.
Performing the copy or remap directly by parsing InputEventsQueue[] seems preferable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
inputs nav keyboard/gamepad navigation
Projects
None yet
Development

No branches or pull requests

2 participants