diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp index c3b7ca9cc..de3ea5ad5 100644 --- a/backends/imgui_impl_win32.cpp +++ b/backends/imgui_impl_win32.cpp @@ -121,6 +121,8 @@ struct ImGui_ImplWin32_Data ImGuiMouseCursor LastMouseCursor; UINT32 KeyboardCodePage; + int MouseX, MouseY; // against spurious WM_MOUSEMOVE events + #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD bool HasGamepad; bool WantUpdateHasGamepad; @@ -180,6 +182,8 @@ static bool ImGui_ImplWin32_InitEx(void* hwnd, bool platform_has_own_dc) bd->Time = perf_counter; bd->LastMouseCursor = ImGuiMouseCursor_COUNT; ImGui_ImplWin32_UpdateKeyboardCodePage(io); + bd->MouseX = -1; + bd->MouseY = -1; // Set platform dependent data in viewport ImGuiViewport* main_viewport = ImGui::GetMainViewport(); @@ -393,20 +397,59 @@ static void ImGui_ImplWin32_UpdateGamepads(ImGuiIO& io) #endif } -void ImGui_ImplWin32_NewFrame() +bool ImGui_ImplWin32_NewFrame(bool poll_only) { ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplWin32_Init()?"); ImGuiIO& io = ImGui::GetIO(); + INT64 current_time; + + + + + for (;;) + { + ::QueryPerformanceCounter((LARGE_INTEGER*)¤t_time); + + double next_refresh = !poll_only ? io.NextRefresh : 0.0; + + double cur_delta = double(current_time - bd->Time) / bd->TicksPerSecond; + if (cur_delta <= next_refresh) + { + double ms_to_wait_double = (next_refresh - cur_delta) * 1000.0f; + unsigned int ms_to_wait = ms_to_wait_double >= MAXDWORD ? INFINITE : unsigned int(ms_to_wait_double); + if (ms_to_wait) + MsgWaitForMultipleObjectsEx(0, nullptr, ms_to_wait, QS_ALLEVENTS, 0); + } + + + MSG msg; + while (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) + { + if (msg.message != WM_QUIT) + { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + continue; + } + return false; + } + + + if (cur_delta <= next_refresh) + continue; + + break; + } + + // Setup display size (every frame to accommodate for window resizing) RECT rect = { 0, 0, 0, 0 }; ::GetClientRect(bd->hWnd, &rect); io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); // Setup time step - INT64 current_time = 0; - ::QueryPerformanceCounter((LARGE_INTEGER*)¤t_time); io.DeltaTime = (float)(current_time - bd->Time) / bd->TicksPerSecond; bd->Time = current_time; @@ -426,6 +469,8 @@ void ImGui_ImplWin32_NewFrame() // Update game controllers (if enabled and available) ImGui_ImplWin32_UpdateGamepads(io); + + return true; } // Map VK_xxx to ImGuiKey_xxx. @@ -628,6 +673,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA // This version is in theory thread-safe in the sense that no path should access ImGui::GetCurrentContext(). IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io) { + const char* key_refresh_reason = nullptr; ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io); if (bd == nullptr) return 0; @@ -652,8 +698,19 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPA POINT mouse_pos = { (LONG)GET_X_LPARAM(lParam), (LONG)GET_Y_LPARAM(lParam) }; if (msg == WM_NCMOUSEMOVE && ::ScreenToClient(hwnd, &mouse_pos) == FALSE) // WM_NCMOUSEMOVE are provided in absolute coordinates. return 0; - io.AddMouseSourceEvent(mouse_source); - io.AddMousePosEvent((float)mouse_pos.x, (float)mouse_pos.y); + + { // SergeyN - filter out spurious mouse events + int m_x = GET_X_LPARAM(lParam), m_y = GET_Y_LPARAM(lParam); + if (bd->MouseX != m_x || bd->MouseY != m_y) // spurious WM_MOUSEMOVE events are a real thing. don't act on them + { + bd->MouseX = m_x, bd->MouseY = m_y; + + io.SetNextRefresh(0, "mouse move"); //return 0; + + io.AddMouseSourceEvent(mouse_source); + io.AddMousePosEvent((float)m_x, (float)m_y); + } + } return 0; } case WM_MOUSELEAVE: @@ -665,6 +722,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPA if (bd->MouseHwnd == hwnd) bd->MouseHwnd = nullptr; bd->MouseTrackedArea = 0; + bd->MouseX = -1, bd->MouseY = -1; io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); } return 0; @@ -679,11 +737,18 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPA io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); } return 0; - case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: - case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: - case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: - case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: + case WM_LBUTTONDOWN: key_refresh_reason = "mouse lbuttonup down"; goto md; + case WM_LBUTTONDBLCLK: key_refresh_reason = "mouse lbuttonup dblclk"; goto md; + case WM_RBUTTONDOWN: key_refresh_reason = "mouse rbuttonup down"; goto md; + case WM_RBUTTONDBLCLK: key_refresh_reason = "mouse rbuttonup dblclk"; goto md; + case WM_MBUTTONDOWN: key_refresh_reason = "mouse mbuttonup donw"; goto md; + case WM_MBUTTONDBLCLK: key_refresh_reason = "mouse mbuttonup dblclk"; goto md; + case WM_XBUTTONDOWN: key_refresh_reason = "mouse xbuttonup down"; goto md; + case WM_XBUTTONDBLCLK: key_refresh_reason = "mouse xbuttonup dblclk"; + md: { + io.SetNextRefresh(0, key_refresh_reason); + ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo(); int button = 0; if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONDBLCLK) { button = 0; } @@ -700,11 +765,14 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPA io.AddMouseButtonEvent(button, true); return 0; } - case WM_LBUTTONUP: - case WM_RBUTTONUP: - case WM_MBUTTONUP: - case WM_XBUTTONUP: + case WM_LBUTTONUP:key_refresh_reason = "mouse lbuttonup up"; goto mu; + case WM_RBUTTONUP:key_refresh_reason = "mouse rbutton up"; goto mu; + case WM_MBUTTONUP:key_refresh_reason = "mouse mbutton up"; goto mu; + case WM_XBUTTONUP:key_refresh_reason = "mouse xbutton up"; + mu: { + io.SetNextRefresh(0, key_refresh_reason); + ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo(); int button = 0; if (msg == WM_LBUTTONUP) { button = 0; } @@ -719,19 +787,27 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPA return 0; } case WM_MOUSEWHEEL: + io.SetNextRefresh(0, "wheel up"); io.AddMouseWheelEvent(0.0f, (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA); return 0; case WM_MOUSEHWHEEL: + io.SetNextRefresh(0, "wheel down"); io.AddMouseWheelEvent(-(float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0.0f); return 0; case WM_KEYDOWN: + key_refresh_reason = "key down"; goto l; case WM_KEYUP: + key_refresh_reason = "key up"; goto l; case WM_SYSKEYDOWN: + key_refresh_reason = "syskey down"; goto l; case WM_SYSKEYUP: { + key_refresh_reason = "key up"; +l: const bool is_key_down = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); if (wParam < 256) { + io.SetNextRefresh(0, key_refresh_reason); // Submit modifiers ImGui_ImplWin32_UpdateKeyModifiers(io); diff --git a/backends/imgui_impl_win32.h b/backends/imgui_impl_win32.h index 5ae399e0e..3a9dc3dd7 100644 --- a/backends/imgui_impl_win32.h +++ b/backends/imgui_impl_win32.h @@ -24,7 +24,7 @@ IMGUI_IMPL_API bool ImGui_ImplWin32_Init(void* hwnd); IMGUI_IMPL_API bool ImGui_ImplWin32_InitForOpenGL(void* hwnd); IMGUI_IMPL_API void ImGui_ImplWin32_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplWin32_NewFrame(); +IMGUI_IMPL_API bool ImGui_ImplWin32_NewFrame(bool poll = false); // Win32 message handler your application need to call. // - Intentionally commented out in a '#if 0' block to avoid dragging dependencies on from this helper. diff --git a/examples/example_win32_directx11/main.cpp b/examples/example_win32_directx11/main.cpp index 9ca7705fb..eb5e6dfd3 100644 --- a/examples/example_win32_directx11/main.cpp +++ b/examples/example_win32_directx11/main.cpp @@ -11,6 +11,8 @@ #include "imgui_impl_dx11.h" #include #include +#include +#include // Data static ID3D11Device* g_pd3dDevice = nullptr; @@ -93,6 +95,10 @@ int main(int, char**) bool show_demo_window = true; bool show_another_window = false; ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + int fame_number = 0; + LARGE_INTEGER last_frame_time, timer_freq; + QueryPerformanceFrequency(&timer_freq); + QueryPerformanceCounter(&last_frame_time); // Main loop bool done = false; @@ -100,6 +106,7 @@ int main(int, char**) { // Poll and handle messages (inputs, window resize, etc.) // See the WndProc() function below for our to dispatch events to the Win32 backend. +#if 0 MSG msg; while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) { @@ -108,6 +115,8 @@ int main(int, char**) if (msg.message == WM_QUIT) done = true; } +#endif + if (done) break; @@ -128,9 +137,19 @@ int main(int, char**) CreateRenderTarget(); } + + // Start the Dear ImGui frame + if (!ImGui_ImplWin32_NewFrame()) + break; + + LARGE_INTEGER t0; QueryPerformanceCounter(&t0); + auto refresh_reason = io.NextRefreshStack.Entries[0];// double refresh_delay = io.NextRefresh >= FLT_MAX ? 99.99f : io.NextRefresh; + // Start the Dear ImGui frame ImGui_ImplDX11_NewFrame(); - ImGui_ImplWin32_NewFrame(); + + LARGE_INTEGER t1; QueryPerformanceCounter(&t1); + ImGui::NewFrame(); // 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!). @@ -170,17 +189,39 @@ int main(int, char**) ImGui::End(); } + LARGE_INTEGER t2; QueryPerformanceCounter(&t2); + // Rendering ImGui::Render(); + const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr); g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); + LARGE_INTEGER t3; QueryPerformanceCounter(&t3); + // Present HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync //HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); + + + LARGE_INTEGER t4; QueryPerformanceCounter(&t4); + + double layout_time = double(t2.QuadPart - t1.QuadPart) / timer_freq.QuadPart; + double render_time = double((t1.QuadPart - t0.QuadPart) + (t3.QuadPart - t2.QuadPart)) / timer_freq.QuadPart; + double present_time = double(t4.QuadPart - t3.QuadPart) / timer_freq.QuadPart; + double sleep_time = double(t0.QuadPart - last_frame_time.QuadPart) / timer_freq.QuadPart; + + printf("ImGui #%i(%+6.3fs %ims(I%ims,R%ims,P%ims)), reason: %s (%0.2fs) ... %s", fame_number++, sleep_time, (int)round((layout_time + render_time) * 1000.0), (int)round(layout_time * 1000.0), (int)round(render_time * 1000.0), (int)round(present_time * 1000.0), refresh_reason.reason, refresh_reason.delay, io.NextRefreshStack.Size ? "" : "\n"); + if (io.NextRefreshStack.Size) + { + printf(" refresh stack:"); + for (int i = 0; i < io.NextRefreshStack.Size; ++i) + printf("%c%s(+%0.2fs)", i == 0 ? ' ' : ',', io.NextRefreshStack.Entries[i].reason, io.NextRefreshStack.Entries[i].delay); + printf("\n"); + } } // Cleanup diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 85c598fb1..d515a821f 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #ifdef _DEBUG #define DX12_ENABLE_DEBUG_LAYER @@ -188,6 +190,9 @@ int main(int, char**) bool show_demo_window = true; bool show_another_window = false; ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + LARGE_INTEGER last_frame_time, timer_freq; + QueryPerformanceFrequency(&timer_freq); + QueryPerformanceCounter(&last_frame_time); // Main loop bool done = false; @@ -195,6 +200,7 @@ int main(int, char**) { // Poll and handle messages (inputs, window resize, etc.) // See the WndProc() function below for our to dispatch events to the Win32 backend. +#if 0 MSG msg; while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) { @@ -203,6 +209,8 @@ int main(int, char**) if (msg.message == WM_QUIT) done = true; } +#endif + if (done) break; @@ -215,8 +223,17 @@ int main(int, char**) g_SwapChainOccluded = false; // Start the Dear ImGui frame + if (!ImGui_ImplWin32_NewFrame()) + break; + + LARGE_INTEGER t0; QueryPerformanceCounter(&t0); + auto refresh_reason = io.NextRefreshStack.Entries[0];// double refresh_delay = io.NextRefresh >= FLT_MAX ? 99.99f : io.NextRefresh; + + ImGui_ImplDX12_NewFrame(); - ImGui_ImplWin32_NewFrame(); + + LARGE_INTEGER t1; QueryPerformanceCounter(&t1); + ImGui::NewFrame(); // 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!). @@ -256,6 +273,8 @@ int main(int, char**) ImGui::End(); } + LARGE_INTEGER t2; QueryPerformanceCounter(&t2); + // Rendering ImGui::Render(); @@ -288,10 +307,32 @@ int main(int, char**) g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue); frameCtx->FenceValue = g_fenceLastSignaledValue; + LARGE_INTEGER t3; QueryPerformanceCounter(&t3); + // Present HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync //HRESULT hr = g_pSwapChain->Present(0, g_SwapChainTearingSupport ? DXGI_PRESENT_ALLOW_TEARING : 0); // Present without vsync g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); + + + LARGE_INTEGER t4; QueryPerformanceCounter(&t4); + + double layout_time = double(t2.QuadPart-t1.QuadPart)/timer_freq.QuadPart; + double render_time = double((t1.QuadPart-t0.QuadPart) + (t3.QuadPart-t2.QuadPart)) / timer_freq.QuadPart; + double present_time = double(t4.QuadPart - t3.QuadPart) / timer_freq.QuadPart; + double sleep_time = double(t0.QuadPart - last_frame_time.QuadPart) / timer_freq.QuadPart; + + printf("ImGui #%i(%+6.3fs %ims(I%ims,R%ims,P%ims)), reason: %s (%0.2fs) ... %s", g_frameIndex, sleep_time, (int)round((layout_time + render_time)*1000.0), (int)round(layout_time * 1000.0), (int)round(render_time * 1000.0), (int)round(present_time * 1000.0), refresh_reason.reason, refresh_reason.delay, io.NextRefreshStack.Size ? "" : "\n"); + if (io.NextRefreshStack.Size) + { + printf(" refresh stack:"); + for (int i = 0; i < io.NextRefreshStack.Size; ++i) + printf("%c%s(+%0.2fs)", i == 0 ? ' ' : ',' ,io.NextRefreshStack.Entries[i].reason, io.NextRefreshStack.Entries[i].delay); + printf("\n"); + } + + + last_frame_time = t3; g_frameIndex++; } @@ -440,7 +481,7 @@ bool CreateDeviceD3D(HWND hWnd) swapChain1->Release(); dxgiFactory->Release(); - g_pSwapChain->SetMaximumFrameLatency(APP_NUM_BACK_BUFFERS); + g_pSwapChain->SetMaximumFrameLatency(/*NUM_BACK_BUFFERS*/1); g_hSwapChainWaitableObject = g_pSwapChain->GetFrameLatencyWaitableObject(); } diff --git a/imgui.cpp b/imgui.cpp index 6ba9d0b43..09fb1d0b2 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4384,6 +4384,8 @@ void ImGui::Initialize() RegisterFontAtlas(atlas); g.Initialized = true; + + g.IO.SetNextRefresh(0, "context created"); } // This function is merely here to free heap allocations. @@ -4855,10 +4857,16 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) // but once unlocked on a given item we also moving. //if (g.HoverDelayTimer >= delay && (g.HoverDelayTimer - g.IO.DeltaTime < delay || g.MouseStationaryTimer - g.IO.DeltaTime < g.Style.HoverStationaryDelay)) { IMGUI_DEBUG_LOG("HoverDelayTimer = %f/%f, MouseStationaryTimer = %f\n", g.HoverDelayTimer, delay, g.MouseStationaryTimer); } if ((flags & ImGuiHoveredFlags_Stationary) != 0 && g.HoverItemUnlockedStationaryId != hover_delay_id) - return false; + { + g.IO.SetNextRefresh(0, "HoverDelay start"); + return false; + } if (g.HoverItemDelayTimer < delay) - return false; + { + g.IO.SetNextRefresh(delay- g.HoverItemDelayTimer, "HoverDelay continue"); + return false; + } } return true; @@ -5438,6 +5446,9 @@ void ImGui::NewFrame() IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); ImGuiContext& g = *GImGui; + // set 'no need for refresh later' initially until one of the widgets tells otherwise + g.IO.ResetNextRefresh(); + // Remove pending delete hooks before frame start. // This deferred removal avoid issues of removal while iterating the hook vector for (int n = g.Hooks.Size - 1; n >= 0; n--) @@ -7220,7 +7231,10 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl // Collapse button (submitting first so it gets priority when choosing a navigation init fallback) if (has_collapse_button) if (CollapseButton(window->GetID("#COLLAPSE"), collapse_button_pos)) + { window->WantCollapseToggle = true; // Defer actual collapsing to next frame as we are too far in the Begin() function + g.IO.SetNextRefresh(0, "window collapse toggled"); + } // Close button if (has_close_button) @@ -7545,13 +7559,15 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Hide new windows for one frame until they calculate their size if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api)) + { window->HiddenFramesCannotSkipItems = 1; + } // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) // We reset Size/ContentSize for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size. if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0) { - window->HiddenFramesCannotSkipItems = 1; + window->HiddenFramesCannotSkipItems = 1; if (flags & ImGuiWindowFlags_AlwaysAutoResize) { if (!window_size_x_set_by_api) @@ -7562,6 +7578,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } } + if (window->HiddenFramesCannotSkipItems|window->HiddenFramesCanSkipItems|window->HiddenFramesForRenderOnly) g.IO.SetNextRefresh(0, "Window hidden frame"); // don't delay rendering of new frame to get hidden frames working + // SELECT VIEWPORT // FIXME-VIEWPORT: In the docking/viewport branch, this is the point where we select the current viewport (which may affect the style) @@ -10508,7 +10526,10 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) if (event_n == g.InputEventsQueue.Size) g.InputEventsQueue.resize(0); else + { g.InputEventsQueue.erase(g.InputEventsQueue.Data, g.InputEventsQueue.Data + event_n); + g.IO.SetNextRefresh(0, "inputs queue drain"); + } // Clear buttons state when focus is lost // - this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle. @@ -15873,7 +15894,7 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const cha #ifdef _WIN32 #include // ShellExecuteA() #ifdef _MSC_VER -#pragma comment(lib, "shell32") +//#pragma comment(lib, "shell32") #endif static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char* path) { @@ -15881,7 +15902,10 @@ static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char* path) ImVector path_wbuf; path_wbuf.resize(path_wsize); ::MultiByteToWideChar(CP_UTF8, 0, path, -1, path_wbuf.Data, path_wsize); - return (INT_PTR)::ShellExecuteW(NULL, L"open", path_wbuf.Data, NULL, NULL, SW_SHOWDEFAULT) > 32; +// return (INT_PTR)::ShellExecuteW(NULL, L"open", path_wbuf.Data, NULL, NULL, SW_SHOWDEFAULT) > 32; + typedef decltype(&::ShellExecuteA) PFN_ShellExecuteA; static PFN_ShellExecuteA fShellExecuteA; + static HMODULE hshell32dll = [] { HMODULE h = LoadLibraryA("SHELL32.dll"); if (h) { atexit([]{ FreeLibrary(hshell32dll); }); fShellExecuteA = (PFN_ShellExecuteA) GetProcAddress(hshell32dll, "ShellExecuteA"); } return h; }(); + if (fShellExecuteA) return (INT_PTR)fShellExecuteA(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT) > 32; else return false; } #else #include @@ -15920,7 +15944,7 @@ static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char*) { ret #include #ifdef _MSC_VER -#pragma comment(lib, "imm32") +//#pragma comment(lib, "imm32") #endif static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) @@ -15930,20 +15954,36 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport* view if (hwnd == 0) return; + typedef decltype(&::ImmGetContext) PFN_ImmGetContext; static PFN_ImmGetContext fImmGetContext; + typedef decltype(&::ImmSetCompositionWindow) PFN_ImmSetCompositionWindow; static PFN_ImmSetCompositionWindow fImmSetCompositionWindow; + typedef decltype(&::ImmSetCandidateWindow) PFN_ImmSetCandidateWindow; static PFN_ImmSetCandidateWindow fImmSetCandidateWindow; + typedef decltype(&::ImmReleaseContext) PFN_ImmReleaseContext; static PFN_ImmReleaseContext fImmReleaseContext; + static HMODULE himm32dll = []() { + HMODULE h = LoadLibraryA("imm32.dll"); if (h) { + fImmGetContext = (PFN_ImmGetContext) GetProcAddress(himm32dll, "ImmGetContext"); + fImmSetCompositionWindow = (PFN_ImmSetCompositionWindow) GetProcAddress(himm32dll, "ImmSetCompositionWindow"); + fImmSetCandidateWindow = (PFN_ImmSetCandidateWindow) GetProcAddress(himm32dll, "ImmSetCandidateWindow"); + fImmReleaseContext = (PFN_ImmReleaseContext) GetProcAddress(himm32dll, "ImmReleaseContext"); + if (fImmGetContext && fImmSetCompositionWindow && fImmSetCandidateWindow && fImmReleaseContext) { atexit([]{ FreeLibrary(himm32dll); }); } + FreeLibrary(h); + h = nullptr; + } return h;}(); + + //::ImmAssociateContextEx(hwnd, NULL, data->WantVisible ? IACE_DEFAULT : 0); - if (HIMC himc = ::ImmGetContext(hwnd)) + if (HIMC himc = himm32dll ? fImmGetContext(hwnd) : nullptr) { COMPOSITIONFORM composition_form = {}; composition_form.ptCurrentPos.x = (LONG)data->InputPos.x; composition_form.ptCurrentPos.y = (LONG)data->InputPos.y; composition_form.dwStyle = CFS_FORCE_POSITION; - ::ImmSetCompositionWindow(himc, &composition_form); + fImmSetCompositionWindow(himc, &composition_form); CANDIDATEFORM candidate_form = {}; candidate_form.dwStyle = CFS_CANDIDATEPOS; candidate_form.ptCurrentPos.x = (LONG)data->InputPos.x; candidate_form.ptCurrentPos.y = (LONG)data->InputPos.y; - ::ImmSetCandidateWindow(himc, &candidate_form); - ::ImmReleaseContext(hwnd, himc); + fImmSetCandidateWindow(himc, &candidate_form); + fImmReleaseContext(hwnd, himc); } } diff --git a/imgui.h b/imgui.h index 62fac5151..ddd2b51e8 100644 --- a/imgui.h +++ b/imgui.h @@ -2538,6 +2538,53 @@ struct ImGuiIO int MetricsRenderWindows; // Number of visible windows int MetricsActiveWindows; // Number of active windows ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. + float NextRefresh; // Optional: tells when should next render happen to see any change in results, 0 on init so that first draw goes through +#if 1 + struct NextRefreshStack_t + { + size_t Size; + struct NextRefreshEntry_t { float delay; char reason[64]; } Entries[16]; + } NextRefreshStack; + + void ResetNextRefresh() + { + NextRefresh = FLT_MAX; + NextRefreshStack.Size = 0; + NextRefreshStack.Entries[0].reason[0]='\0'; + NextRefreshStack.Entries[0].delay = FLT_MAX; + } + void SetNextRefresh(float pause_ms, const char* dbg_reason) + { + if (pause_ms <= NextRefresh) + NextRefresh = pause_ms; + + auto* s = NextRefreshStack.Entries; + while (s != NextRefreshStack.Entries + NextRefreshStack.Size) + { + if (pause_ms <= s->delay) + break; + ++s; + } + + if (s < NextRefreshStack.Entries + sizeof(NextRefreshStack.Entries) / sizeof(NextRefreshStack.Entries[0])) + { + size_t new_size = (NextRefreshStack.Size < sizeof(NextRefreshStack.Entries) / sizeof(NextRefreshStack.Entries[0]) ? NextRefreshStack.Size + 1 : sizeof(NextRefreshStack.Entries) / sizeof(NextRefreshStack.Entries[0])); + memmove(s + 1, s, sizeof(NextRefreshStack.Entries[0]) * (NextRefreshStack.Entries + new_size - 1 - s)); + s->delay = pause_ms; +#ifdef _MSC_VER + strncpy_s(s->reason, dbg_reason, sizeof(s->reason) / sizeof(char) - 1); +#else + strncpy(s->reason, dbg_reason, sizeof(s->reason) / sizeof(char) - 1); +#endif + NextRefreshStack.Size = new_size; + } + } + //const char* GetNextRefreshReason() const { return NextRefresh_dbg; } +#else + void ResetNextRefresh() { NextRefresh = FLT_MAX; } + void SetNextRefresh(float pause_ms, const char* /*dbg_line*/) { if (pause_ms <= NextRefresh) NextRefresh = pause_ms; } + const char* GetNextRefreshReason() const { return ""; } +#endif //------------------------------------------------------------------ // [Internal] Dear ImGui will maintain those fields. Forward compatibility not guaranteed! diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index fac10d832..929376f7e 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -757,7 +757,10 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool ClearActiveID(); } if (pressed) + { g.ActiveIdHasBeenPressedBefore = true; + g.IO.SetNextRefresh(0, "button pressed"); + } } // Activation highlight (this may be a remote activation) @@ -1260,6 +1263,7 @@ bool ImGui::Checkbox(const char* label, bool* v) *v = checked; pressed = true; // return value MarkItemEdited(id); + g.IO.SetNextRefresh(0, "checkbox pressed"); } const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); @@ -5576,9 +5580,24 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (render_cursor) { state->CursorAnim += io.DeltaTime; - bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); + + bool cursor_is_visible = true; + if (g.IO.ConfigInputTextCursorBlink) + { + float next_refresh_time = -state->CursorAnim; + if (state->CursorAnim > 0.0f) + { + float modded = ImFmod(state->CursorAnim, 1.20f); + bool now_visible_blink_phase = modded <= 0.80f; + state->CursorAnim = modded; //prevent fp error from accumulation + next_refresh_time = now_visible_blink_phase ? 0.80f - modded : 1.20f - modded; + cursor_is_visible = now_visible_blink_phase; + } + g.IO.SetNextRefresh( next_refresh_time, "text cursor blink"); + } + if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_InputTextCursor), 1.0f); // FIXME-DPI: Cursor thickness (#7031) @@ -9486,6 +9505,9 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut if (menuset_is_open) PopItemFlag(); + if (pressed) + g.IO.SetNextRefresh(0,"menu item pressed"); + return pressed; }