ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
60.45k stars 10.21k forks source link

WM_CHAR input message repeat instantly without delay after first character #1808

Closed JX-Master closed 6 years ago

JX-Master commented 6 years ago

Version/Branch of Dear ImGui:

v1.60

Back-end/Renderer/OS:

Hardware Info:

My Issue/Question: The Input text repeats instantly without any delay even if I set io.KeyRepeatDelay correctly using default value 0.25f, and the io.DeltaTime is updated correctly, wiggling around 0.016 since V-Sync is on.

I'm using default English Keyboard and English IME (United States).

This problem only happens when inputting characters like numbers, letters and punctuations. Backspaces work out fine with the io.KeyRepeatDelay applied correctly.

The Dx11 example shipped with v1.60 doesn't have this problem, so I think there must be something missing when migrating the code from example file to my application framework.

I checked the WM_CHAR event when I tried to figure out the problem. It seems that in the DX11 example code, the second WM_CHAR event delayed a litter bit after the first WM_CHAR event is triggered. This delay is not being controlled by io.KeyRepeatDelay. While in my code, the second WM_CHAR event triggers instantly without any delay, so when I press a key of character, it instantly spawns a series of WM_CHAR events. The number of events triggered is based on how fast I release the key, ranging from 1 to infinite. :(

I didn't do much change to imgui_impl_dx11.cpp file, only intergrated the ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) function directly to my WinProc function because referencing another WndProc function in my code will somehow cause the Window Registration failed.

Standalone, minimal, complete and verifiable example: Here is my WndProc function. It is not standalone actually, but it's almost like what it writes in imgui_impl_dx11.cpp. Any other functions related to ImGui is directly moved from imgui_impl_dx11.cpp without any change except modifying the ID3D11XX interface to my own resources.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static TWorld world(*pWorld);
    static auto pWindowInfo = world.FetchWorldState<SWindowInfo>();
    LRESULT returnValue = 0;
    switch (message)
    {
    case WM_SIZE:
        // Do some common DX11 resize thing.
        break;
    case WM_ENTERSIZEMOVE:
        // The Application will not update when paused.
        pWindowInfo->bAppPaused = true;
        pWindowInfo->bResizing = true;
        // Time is the Timer I use to query system performance counter.
        pWindowInfo->Time.Stop();
        break;
    case WM_EXITSIZEMOVE:
        pWindowInfo->bAppPaused = false;
        pWindowInfo->bResizing = false;
        pWindowInfo->Time.Start();
        OnResize(*pWorld);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK:
    case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK:
    case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK:
    {
        int button = 0;
        if (message == WM_LBUTTONDOWN || message == WM_LBUTTONDBLCLK) button = 0;
        if (message == WM_RBUTTONDOWN || message == WM_RBUTTONDBLCLK) button = 1;
        if (message == WM_MBUTTONDOWN || message == WM_MBUTTONDBLCLK) button = 2;
        if (!ImGui::IsAnyMouseDown() && ::GetCapture() == NULL)
            ::SetCapture(hWnd);
        ImGuiIO& io = ImGui::GetIO();
        io.MouseDown[button] = true;
        break;
    }
    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
    case WM_MBUTTONUP:
    {
        ImGuiIO& io = ImGui::GetIO();
        int button = 0;
        if (message == WM_LBUTTONUP) button = 0;
        if (message == WM_RBUTTONUP) button = 1;
        if (message == WM_MBUTTONUP) button = 2;
        io.MouseDown[button] = false;
        if (!ImGui::IsAnyMouseDown() && ::GetCapture() == hWnd)
            ::ReleaseCapture();
        break;
    }
    case WM_MOUSEWHEEL:
    {
        ImGuiIO& io = ImGui::GetIO();
        io.MouseWheel += GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1.0f : -1.0f;
        break;
    }

    case WM_MOUSEHWHEEL:
    {
        ImGuiIO& io = ImGui::GetIO();
        io.MouseWheelH += GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1.0f : -1.0f;
        break;
    }
    case WM_MOUSEMOVE:
    {
        ImGuiIO& io = ImGui::GetIO();
        io.MousePos.x = (signed short)(lParam);
        io.MousePos.y = (signed short)(lParam >> 16);
        break;
    }
    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
    {
        ImGuiIO& io = ImGui::GetIO();
        if (wParam < 256)
            io.KeysDown[wParam] = 1;
        if (wParam == VK_OEM_3) //~ Key
        {
            pWindowInfo->bDisplayDebugWindow = !pWindowInfo->bDisplayDebugWindow;
        }
        if (wParam == VK_ESCAPE)
        {
            PostQuitMessage(0);
        }
        break;
    }
    case WM_KEYUP:
    case WM_SYSKEYUP:
    {
        ImGuiIO& io = ImGui::GetIO();
        if (wParam < 256)
            io.KeysDown[wParam] = 0;
        break;
    }
    case WM_CHAR:
    {
        ImGuiIO& io = ImGui::GetIO();
        // You can also use ToAscii()+GetKeyboardState() to retrieve characters.
        if (wParam > 0 && wParam < 0x10000)
            io.AddInputCharacter((unsigned short)wParam);
        break;
    }
    case WM_SETCURSOR:
    {
        if (LOWORD(lParam) == HTCLIENT)
        {
            ImGuiUpdateMouseCursor();
            returnValue = 1;
        }
        break;
    }
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return returnValue;
}

Screenshots/Video Image: problem

ocornut commented 6 years ago

Hello,

Backspaces work out fine with the io.KeyRepeatDelay applied correctly. It seems that in the DX11 example code, the second WM_CHAR event delayed a litter bit after the first WM_CHAR event is triggered. This delay is not being controlled by io.KeyRepeatDelay.

You are right that imgui doesn't control/care about the frequency of character inputs that are fed through io.AddInputCharacter(). The repeat rate of WM_CHAR is entirely Windows' jurisdiction. I don't have the answer for you issue I guess if you Google for things like "WM_CHAR repeat rate" you'll find the source of your fast repeat rate (I assume it is driven by a system settings and there are perhaps ways to override it on a per-process basis?).

Backspace is treated as a key rather than a character (in the specific case of Backspace and Tabs, in some cases they have both keys and char representation, but we use the keys).

We distinguish keys from char which are different concepts in most input systems. It is arguably inconsistent that we handle the repeat rate for keys ourselves but not for characters. The reasoning is that we uses key interactions for various different things, some of them requiring different repeat delay/rate. io.KeyRepeatDelay and io.KeyRepeatRate are a little misleading because not all interactions use those values. Some interactions use modified/scaled values (for example Navigation internally has 3 repeat settings which are scaled based on the user provided io values).

Ideally we would: allow the back-end to notify imgui of standard key repeats event (will look into that when adding event/queue system over current IO) so things like Arrows and Backspace when used in the context of text edition can use the system-driven repeat delay/rate.. And for other interaction clarify, standardize and perhaps expose all the internal delays/rates if used. Surprisingly I don't think anyone before complained about the fact that the Backspace and Arrow Keys repeat delay/rate in the context of an InputText() is not the same as the delay/rate used by characters in the same field. I think the reason is that people rarely rely on repeat for characters.

A general purpose useful article on keyboard handling https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/ (It doesn't have your answer on the WM_CHAR repeat delay/rate unfortunately, just checked. It's a great general purpose article though).

JX-Master commented 6 years ago

Well it has been 3 days, I have Googled a few times but still couldn't find out the reason. I recreate the whole project, recode everything, and the problem still exists. So I made this MINIMUM standalone runnable project that identifies my problem, which is the "Test" project in my solution file. It simply initializes the Direct3D 11 environment and draws a simple window. There is an exe file in "debug" folder in case of any compile error that may occur( It shouldn't although). Issue1808.zip

ocornut commented 6 years ago
ocornut commented 6 years ago

I downloaded your package, looked at your Windows message loop only to find that your PeekMessage/TranslateMessage/DispatchMessage code is incorrect, which is most likely the reason for duplicate messages.

EDIT Closing this, finding why your message handling is incorrect will be an exercise for you. I presume you'll notice that every example of handling Win32 messages differs from your code.