From df317743272165a8bda2dbe85a0f751495a1e150 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 3 Mar 2025 19:20:10 +0100 Subject: [PATCH 1/7] Disabled: Fixed an issue restoring Alpha in EndDisabled() when using nested BeginDisabled() calls with PushStyleVar(ImGuiStyleVar_DisabledAlpha) within. (#8454, #7640) --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 4 +++- imgui_internal.h | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 390681161..2df26e655 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -103,6 +103,8 @@ Other changes: (#8451, #7660) [@achabense] - TextLinkOpenURL(): fixed default Win32 io.PlatformOpenInShellFn handler to handle UTF-8 regardless of system regional settings. (#7660) [@achabense] +- Disabled: Fixed an issue restoring Alpha in EndDisabled() when using nested + BeginDisabled() calls with PushStyleVar(ImGuiStyleVar_DisabledAlpha) within. (#8454, #7640) - Clipper: Fixed an issue where passing an out of bound index to IncludeItemByIndex() could incorrectly offset the final cursor, even if that index was not iterated through. One case where it would manifest was calling Combo() with an out of range index. (#8450) diff --git a/imgui.cpp b/imgui.cpp index 77e3c1d36..bae74db5f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7084,6 +7084,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window_stack_data.Window = window; window_stack_data.ParentLastItemDataBackup = g.LastItemData; window_stack_data.DisabledOverrideReenable = (flags & ImGuiWindowFlags_Tooltip) && (g.CurrentItemFlags & ImGuiItemFlags_Disabled); + window_stack_data.DisabledOverrideReenableAlphaBackup = 0.0f; ErrorRecoveryStoreState(&window_stack_data.StackSizesInBegin); g.StackSizesInBeginForCurrentWindow = &window_stack_data.StackSizesInBegin; if (flags & ImGuiWindowFlags_ChildMenu) @@ -7950,6 +7951,7 @@ void ImGui::BeginDisabledOverrideReenable() { ImGuiContext& g = *GImGui; IM_ASSERT(g.CurrentItemFlags & ImGuiItemFlags_Disabled); + g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup = g.Style.Alpha; g.Style.Alpha = g.DisabledAlphaBackup; g.CurrentItemFlags &= ~ImGuiItemFlags_Disabled; g.ItemFlagsStack.push_back(g.CurrentItemFlags); @@ -7963,7 +7965,7 @@ void ImGui::EndDisabledOverrideReenable() IM_ASSERT(g.DisabledStackSize > 0); g.ItemFlagsStack.pop_back(); g.CurrentItemFlags = g.ItemFlagsStack.back(); - g.Style.Alpha = g.DisabledAlphaBackup * g.Style.DisabledAlpha; + g.Style.Alpha = g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup; } void ImGui::PushTextWrapPos(float wrap_pos_x) diff --git a/imgui_internal.h b/imgui_internal.h index 4bdc6e947..40b854559 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1316,6 +1316,7 @@ struct ImGuiWindowStackData ImGuiLastItemData ParentLastItemDataBackup; ImGuiErrorRecoveryState StackSizesInBegin; // Store size of various stacks for asserting bool DisabledOverrideReenable; // Non-child window override disabled flag + float DisabledOverrideReenableAlphaBackup; }; struct ImGuiShrinkWidthItem From a7657f2ed45508bb719a8b9e2d8e7bda57eff4a0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 4 Mar 2025 14:36:00 +0100 Subject: [PATCH 2/7] Examples: SDL3: Added comments to clarify setup for users of the unfortunate SDL_MAIN_USE_CALLBACKS feature. (#8455) --- docs/CHANGELOG.txt | 2 ++ examples/example_sdl3_opengl3/main.cpp | 5 +++++ examples/example_sdl3_sdlgpu3/main.cpp | 7 ++++++- examples/example_sdl3_sdlrenderer3/main.cpp | 5 +++++ examples/example_sdl3_vulkan/main.cpp | 7 ++++++- 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 2df26e655..78c6f09da 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -115,6 +115,8 @@ Other changes: - Demo: Reorganized "Widgets" section to be alphabetically ordered and split in more functions. - Demo: Combos: demonstrate a very simple way to add a filter to a combo, by showing the filter inside the combo contents. (#718) +- Examples: SDL3: Added comments to clarify setup for users of the unfortunate + SDL_MAIN_USE_CALLBACKS feature. (#8455) - Backends: GLFW: Fixed clipboard handler assertion when using GLFW <= 3.2.1 compiled with asserts enabled. (#8452) - Backends: SDL2, SDL3: Using SDL_OpenURL() in platform_io.Platform_OpenInShellFn diff --git a/examples/example_sdl3_opengl3/main.cpp b/examples/example_sdl3_opengl3/main.cpp index 8cd54bd4e..d8666061d 100644 --- a/examples/example_sdl3_opengl3/main.cpp +++ b/examples/example_sdl3_opengl3/main.cpp @@ -26,6 +26,7 @@ int main(int, char**) { // Setup SDL + // [If using SDL_MAIN_USE_CALLBACKS: all code below until the main loop starts would likely be your SDL_AppInit() function] if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { printf("Error: SDL_Init(): %s\n", SDL_GetError()); @@ -139,6 +140,7 @@ int main(int, char**) // - 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. + // [If using SDL_MAIN_USE_CALLBACKS: call ImGui_ImplSDL3_ProcessEvent() from your SDL_AppEvent() function] SDL_Event event; while (SDL_PollEvent(&event)) { @@ -148,6 +150,8 @@ int main(int, char**) if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window)) done = true; } + + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppIterate() function] if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) { SDL_Delay(10); @@ -209,6 +213,7 @@ int main(int, char**) #endif // Cleanup + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppQuit() function] ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL3_Shutdown(); ImGui::DestroyContext(); diff --git a/examples/example_sdl3_sdlgpu3/main.cpp b/examples/example_sdl3_sdlgpu3/main.cpp index dbc7759ba..630639f3f 100644 --- a/examples/example_sdl3_sdlgpu3/main.cpp +++ b/examples/example_sdl3_sdlgpu3/main.cpp @@ -26,7 +26,8 @@ int main(int, char**) { // Setup SDL - if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD) != 0) + // [If using SDL_MAIN_USE_CALLBACKS: all code below until the main loop starts would likely be your SDL_AppInit() function] + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { printf("Error: SDL_Init(): %s\n", SDL_GetError()); return -1; @@ -105,6 +106,7 @@ int main(int, char**) // - 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. + // [If using SDL_MAIN_USE_CALLBACKS: call ImGui_ImplSDL3_ProcessEvent() from your SDL_AppEvent() function] SDL_Event event; while (SDL_PollEvent(&event)) { @@ -114,6 +116,8 @@ int main(int, char**) if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window)) done = true; } + + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppIterate() function] if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) { SDL_Delay(10); @@ -199,6 +203,7 @@ int main(int, char**) } // Cleanup + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppQuit() function] SDL_WaitForGPUIdle(gpu_device); ImGui_ImplSDL3_Shutdown(); ImGui_ImplSDLGPU3_Shutdown(); diff --git a/examples/example_sdl3_sdlrenderer3/main.cpp b/examples/example_sdl3_sdlrenderer3/main.cpp index 68078eebd..ad05a0f91 100644 --- a/examples/example_sdl3_sdlrenderer3/main.cpp +++ b/examples/example_sdl3_sdlrenderer3/main.cpp @@ -24,6 +24,7 @@ int main(int, char**) { // Setup SDL + // [If using SDL_MAIN_USE_CALLBACKS: all code below until the main loop starts would likely be your SDL_AppInit() function] if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { printf("Error: SDL_Init(): %s\n", SDL_GetError()); @@ -101,6 +102,7 @@ int main(int, char**) // - 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. + // [If using SDL_MAIN_USE_CALLBACKS: call ImGui_ImplSDL3_ProcessEvent() from your SDL_AppEvent() function] SDL_Event event; while (SDL_PollEvent(&event)) { @@ -110,6 +112,8 @@ int main(int, char**) if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window)) done = true; } + + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppIterate() function] if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) { SDL_Delay(10); @@ -171,6 +175,7 @@ int main(int, char**) #endif // Cleanup + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppQuit() function] ImGui_ImplSDLRenderer3_Shutdown(); ImGui_ImplSDL3_Shutdown(); ImGui::DestroyContext(); diff --git a/examples/example_sdl3_vulkan/main.cpp b/examples/example_sdl3_vulkan/main.cpp index 99e441d7e..170eae45b 100644 --- a/examples/example_sdl3_vulkan/main.cpp +++ b/examples/example_sdl3_vulkan/main.cpp @@ -345,7 +345,8 @@ static void FramePresent(ImGui_ImplVulkanH_Window* wd) int main(int, char**) { // Setup SDL - if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD) != 0) + // [If using SDL_MAIN_USE_CALLBACKS: all code below until the main loop starts would likely be your SDL_AppInit() function] + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { printf("Error: SDL_Init(): %s\n", SDL_GetError()); return -1; @@ -447,6 +448,7 @@ int main(int, char**) // - 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. + // [If using SDL_MAIN_USE_CALLBACKS: call ImGui_ImplSDL3_ProcessEvent() from your SDL_AppEvent() function] SDL_Event event; while (SDL_PollEvent(&event)) { @@ -456,6 +458,8 @@ int main(int, char**) if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window)) done = true; } + + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppIterate() function] if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) { SDL_Delay(10); @@ -531,6 +535,7 @@ int main(int, char**) } // Cleanup + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppQuit() function] err = vkDeviceWaitIdle(g_Device); check_vk_result(err); ImGui_ImplVulkan_Shutdown(); From 324172fb1f096739e81c7f9f1f29939c5781e222 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Mar 2025 13:35:14 +0100 Subject: [PATCH 3/7] Demo: (Refactor) Moved DemoWindowWidgets() below the functions it calls, reducing amount of forward declarations. --- imgui_demo.cpp | 126 ++++++++++++++++++++----------------------------- 1 file changed, 51 insertions(+), 75 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 31904b1b4..775e0c731 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -73,7 +73,6 @@ Index of this file: // [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos) // [SECTION] Demo Window / ShowDemoWindow() // [SECTION] DemoWindowMenuBar() -// [SECTION] DemoWindowWidgets() // [SECTION] DemoWindowWidgetsBasic() // [SECTION] DemoWindowWidgetsBullets() // [SECTION] DemoWindowWidgetsCollapsingHeaders() @@ -98,6 +97,7 @@ Index of this file: // [SECTION] DemoWindowWidgetsTooltips() // [SECTION] DemoWindowWidgetsTreeNodes() // [SECTION] DemoWindowWidgetsVerticalSliders() +// [SECTION] DemoWindowWidgets() // [SECTION] DemoWindowLayout() // [SECTION] DemoWindowPopups() // [SECTION] DemoWindowTables() @@ -250,30 +250,6 @@ static void ShowExampleMenuFile(); // (because the link time of very large functions tends to grow non-linearly) static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data); static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data); -static void DemoWindowWidgetsBasic(); -static void DemoWindowWidgetsBullets(); -static void DemoWindowWidgetsCollapsingHeaders(); -static void DemoWindowWidgetsComboBoxes(); -static void DemoWindowWidgetsColorAndPickers(); -static void DemoWindowWidgetsDataTypes(); -static void DemoWindowWidgetsDisableBlocks(ImGuiDemoWindowData* demo_data); -static void DemoWindowWidgetsDragAndDrop(); -static void DemoWindowWidgetsDragsAndSliders(); -static void DemoWindowWidgetsImages(); -static void DemoWindowWidgetsListBoxes(); -static void DemoWindowWidgetsPlotting(); -static void DemoWindowWidgetsMultiComponents(); -static void DemoWindowWidgetsProgressBars(); -static void DemoWindowWidgetsQueryingStatuses(); -static void DemoWindowWidgetsSelectables(); -static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_data); -static void DemoWindowWidgetsTabs(); -static void DemoWindowWidgetsText(); -static void DemoWindowWidgetsTextFilter(); -static void DemoWindowWidgetsTextInput(); -static void DemoWindowWidgetsTooltips(); -static void DemoWindowWidgetsTreeNodes(); -static void DemoWindowWidgetsVerticalSliders(); static void DemoWindowLayout(); static void DemoWindowPopups(); static void DemoWindowTables(); @@ -816,56 +792,6 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) } } -//----------------------------------------------------------------------------- -// [SECTION] DemoWindowWidgets() -//----------------------------------------------------------------------------- - -static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) -{ - IMGUI_DEMO_MARKER("Widgets"); - //ImGui::SetNextItemOpen(true, ImGuiCond_Once); - if (!ImGui::CollapsingHeader("Widgets")) - return; - - const bool disable_all = demo_data->DisableSections; // The Checkbox for that is inside the "Disabled" section at the bottom - if (disable_all) - ImGui::BeginDisabled(); - - DemoWindowWidgetsBasic(); - DemoWindowWidgetsBullets(); - DemoWindowWidgetsCollapsingHeaders(); - DemoWindowWidgetsComboBoxes(); - DemoWindowWidgetsColorAndPickers(); - DemoWindowWidgetsDataTypes(); - - if (disable_all) - ImGui::EndDisabled(); - DemoWindowWidgetsDisableBlocks(demo_data); - if (disable_all) - ImGui::BeginDisabled(); - - DemoWindowWidgetsDragAndDrop(); - DemoWindowWidgetsDragsAndSliders(); - DemoWindowWidgetsImages(); - DemoWindowWidgetsListBoxes(); - DemoWindowWidgetsMultiComponents(); - DemoWindowWidgetsPlotting(); - DemoWindowWidgetsProgressBars(); - DemoWindowWidgetsQueryingStatuses(); - DemoWindowWidgetsSelectables(); - DemoWindowWidgetsSelectionAndMultiSelect(demo_data); - DemoWindowWidgetsTabs(); - DemoWindowWidgetsText(); - DemoWindowWidgetsTextFilter(); - DemoWindowWidgetsTextInput(); - DemoWindowWidgetsTooltips(); - DemoWindowWidgetsTreeNodes(); - DemoWindowWidgetsVerticalSliders(); - - if (disable_all) - ImGui::EndDisabled(); -} - //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsBasic() //----------------------------------------------------------------------------- @@ -4183,6 +4109,56 @@ static void DemoWindowWidgetsVerticalSliders() } } +//----------------------------------------------------------------------------- +// [SECTION] DemoWindowWidgets() +//----------------------------------------------------------------------------- + +static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) +{ + IMGUI_DEMO_MARKER("Widgets"); + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); + if (!ImGui::CollapsingHeader("Widgets")) + return; + + const bool disable_all = demo_data->DisableSections; // The Checkbox for that is inside the "Disabled" section at the bottom + if (disable_all) + ImGui::BeginDisabled(); + + DemoWindowWidgetsBasic(); + DemoWindowWidgetsBullets(); + DemoWindowWidgetsCollapsingHeaders(); + DemoWindowWidgetsComboBoxes(); + DemoWindowWidgetsColorAndPickers(); + DemoWindowWidgetsDataTypes(); + + if (disable_all) + ImGui::EndDisabled(); + DemoWindowWidgetsDisableBlocks(demo_data); + if (disable_all) + ImGui::BeginDisabled(); + + DemoWindowWidgetsDragAndDrop(); + DemoWindowWidgetsDragsAndSliders(); + DemoWindowWidgetsImages(); + DemoWindowWidgetsListBoxes(); + DemoWindowWidgetsMultiComponents(); + DemoWindowWidgetsPlotting(); + DemoWindowWidgetsProgressBars(); + DemoWindowWidgetsQueryingStatuses(); + DemoWindowWidgetsSelectables(); + DemoWindowWidgetsSelectionAndMultiSelect(demo_data); + DemoWindowWidgetsTabs(); + DemoWindowWidgetsText(); + DemoWindowWidgetsTextFilter(); + DemoWindowWidgetsTextInput(); + DemoWindowWidgetsTooltips(); + DemoWindowWidgetsTreeNodes(); + DemoWindowWidgetsVerticalSliders(); + + if (disable_all) + ImGui::EndDisabled(); +} + //----------------------------------------------------------------------------- // [SECTION] DemoWindowLayout() //----------------------------------------------------------------------------- From fcec08f7ae32656a00e5dde709ff390870a7a84b Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Mar 2025 13:39:48 +0100 Subject: [PATCH 4/7] Demo: (Refactor) Moved ExampleTreeNode contents below ShowDemoWindow() so main entry point is more visible to casual reader. --- imgui_demo.cpp | 189 +++++++++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 92 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 775e0c731..beffe618d 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -70,9 +70,9 @@ Index of this file: // [SECTION] Forward Declarations // [SECTION] Helpers -// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos) // [SECTION] Demo Window / ShowDemoWindow() // [SECTION] DemoWindowMenuBar() +// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos) // [SECTION] DemoWindowWidgetsBasic() // [SECTION] DemoWindowWidgetsBullets() // [SECTION] DemoWindowWidgetsCollapsingHeaders() @@ -256,6 +256,11 @@ static void DemoWindowTables(); static void DemoWindowColumns(); static void DemoWindowInputs(); +// Helper tree functions used by Property Editor & Multi-Select demos +struct ExampleTreeNode; +static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent); +static void ExampleTree_DestroyNode(ExampleTreeNode* node); + //----------------------------------------------------------------------------- // [SECTION] Helpers //----------------------------------------------------------------------------- @@ -282,97 +287,6 @@ ImGuiDemoMarkerCallback GImGuiDemoMarkerCallback = NULL; void* GImGuiDemoMarkerCallbackUserData = NULL; #define IMGUI_DEMO_MARKER(section) do { if (GImGuiDemoMarkerCallback != NULL) GImGuiDemoMarkerCallback(__FILE__, __LINE__, section, GImGuiDemoMarkerCallbackUserData); } while (0) -//----------------------------------------------------------------------------- -// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor etc.) -//----------------------------------------------------------------------------- - -// Simple representation for a tree -// (this is designed to be simple to understand for our demos, not to be fancy or efficient etc.) -struct ExampleTreeNode -{ - // Tree structure - char Name[28] = ""; - int UID = 0; - ExampleTreeNode* Parent = NULL; - ImVector Childs; - unsigned short IndexInParent = 0; // Maintaining this allows us to implement linear traversal more easily - - // Leaf Data - bool HasData = false; // All leaves have data - bool DataMyBool = true; - int DataMyInt = 128; - ImVec2 DataMyVec2 = ImVec2(0.0f, 3.141592f); -}; - -// Simple representation of struct metadata/serialization data. -// (this is a minimal version of what a typical advanced application may provide) -struct ExampleMemberInfo -{ - const char* Name; // Member name - ImGuiDataType DataType; // Member type - int DataCount; // Member count (1 when scalar) - int Offset; // Offset inside parent structure -}; - -// Metadata description of ExampleTreeNode struct. -static const ExampleMemberInfo ExampleTreeNodeMemberInfos[] -{ - { "MyName", ImGuiDataType_String, 1, offsetof(ExampleTreeNode, Name) }, - { "MyBool", ImGuiDataType_Bool, 1, offsetof(ExampleTreeNode, DataMyBool) }, - { "MyInt", ImGuiDataType_S32, 1, offsetof(ExampleTreeNode, DataMyInt) }, - { "MyVec2", ImGuiDataType_Float, 2, offsetof(ExampleTreeNode, DataMyVec2) }, -}; - -static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent) -{ - ExampleTreeNode* node = IM_NEW(ExampleTreeNode); - snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name); - node->UID = uid; - node->Parent = parent; - node->IndexInParent = parent ? (unsigned short)parent->Childs.Size : 0; - if (parent) - parent->Childs.push_back(node); - return node; -} - -static void ExampleTree_DestroyNode(ExampleTreeNode* node) -{ - for (ExampleTreeNode* child_node : node->Childs) - ExampleTree_DestroyNode(child_node); - IM_DELETE(node); -} - -// Create example tree data -// (this allocates _many_ more times than most other code in either Dear ImGui or others demo) -static ExampleTreeNode* ExampleTree_CreateDemoTree() -{ - static const char* root_names[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pear", "Pineapple", "Strawberry", "Watermelon" }; - const size_t NAME_MAX_LEN = sizeof(ExampleTreeNode::Name); - char name_buf[NAME_MAX_LEN]; - int uid = 0; - ExampleTreeNode* node_L0 = ExampleTree_CreateNode("", ++uid, NULL); - const int root_items_multiplier = 2; - for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * root_items_multiplier; idx_L0++) - { - snprintf(name_buf, IM_ARRAYSIZE(name_buf), "%s %d", root_names[idx_L0 / root_items_multiplier], idx_L0 % root_items_multiplier); - ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0); - const int number_of_childs = (int)strlen(node_L1->Name); - for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++) - { - snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Child %d", idx_L1); - ExampleTreeNode* node_L2 = ExampleTree_CreateNode(name_buf, ++uid, node_L1); - node_L2->HasData = true; - if (idx_L1 == 0) - { - snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Sub-child %d", 0); - ExampleTreeNode* node_L3 = ExampleTree_CreateNode(name_buf, ++uid, node_L2); - node_L3->HasData = true; - } - } - } - return node_L0; -} - //----------------------------------------------------------------------------- // [SECTION] Demo Window / ShowDemoWindow() //----------------------------------------------------------------------------- @@ -792,6 +706,97 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) } } +//----------------------------------------------------------------------------- +// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos) +//----------------------------------------------------------------------------- + +// Simple representation for a tree +// (this is designed to be simple to understand for our demos, not to be fancy or efficient etc.) +struct ExampleTreeNode +{ + // Tree structure + char Name[28] = ""; + int UID = 0; + ExampleTreeNode* Parent = NULL; + ImVector Childs; + unsigned short IndexInParent = 0; // Maintaining this allows us to implement linear traversal more easily + + // Leaf Data + bool HasData = false; // All leaves have data + bool DataMyBool = true; + int DataMyInt = 128; + ImVec2 DataMyVec2 = ImVec2(0.0f, 3.141592f); +}; + +// Simple representation of struct metadata/serialization data. +// (this is a minimal version of what a typical advanced application may provide) +struct ExampleMemberInfo +{ + const char* Name; // Member name + ImGuiDataType DataType; // Member type + int DataCount; // Member count (1 when scalar) + int Offset; // Offset inside parent structure +}; + +// Metadata description of ExampleTreeNode struct. +static const ExampleMemberInfo ExampleTreeNodeMemberInfos[] +{ + { "MyName", ImGuiDataType_String, 1, offsetof(ExampleTreeNode, Name) }, + { "MyBool", ImGuiDataType_Bool, 1, offsetof(ExampleTreeNode, DataMyBool) }, + { "MyInt", ImGuiDataType_S32, 1, offsetof(ExampleTreeNode, DataMyInt) }, + { "MyVec2", ImGuiDataType_Float, 2, offsetof(ExampleTreeNode, DataMyVec2) }, +}; + +static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent) +{ + ExampleTreeNode* node = IM_NEW(ExampleTreeNode); + snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name); + node->UID = uid; + node->Parent = parent; + node->IndexInParent = parent ? (unsigned short)parent->Childs.Size : 0; + if (parent) + parent->Childs.push_back(node); + return node; +} + +static void ExampleTree_DestroyNode(ExampleTreeNode* node) +{ + for (ExampleTreeNode* child_node : node->Childs) + ExampleTree_DestroyNode(child_node); + IM_DELETE(node); +} + +// Create example tree data +// (this allocates _many_ more times than most other code in either Dear ImGui or others demo) +static ExampleTreeNode* ExampleTree_CreateDemoTree() +{ + static const char* root_names[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pear", "Pineapple", "Strawberry", "Watermelon" }; + const size_t NAME_MAX_LEN = sizeof(ExampleTreeNode::Name); + char name_buf[NAME_MAX_LEN]; + int uid = 0; + ExampleTreeNode* node_L0 = ExampleTree_CreateNode("", ++uid, NULL); + const int root_items_multiplier = 2; + for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * root_items_multiplier; idx_L0++) + { + snprintf(name_buf, IM_ARRAYSIZE(name_buf), "%s %d", root_names[idx_L0 / root_items_multiplier], idx_L0 % root_items_multiplier); + ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0); + const int number_of_childs = (int)strlen(node_L1->Name); + for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++) + { + snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Child %d", idx_L1); + ExampleTreeNode* node_L2 = ExampleTree_CreateNode(name_buf, ++uid, node_L1); + node_L2->HasData = true; + if (idx_L1 == 0) + { + snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Sub-child %d", 0); + ExampleTreeNode* node_L3 = ExampleTree_CreateNode(name_buf, ++uid, node_L2); + node_L3->HasData = true; + } + } + } + return node_L0; +} + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsBasic() //----------------------------------------------------------------------------- From 119dfbc6270cedf29e8655b296f9ca0f31cede32 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Mar 2025 15:04:26 +0100 Subject: [PATCH 5/7] Debug Tools: Tweaked layout of ID Stack Tool and always display full path. (#4631) --- docs/CHANGELOG.txt | 1 + imgui.cpp | 50 ++++++++++++++++++++++++---------------------- imgui.h | 3 ++- imgui_internal.h | 1 + 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 78c6f09da..a6110cd55 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -110,6 +110,7 @@ Other changes: One case where it would manifest was calling Combo() with an out of range index. (#8450) - Debug Tools: Added io.ConfigDebugHighlightIdConflictsShowItemPicker (defaults to true) to allow disabled Item Picker suggestion in user facing builds. (#7961, #7669) +- Debug Tools: Tweaked layout of ID Stack Tool and always display full path. (#4631) - Misc: Added ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress mouse cursors (busy/wait/hourglass shape, with or without an arrow cursor). - Demo: Reorganized "Widgets" section to be alphabetically ordered and split in more functions. diff --git a/imgui.cpp b/imgui.cpp index bae74db5f..896330913 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16924,42 +16924,44 @@ void ImGui::ShowIDStackToolWindow(bool* p_open) // Display hovered/active status ImGuiIDStackTool* tool = &g.DebugIDStackTool; - const ImGuiID hovered_id = g.HoveredIdPreviousFrame; - const ImGuiID active_id = g.ActiveId; -#ifdef IMGUI_ENABLE_TEST_ENGINE - Text("HoveredId: 0x%08X (\"%s\"), ActiveId: 0x%08X (\"%s\")", hovered_id, hovered_id ? ImGuiTestEngine_FindItemDebugLabel(&g, hovered_id) : "", active_id, active_id ? ImGuiTestEngine_FindItemDebugLabel(&g, active_id) : ""); -#else - Text("HoveredId: 0x%08X, ActiveId: 0x%08X", hovered_id, active_id); -#endif + + // Build and display path + tool->ResultPathBuf.resize(0); + for (int stack_n = 0; stack_n < tool->Results.Size; stack_n++) + { + char level_desc[256]; + StackToolFormatLevelInfo(tool, stack_n, false, level_desc, IM_ARRAYSIZE(level_desc)); + tool->ResultPathBuf.append(stack_n == 0 ? "//" : "/"); + for (int n = 0; level_desc[n]; n++) + { + if (level_desc[n] == '/') + tool->ResultPathBuf.append("\\"); + tool->ResultPathBuf.append(level_desc + n, level_desc + n + 1); + } + } + Text("0x%08X", tool->QueryId); SameLine(); MetricsHelpMarker("Hover an item with the mouse to display elements of the ID Stack leading to the item's final ID.\nEach level of the stack correspond to a PushID() call.\nAll levels of the stack are hashed together to make the final ID of a widget (ID displayed at the bottom level of the stack).\nRead FAQ entry about the ID stack for details."); // CTRL+C to copy path const float time_since_copy = (float)g.Time - tool->CopyToClipboardLastTime; - Checkbox("Ctrl+C: copy path to clipboard", &tool->CopyToClipboardOnCtrlC); + SameLine(); + PushStyleVarY(ImGuiStyleVar_FramePadding, 0.0f); Checkbox("Ctrl+C: copy path", &tool->CopyToClipboardOnCtrlC); PopStyleVar(); SameLine(); TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.f, 1.f, 0.3f, 1.f) : ImVec4(), "*COPIED*"); if (tool->CopyToClipboardOnCtrlC && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused)) { tool->CopyToClipboardLastTime = (float)g.Time; - char* p = g.TempBuffer.Data; - char* p_end = p + g.TempBuffer.Size; - for (int stack_n = 0; stack_n < tool->Results.Size && p + 3 < p_end; stack_n++) - { - *p++ = '/'; - char level_desc[256]; - StackToolFormatLevelInfo(tool, stack_n, false, level_desc, IM_ARRAYSIZE(level_desc)); - for (int n = 0; level_desc[n] && p + 2 < p_end; n++) - { - if (level_desc[n] == '/') - *p++ = '\\'; - *p++ = level_desc[n]; - } - } - *p = '\0'; - SetClipboardText(g.TempBuffer.Data); + SetClipboardText(tool->ResultPathBuf.c_str()); } + Text("- Path \"%s\"", tool->ResultPathBuf.c_str()); +#ifdef IMGUI_ENABLE_TEST_ENGINE + Text("- Label \"%s\"", tool->QueryId ? ImGuiTestEngine_FindItemDebugLabel(&g, tool->QueryId) : ""); +#endif + + Separator(); + // Display decorated stack tool->LastActiveFrame = g.FrameCount; if (tool->Results.Size > 0 && BeginTable("##table", 3, ImGuiTableFlags_Borders)) diff --git a/imgui.h b/imgui.h index 1cda94914..2e6a67b33 100644 --- a/imgui.h +++ b/imgui.h @@ -2600,10 +2600,11 @@ struct ImGuiTextBuffer ImGuiTextBuffer() { } inline char operator[](int i) const { IM_ASSERT(Buf.Data != NULL); return Buf.Data[i]; } const char* begin() const { return Buf.Data ? &Buf.front() : EmptyString; } - const char* end() const { return Buf.Data ? &Buf.back() : EmptyString; } // Buf is zero-terminated, so end() will point on the zero-terminator + const char* end() const { return Buf.Data ? &Buf.back() : EmptyString; } // Buf is zero-terminated, so end() will point on the zero-terminator int size() const { return Buf.Size ? Buf.Size - 1 : 0; } bool empty() const { return Buf.Size <= 1; } void clear() { Buf.clear(); } + void resize(int size) { IM_ASSERT(size == 0); if (Buf.Size > 0) Buf.Data[0] = 0; Buf.resize(0); } // Similar to resize(0) on ImVector: empty string but don't free buffer. Only resize(0) supported for now. void reserve(int capacity) { Buf.reserve(capacity); } const char* c_str() const { return Buf.Data ? Buf.Data : EmptyString; } IMGUI_API void append(const char* str, const char* str_end = NULL); diff --git a/imgui_internal.h b/imgui_internal.h index 40b854559..455a9e066 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2030,6 +2030,7 @@ struct ImGuiIDStackTool ImVector Results; bool CopyToClipboardOnCtrlC; float CopyToClipboardLastTime; + ImGuiTextBuffer ResultPathBuf; ImGuiIDStackTool() { memset(this, 0, sizeof(*this)); CopyToClipboardLastTime = -FLT_MAX; } }; From 9f49292b357bacdf23f7131d0fa735672912f50e Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Mar 2025 15:23:49 +0100 Subject: [PATCH 6/7] Internals: Menus: reworked mangling of menu windows to use "###Menu_00" etc. instead of "##Menu_00". --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 30 ++++++++++++++++++++++++------ imgui.h | 2 +- imgui_internal.h | 1 + imgui_widgets.cpp | 4 ++-- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a6110cd55..39d73571d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -62,6 +62,9 @@ Breaking changes: - Backends: Vulkan: Added 'uint32_t api_version' argument to ImGui_ImplVulkan_LoadFunctions(). Note that it was also added to ImGui_ImplVulkan_InitInfo but for the later it is optional. (#8326, #8365, #8400) +- Internals: Menus: reworked mangling of menu windows to use "###Menu_00" etc. instead + of "##Menu_00", allowing them to also store the menu name before it. This shouldn't + affect code unless directly accessing menu window from their mangled name. Other changes: diff --git a/imgui.cpp b/imgui.cpp index 896330913..b2738f170 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -431,6 +431,7 @@ CODE When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2025/03/05 (1.91.9) - BeginMenu(): Internals: reworked mangling of menu windows to use "###Menu_00" etc. instead of "##Menu_00", allowing them to also store the menu name before it. This shouldn't affect code unless directly accessing menu window from their mangled name. - 2025/02/27 (1.91.9) - Image(): removed 'tint_col' and 'border_col' parameter from Image() function. Added ImageWithBg() replacement. (#8131, #8238) - old: void Image (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1), ImVec4 tint_col = (1,1,1,1), ImVec4 border_col = (0,0,0,0)); - new: void Image (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1)); @@ -7202,7 +7203,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would stay unchanged). // The title bar always display the 'name' parameter, so we only update the string storage if it needs to be visible to the end-user elsewhere. bool window_title_visible_elsewhere = false; - if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB + if (g.NavWindowingListWindow != NULL && (flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB + window_title_visible_elsewhere = true; + if (flags & ImGuiWindowFlags_ChildMenu) window_title_visible_elsewhere = true; if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0) { @@ -11714,17 +11717,32 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags) } char name[20]; - if (extra_window_flags & ImGuiWindowFlags_ChildMenu) - ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginMenuDepth); // Recycle windows based on depth - else - ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame + IM_ASSERT((extra_window_flags & ImGuiWindowFlags_ChildMenu) == 0); // Use BeginPopupMenuEx() + ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // No recycling, so we can close/open during the same frame bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup); if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) EndPopup(); - //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack; + return is_open; +} +bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags) +{ + ImGuiContext& g = *GImGui; + if (!IsPopupOpen(id, ImGuiPopupFlags_None)) + { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + return false; + } + + char name[128]; + IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu); + ImFormatString(name, IM_ARRAYSIZE(name), "%s###Menu_%02d", label, g.BeginMenuDepth); // Recycle windows based on depth + bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup); + if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) + EndPopup(); + //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack; return is_open; } diff --git a/imgui.h b/imgui.h index 2e6a67b33..f154efcb9 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.9 WIP" -#define IMGUI_VERSION_NUM 19186 +#define IMGUI_VERSION_NUM 19187 #define IMGUI_HAS_TABLE /* diff --git a/imgui_internal.h b/imgui_internal.h index 455a9e066..a78ae9827 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3136,6 +3136,7 @@ namespace ImGui // Popups, Modals IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags); + IMGUI_API bool BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags); IMGUI_API void OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_None); IMGUI_API void ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup); IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 76dabb8f9..b8b872b1a 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -8867,7 +8867,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) if (g.MenusIdSubmittedThisFrame.contains(id)) { if (menu_is_open) - menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + menu_is_open = BeginPopupMenuEx(id, label, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) else g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values return menu_is_open; @@ -9029,7 +9029,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) ImGuiLastItemData last_item_in_parent = g.LastItemData; SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos. PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding - menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + menu_is_open = BeginPopupMenuEx(id, label, window_flags); // menu_is_open may be 'false' when the popup is completely clipped (e.g. zero size display) PopStyleVar(); if (menu_is_open) { From 377a387a427f152d63596de66bbaa590234a2b1c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Mar 2025 16:23:31 +0100 Subject: [PATCH 7/7] Add proper ImGuiTextBuffer::resize() support other than 0. --- imgui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index f154efcb9..2f6a18d2a 100644 --- a/imgui.h +++ b/imgui.h @@ -2604,7 +2604,7 @@ struct ImGuiTextBuffer int size() const { return Buf.Size ? Buf.Size - 1 : 0; } bool empty() const { return Buf.Size <= 1; } void clear() { Buf.clear(); } - void resize(int size) { IM_ASSERT(size == 0); if (Buf.Size > 0) Buf.Data[0] = 0; Buf.resize(0); } // Similar to resize(0) on ImVector: empty string but don't free buffer. Only resize(0) supported for now. + void resize(int size) { if (Buf.Size > size) Buf.Data[size] = 0; Buf.resize(size ? size + 1 : 0, 0); } // Similar to resize(0) on ImVector: empty string but don't free buffer. void reserve(int capacity) { Buf.reserve(capacity); } const char* c_str() const { return Buf.Data ? Buf.Data : EmptyString; } IMGUI_API void append(const char* str, const char* str_end = NULL);