From e7fb97208a876a2c57143e9cda0372d878c6abe9 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Thu, 9 Jan 2025 23:12:07 +0100 Subject: [PATCH 1/5] Backends: Metal: Added missing IM_UNUSED (#8302) --- backends/imgui_impl_metal.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 3ff905a60..1e0c47f52 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -143,6 +143,7 @@ bool ImGui_ImplMetal_Init(id device) void ImGui_ImplMetal_Shutdown() { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + IM_UNUSED(bd); IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); ImGui_ImplMetal_DestroyDeviceObjects(); ImGui_ImplMetal_DestroyBackendData(); From 0b8ff4b2382d4bbd5c168b1a5373f86ea3145957 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Jan 2025 23:15:59 +0100 Subject: [PATCH 2/5] Backends, Examples: Vulkan: add IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE. use in descriptor pools sizes. (#6642) --- backends/imgui_impl_vulkan.cpp | 4 +++- backends/imgui_impl_vulkan.h | 6 ++++-- docs/CHANGELOG.txt | 4 ++++ examples/example_glfw_vulkan/main.cpp | 9 +++++---- examples/example_sdl2_vulkan/main.cpp | 9 +++++---- examples/example_sdl3_vulkan/main.cpp | 9 +++++---- examples/example_win32_vulkan/main.cpp | 9 +++++---- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 4345f2b11..940ea2d79 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -26,6 +26,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-01-09: Vulkan: Added IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE to clarify how many image sampler descriptors are expected to be available in descriptor pool. (#6642) // 2025-01-06: Vulkan: Added more ImGui_ImplVulkanH_XXXX helper functions to simplify our examples. // 2024-12-11: Vulkan: Fixed setting VkSwapchainCreateInfoKHR::preTransform for platforms not supporting VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR. (#8222) // 2024-11-27: Vulkan: Make user-provided descriptor pool optional. As a convenience, when setting init_info->DescriptorPoolSize the backend will create one itself. (#8172, #4867) @@ -1022,8 +1023,9 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() check_vk_result(err); } - if (v->DescriptorPoolSize) + if (v->DescriptorPoolSize != 0) { + IM_ASSERT(v->DescriptorPoolSize > IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE); VkDescriptorPoolSize pool_size = { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, v->DescriptorPoolSize }; VkDescriptorPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 5621acbdd..3bd994864 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -61,14 +61,16 @@ #define IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING #endif +// Current version of the backend use 1 descriptor for the font atlas + as many as additional calls done to ImGui_ImplVulkan_AddTexture(). +// It is expected that as early as Q1 2025 the backend will use a few more descriptors. Use this value + number of desired calls to ImGui_ImplVulkan_AddTexture(). +#define IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE (1) // Minimum per atlas + // Initialization data, for ImGui_ImplVulkan_Init() // [Please zero-clear before use!] // - About descriptor pool: // - A VkDescriptorPool should be created with VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, // and must contain a pool size large enough to hold a small number of VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER descriptors. // - As an convenience, by setting DescriptorPoolSize > 0 the backend will create one for you. -// - Current version of the backend use 1 descriptor for the font atlas + as many as additional calls done to ImGui_ImplVulkan_AddTexture(). -// - It is expected that as early as Q1 2025 the backend will use a few more descriptors, so aim at 10 + number of desierd calls to ImGui_ImplVulkan_AddTexture(). // - About dynamic rendering: // - When using dynamic rendering, set UseDynamicRendering=true and fill PipelineRenderingCreateInfo structure. struct ImGui_ImplVulkan_InitInfo diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 56ad9b8d0..1b713e17f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -79,6 +79,10 @@ Other changes: platforms not supporting VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR. (#8222) [@Zer0xFF] - Backends: Vulkan: Added a few more ImGui_ImplVulkanH_XXX helper functions primarily for the purpose of making our examples simpler. +- Backends: Vulkan: Added IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE to clarify + how many image sampler descriptors are expected to be available in the provided + descriptor pool. Current backend needs 1 but it is expected that by end of Q1 2025 + this number will grow (will staying a very small number). (#6642) - Backends: DX11: Expose vertex constant buffer in ImGui_ImplDX11_RenderState. Reset projection matrix in ImDrawCallback_ResetRenderState handlers. (#6969, #5834, #7468, #3590) - Backends: DX10: Expose ImGui_ImplDX10_RenderState for completeness. (#6969, #5834, #7468, #3590) diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index 9f8dced2f..b61dcaae0 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -189,17 +189,18 @@ static void SetupVulkan(ImVector instance_extensions) } // Create Descriptor Pool - // The example only requires a single combined image sampler descriptor for the font image and only uses one descriptor set (for that) - // If you wish to load e.g. additional textures you may need to alter pools sizes. + // If you wish to load e.g. additional textures you may need to alter pools sizes and maxSets. { VkDescriptorPoolSize pool_sizes[] = { - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE }, }; VkDescriptorPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - pool_info.maxSets = 1; + pool_info.maxSets = 0; + for (VkDescriptorPoolSize& pool_size : pool_sizes) + pool_info.maxSets += pool_size.descriptorCount; pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); pool_info.pPoolSizes = pool_sizes; err = vkCreateDescriptorPool(g_Device, &pool_info, g_Allocator, &g_DescriptorPool); diff --git a/examples/example_sdl2_vulkan/main.cpp b/examples/example_sdl2_vulkan/main.cpp index fafa8ea42..b82ed0b7a 100644 --- a/examples/example_sdl2_vulkan/main.cpp +++ b/examples/example_sdl2_vulkan/main.cpp @@ -177,17 +177,18 @@ static void SetupVulkan(ImVector instance_extensions) } // Create Descriptor Pool - // The example only requires a single combined image sampler descriptor for the font image and only uses one descriptor set (for that) - // If you wish to load e.g. additional textures you may need to alter pools sizes. + // If you wish to load e.g. additional textures you may need to alter pools sizes and maxSets. { VkDescriptorPoolSize pool_sizes[] = { - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE }, }; VkDescriptorPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - pool_info.maxSets = 1; + pool_info.maxSets = 0; + for (VkDescriptorPoolSize& pool_size : pool_sizes) + pool_info.maxSets += pool_size.descriptorCount; pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); pool_info.pPoolSizes = pool_sizes; err = vkCreateDescriptorPool(g_Device, &pool_info, g_Allocator, &g_DescriptorPool); diff --git a/examples/example_sdl3_vulkan/main.cpp b/examples/example_sdl3_vulkan/main.cpp index 569eaeffb..e36f87194 100644 --- a/examples/example_sdl3_vulkan/main.cpp +++ b/examples/example_sdl3_vulkan/main.cpp @@ -182,17 +182,18 @@ static void SetupVulkan(ImVector instance_extensions) } // Create Descriptor Pool - // The example only requires a single combined image sampler descriptor for the font image and only uses one descriptor set (for that) - // If you wish to load e.g. additional textures you may need to alter pools sizes. + // If you wish to load e.g. additional textures you may need to alter pools sizes and maxSets. { VkDescriptorPoolSize pool_sizes[] = { - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE }, }; VkDescriptorPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - pool_info.maxSets = 1; + pool_info.maxSets = 0; + for (VkDescriptorPoolSize& pool_size : pool_sizes) + pool_info.maxSets += pool_size.descriptorCount; pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); pool_info.pPoolSizes = pool_sizes; err = vkCreateDescriptorPool(g_Device, &pool_info, g_Allocator, &g_DescriptorPool); diff --git a/examples/example_win32_vulkan/main.cpp b/examples/example_win32_vulkan/main.cpp index 3fabf7a2d..c6c138695 100644 --- a/examples/example_win32_vulkan/main.cpp +++ b/examples/example_win32_vulkan/main.cpp @@ -178,17 +178,18 @@ static void SetupVulkan(ImVector instance_extensions) } // Create Descriptor Pool - // The example only requires a single combined image sampler descriptor for the font image and only uses one descriptor set (for that) - // If you wish to load e.g. additional textures you may need to alter pools sizes. + // If you wish to load e.g. additional textures you may need to alter pools sizes and maxSets. { VkDescriptorPoolSize pool_sizes[] = { - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE }, }; VkDescriptorPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - pool_info.maxSets = 1; + pool_info.maxSets = 0; + for (VkDescriptorPoolSize& pool_size : pool_sizes) + pool_info.maxSets += pool_size.descriptorCount; pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); pool_info.pPoolSizes = pool_sizes; err = vkCreateDescriptorPool(g_Device, &pool_info, g_Allocator, &g_DescriptorPool); From 1d962820d8b972d93a4627afc3cb4c09837c298c Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 12 Jan 2025 15:11:30 +0100 Subject: [PATCH 3/5] Error Handling: Turned common EndTable() and other TableXXX functions fail cases into recoverable errors. (#1651, #8314) --- docs/CHANGELOG.txt | 2 ++ imgui_tables.cpp | 45 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 1b713e17f..bf9f209c4 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -45,6 +45,8 @@ Other changes: - Error Handling: Fixed bugs recovering from within a table that created a child window, and from nested child windows. (#1651) +- Error Handling: Turned common EndTable() and other TableXXX functions + fail cases into a recoverable error. (#1651, #8314) - InputText: Fixed a bug where character replacements performed from a callback were not applied when pasting from clipbard. (#8229) - InputText: Fixed issue when activating a ReadOnly field when the underlying diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 1ab9c703e..ec25bf906 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1337,7 +1337,11 @@ void ImGui::EndTable() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "EndTable() call should only be done while in BeginTable() scope!"); + return; + } // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) @@ -1560,8 +1564,12 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); - IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } + IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); if (table->DeclColumnsCount >= table->ColumnsCount) { @@ -1634,7 +1642,11 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit @@ -1711,9 +1723,11 @@ void ImGui::TableSetColumnEnabled(int column_n, bool enabled) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL); - if (!table) + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); return; + } IM_ASSERT(table->Flags & ImGuiTableFlags_Hideable); // See comments above if (column_n < 0) column_n = table->CurrentColumn; @@ -3034,7 +3048,11 @@ void ImGui::TableHeadersRow() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } // Call layout if not already done. This is automatically done by TableNextRow: we do it here _only_ to make // it easier to debug-step in TableUpdateLayout(). Your own version of this function doesn't need this. @@ -3079,7 +3097,12 @@ void ImGui::TableHeader(const char* label) return; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableHeader() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } + IM_ASSERT(table->CurrentColumn != -1); const int column_n = table->CurrentColumn; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -3254,7 +3277,11 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ImGuiTable* table = g.CurrentTable; ImGuiWindow* window = g.CurrentWindow; ImDrawList* draw_list = window->DrawList; - IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } IM_ASSERT(table->CurrentRow == -1 && "Must be first row"); if (max_label_width == 0.0f) From a28ffa81c4a8e244e2682587285a63e3d0f2c67e Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 13 Jan 2025 13:26:15 +0100 Subject: [PATCH 4/5] Docs: added more references to IsKeyDown(), InFlags. (#8317) --- docs/CHANGELOG.txt | 4 +++- imgui.cpp | 2 +- imgui_internal.h | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index bf9f209c4..9c1b280b3 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -153,11 +153,12 @@ Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v Breaking changes: - Commented out pre-1.87 IO system (equivalent to using IMGUI_DISABLE_OBSOLETE_KEYIO or IMGUI_DISABLE_OBSOLETE_FUNCTIONS before). - - io.KeyMap[] and io.KeysDown[] are removed (obsoleted February 2022). + - io.KeyMap[] and io.KeysDown[] are removed (obsoleted February 2022). Use IsKeyDown() instead. - io.NavInputs[] and ImGuiNavInput are removed (obsoleted July 2022). - Pre-1.87 backends are not supported: - backends need to call io.AddKeyEvent(), io.AddMouseEvent() instead of writing to io.KeysDown[], io.MouseDown[] fields. - backends need to call io.AddKeyAnalogEvent() for gamepad values instead of writing to io.NavInputs[] fields. + - you can use IsKeyDown() instead of reading from io.KeysDown[]. - For more references: - read 1.87 and 1.88 part of API BREAKING CHANGES in imgui.cpp or read Changelog for 1.87 and 1.88. - read https://github.com/ocornut/imgui/issues/4921 @@ -2390,6 +2391,7 @@ Breaking Changes: - For all calls to IO new functions, the Dear ImGui context should be bound/current. - Reworked IO keyboard input API: (#4921, #2625, #3724) [@thedmd, @ocornut] - Added io.AddKeyEvent() function, obsoleting writing directly to io.KeyMap[], io.KeysDown[] arrays. + - You can use IsKeyDown() instead of reading from io.KeysDown[]. - For keyboard modifiers, you can call io.AddKeyEvent() with ImGuiKey_ModXXX values, obsoleting writing directly to io.KeyCtrl, io.KeyShift etc. - Added io.SetKeyEventNativeData() function (optional) to pass native and old legacy indices. diff --git a/imgui.cpp b/imgui.cpp index bad766f03..c676172ef 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -647,7 +647,7 @@ CODE - Backend writing to io.MouseHoveredViewport -> backend should call io.AddMouseViewportEvent() [Docking branch w/ multi-viewports only] note: for all calls to IO new functions, the Dear ImGui context should be bound/current. read https://github.com/ocornut/imgui/issues/4921 for details. - - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details. + - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(), ImGui::IsKeyDown(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details. - IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX) - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX) - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to stil function with legacy key codes). diff --git a/imgui_internal.h b/imgui_internal.h index 850136b0d..9ff2b6d3e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1247,11 +1247,11 @@ struct ImGuiNextItemData struct ImGuiLastItemData { ImGuiID ID; - ImGuiItemFlags ItemFlags; // See ImGuiItemFlags_ + ImGuiItemFlags ItemFlags; // See ImGuiItemFlags_ (called 'InFlags' before v1.91.4). ImGuiItemStatusFlags StatusFlags; // See ImGuiItemStatusFlags_ ImRect Rect; // Full rectangle ImRect NavRect; // Navigation scoring rectangle (not displayed) - // Rarely used fields are not explicitly cleared, only valid when the corresponding ImGuiItemStatusFlags ar set. + // Rarely used fields are not explicitly cleared, only valid when the corresponding ImGuiItemStatusFlags are set. ImRect DisplayRect; // Display rectangle. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) is set. ImRect ClipRect; // Clip rectangle at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasClipRect) is set.. ImGuiKeyChord Shortcut; // Shortcut at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasShortcut) is set.. From a604d4f717bb99a704fcb17d29a6f0dbac78c060 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 10 Jan 2025 17:08:09 +0100 Subject: [PATCH 5/5] Fixed IsItemDeactivated(), IsItemDeactivatedAfterEdit() to work when interrupted before/after the active id is submitted. (#5184, #5904, #6766, #8303, #8004) --- docs/CHANGELOG.txt | 9 +++++++++ imgui.cpp | 40 ++++++++++++++++++++++++---------------- imgui.h | 2 +- imgui_internal.h | 16 ++++++++++++---- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9c1b280b3..1f1eb05a9 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -43,6 +43,15 @@ Breaking changes: Other changes: +- Fixed issues with IsItemDeactivated() and IsItemDeactivatedAfterEdit() not + emitting a reliable signal when an item is deactivated externally: e.g. + via an explicit clear of focus, clear of active id, opening of modal etc. + (#5184, #5904, #6766, #8303, #8004) + - It used to work when the interruption happened in the frame before the + active item as submitted, but not after. It should work in both cases now. + - While this is not specific to a certain widgets, typically it would + mostly be noticeable on InputText() because it keeps ActiveId for a + longer time while allowing other interaction to happen. - Error Handling: Fixed bugs recovering from within a table that created a child window, and from nested child windows. (#1651) - Error Handling: Turned common EndTable() and other TableXXX functions diff --git a/imgui.cpp b/imgui.cpp index c676172ef..6b9f47164 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3930,9 +3930,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) ActiveIdSource = ImGuiInputSource_None; ActiveIdMouseButton = -1; ActiveIdPreviousFrame = 0; - ActiveIdPreviousFrameIsAlive = false; - ActiveIdPreviousFrameHasBeenEditedBefore = false; - ActiveIdPreviousFrameWindow = NULL; + memset(&DeactivatedItemData, 0, sizeof(DeactivatedItemData)); memset(&ActiveIdValueOnActivation, 0, sizeof(ActiveIdValueOnActivation)); LastActiveId = 0; LastActiveIdTimer = 0.0f; @@ -4175,7 +4173,7 @@ void ImGui::Shutdown() g.WindowsById.Clear(); g.NavWindow = NULL; g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; - g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; + g.ActiveIdWindow = NULL; g.MovingWindow = NULL; g.KeysRoutingTable.Clear(); @@ -4359,6 +4357,13 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) g.MovingWindow = NULL; } + // Store deactivate data + ImGuiDeactivatedItemData* deactivated_data = &g.DeactivatedItemData; + deactivated_data->ID = g.ActiveId; + deactivated_data->ElapseFrame = (g.LastItemData.ID == g.ActiveId) ? g.FrameCount : g.FrameCount + 1; // FIXME: OK to use LastItemData? + deactivated_data->HasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore; + deactivated_data->IsAlive = (g.ActiveIdIsAlive == g.ActiveId); + // This could be written in a more general way (e.g associate a hook to ActiveId), // but since this is currently quite an exception we'll leave it as is. // One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveID() @@ -5189,11 +5194,8 @@ void ImGui::NewFrame() g.ActiveIdTimer += g.IO.DeltaTime; g.LastActiveIdTimer += g.IO.DeltaTime; g.ActiveIdPreviousFrame = g.ActiveId; - g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow; - g.ActiveIdPreviousFrameHasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore; g.ActiveIdIsAlive = 0; g.ActiveIdHasBeenEditedThisFrame = false; - g.ActiveIdPreviousFrameIsAlive = false; g.ActiveIdIsJustActivated = false; if (g.TempInputId != 0 && g.ActiveId != g.TempInputId) g.TempInputId = 0; @@ -5202,6 +5204,9 @@ void ImGui::NewFrame() g.ActiveIdUsingNavDirMask = 0x00; g.ActiveIdUsingAllKeyboardKeys = false; } + if (g.DeactivatedItemData.ElapseFrame < g.FrameCount) + g.DeactivatedItemData.ID = 0; + g.DeactivatedItemData.IsAlive = false; // Record when we have been stationary as this state is preserved while over same item. // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values. @@ -5833,13 +5838,13 @@ bool ImGui::IsItemDeactivated() ImGuiContext& g = *GImGui; if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated) return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; - return (g.ActiveIdPreviousFrame == g.LastItemData.ID && g.ActiveIdPreviousFrame != 0 && g.ActiveId != g.LastItemData.ID); + return (g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount); } bool ImGui::IsItemDeactivatedAfterEdit() { ImGuiContext& g = *GImGui; - return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore)); + return IsItemDeactivated() && g.DeactivatedItemData.HasBeenEditedBefore; } // == (GetItemID() == GetFocusID() && GetFocusID() != 0) @@ -10443,8 +10448,8 @@ void ImGui::KeepAliveID(ImGuiID id) ImGuiContext& g = *GImGui; if (g.ActiveId == id) g.ActiveIdIsAlive = id; - if (g.ActiveIdPreviousFrame == id) - g.ActiveIdPreviousFrameIsAlive = true; + if (g.DeactivatedItemData.ID == id) + g.DeactivatedItemData.IsAlive = true; } // Declare item bounding box for clipping and interaction. @@ -10529,6 +10534,9 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // window->DrawList->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, IM_COL32(255,255,0,255)); // [DEBUG] #endif + if (id != 0 && g.DeactivatedItemData.ID == id) + g.DeactivatedItemData.ElapseFrame = g.FrameCount; + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) if (is_rect_visible) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible; @@ -10888,7 +10896,7 @@ void ImGui::BeginGroup() group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; group_data.BackupHoveredIdIsAlive = g.HoveredId != 0; group_data.BackupIsSameLine = window->DC.IsSameLine; - group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive; + group_data.BackupDeactivatedIdIsAlive = g.DeactivatedItemData.IsAlive; group_data.EmitItem = true; window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x; @@ -10939,11 +10947,11 @@ void ImGui::EndGroup() // Also if you grep for LastItemId you'll notice it is only used in that context. // (The two tests not the same because ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the frame.) const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId; - const bool group_contains_prev_active_id = (group_data.BackupActiveIdPreviousFrameIsAlive == false) && (g.ActiveIdPreviousFrameIsAlive == true); + const bool group_contains_deactivated_id = (group_data.BackupDeactivatedIdIsAlive == false) && (g.DeactivatedItemData.IsAlive == true); if (group_contains_curr_active_id) g.LastItemData.ID = g.ActiveId; - else if (group_contains_prev_active_id) - g.LastItemData.ID = g.ActiveIdPreviousFrame; + else if (group_contains_deactivated_id) + g.LastItemData.ID = g.DeactivatedItemData.ID; g.LastItemData.Rect = group_bb; // Forward Hovered flag @@ -10957,7 +10965,7 @@ void ImGui::EndGroup() // Forward Deactivated flag g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated; - if (group_contains_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame) + if (group_contains_deactivated_id) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated; g.GroupStack.pop_back(); diff --git a/imgui.h b/imgui.h index 0f7a99862..5a3a7af65 100644 --- a/imgui.h +++ b/imgui.h @@ -29,7 +29,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.91.7 WIP" -#define IMGUI_VERSION_NUM 19164 +#define IMGUI_VERSION_NUM 19165 #define IMGUI_HAS_TABLE /* diff --git a/imgui_internal.h b/imgui_internal.h index 9ff2b6d3e..bb5a56774 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -140,6 +140,7 @@ struct ImGuiContext; // Main Dear ImGui context struct ImGuiContextHook; // Hook for extensions like ImGuiTestEngine struct ImGuiDataVarInfo; // Variable information (e.g. to access style variables from an enum) struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum +struct ImGuiDeactivatedItemData; // Data for IsItemDeactivated()/IsItemDeactivatedAfterEdit() function. struct ImGuiErrorRecoveryState; // Storage of stack sizes for error handling and recovery struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box @@ -1070,7 +1071,7 @@ struct IMGUI_API ImGuiGroupData ImVec2 BackupCurrLineSize; float BackupCurrLineTextBaseOffset; ImGuiID BackupActiveIdIsAlive; - bool BackupActiveIdPreviousFrameIsAlive; + bool BackupDeactivatedIdIsAlive; bool BackupHoveredIdIsAlive; bool BackupIsSameLine; bool EmitItem; @@ -1314,6 +1315,15 @@ struct ImGuiPtrOrIndex ImGuiPtrOrIndex(int index) { Ptr = NULL; Index = index; } }; +// Data used by IsItemDeactivated()/IsItemDeactivatedAfterEdit() functions +struct ImGuiDeactivatedItemData +{ + ImGuiID ID; + int ElapseFrame; + bool HasBeenEditedBefore; + bool IsAlive; +}; + //----------------------------------------------------------------------------- // [SECTION] Popup support //----------------------------------------------------------------------------- @@ -2106,9 +2116,7 @@ struct ImGuiContext ImGuiWindow* ActiveIdWindow; ImGuiInputSource ActiveIdSource; // Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad ImGuiID ActiveIdPreviousFrame; - bool ActiveIdPreviousFrameIsAlive; - bool ActiveIdPreviousFrameHasBeenEditedBefore; - ImGuiWindow* ActiveIdPreviousFrameWindow; + ImGuiDeactivatedItemData DeactivatedItemData; ImGuiDataTypeStorage ActiveIdValueOnActivation; // Backup of initial value at the time of activation. ONLY SET BY SPECIFIC WIDGETS: DragXXX and SliderXXX. ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation.