From 2f483373355f77a1591c6ee70eb1d5b77537ba9a Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jan 2024 14:47:56 +0100 Subject: [PATCH 01/17] Examples: Vulkan: Rename compile-time defies for the examples to remove misleading IMGUI_ prefixes. --- examples/example_glfw_vulkan/main.cpp | 18 +++++++++--------- examples/example_sdl2_vulkan/main.cpp | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index e8027c44..7374ebfa 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -31,9 +31,9 @@ #pragma comment(lib, "legacy_stdio_definitions") #endif -//#define IMGUI_UNLIMITED_FRAME_RATE +//#define APP_USE_UNLIMITED_FRAME_RATE #ifdef _DEBUG -#define IMGUI_VULKAN_DEBUG_REPORT +#define APP_USE_VULKAN_DEBUG_REPORT #endif // Data @@ -64,14 +64,14 @@ static void check_vk_result(VkResult err) abort(); } -#ifdef IMGUI_VULKAN_DEBUG_REPORT +#ifdef APP_USE_VULKAN_DEBUG_REPORT static VKAPI_ATTR VkBool32 VKAPI_CALL debug_report(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) { (void)flags; (void)object; (void)location; (void)messageCode; (void)pUserData; (void)pLayerPrefix; // Unused arguments fprintf(stderr, "[vulkan] Debug report from ObjectType: %i\nMessage: %s\n\n", objectType, pMessage); return VK_FALSE; } -#endif // IMGUI_VULKAN_DEBUG_REPORT +#endif // APP_USE_VULKAN_DEBUG_REPORT static bool IsExtensionAvailable(const ImVector& properties, const char* extension) { @@ -139,7 +139,7 @@ static void SetupVulkan(ImVector instance_extensions) #endif // Enabling validation layers -#ifdef IMGUI_VULKAN_DEBUG_REPORT +#ifdef APP_USE_VULKAN_DEBUG_REPORT const char* layers[] = { "VK_LAYER_KHRONOS_validation" }; create_info.enabledLayerCount = 1; create_info.ppEnabledLayerNames = layers; @@ -153,7 +153,7 @@ static void SetupVulkan(ImVector instance_extensions) check_vk_result(err); // Setup the debug report callback -#ifdef IMGUI_VULKAN_DEBUG_REPORT +#ifdef APP_USE_VULKAN_DEBUG_REPORT auto vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkCreateDebugReportCallbackEXT"); IM_ASSERT(vkCreateDebugReportCallbackEXT != nullptr); VkDebugReportCallbackCreateInfoEXT debug_report_ci = {}; @@ -258,7 +258,7 @@ static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface wd->SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(g_PhysicalDevice, wd->Surface, requestSurfaceImageFormat, (size_t)IM_ARRAYSIZE(requestSurfaceImageFormat), requestSurfaceColorSpace); // Select Present Mode -#ifdef IMGUI_UNLIMITED_FRAME_RATE +#ifdef APP_USE_UNLIMITED_FRAME_RATE VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR }; #else VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_FIFO_KHR }; @@ -275,11 +275,11 @@ static void CleanupVulkan() { vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); -#ifdef IMGUI_VULKAN_DEBUG_REPORT +#ifdef APP_USE_VULKAN_DEBUG_REPORT // Remove the debug report callback auto vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkDestroyDebugReportCallbackEXT"); vkDestroyDebugReportCallbackEXT(g_Instance, g_DebugReport, g_Allocator); -#endif // IMGUI_VULKAN_DEBUG_REPORT +#endif // APP_USE_VULKAN_DEBUG_REPORT vkDestroyDevice(g_Device, g_Allocator); vkDestroyInstance(g_Instance, g_Allocator); diff --git a/examples/example_sdl2_vulkan/main.cpp b/examples/example_sdl2_vulkan/main.cpp index bbd9eec4..0b91b128 100644 --- a/examples/example_sdl2_vulkan/main.cpp +++ b/examples/example_sdl2_vulkan/main.cpp @@ -23,9 +23,9 @@ #include //#include -//#define IMGUI_UNLIMITED_FRAME_RATE +//#define APP_USE_UNLIMITED_FRAME_RATE #ifdef _DEBUG -#define IMGUI_VULKAN_DEBUG_REPORT +#define APP_USE_VULKAN_DEBUG_REPORT #endif // Data @@ -52,14 +52,14 @@ static void check_vk_result(VkResult err) abort(); } -#ifdef IMGUI_VULKAN_DEBUG_REPORT +#ifdef APP_USE_VULKAN_DEBUG_REPORT static VKAPI_ATTR VkBool32 VKAPI_CALL debug_report(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) { (void)flags; (void)object; (void)location; (void)messageCode; (void)pUserData; (void)pLayerPrefix; // Unused arguments fprintf(stderr, "[vulkan] Debug report from ObjectType: %i\nMessage: %s\n\n", objectType, pMessage); return VK_FALSE; } -#endif // IMGUI_VULKAN_DEBUG_REPORT +#endif // APP_USE_VULKAN_DEBUG_REPORT static bool IsExtensionAvailable(const ImVector& properties, const char* extension) { @@ -127,7 +127,7 @@ static void SetupVulkan(ImVector instance_extensions) #endif // Enabling validation layers -#ifdef IMGUI_VULKAN_DEBUG_REPORT +#ifdef APP_USE_VULKAN_DEBUG_REPORT const char* layers[] = { "VK_LAYER_KHRONOS_validation" }; create_info.enabledLayerCount = 1; create_info.ppEnabledLayerNames = layers; @@ -141,7 +141,7 @@ static void SetupVulkan(ImVector instance_extensions) check_vk_result(err); // Setup the debug report callback -#ifdef IMGUI_VULKAN_DEBUG_REPORT +#ifdef APP_USE_VULKAN_DEBUG_REPORT auto vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkCreateDebugReportCallbackEXT"); IM_ASSERT(vkCreateDebugReportCallbackEXT != nullptr); VkDebugReportCallbackCreateInfoEXT debug_report_ci = {}; @@ -246,7 +246,7 @@ static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface wd->SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(g_PhysicalDevice, wd->Surface, requestSurfaceImageFormat, (size_t)IM_ARRAYSIZE(requestSurfaceImageFormat), requestSurfaceColorSpace); // Select Present Mode -#ifdef IMGUI_UNLIMITED_FRAME_RATE +#ifdef APP_UNLIMITED_FRAME_RATE VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR }; #else VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_FIFO_KHR }; @@ -263,11 +263,11 @@ static void CleanupVulkan() { vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); -#ifdef IMGUI_VULKAN_DEBUG_REPORT +#ifdef APP_USE_VULKAN_DEBUG_REPORT // Remove the debug report callback auto vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkDestroyDebugReportCallbackEXT"); vkDestroyDebugReportCallbackEXT(g_Instance, g_DebugReport, g_Allocator); -#endif // IMGUI_VULKAN_DEBUG_REPORT +#endif // APP_USE_VULKAN_DEBUG_REPORT vkDestroyDevice(g_Device, g_Allocator); vkDestroyInstance(g_Instance, g_Allocator); From 5ddfbb80d8667efd16245e450e109890ac950fbc Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jan 2024 14:50:32 +0100 Subject: [PATCH 02/17] Backends: Vulkan: Fixed vkAcquireNextImageKHR() validation errors in VulkanSDK 1.3.275 by allocating one extra semaphore than in-flight frames. (#7236) --- backends/imgui_impl_vulkan.cpp | 23 +++++++++++++---------- backends/imgui_impl_vulkan.h | 1 + docs/CHANGELOG.txt | 2 ++ examples/example_glfw_vulkan/main.cpp | 2 +- examples/example_sdl2_vulkan/main.cpp | 2 +- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 73213c98..91bd43b6 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -33,6 +33,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2024-01-19: Vulkan: Fixed vkAcquireNextImageKHR() validation errors in VulkanSDK 1.3.275 by allocating one extra semaphore than in-flight frames. (#7236) // 2024-01-11: Vulkan: Fixed vkMapMemory() calls unnecessarily using full buffer size (#3957). Fixed MinAllocationSize handing (#7189). // 2024-01-03: Vulkan: Added MinAllocationSize field in ImGui_ImplVulkan_InitInfo to workaround zealous "best practice" validation layer. (#7189, #4238) // 2024-01-03: Vulkan: Stoped creating command pools with VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT as we don't reset them. @@ -1305,15 +1306,13 @@ VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_d void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator) { IM_ASSERT(physical_device != VK_NULL_HANDLE && device != VK_NULL_HANDLE); - (void)physical_device; - (void)allocator; + IM_UNUSED(physical_device); // Create Command Buffers VkResult err; for (uint32_t i = 0; i < wd->ImageCount; i++) { ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; - ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[i]; { VkCommandPoolCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; @@ -1338,6 +1337,11 @@ void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_devi err = vkCreateFence(device, &info, allocator, &fd->Fence); check_vk_result(err); } + } + + for (uint32_t i = 0; i < wd->SemaphoreCount; i++) + { + ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[i]; { VkSemaphoreCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; @@ -1373,10 +1377,9 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V // We don't use ImGui_ImplVulkanH_DestroyWindow() because we want to preserve the old swapchain to create the new one. // Destroy old Framebuffer for (uint32_t i = 0; i < wd->ImageCount; i++) - { ImGui_ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator); + for (uint32_t i = 0; i < wd->SemaphoreCount; i++) ImGui_ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator); - } IM_FREE(wd->Frames); IM_FREE(wd->FrameSemaphores); wd->Frames = nullptr; @@ -1435,11 +1438,12 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, backbuffers); check_vk_result(err); - IM_ASSERT(wd->Frames == nullptr); + IM_ASSERT(wd->Frames == nullptr && wd->FrameSemaphores == nullptr); + wd->SemaphoreCount = wd->ImageCount + 1; wd->Frames = (ImGui_ImplVulkanH_Frame*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_Frame) * wd->ImageCount); - wd->FrameSemaphores = (ImGui_ImplVulkanH_FrameSemaphores*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameSemaphores) * wd->ImageCount); + wd->FrameSemaphores = (ImGui_ImplVulkanH_FrameSemaphores*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameSemaphores) * wd->SemaphoreCount); memset(wd->Frames, 0, sizeof(wd->Frames[0]) * wd->ImageCount); - memset(wd->FrameSemaphores, 0, sizeof(wd->FrameSemaphores[0]) * wd->ImageCount); + memset(wd->FrameSemaphores, 0, sizeof(wd->FrameSemaphores[0]) * wd->SemaphoreCount); for (uint32_t i = 0; i < wd->ImageCount; i++) wd->Frames[i].Backbuffer = backbuffers[i]; } @@ -1546,10 +1550,9 @@ void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui //vkQueueWaitIdle(bd->Queue); for (uint32_t i = 0; i < wd->ImageCount; i++) - { ImGui_ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator); + for (uint32_t i = 0; i < wd->SemaphoreCount; i++) ImGui_ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator); - } IM_FREE(wd->Frames); IM_FREE(wd->FrameSemaphores); wd->Frames = nullptr; diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 490fbb04..94d0bf27 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -155,6 +155,7 @@ struct ImGui_ImplVulkanH_Window VkClearValue ClearValue; uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount) uint32_t ImageCount; // Number of simultaneous in-flight frames (returned by vkGetSwapchainImagesKHR, usually derived from min_image_count) + uint32_t SemaphoreCount; // Number of simultaneous in-flight frames + 1, to be able to use it in vkAcquireNextImageKHR uint32_t SemaphoreIndex; // Current set of swapchain wait semaphores we're using (needs to be distinct from per frame data) ImGui_ImplVulkanH_Frame* Frames; ImGui_ImplVulkanH_FrameSemaphores* FrameSemaphores; diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 418fdc90..c1eae35b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -53,6 +53,8 @@ Other changes: regression from 1.90.1 related to code scoping Tab presses to local scope. (#7226) [@bratpilz] - Debug Tools: Metrics: Fixed debug break in SetShortcutRouting() not handling ImGuiMod_Shortcut redirect. - Debug Tools: Debug Log: Added "Input Routing" logging. +- Backends: Vulkan: Fixed vkAcquireNextImageKHR() validation errors in VulkanSDK 1.3.275 by + allocating one extra semaphore than in-flight frames. (#7236) [@mklefrancois] - Backends: Vulkan: Fixed vkMapMemory() calls unnecessarily using full buffer size. (#3957) - Backends: Vulkan: Fixed handling of ImGui_ImplVulkan_InitInfo::MinAllocationSize field. (#7189, #4238) - Backends: WebGPU: Filling all WGPUDepthStencilState fields explicitly as a recent Dawn diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index 7374ebfa..98e8dc27 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -376,7 +376,7 @@ static void FramePresent(ImGui_ImplVulkanH_Window* wd) return; } check_vk_result(err); - wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->ImageCount; // Now we can use the next set of semaphores + wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->SemaphoreCount; // Now we can use the next set of semaphores } // Main code diff --git a/examples/example_sdl2_vulkan/main.cpp b/examples/example_sdl2_vulkan/main.cpp index 0b91b128..6de55701 100644 --- a/examples/example_sdl2_vulkan/main.cpp +++ b/examples/example_sdl2_vulkan/main.cpp @@ -364,7 +364,7 @@ static void FramePresent(ImGui_ImplVulkanH_Window* wd) return; } check_vk_result(err); - wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->ImageCount; // Now we can use the next set of semaphores + wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->SemaphoreCount; // Now we can use the next set of semaphores } // Main code From 1844f903d5538323cb8d15b68b19671149142c70 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jan 2024 15:52:11 +0100 Subject: [PATCH 03/17] Nav: space/enter poll check ownership. InputText: declare ownership of Enter key as it doesn't go through Shortcut InputText: no need to call SetShortcutRouting() directly. Tangential to experiments for #7237 --- imgui.cpp | 10 ++++++---- imgui.h | 2 +- imgui_widgets.cpp | 21 +++++++++++++++------ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 6ceee225..6bcfd90c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8445,6 +8445,7 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI return false; } + // FIXME-SHORTCUT: A way to configure the location/focus-scope to test would render this more flexible. const int score = CalcRoutingScore(g.CurrentFocusScopeId, owner_id, flags); IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> score %d\n", GetKeyChordName(key_chord), owner_id, flags, score); if (score == 255) @@ -11825,10 +11826,10 @@ static void ImGui::NavUpdate() g.NavActivateFlags = ImGuiActivateFlags_None; if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) { - const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate)); - const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, false)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, false))); - const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_KeypadEnter))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput)); - const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, false) || IsKeyPressed(ImGuiKey_KeypadEnter, false))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, false))); + const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_None)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_None)); + const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, ImGuiKeyOwner_None)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_None))); + const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter, ImGuiKeyOwner_None) || IsKeyDown(ImGuiKey_KeypadEnter, ImGuiKeyOwner_None))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_None)); + const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, ImGuiKeyOwner_None) || IsKeyPressed(ImGuiKey_KeypadEnter, ImGuiKeyOwner_None))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_None))); if (g.ActiveId == 0 && activate_pressed) { g.NavActivateId = g.NavId; @@ -15304,6 +15305,7 @@ void ImGui::DebugLocateItem(ImGuiID target_id) g.DebugBreakInLocateId = false; } +// FIXME: Doesn't work over through a modal window, because they clear HoveredWindow. void ImGui::DebugLocateItemOnHover(ImGuiID target_id) { if (target_id == 0 || !IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup)) diff --git a/imgui.h b/imgui.h index c8054a5c..8b4ca04d 100644 --- a/imgui.h +++ b/imgui.h @@ -24,7 +24,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.90.2 WIP" -#define IMGUI_VERSION_NUM 19013 +#define IMGUI_VERSION_NUM 19014 #define IMGUI_HAS_TABLE /* diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3c6937a9..6f1ab902 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4251,6 +4251,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + SetKeyOwner(ImGuiKey_Enter, id); + SetKeyOwner(ImGuiKey_KeypadEnter, id); SetKeyOwner(ImGuiKey_Home, id); SetKeyOwner(ImGuiKey_End, id); if (is_multiline) @@ -4260,8 +4262,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (is_osx) SetKeyOwner(ImGuiMod_Alt, id); - if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character. - SetShortcutRouting(ImGuiKey_Tab, id); } // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) @@ -4390,11 +4390,20 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336) // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes) - if ((flags & ImGuiInputTextFlags_AllowTabInput) && Shortcut(ImGuiKey_Tab, id, ImGuiInputFlags_Repeat) && !is_readonly) + if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly) { - unsigned int c = '\t'; // Insert TAB - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) - state->OnKeyPressed((int)c); + if (Shortcut(ImGuiKey_Tab, id, ImGuiInputFlags_Repeat)) + { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + state->OnKeyPressed((int)c); + } + // FIXME: Implement Shift+Tab + /* + if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, id, ImGuiInputFlags_Repeat)) + { + } + */ } // Process regular text input (before we check for Return because using some IME will effectively send a Return?) From c7edb446caae0b4a30687408d56f4590c3ac2ae3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jan 2024 16:38:25 +0100 Subject: [PATCH 04/17] Shortcut(): always test ownership. - It doesn't sense to test route without ownership (which may be overrided by code not using routing) - It also wouldn't be possible to call Shortcut() with _None anyway, since successful routing sets ownership. Tangential to experiments for #7237 --- imgui.cpp | 18 ++++++++++++------ imgui_internal.h | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 6bcfd90c..f9297f3c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8406,8 +8406,6 @@ static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord) // - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state. // (Conceptually this does a "Submit for next frame" + "Test for current frame". // As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.) -// - Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default) -// - Using 'owner_id == ImGuiKeyOwner_None': allows disabling/locking a shortcut. bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; @@ -8415,6 +8413,8 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI flags |= ImGuiInputFlags_RouteGlobalHigh; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut() else IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used + IM_ASSERT(owner_id != ImGuiKeyOwner_Any && owner_id != ImGuiKeyOwner_None); + if (key_chord & ImGuiMod_Shortcut) key_chord = ConvertShortcutMod(key_chord); @@ -8454,18 +8454,17 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI // Submit routing for NEXT frame (assuming score is sufficient) // FIXME: Could expose a way to use a "serve last" policy for same score resolution (using <= instead of <). ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); - const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); //const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore); if (score < routing_data->RoutingNextScore) { - routing_data->RoutingNext = routing_id; + routing_data->RoutingNext = owner_id; routing_data->RoutingNextScore = (ImU8)score; } // Return routing state for CURRENT frame - if (routing_data->RoutingCurr == routing_id) + if (routing_data->RoutingCurr == owner_id) IMGUI_DEBUG_LOG_INPUTROUTING("--> granting current route\n"); - return routing_data->RoutingCurr == routing_id; + return routing_data->RoutingCurr == owner_id; } // Currently unused by core (but used by tests) @@ -9428,6 +9427,13 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any. if ((flags & ImGuiInputFlags_RouteMask_) == 0) flags |= ImGuiInputFlags_RouteFocused; + + // Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default) + // Effectively makes Shortcut() always input-owner aware. + if (owner_id == ImGuiKeyOwner_Any || owner_id == ImGuiKeyOwner_None) + owner_id = GetRoutingIdFromOwnerId(owner_id); + + // Submit route if (!SetShortcutRouting(key_chord, owner_id, flags)) return false; diff --git a/imgui_internal.h b/imgui_internal.h index 1b485537..c51c8279 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3210,7 +3210,7 @@ namespace ImGui // - Shortcut() submits a route then if currently can be routed calls IsKeyChordPressed() -> function has (desirable) side-effects. IMGUI_API bool IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags = 0); IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); - IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); + IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags = 0); // owner_id needs to be explicit and cannot be 0 IMGUI_API bool TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id); IMGUI_API ImGuiKeyRoutingData* GetShortcutRoutingData(ImGuiKeyChord key_chord); From 763100b38580dc661e190452b35bf64efd01147e Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jan 2024 18:37:21 +0100 Subject: [PATCH 05/17] Nav: Fixed pressing Escape while in a child window with _NavFlattened flag. (#7237) --- docs/CHANGELOG.txt | 1 + imgui.cpp | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c1eae35b..4c524a73 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -51,6 +51,7 @@ Other changes: the hover highlight to stay even while another item is activated. - Nav: Fixed SetKeyboardFocusHere() not working when current nav focus is in different scope, regression from 1.90.1 related to code scoping Tab presses to local scope. (#7226) [@bratpilz] +- Nav: Fixed pressing Escape while in a child window with _NavFlattened flag. (#7237) - Debug Tools: Metrics: Fixed debug break in SetShortcutRouting() not handling ImGuiMod_Shortcut redirect. - Debug Tools: Debug Log: Added "Input Routing" logging. - Backends: Vulkan: Fixed vkAcquireNextImageKHR() validation errors in VulkanSDK 1.3.275 by diff --git a/imgui.cpp b/imgui.cpp index f9297f3c..ba3c97b0 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -12247,15 +12247,14 @@ static void ImGui::NavUpdateCancelRequest() NavRestoreLayer(ImGuiNavLayer_Main); NavRestoreHighlightAfterMove(); } - else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) + else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->RootWindowForNav->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->RootWindowForNav->ParentWindow) { // Exit child window - ImGuiWindow* child_window = g.NavWindow; - ImGuiWindow* parent_window = g.NavWindow->ParentWindow; + ImGuiWindow* child_window = g.NavWindow->RootWindowForNav; + ImGuiWindow* parent_window = child_window->ParentWindow; IM_ASSERT(child_window->ChildId != 0); - ImRect child_rect = child_window->Rect(); FocusWindow(parent_window); - SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_rect)); + SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_window->Rect())); NavRestoreHighlightAfterMove(); } else if (g.OpenPopupStack.Size > 0 && g.OpenPopupStack.back().Window != NULL && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) From 788747f86359066d850783ebf8d4ebfad7656fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Cicho=C5=84?= Date: Mon, 22 Jan 2024 10:45:41 +0100 Subject: [PATCH 06/17] Examples: Emscripten+WebGPU: Remove use of deprecated ObjectBase<...>::Release in favor of ::MoveToCHandle (#7251) --- examples/example_emscripten_wgpu/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index 590453c1..73791a54 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -128,7 +128,7 @@ static bool InitWGPU() wgpu::Surface surface = instance.CreateSurface(&surface_desc); wgpu::Adapter adapter = {}; wgpu_preferred_fmt = (WGPUTextureFormat)surface.GetPreferredFormat(adapter); - wgpu_surface = surface.Release(); + wgpu_surface = surface.MoveToCHandle(); return true; } From 15908502ed6da3ca3a3b72d1a9d8f6097315db1c Mon Sep 17 00:00:00 2001 From: Frank McCoy Date: Mon, 22 Jan 2024 01:03:46 -0800 Subject: [PATCH 07/17] Backends: Vulkan: Define NOMINMAX when VK_USE_PLATFORM_WIN32_KHR is defined. (#7250) --- backends/imgui_impl_vulkan.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 94d0bf27..94c5b1d3 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -46,7 +46,12 @@ #if defined(IMGUI_IMPL_VULKAN_NO_PROTOTYPES) && !defined(VK_NO_PROTOTYPES) #define VK_NO_PROTOTYPES #endif +#if defined(VK_USE_PLATFORM_WIN32_KHR) && !defined(NOMINMAX) +#define NOMINMAX #include +#else +#include +#endif // Initialization data, for ImGui_ImplVulkan_Init() // [Please zero-clear before use!] From e3c7ff944d52154c3e5e6db6d3b8fba65b1a1547 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 22 Jan 2024 14:20:24 +0100 Subject: [PATCH 08/17] Examples: Emscripten+WebGPU: slightly refactor like other Emscripten compatible Desktop examples, as aiming to make this suppot desktop eventually. Also aimed at reducing diff for https://github.com/ocornut/imgui/pull/7132 tho this will lead in conflict. --- .../{Makefile => Makefile.emscripten} | 0 examples/example_emscripten_wgpu/README.md | 2 +- examples/example_emscripten_wgpu/main.cpp | 289 ++++++++++-------- 3 files changed, 156 insertions(+), 135 deletions(-) rename examples/example_emscripten_wgpu/{Makefile => Makefile.emscripten} (100%) diff --git a/examples/example_emscripten_wgpu/Makefile b/examples/example_emscripten_wgpu/Makefile.emscripten similarity index 100% rename from examples/example_emscripten_wgpu/Makefile rename to examples/example_emscripten_wgpu/Makefile.emscripten diff --git a/examples/example_emscripten_wgpu/README.md b/examples/example_emscripten_wgpu/README.md index c4c4dec7..e60025da 100644 --- a/examples/example_emscripten_wgpu/README.md +++ b/examples/example_emscripten_wgpu/README.md @@ -6,7 +6,7 @@ - You may also refer to our [Continuous Integration setup](https://github.com/ocornut/imgui/tree/master/.github/workflows) for Emscripten setup. -- Then build using `make` while in the `example_emscripten_wgpu/` directory. +- Then build using `make -f Makefile.emscripten` while in the `example_emscripten_wgpu/` directory. - Requires recent Emscripten as WGPU is still a work-in-progress API. diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index 73791a54..cb09d422 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -11,13 +11,20 @@ #include "imgui_impl_glfw.h" #include "imgui_impl_wgpu.h" #include +#ifdef __EMSCRIPTEN__ #include #include #include +#endif #include #include #include +// This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. +#ifdef __EMSCRIPTEN__ +#include "../libs/emscripten/emscripten_mainloop_stub.h" +#endif + // Global WebGPU required states static WGPUDevice wgpu_device = nullptr; static WGPUSurface wgpu_surface = nullptr; @@ -27,15 +34,32 @@ static int wgpu_swap_chain_width = 0; static int wgpu_swap_chain_height = 0; // Forward declarations -static void MainLoopStep(void* window); static bool InitWGPU(); -static void print_glfw_error(int error, const char* description); -static void print_wgpu_error(WGPUErrorType error_type, const char* message, void*); +static void CreateSwapChain(int width, int height); + +static void glfw_error_callback(int error, const char* description) +{ + printf("GLFW Error %d: %s\n", error, description); +} + +static void wgpu_error_callback(WGPUErrorType error_type, const char* message, void*) +{ + const char* error_type_lbl = ""; + switch (error_type) + { + case WGPUErrorType_Validation: error_type_lbl = "Validation"; break; + case WGPUErrorType_OutOfMemory: error_type_lbl = "Out of memory"; break; + case WGPUErrorType_Unknown: error_type_lbl = "Unknown"; break; + case WGPUErrorType_DeviceLost: error_type_lbl = "Device lost"; break; + default: error_type_lbl = "Unknown"; + } + printf("%s error: %s\n", error_type_lbl, message); +} // Main code int main(int, char**) { - glfwSetErrorCallback(print_glfw_error); + glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) return 1; @@ -43,11 +67,8 @@ int main(int, char**) // This needs to be done explicitly later. glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui GLFW+WebGPU example", nullptr, nullptr); - if (!window) - { - glfwTerminate(); + if (window == nullptr) return 1; - } // Initialize the WebGPU environment if (!InitWGPU()) @@ -66,17 +87,15 @@ int main(int, char**) io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. - // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. - io.IniFilename = nullptr; - // Setup Dear ImGui style ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); // Setup Platform/Renderer backends ImGui_ImplGlfw_InitForOther(window, true); +#ifdef __EMSCRIPTEN__ ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback("#canvas"); +#endif ImGui_ImplWGPU_Init(wgpu_device, 3, wgpu_preferred_fmt, WGPUTextureFormat_Undefined); // Load Fonts @@ -99,10 +118,117 @@ int main(int, char**) //IM_ASSERT(font != nullptr); #endif - // This function will directly return and exit the main function. - // Make sure that no required objects get cleaned up. - // This way we can use the browsers 'requestAnimationFrame' to control the rendering. - emscripten_set_main_loop_arg(MainLoopStep, window, 0, false); + // Our state + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // Main loop +#ifdef __EMSCRIPTEN__ + // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. + // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. + io.IniFilename = nullptr; + EMSCRIPTEN_MAINLOOP_BEGIN +#else + while (!glfwWindowShouldClose(window)) +#endif + { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + glfwPollEvents(); + + // React to changes in screen size + int width, height; + glfwGetFramebufferSize((GLFWwindow*)window, &width, &height); + if (width != wgpu_swap_chain_width && height != wgpu_swap_chain_height) + { + ImGui_ImplWGPU_InvalidateDeviceObjects(); + CreateSwapChain(width, height); + ImGui_ImplWGPU_CreateDeviceObjects(); + } + + // Start the Dear ImGui frame + ImGui_ImplWGPU_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + 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!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + + WGPURenderPassColorAttachment color_attachments = {}; + color_attachments.loadOp = WGPULoadOp_Clear; + color_attachments.storeOp = WGPUStoreOp_Store; + color_attachments.clearValue = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; + color_attachments.view = wgpuSwapChainGetCurrentTextureView(wgpu_swap_chain); + + WGPURenderPassDescriptor render_pass_desc = {}; + render_pass_desc.colorAttachmentCount = 1; + render_pass_desc.colorAttachments = &color_attachments; + render_pass_desc.depthStencilAttachment = nullptr; + + WGPUCommandEncoderDescriptor enc_desc = {}; + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device, &enc_desc); + + WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc); + ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass); + wgpuRenderPassEncoderEnd(pass); + + WGPUCommandBufferDescriptor cmd_buffer_desc = {}; + WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); + WGPUQueue queue = wgpuDeviceGetQueue(wgpu_device); + wgpuQueueSubmit(queue, 1, &cmd_buffer); + } +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_END; +#endif + + // Cleanup + ImGui_ImplWGPU_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(window); + glfwTerminate(); return 0; } @@ -113,7 +239,7 @@ static bool InitWGPU() if (!wgpu_device) return false; - wgpuDeviceSetUncapturedErrorCallback(wgpu_device, print_wgpu_error, nullptr); + wgpuDeviceSetUncapturedErrorCallback(wgpu_device, wgpu_error_callback, nullptr); // Use C++ wrapper due to misbehavior in Emscripten. // Some offset computation for wgpuInstanceCreateSurface in JavaScript @@ -133,122 +259,17 @@ static bool InitWGPU() return true; } -static void MainLoopStep(void* window) +static void CreateSwapChain(int width, int height) { - ImGuiIO& io = ImGui::GetIO(); - - glfwPollEvents(); - - int width, height; - glfwGetFramebufferSize((GLFWwindow*)window, &width, &height); - - // React to changes in screen size - if (width != wgpu_swap_chain_width && height != wgpu_swap_chain_height) - { - ImGui_ImplWGPU_InvalidateDeviceObjects(); - if (wgpu_swap_chain) - wgpuSwapChainRelease(wgpu_swap_chain); - wgpu_swap_chain_width = width; - wgpu_swap_chain_height = height; - WGPUSwapChainDescriptor swap_chain_desc = {}; - swap_chain_desc.usage = WGPUTextureUsage_RenderAttachment; - swap_chain_desc.format = wgpu_preferred_fmt; - swap_chain_desc.width = width; - swap_chain_desc.height = height; - swap_chain_desc.presentMode = WGPUPresentMode_Fifo; - wgpu_swap_chain = wgpuDeviceCreateSwapChain(wgpu_device, wgpu_surface, &swap_chain_desc); - ImGui_ImplWGPU_CreateDeviceObjects(); - } - - // Start the Dear ImGui frame - ImGui_ImplWGPU_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - // Our state - // (we use static, which essentially makes the variable globals, as a convenience to keep the example code easy to follow) - static bool show_demo_window = true; - static bool show_another_window = false; - static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - - // 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); - - // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. - { - static float f = 0.0f; - static int counter = 0; - - ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. - - ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) - ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state - ImGui::Checkbox("Another Window", &show_another_window); - - ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f - ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color - - if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) - counter++; - ImGui::SameLine(); - ImGui::Text("counter = %d", counter); - - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); - ImGui::End(); - } - - // 3. Show another simple window. - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) - ImGui::Text("Hello from another window!"); - if (ImGui::Button("Close Me")) - show_another_window = false; - ImGui::End(); - } - - // Rendering - ImGui::Render(); - - WGPURenderPassColorAttachment color_attachments = {}; - color_attachments.loadOp = WGPULoadOp_Clear; - color_attachments.storeOp = WGPUStoreOp_Store; - color_attachments.clearValue = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; - color_attachments.view = wgpuSwapChainGetCurrentTextureView(wgpu_swap_chain); - WGPURenderPassDescriptor render_pass_desc = {}; - render_pass_desc.colorAttachmentCount = 1; - render_pass_desc.colorAttachments = &color_attachments; - render_pass_desc.depthStencilAttachment = nullptr; - - WGPUCommandEncoderDescriptor enc_desc = {}; - WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device, &enc_desc); - - WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc); - ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass); - wgpuRenderPassEncoderEnd(pass); - - WGPUCommandBufferDescriptor cmd_buffer_desc = {}; - WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); - WGPUQueue queue = wgpuDeviceGetQueue(wgpu_device); - wgpuQueueSubmit(queue, 1, &cmd_buffer); -} - -static void print_glfw_error(int error, const char* description) -{ - printf("GLFW Error %d: %s\n", error, description); -} - -static void print_wgpu_error(WGPUErrorType error_type, const char* message, void*) -{ - const char* error_type_lbl = ""; - switch (error_type) - { - case WGPUErrorType_Validation: error_type_lbl = "Validation"; break; - case WGPUErrorType_OutOfMemory: error_type_lbl = "Out of memory"; break; - case WGPUErrorType_Unknown: error_type_lbl = "Unknown"; break; - case WGPUErrorType_DeviceLost: error_type_lbl = "Device lost"; break; - default: error_type_lbl = "Unknown"; - } - printf("%s error: %s\n", error_type_lbl, message); + if (wgpu_swap_chain) + wgpuSwapChainRelease(wgpu_swap_chain); + wgpu_swap_chain_width = width; + wgpu_swap_chain_height = height; + WGPUSwapChainDescriptor swap_chain_desc = {}; + swap_chain_desc.usage = WGPUTextureUsage_RenderAttachment; + swap_chain_desc.format = wgpu_preferred_fmt; + swap_chain_desc.width = width; + swap_chain_desc.height = height; + swap_chain_desc.presentMode = WGPUPresentMode_Fifo; + wgpu_swap_chain = wgpuDeviceCreateSwapChain(wgpu_device, wgpu_surface, &swap_chain_desc); } From 831d42c1ab3df7324328a60ccb544a4dd02aa185 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 22 Jan 2024 14:46:41 +0100 Subject: [PATCH 09/17] Backends: WebGPU: ImGui_ImplWGPU_Init() now takes a ImGui_ImplWGPU_InitInfo structure instead of variety of parameters, allowing for easier further changes. (#7240) --- backends/imgui_impl_wgpu.cpp | 37 ++++++++++++----------- backends/imgui_impl_wgpu.h | 11 ++++++- docs/CHANGELOG.txt | 2 ++ examples/example_emscripten_wgpu/main.cpp | 7 ++++- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index c987c6d1..565aaad7 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -16,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2024-01-22: (Breaking) ImGui_ImplWGPU_Init() now takes a ImGui_ImplWGPU_InitInfo structure instead of variety of parameters, allowing for easier further changes. // 2024-01-17: Explicitly fill all of WGPUDepthStencilState since standard removed defaults. // 2023-07-13: Use WGPUShaderModuleWGSLDescriptor's code instead of source. use WGPUMipmapFilterMode_Linear instead of WGPUFilterMode_Linear. (#6602) // 2023-04-11: Align buffer sizes. Use WGSL shaders instead of precompiled SPIR-V. @@ -73,16 +74,17 @@ struct Uniforms struct ImGui_ImplWGPU_Data { - WGPUDevice wgpuDevice = nullptr; - WGPUQueue defaultQueue = nullptr; - WGPUTextureFormat renderTargetFormat = WGPUTextureFormat_Undefined; - WGPUTextureFormat depthStencilFormat = WGPUTextureFormat_Undefined; - WGPURenderPipeline pipelineState = nullptr; - - RenderResources renderResources; - FrameResources* pFrameResources = nullptr; - unsigned int numFramesInFlight = 0; - unsigned int frameIndex = UINT_MAX; + ImGui_ImplWGPU_InitInfo initInfo; + WGPUDevice wgpuDevice = nullptr; + WGPUQueue defaultQueue = nullptr; + WGPUTextureFormat renderTargetFormat = WGPUTextureFormat_Undefined; + WGPUTextureFormat depthStencilFormat = WGPUTextureFormat_Undefined; + WGPURenderPipeline pipelineState = nullptr; + + RenderResources renderResources; + FrameResources* pFrameResources = nullptr; + unsigned int numFramesInFlight = 0; + unsigned int frameIndex = UINT_MAX; }; // Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts @@ -712,7 +714,7 @@ void ImGui_ImplWGPU_InvalidateDeviceObjects() SafeRelease(bd->pFrameResources[i]); } -bool ImGui_ImplWGPU_Init(WGPUDevice device, int num_frames_in_flight, WGPUTextureFormat rt_format, WGPUTextureFormat depth_format) +bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info) { ImGuiIO& io = ImGui::GetIO(); IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); @@ -723,11 +725,12 @@ bool ImGui_ImplWGPU_Init(WGPUDevice device, int num_frames_in_flight, WGPUTextur io.BackendRendererName = "imgui_impl_webgpu"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - bd->wgpuDevice = device; + bd->initInfo = *init_info; + bd->wgpuDevice = init_info->Device; bd->defaultQueue = wgpuDeviceGetQueue(bd->wgpuDevice); - bd->renderTargetFormat = rt_format; - bd->depthStencilFormat = depth_format; - bd->numFramesInFlight = num_frames_in_flight; + bd->renderTargetFormat = init_info->RenderTargetFormat; + bd->depthStencilFormat = init_info->DepthStencilFormat; + bd->numFramesInFlight = init_info->NumFramesInFlight; bd->frameIndex = UINT_MAX; bd->renderResources.FontTexture = nullptr; @@ -740,8 +743,8 @@ bool ImGui_ImplWGPU_Init(WGPUDevice device, int num_frames_in_flight, WGPUTextur bd->renderResources.ImageBindGroupLayout = nullptr; // Create buffers with a default size (they will later be grown as needed) - bd->pFrameResources = new FrameResources[num_frames_in_flight]; - for (int i = 0; i < num_frames_in_flight; i++) + bd->pFrameResources = new FrameResources[bd->numFramesInFlight]; + for (int i = 0; i < bd->numFramesInFlight; i++) { FrameResources* fr = &bd->pFrameResources[i]; fr->IndexBuffer = nullptr; diff --git a/backends/imgui_impl_wgpu.h b/backends/imgui_impl_wgpu.h index 14d9fccc..55f96b8e 100644 --- a/backends/imgui_impl_wgpu.h +++ b/backends/imgui_impl_wgpu.h @@ -20,7 +20,16 @@ #include -IMGUI_IMPL_API bool ImGui_ImplWGPU_Init(WGPUDevice device, int num_frames_in_flight, WGPUTextureFormat rt_format, WGPUTextureFormat depth_format = WGPUTextureFormat_Undefined); +// Initialization data, for ImGui_ImplWGPU_Init() +struct ImGui_ImplWGPU_InitInfo +{ + WGPUDevice Device; + int NumFramesInFlight = 3; + WGPUTextureFormat RenderTargetFormat = WGPUTextureFormat_Undefined; + WGPUTextureFormat DepthStencilFormat = WGPUTextureFormat_Undefined; +}; + +IMGUI_IMPL_API bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info); IMGUI_IMPL_API void ImGui_ImplWGPU_Shutdown(); IMGUI_IMPL_API void ImGui_ImplWGPU_NewFrame(); IMGUI_IMPL_API void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder pass_encoder); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 4c524a73..36ad82f4 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -43,6 +43,8 @@ Breaking changes: - Commented out ImGuiIO::ImeWindowHandle obsoleted in 1.87 in favor of writing to 'void* ImGuiViewport::PlatformHandleRaw'. +- Backends: WebGPU: ImGui_ImplWGPU_Init() now takes a ImGui_ImplWGPU_InitInfo structure + instead of variety of parameters, allowing for easier further changes. (#7240) Other changes: diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index cb09d422..910da083 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -96,7 +96,12 @@ int main(int, char**) #ifdef __EMSCRIPTEN__ ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback("#canvas"); #endif - ImGui_ImplWGPU_Init(wgpu_device, 3, wgpu_preferred_fmt, WGPUTextureFormat_Undefined); + ImGui_ImplWGPU_InitInfo init_info; + init_info.Device = wgpu_device; + init_info.NumFramesInFlight = 3; + init_info.RenderTargetFormat = wgpu_preferred_fmt; + init_info.DepthStencilFormat = WGPUTextureFormat_Undefined; + ImGui_ImplWGPU_Init(&init_info); // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. From 5fc0a361b2467b61c5ea50065fc843a1b84ee4d7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 22 Jan 2024 14:54:45 +0100 Subject: [PATCH 10/17] Backends: WebGPU: added ImGui_ImplWGPU_InitInfo::PipelineMultisampleState. (#7240) --- backends/imgui_impl_wgpu.cpp | 5 ++--- backends/imgui_impl_wgpu.h | 8 ++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 565aaad7..9078287d 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -16,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2024-01-22: Added configurable PipelineMultisampleState struct. (#7240) // 2024-01-22: (Breaking) ImGui_ImplWGPU_Init() now takes a ImGui_ImplWGPU_InitInfo structure instead of variety of parameters, allowing for easier further changes. // 2024-01-17: Explicitly fill all of WGPUDepthStencilState since standard removed defaults. // 2023-07-13: Use WGPUShaderModuleWGSLDescriptor's code instead of source. use WGPUMipmapFilterMode_Linear instead of WGPUFilterMode_Linear. (#6602) @@ -568,9 +569,7 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() graphics_pipeline_desc.primitive.stripIndexFormat = WGPUIndexFormat_Undefined; graphics_pipeline_desc.primitive.frontFace = WGPUFrontFace_CW; graphics_pipeline_desc.primitive.cullMode = WGPUCullMode_None; - graphics_pipeline_desc.multisample.count = 1; - graphics_pipeline_desc.multisample.mask = UINT_MAX; - graphics_pipeline_desc.multisample.alphaToCoverageEnabled = false; + graphics_pipeline_desc.multisample = bd->initInfo.PipelineMultisampleState; // Bind group layouts WGPUBindGroupLayoutEntry common_bg_layout_entries[2] = {}; diff --git a/backends/imgui_impl_wgpu.h b/backends/imgui_impl_wgpu.h index 55f96b8e..9dc3ff29 100644 --- a/backends/imgui_impl_wgpu.h +++ b/backends/imgui_impl_wgpu.h @@ -27,6 +27,14 @@ struct ImGui_ImplWGPU_InitInfo int NumFramesInFlight = 3; WGPUTextureFormat RenderTargetFormat = WGPUTextureFormat_Undefined; WGPUTextureFormat DepthStencilFormat = WGPUTextureFormat_Undefined; + WGPUMultisampleState PipelineMultisampleState = {}; + + ImGui_ImplWGPU_InitInfo() + { + PipelineMultisampleState.count = 1; + PipelineMultisampleState.mask = -1u; + PipelineMultisampleState.alphaToCoverageEnabled = false; + } }; IMGUI_IMPL_API bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info); From 6850194f60a28fe9cf7e49554b2320cf68034f94 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 22 Jan 2024 15:01:21 +0100 Subject: [PATCH 11/17] CI: Fixes WGPU example build. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 45688c47..c77e9a8c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -494,7 +494,7 @@ jobs: pushd emsdk-master source ./emsdk_env.sh popd - make -C examples/example_emscripten_wgpu + make -C examples/example_emscripten_wgpu -f Makefile.emscripten Android: runs-on: ubuntu-22.04 From 595eb86624dc92cd19d36af127402ba8daa321c3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 22 Jan 2024 15:02:22 +0100 Subject: [PATCH 12/17] Changelog, comment, minor data compaction --- docs/CHANGELOG.txt | 1 + docs/README.md | 2 ++ imgui_internal.h | 5 +++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 36ad82f4..d5349d0d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -60,6 +60,7 @@ Other changes: allocating one extra semaphore than in-flight frames. (#7236) [@mklefrancois] - Backends: Vulkan: Fixed vkMapMemory() calls unnecessarily using full buffer size. (#3957) - Backends: Vulkan: Fixed handling of ImGui_ImplVulkan_InitInfo::MinAllocationSize field. (#7189, #4238) +- Backends: WebGPU: Added ImGui_ImplWGPU_InitInfo::PipelineMultisampleState. (#7240) - Backends: WebGPU: Filling all WGPUDepthStencilState fields explicitly as a recent Dawn update stopped setting default values. (#7232) [@GrigoryGraborenko] diff --git a/docs/README.md b/docs/README.md index ef4fb975..a2be7fde 100644 --- a/docs/README.md +++ b/docs/README.md @@ -139,6 +139,8 @@ Also see [Wiki](https://github.com/ocornut/imgui/wiki) for more links and ideas. ### Gallery +Examples projects using Dear ImGui: [Tracy](https://github.com/wolfpld/tracy) (profiler), [ImHex](https://github.com/WerWolv/ImHex) (hex editor/data analysis), [RemedyBG](https://remedybg.itch.io/remedybg) (debugger) and [hundreds of others](https://github.com/ocornut/imgui/wiki/Software-using-Dear-ImGui). + For more user-submitted screenshots of projects using Dear ImGui, check out the [Gallery Threads](https://github.com/ocornut/imgui/issues/6897)! For a list of third-party widgets and extensions, check out the [Useful Extensions/Widgets](https://github.com/ocornut/imgui/wiki/Useful-Extensions) wiki page. diff --git a/imgui_internal.h b/imgui_internal.h index c51c8279..97fe8346 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1955,8 +1955,8 @@ struct ImGuiContext bool ActiveIdHasBeenEditedThisFrame; ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) ImGuiWindow* ActiveIdWindow; - ImGuiInputSource ActiveIdSource; // Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad - int ActiveIdMouseButton; + ImGuiInputSource ActiveIdSource : 16; // Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad + int ActiveIdMouseButton : 16; ImGuiID ActiveIdPreviousFrame; bool ActiveIdPreviousFrameIsAlive; bool ActiveIdPreviousFrameHasBeenEditedBefore; @@ -2181,6 +2181,7 @@ struct ImGuiContext int LogDepthToExpandDefault; // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call. // Debug Tools + // (some of the highly frequently used data are interleaved in other structures above: DebugBreakXXX fields, DebugHookIdInfo, DebugLocateId etc.) ImGuiDebugLogFlags DebugLogFlags; ImGuiTextBuffer DebugLogBuf; ImGuiTextIndex DebugLogIndex; From 9266c0d2d1390e50d2d8070896932c2564594407 Mon Sep 17 00:00:00 2001 From: rajveermalviya Date: Sat, 20 Jan 2024 18:44:53 +0530 Subject: [PATCH 13/17] Backends: WebGPU: Avoid leaking pipeline layout. (#7245) --- backends/imgui_impl_wgpu.cpp | 8 ++++++++ docs/CHANGELOG.txt | 1 + 2 files changed, 9 insertions(+) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 9078287d..618dbb4c 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -18,6 +18,7 @@ // (minor and older changes stripped away, please see git history for details) // 2024-01-22: Added configurable PipelineMultisampleState struct. (#7240) // 2024-01-22: (Breaking) ImGui_ImplWGPU_Init() now takes a ImGui_ImplWGPU_InitInfo structure instead of variety of parameters, allowing for easier further changes. +// 2024-01-22: Fixed pipeline layout leak. (#7245) // 2024-01-17: Explicitly fill all of WGPUDepthStencilState since standard removed defaults. // 2023-07-13: Use WGPUShaderModuleWGSLDescriptor's code instead of source. use WGPUMipmapFilterMode_Linear instead of WGPUFilterMode_Linear. (#6602) // 2023-04-11: Align buffer sizes. Use WGSL shaders instead of precompiled SPIR-V. @@ -183,6 +184,12 @@ static void SafeRelease(WGPUBuffer& res) wgpuBufferRelease(res); res = nullptr; } +static void SafeRelease(WGPUPipelineLayout& res) +{ + if (res) + wgpuPipelineLayoutRelease(res); + res = nullptr; +} static void SafeRelease(WGPURenderPipeline& res) { if (res) @@ -692,6 +699,7 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() SafeRelease(vertex_shader_desc.module); SafeRelease(pixel_shader_desc.module); + SafeRelease(graphics_pipeline_desc.layout); SafeRelease(bg_layouts[0]); return true; diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d5349d0d..c1313218 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -63,6 +63,7 @@ Other changes: - Backends: WebGPU: Added ImGui_ImplWGPU_InitInfo::PipelineMultisampleState. (#7240) - Backends: WebGPU: Filling all WGPUDepthStencilState fields explicitly as a recent Dawn update stopped setting default values. (#7232) [@GrigoryGraborenko] +- Backends: WebGPU: Fixed pipeline layout leak. (#7245) [@rajveermalviya] ----------------------------------------------------------------------- From 7c3fa7d049ad752fcb4aed691378a8b90e7d7825 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 22 Jan 2024 16:32:08 +0100 Subject: [PATCH 14/17] Refactor: moved section in imgui_internal.h --- imgui.h | 2 + imgui_internal.h | 114 +++++++++++++++++++++++++--------------------- imgui_widgets.cpp | 2 +- 3 files changed, 65 insertions(+), 53 deletions(-) diff --git a/imgui.h b/imgui.h index 8b4ca04d..0b2fd415 100644 --- a/imgui.h +++ b/imgui.h @@ -89,6 +89,8 @@ Index of this file: #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) // Helper Macros - IM_FMTARGS, IM_FMTLIST: Apply printf-style warnings to our formatting functions. +// (MSVC provides an equivalent mechanism via SAL Annotations but it would require the macros in a different +// location. e.g. #include + void myprintf(_Printf_format_string_ const char* format, ...)) #if !defined(IMGUI_USE_STB_SPRINTF) && defined(__MINGW32__) && !defined(__clang__) #define IM_FMTARGS(FMT) __attribute__((format(gnu_printf, FMT, FMT+1))) #define IM_FMTLIST(FMT) __attribute__((format(gnu_printf, FMT, 0))) diff --git a/imgui_internal.h b/imgui_internal.h index 97fe8346..575bf9f2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -15,6 +15,8 @@ Index of this file: // [SECTION] Generic helpers // [SECTION] ImDrawList support // [SECTION] Widgets support: flags, enums, data structures +// [SECTION] Data types support +// [SECTION] Popup support // [SECTION] Inputs support // [SECTION] Clipper support // [SECTION] Navigation support @@ -983,43 +985,6 @@ enum ImGuiPlotType ImGuiPlotType_Histogram, }; -enum ImGuiPopupPositionPolicy -{ - ImGuiPopupPositionPolicy_Default, - ImGuiPopupPositionPolicy_ComboBox, - ImGuiPopupPositionPolicy_Tooltip, -}; - -struct ImGuiDataVarInfo -{ - ImGuiDataType Type; - ImU32 Count; // 1+ - ImU32 Offset; // Offset in parent structure - void* GetVarPtr(void* parent) const { return (void*)((unsigned char*)parent + Offset); } -}; - -struct ImGuiDataTypeTempStorage -{ - ImU8 Data[8]; // Can fit any data up to ImGuiDataType_COUNT -}; - -// Type information associated to one ImGuiDataType. Retrieve with DataTypeGetInfo(). -struct ImGuiDataTypeInfo -{ - size_t Size; // Size in bytes - const char* Name; // Short descriptive name for the type, for debugging - const char* PrintFmt; // Default printf format for the type - const char* ScanFmt; // Default scanf format for the type -}; - -// Extend ImGuiDataType_ -enum ImGuiDataTypePrivate_ -{ - ImGuiDataType_String = ImGuiDataType_COUNT + 1, - ImGuiDataType_Pointer, - ImGuiDataType_ID, -}; - // Stacked color modifier, backup of modified data so we can restore it struct ImGuiColorMod { @@ -1133,21 +1098,6 @@ struct IMGUI_API ImGuiInputTextState void SelectAll() { Stb.select_start = 0; Stb.cursor = Stb.select_end = CurLenW; Stb.has_preferred_x = 0; } }; -// Storage for current popup stack -struct ImGuiPopupData -{ - ImGuiID PopupId; // Set on OpenPopup() - ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() - ImGuiWindow* BackupNavWindow;// Set on OpenPopup(), a NavWindow that will be restored on popup close - int ParentNavLayer; // Resolved on BeginPopup(). Actually a ImGuiNavLayer type (declared down below), initialized to -1 which is not part of an enum, but serves well-enough as "not any of layers" value - int OpenFrameCount; // Set on OpenPopup() - ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) - ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) - ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup - - ImGuiPopupData() { memset(this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; } -}; - enum ImGuiNextWindowDataFlags_ { ImGuiNextWindowDataFlags_None = 0, @@ -1277,6 +1227,66 @@ struct ImGuiPtrOrIndex ImGuiPtrOrIndex(int index) { Ptr = NULL; Index = index; } }; +//----------------------------------------------------------------------------- +// [SECTION] Data types support +//----------------------------------------------------------------------------- + +struct ImGuiDataVarInfo +{ + ImGuiDataType Type; + ImU32 Count; // 1+ + ImU32 Offset; // Offset in parent structure + void* GetVarPtr(void* parent) const { return (void*)((unsigned char*)parent + Offset); } +}; + +struct ImGuiDataTypeTempStorage +{ + ImU8 Data[8]; // Can fit any data up to ImGuiDataType_COUNT +}; + +// Type information associated to one ImGuiDataType. Retrieve with DataTypeGetInfo(). +struct ImGuiDataTypeInfo +{ + size_t Size; // Size in bytes + const char* Name; // Short descriptive name for the type, for debugging + const char* PrintFmt; // Default printf format for the type + const char* ScanFmt; // Default scanf format for the type +}; + +// Extend ImGuiDataType_ +enum ImGuiDataTypePrivate_ +{ + ImGuiDataType_String = ImGuiDataType_COUNT + 1, + ImGuiDataType_Pointer, + ImGuiDataType_ID, +}; + +//----------------------------------------------------------------------------- +// [SECTION] Popup support +//----------------------------------------------------------------------------- + +enum ImGuiPopupPositionPolicy +{ + ImGuiPopupPositionPolicy_Default, + ImGuiPopupPositionPolicy_ComboBox, + ImGuiPopupPositionPolicy_Tooltip, +}; + +// Storage for popup stacks (g.OpenPopupStack and g.BeginPopupStack) +struct ImGuiPopupData +{ + ImGuiID PopupId; // Set on OpenPopup() + ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() + ImGuiWindow* BackupNavWindow;// Set on OpenPopup(), a NavWindow that will be restored on popup close + int ParentNavLayer; // Resolved on BeginPopup(). Actually a ImGuiNavLayer type (declared down below), initialized to -1 which is not part of an enum, but serves well-enough as "not any of layers" value + int OpenFrameCount; // Set on OpenPopup() + ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) + ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) + ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup + + ImGuiPopupData() { memset(this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; } +}; + //----------------------------------------------------------------------------- // [SECTION] Inputs support //----------------------------------------------------------------------------- diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6f1ab902..8ec08969 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -597,7 +597,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool g.NavDisableHighlight = true; } - // Gamepad/Keyboard navigation + // Gamepad/Keyboard handling // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover) if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) From 5b5e9bd0cb3db1763e0c122c48f2e2c0efea51cb Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 22 Jan 2024 17:43:58 +0100 Subject: [PATCH 15/17] Internals: Tweak shallow compaction as Clang complains about MS ABI signage of enums. --- imgui_internal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 575bf9f2..ec4f5ca2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1963,10 +1963,10 @@ struct ImGuiContext bool ActiveIdHasBeenPressedBefore; // Track whether the active id led to a press (this is to allow changing between PressOnClick and PressOnRelease without pressing twice). Used by range_select branch. bool ActiveIdHasBeenEditedBefore; // Was the value associated to the widget Edited over the course of the Active state. bool ActiveIdHasBeenEditedThisFrame; + int ActiveIdMouseButton : 8; ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) ImGuiWindow* ActiveIdWindow; - ImGuiInputSource ActiveIdSource : 16; // Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad - int ActiveIdMouseButton : 16; + ImGuiInputSource ActiveIdSource; // Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad ImGuiID ActiveIdPreviousFrame; bool ActiveIdPreviousFrameIsAlive; bool ActiveIdPreviousFrameHasBeenEditedBefore; From 4c2c09450a6000af122b15cbe62687a50e257076 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 22 Jan 2024 17:40:48 +0100 Subject: [PATCH 16/17] Nav: keyboard/gamepad activation feedback properly timed instead of frame buffer. (#456) Amend d10641b --- imgui.cpp | 18 ++++++++++++++++++ imgui_internal.h | 5 +++++ imgui_widgets.cpp | 4 ++++ 3 files changed, 27 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index ba3c97b0..61ac8836 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1047,6 +1047,8 @@ CODE static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear +static const float NAV_ACTIVATE_HIGHLIGHT_TIMER = 0.10f; // Time to highlight an item activated by a shortcut. + // Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend) static const float WINDOWS_HOVER_PADDING = 4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow(). static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time. @@ -11169,6 +11171,13 @@ void ImGui::SetNavWindow(ImGuiWindow* window) NavUpdateAnyRequestFlag(); } +void ImGui::NavHighlightActivated(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.NavHighlightActivatedId = id; + g.NavHighlightActivatedTimer = NAV_ACTIVATE_HIGHLIGHT_TIMER; +} + void ImGui::NavClearPreferredPosForAxis(ImGuiAxis axis) { ImGuiContext& g = *GImGui; @@ -11849,13 +11858,22 @@ static void ImGui::NavUpdate() if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_down || input_down)) g.NavActivateDownId = g.NavId; if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_pressed || input_pressed)) + { g.NavActivatePressedId = g.NavId; + NavHighlightActivated(g.NavId); + } } if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) g.NavDisableHighlight = true; if (g.NavActivateId != 0) IM_ASSERT(g.NavActivateDownId == g.NavActivateId); + // Highlight + if (g.NavHighlightActivatedTimer > 0.0f) + g.NavHighlightActivatedTimer = ImMax(0.0f, g.NavHighlightActivatedTimer - io.DeltaTime); + if (g.NavHighlightActivatedTimer == 0.0f) + g.NavHighlightActivatedId = 0; + // Process programmatic activation request // FIXME-NAV: Those should eventually be queued (unlike focus they don't cancel each others) if (g.NavNextActivateId != 0) diff --git a/imgui_internal.h b/imgui_internal.h index ec4f5ca2..283a9d1a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2026,6 +2026,8 @@ struct ImGuiContext ImGuiID NavActivateDownId; // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) ImGuiActivateFlags NavActivateFlags; + ImGuiID NavHighlightActivatedId; + float NavHighlightActivatedTimer; ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). ImGuiKeyChord NavJustMovedToKeyMods; @@ -2293,6 +2295,8 @@ struct ImGuiContext NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0; NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; + NavHighlightActivatedId = 0; + NavHighlightActivatedTimer = 0.0f; NavJustMovedToKeyMods = ImGuiMod_None; NavInputSource = ImGuiInputSource_Keyboard; NavLayer = ImGuiNavLayer_Main; @@ -3127,6 +3131,7 @@ namespace ImGui IMGUI_API void NavMoveRequestCancel(); IMGUI_API void NavMoveRequestApplyResult(); IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); + IMGUI_API void NavHighlightActivated(ImGuiID id); IMGUI_API void NavClearPreferredPosForAxis(ImGuiAxis axis); IMGUI_API void NavRestoreHighlightAfterMove(); IMGUI_API void NavUpdateCurrentWindowIsScrollPushableX(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 8ec08969..538b91b5 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -675,6 +675,10 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool g.ActiveIdHasBeenPressedBefore = true; } + // Activation highlight + if (g.NavHighlightActivatedId == id) + hovered = true; + if (out_hovered) *out_hovered = hovered; if (out_held) *out_held = held; From a201af7354435f1167046bb1fa4c32dd2a6a1c8d Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 22 Jan 2024 17:56:34 +0100 Subject: [PATCH 17/17] Added SetNextItemShortcut() wip function. (#456) Mark widget as hovered. Amend d10641b. --- imgui.cpp | 35 ++++++++++++++++++++++++++++++++--- imgui.h | 2 +- imgui_internal.h | 6 ++++++ imgui_widgets.cpp | 13 +++++++++---- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 61ac8836..75050e35 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3891,6 +3891,7 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) g.ActiveIdNoClearOnFocusLoss = false; g.ActiveIdWindow = window; g.ActiveIdHasBeenEditedThisFrame = false; + g.ActiveIdFromShortcut = false; if (id) { g.ActiveIdIsAlive = id; @@ -4100,7 +4101,8 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) return false; if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) - return false; + if (!g.ActiveIdFromShortcut) + return false; // Done with rectangle culling so we can perform heavier checks now. if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) && !IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) @@ -4160,12 +4162,13 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag } // FIXME: This is inlined/duplicated in ItemAdd() +// FIXME: The id != 0 path is not used by our codebase, may get rid of it? bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (!bb.Overlaps(window->ClipRect)) - if (id == 0 || (id != g.ActiveId && id != g.NavId)) + if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId)) if (!g.LogEnabled) return true; return false; @@ -9421,6 +9424,13 @@ bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiIn return true; } +void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord) +{ + ImGuiContext& g = *GImGui; + g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasShortcut; + g.NextItemData.Shortcut = key_chord; +} + bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) { //ImGuiContext& g = *GImGui; @@ -9731,6 +9741,7 @@ void ImGuiStackSizes::CompareWithContextState(ImGuiContext* ctx) // [SECTION] ITEM SUBMISSION //----------------------------------------------------------------------------- // - KeepAliveID() +// - ItemHandleShortcut() [Internal] // - ItemAdd() //----------------------------------------------------------------------------- @@ -9744,6 +9755,20 @@ void ImGui::KeepAliveID(ImGuiID id) g.ActiveIdPreviousFrameIsAlive = true; } +static void ItemHandleShortcut(ImGuiID id) +{ + // FIXME: Generalize Activation queue? + ImGuiContext& g = *GImGui; + if (ImGui::Shortcut(g.NextItemData.Shortcut, id, ImGuiInputFlags_None) && g.NavActivateId == 0) + { + g.NavActivateId = id; // Will effectively disable clipping. + g.NavActivateFlags = ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_FromShortcut; + if (g.ActiveId == 0 || g.ActiveId == id) + g.NavActivateDownId = g.NavActivatePressedId = id; + ImGui::NavHighlightActivated(id); + } +} + // Declare item bounding box for clipping and interaction. // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface // declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. @@ -9785,6 +9810,9 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) NavProcessItem(); } + + if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasShortcut) + ItemHandleShortcut(id); } // Lightweight clear of SetNextItemXXX data. @@ -9801,9 +9829,10 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu //const bool is_clipped = IsClippedEx(bb, id); //if (is_clipped) // return false; + // g.NavActivateId is not necessarily == g.NavId, in the case of remote activation (e.g. shortcuts) const bool is_rect_visible = bb.Overlaps(window->ClipRect); if (!is_rect_visible) - if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId)) + if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId)) if (!g.LogEnabled) return false; diff --git a/imgui.h b/imgui.h index 0b2fd415..88df9491 100644 --- a/imgui.h +++ b/imgui.h @@ -24,7 +24,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.90.2 WIP" -#define IMGUI_VERSION_NUM 19014 +#define IMGUI_VERSION_NUM 19015 #define IMGUI_HAS_TABLE /* diff --git a/imgui_internal.h b/imgui_internal.h index 283a9d1a..786ea68e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1145,6 +1145,7 @@ enum ImGuiNextItemDataFlags_ ImGuiNextItemDataFlags_None = 0, ImGuiNextItemDataFlags_HasWidth = 1 << 0, ImGuiNextItemDataFlags_HasOpen = 1 << 1, + ImGuiNextItemDataFlags_HasShortcut = 1 << 2, }; struct ImGuiNextItemData @@ -1154,6 +1155,7 @@ struct ImGuiNextItemData // Non-flags members are NOT cleared by ItemAdd() meaning they are still valid during NavProcessItem() ImGuiSelectionUserData SelectionUserData; // Set by SetNextItemSelectionUserData() (note that NULL/0 is a valid value, we use -1 == ImGuiSelectionUserData_Invalid to mark invalid values) float Width; // Set by SetNextItemWidth() + ImGuiKeyChord Shortcut; // Set by SetNextItemShortcut() bool OpenVal; // Set by SetNextItemOpen() ImGuiCond OpenCond : 8; @@ -1518,6 +1520,7 @@ enum ImGuiActivateFlags_ ImGuiActivateFlags_PreferTweak = 1 << 1, // Favor activation for tweaking with arrows or gamepad (e.g. for Slider/Drag). Default for Space key and if keyboard is not used. ImGuiActivateFlags_TryToPreserveState = 1 << 2, // Request widget to preserve state if it can (e.g. InputText will try to preserve cursor/selection) ImGuiActivateFlags_FromTabbing = 1 << 3, // Activation requested by a tabbing request + ImGuiActivateFlags_FromShortcut = 1 << 4, // Activation requested by an item shortcut via SetNextItemShortcut() function. }; // Early work-in-progress API for ScrollToItem() @@ -1963,6 +1966,7 @@ struct ImGuiContext bool ActiveIdHasBeenPressedBefore; // Track whether the active id led to a press (this is to allow changing between PressOnClick and PressOnRelease without pressing twice). Used by range_select branch. bool ActiveIdHasBeenEditedBefore; // Was the value associated to the widget Edited over the course of the Active state. bool ActiveIdHasBeenEditedThisFrame; + bool ActiveIdFromShortcut; int ActiveIdMouseButton : 8; ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) ImGuiWindow* ActiveIdWindow; @@ -2267,6 +2271,7 @@ struct ImGuiContext ActiveIdHasBeenPressedBefore = false; ActiveIdHasBeenEditedBefore = false; ActiveIdHasBeenEditedThisFrame = false; + ActiveIdFromShortcut = false; ActiveIdClickOffset = ImVec2(-1, -1); ActiveIdWindow = NULL; ActiveIdSource = ImGuiInputSource_None; @@ -3225,6 +3230,7 @@ namespace ImGui // - IsKeyChordPressed() compares mods + call IsKeyPressed() -> function has no side-effect. // - Shortcut() submits a route then if currently can be routed calls IsKeyChordPressed() -> function has (desirable) side-effects. IMGUI_API bool IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags = 0); + IMGUI_API void SetNextItemShortcut(ImGuiKeyChord key_chord); IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags = 0); // owner_id needs to be explicit and cannot be 0 IMGUI_API bool TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 538b91b5..93cfa38f 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -477,6 +477,9 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // Frame N + RepeatDelay + RepeatRate*N true true - true //------------------------------------------------------------------------------------------------------------------------------------------------- +// FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc. +// And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);' +// For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading. bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; @@ -598,7 +601,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool } // Gamepad/Keyboard handling - // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. + // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse. if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover) if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) hovered = true; @@ -621,8 +624,10 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool pressed = true; SetActiveID(id, window); g.ActiveIdSource = g.NavInputSource; - if (!(flags & ImGuiButtonFlags_NoNavFocus)) + if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)) SetFocusID(id, window); + if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut) + g.ActiveIdFromShortcut = true; } } @@ -667,7 +672,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool { // When activated using Nav, we hold on the ActiveID until activation button is released if (g.NavActivateDownId == id) - held = true; + held = true; // hovered == true not true as we are already likely hovered on direct activation. else ClearActiveID(); } @@ -675,7 +680,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool g.ActiveIdHasBeenPressedBefore = true; } - // Activation highlight + // Activation highlight (this may be a remote activation) if (g.NavHighlightActivatedId == id) hovered = true;