Skip to content

Commit 67cd4ea

Browse files
committed
Added io.ConfigDebugHighlightIdConflicts debug feature! (#7961, #7669)
Also #74, #96, #480, #501, #647, #654, #719, #843, #894, #1057, #1173, #1390, #1414, #1556, #1768, #2041, #2116, #2330, #2475, #2562, #2667, #2807, #2885, #3102, #3375, #3526, #3964, #4008, #4070, #4158, #4172, #4199, #4375, #4395, #4471, #4548, #4612, #4631, #4657, #4796, #5210, #5303, #5360, #5393, #5533, #5692, #5707, #5729, #5773, #5787, #5884, #6046, #6093, #6186, #6223, #6364, #6387, #6567, #6692, #6724, #6939, #6984, #7246, #7270, #7375, #7421, #7434, #7472, #7581, #7724, #7926, #7937 and probably more.. Tagging to increase visibility!
1 parent a8eec24 commit 67cd4ea

File tree

7 files changed

+95
-7
lines changed

7 files changed

+95
-7
lines changed

docs/CHANGELOG.txt

+19
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,25 @@ Breaking changes:
4343

4444
Other changes:
4545

46+
- Added io.ConfigDebugHighlightIdConflicts debug feature! (#7961, #7669)
47+
THIS DETECTS THE MOST COMMON USER ERROR BY FIRST-TIME DEAR IMGUI PROGRAMMERS!
48+
- The tool detects when multiple items are sharing the same identifier, due to not
49+
using PushID/PopID in loops, or not using ID stack facilities such as "##" suffixes.
50+
Very frequently it happens when using empty "" labels.
51+
- When hovering an item with a conflicting ID, all visible items with the same ID will
52+
be highlighted and an explanatory tooltip is made visible.
53+
- The feature may be disabled and is exposed in Demo->Tools menu.
54+
- I've been wanting to add this tool for a long time, but was stalled by finding a way to
55+
not make it spammy + make it practically zero cost. After @pthom made various proposals to
56+
solve the same problem (thanks for pushing me!), I decided it was time to finish it.
57+
- Added ImGuiItemFlags_AllowDuplicateId to use with PushItemFlag/PopItemFlag() if for some
58+
reason you intend to have duplicate identifiers.
59+
- (#74, #96, #480, #501, #647, #654, #719, #843, #894, #1057, #1173, #1390, #1414, #1556, #1768,
60+
#2041, #2116, #2330, #2475, #2562, #2667, #2807, #2885, #3102, #3375, #3526, #3964, #4008,
61+
#4070, #4158, #4172, #4199, #4375, #4395, #4471, #4548, #4612, #4631, #4657, #4796, #5210,
62+
#5303, #5360, #5393, #5533, #5692, #5707, #5729, #5773, #5787, #5884, #6046, #6093, #6186,
63+
#6223, #6364, #6387, #6567, #6692, #6724, #6939, #6984, #7246, #7270, #7375, #7421, #7434,
64+
#7472, #7581, #7724, #7926, #7937 and probably more..)
4665
- Nav: pressing any keyboard key while holding Alt disable toggling nav layer on Alt release. (#4439)
4766
- InputText: added CJK double-width punctuation to list of separators considered for CTRL+Arrow.
4867
- TextLinkOpenURL(): modified tooltip to display a verb "Open 'xxxx'". (#7885, #7660)

docs/FAQ.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,11 @@ ctx->RSSetScissorRects(1, &r);
204204
### Q: How can I have multiple widgets with the same label?
205205
### Q: How can I have multiple windows with the same label?
206206

207-
**USING THE SAME LABEL+ID IS THE MOST COMMON USER MISTAKE:**
207+
**USING THE SAME LABEL+ID IS THE MOST COMMON USER MISTAKE!**
208+
<br>**USING AN EMPTY LABEL IS THE SAME AS USING THE SAME LABEL AS YOUR PARENT WIDGET!**
208209
<table>
209210
<tr>
210-
<td><img src="https://github.com/ocornut/imgui/assets/8225057/76eb9467-74d1-4e95-9f56-be81c6dd029d"></td>
211+
<td><img src="https://github.com/user-attachments/assets/776a8315-1164-4178-9a8c-df52e0ff28aa"></td>
211212
<td>
212213
<pre lang="cpp">
213214
ImGui::Begin("Incorrect!");

imgui.cpp

+42
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,8 @@ ImGuiIO::ImGuiIO()
13951395
ConfigWindowsResizeFromEdges = true;
13961396
ConfigWindowsMoveFromTitleBarOnly = false;
13971397
ConfigMemoryCompactTimer = 60.0f;
1398+
ConfigDebugIsDebuggerPresent = false;
1399+
ConfigDebugHighlightIdConflicts = true;
13981400
ConfigDebugBeginReturnValueOnce = false;
13991401
ConfigDebugBeginReturnValueLoop = false;
14001402

@@ -4290,6 +4292,17 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag
42904292
{
42914293
ImGuiContext& g = *GImGui;
42924294
ImGuiWindow* window = g.CurrentWindow;
4295+
4296+
// Detect ID conflicts
4297+
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
4298+
if (id != 0 && g.HoveredIdPreviousFrame == id && (item_flags & ImGuiItemFlags_AllowDuplicateId) == 0)
4299+
{
4300+
g.HoveredIdPreviousFrameItemCount++;
4301+
if (g.DebugDrawIdConflicts == id)
4302+
window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f);
4303+
}
4304+
#endif
4305+
42934306
if (g.HoveredWindow != window)
42944307
return false;
42954308
if (!IsMouseHoveringRect(bb.Min, bb.Max))
@@ -4833,6 +4846,11 @@ void ImGui::NewFrame()
48334846
if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId)
48344847
KeepAliveID(g.DragDropPayload.SourceId);
48354848

4849+
// [DEBUG]
4850+
g.DebugDrawIdConflicts = 0;
4851+
if (g.IO.ConfigDebugHighlightIdConflicts && g.HoveredIdPreviousFrameItemCount > 1)
4852+
g.DebugDrawIdConflicts = g.HoveredIdPreviousFrame;
4853+
48364854
// Update HoveredId data
48374855
if (!g.HoveredIdPreviousFrame)
48384856
g.HoveredIdTimer = 0.0f;
@@ -4843,6 +4861,7 @@ void ImGui::NewFrame()
48434861
if (g.HoveredId && g.ActiveId != g.HoveredId)
48444862
g.HoveredIdNotActiveTimer += g.IO.DeltaTime;
48454863
g.HoveredIdPreviousFrame = g.HoveredId;
4864+
g.HoveredIdPreviousFrameItemCount = 0;
48464865
g.HoveredId = 0;
48474866
g.HoveredIdAllowOverlap = false;
48484867
g.HoveredIdIsDisabled = false;
@@ -5235,6 +5254,29 @@ void ImGui::EndFrame()
52355254
return;
52365255
IM_ASSERT(g.WithinFrameScope && "Forgot to call ImGui::NewFrame()?");
52375256

5257+
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
5258+
if (g.DebugDrawIdConflicts != 0)
5259+
{
5260+
PushStyleColor(ImGuiCol_PopupBg, ImLerp(g.Style.Colors[ImGuiCol_PopupBg], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.10f));
5261+
if (g.DebugItemPickerActive == false && BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))
5262+
{
5263+
SeparatorText("MESSAGE FROM DEAR IMGUI");
5264+
Text("Programmer error: %d visible items with conflicting ID!", g.HoveredIdPreviousFrameItemCount);
5265+
BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!");
5266+
BulletText("Empty label e.g. Button(\"\") == same ID as parent widget/node. Use Button(\"##xx\") instead!");
5267+
BulletText("Press F1 to open \"FAQ -> About the ID Stack System\" and read details.");
5268+
BulletText("Press CTRL+P to activate Item Picker and debug-break in item call-stack.");
5269+
BulletText("Set io.ConfigDebugDetectIdConflicts=false to disable this warning in non-programmers builds.");
5270+
EndTooltip();
5271+
}
5272+
PopStyleColor();
5273+
if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_P, ImGuiInputFlags_RouteGlobal))
5274+
DebugStartItemPicker();
5275+
if (Shortcut(ImGuiKey_F1, ImGuiInputFlags_RouteGlobal) && g.PlatformIO.Platform_OpenInShellFn != NULL)
5276+
g.PlatformIO.Platform_OpenInShellFn(&g, "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage");
5277+
}
5278+
#endif
5279+
52385280
CallContextHooks(&g, ImGuiContextHookType_EndFramePre);
52395281

52405282
ErrorCheckEndFrameSanityChecks();

imgui.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
// Library Version
3030
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
3131
#define IMGUI_VERSION "1.91.2 WIP"
32-
#define IMGUI_VERSION_NUM 19112
32+
#define IMGUI_VERSION_NUM 19113
3333
#define IMGUI_HAS_TABLE
3434

3535
/*
@@ -1135,6 +1135,7 @@ enum ImGuiItemFlags_
11351135
ImGuiItemFlags_NoNavDefaultFocus = 1 << 2, // false // Disable item being a candidate for default focus (e.g. used by title bar items).
11361136
ImGuiItemFlags_ButtonRepeat = 1 << 3, // false // Any button-like behavior will have repeat mode enabled (based on io.KeyRepeatDelay and io.KeyRepeatRate values). Note that you can also call IsItemActive() after any button to tell if it is being held.
11371137
ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window.
1138+
ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set.
11381139
};
11391140

11401141
// Flags for ImGui::InputText()
@@ -2231,13 +2232,15 @@ struct ImGuiIO
22312232
const char* LogFilename; // = "imgui_log.txt"// Path to .log file (default parameter to ImGui::LogToFile when no file is specified).
22322233
void* UserData; // = NULL // Store your own data.
22332234

2235+
// Font system
22342236
ImFontAtlas*Fonts; // <auto> // Font atlas: load, rasterize and pack one or more fonts into a single texture.
22352237
float FontGlobalScale; // = 1.0f // Global scale all fonts
22362238
bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel.
22372239
ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0].
22382240
ImVec2 DisplayFramebufferScale; // = (1, 1) // For retina display or other situations where window coordinates are different from framebuffer coordinates. This generally ends up in ImDrawData::FramebufferScale.
22392241

22402242
// Miscellaneous options
2243+
// (you can visualize and interact with all options in 'Demo->Configuration')
22412244
bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations.
22422245
bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // Swap Cmd<>Ctrl keys + OS X style text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl.
22432246
bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout.
@@ -2267,6 +2270,12 @@ struct ImGuiIO
22672270
// e.g. io.ConfigDebugIsDebuggerPresent = ::IsDebuggerPresent() on Win32, or refer to ImOsIsDebuggerPresent() imgui_test_engine/imgui_te_utils.cpp for a Unix compatible version).
22682271
bool ConfigDebugIsDebuggerPresent; // = false // Enable various tools calling IM_DEBUG_BREAK().
22692272

2273+
// Tools to detect code submitting items with conflicting/duplicate IDs
2274+
// - Code should use PushID()/PopID() in loops, or append "##xx" to same-label identifiers.
2275+
// - Empty label e.g. Button("") == same ID as parent widget/node. Use Button("##xx") instead!
2276+
// - See FAQ https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system
2277+
bool ConfigDebugHighlightIdConflicts;// = true // Highlight and show an error message when multiple items have conflicting identifiers.
2278+
22702279
// Tools to test correct Begin/End and BeginChild/EndChild behaviors.
22712280
// - Presently Begin()/End() and BeginChild()/EndChild() needs to ALWAYS be called in tandem, regardless of return value of BeginXXX()
22722281
// - This is inconsistent with other BeginXXX functions and create confusion for many users.

imgui_demo.cpp

+14-4
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,8 @@ void ImGui::ShowDemoWindow(bool* p_open)
546546
ImGui::SeparatorText("Debug");
547547
ImGui::Checkbox("io.ConfigDebugIsDebuggerPresent", &io.ConfigDebugIsDebuggerPresent);
548548
ImGui::SameLine(); HelpMarker("Enable various tools calling IM_DEBUG_BREAK().\n\nRequires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application.");
549+
ImGui::Checkbox("io.ConfigDebugHighlightIdConflicts", &io.ConfigDebugHighlightIdConflicts);
550+
ImGui::SameLine(); HelpMarker("Highlight and show an error message when multiple items have conflicting identifiers.");
549551
ImGui::BeginDisabled();
550552
ImGui::Checkbox("io.ConfigDebugBeginReturnValueOnce", &io.ConfigDebugBeginReturnValueOnce);
551553
ImGui::EndDisabled();
@@ -684,6 +686,7 @@ static void ShowDemoWindowMenuBar(ImGuiDemoWindowData* demo_data)
684686
if (ImGui::BeginMenu("Tools"))
685687
{
686688
IMGUI_DEMO_MARKER("Menu/Tools");
689+
ImGuiIO& io = ImGui::GetIO();
687690
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
688691
const bool has_debug_tools = true;
689692
#else
@@ -692,14 +695,16 @@ static void ShowDemoWindowMenuBar(ImGuiDemoWindowData* demo_data)
692695
ImGui::MenuItem("Metrics/Debugger", NULL, &demo_data->ShowMetrics, has_debug_tools);
693696
ImGui::MenuItem("Debug Log", NULL, &demo_data->ShowDebugLog, has_debug_tools);
694697
ImGui::MenuItem("ID Stack Tool", NULL, &demo_data->ShowIDStackTool, has_debug_tools);
695-
ImGui::MenuItem("Style Editor", NULL, &demo_data->ShowStyleEditor);
696-
bool is_debugger_present = ImGui::GetIO().ConfigDebugIsDebuggerPresent;
698+
bool is_debugger_present = io.ConfigDebugIsDebuggerPresent;
697699
if (ImGui::MenuItem("Item Picker", NULL, false, has_debug_tools && is_debugger_present))
698700
ImGui::DebugStartItemPicker();
699701
if (!is_debugger_present)
700702
ImGui::SetItemTooltip("Requires io.ConfigDebugIsDebuggerPresent=true to be set.\n\nWe otherwise disable the menu option to avoid casual users crashing the application.\n\nYou can however always access the Item Picker in Metrics->Tools.");
701-
ImGui::Separator();
703+
ImGui::MenuItem("Style Editor", NULL, &demo_data->ShowStyleEditor);
702704
ImGui::MenuItem("About Dear ImGui", NULL, &demo_data->ShowAbout);
705+
706+
ImGui::SeparatorText("Debug Options");
707+
ImGui::MenuItem("Highlight ID Conflicts", NULL, &io.ConfigDebugHighlightIdConflicts, has_debug_tools);
703708
ImGui::EndMenu();
704709
}
705710
ImGui::EndMenuBar();
@@ -2596,7 +2601,10 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
25962601
IMGUI_DEMO_MARKER("Widgets/Drag and Drop/Drag to reorder items (simple)");
25972602
if (ImGui::TreeNode("Drag to reorder items (simple)"))
25982603
{
2599-
// FIXME: temporary ID Conflict during reordering as a same item may be submitting twice.
2604+
// FIXME: there is temporary (usually single-frame) ID Conflict during reordering as a same item may be submitting twice.
2605+
// This code was always slightly faulty but in a way which was not easily noticeable.
2606+
// Until we fix this, enable ImGuiItemFlags_AllowDuplicateId to disable detecting the issue.
2607+
ImGui::PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true);
26002608

26012609
// Simple reordering
26022610
HelpMarker(
@@ -2619,6 +2627,8 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
26192627
}
26202628
}
26212629
}
2630+
2631+
ImGui::PopItemFlag();
26222632
ImGui::TreePop();
26232633
}
26242634

imgui_internal.h

+4
Original file line numberDiff line numberDiff line change
@@ -2042,9 +2042,11 @@ struct ImGuiContext
20422042
ImVec2 WheelingAxisAvg;
20432043

20442044
// Item/widgets state and tracking information
2045+
ImGuiID DebugDrawIdConflicts; // Set when we detect multiple items with the same identifier
20452046
ImGuiID DebugHookIdInfo; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line]
20462047
ImGuiID HoveredId; // Hovered widget, filled during the frame
20472048
ImGuiID HoveredIdPreviousFrame;
2049+
int HoveredIdPreviousFrameItemCount; // Count numbers of items using the same ID as last frame's hovered id
20482050
float HoveredIdTimer; // Measure contiguous hovering time
20492051
float HoveredIdNotActiveTimer; // Measure contiguous hovering time where the item has not been active
20502052
bool HoveredIdAllowOverlap;
@@ -2365,8 +2367,10 @@ struct ImGuiContext
23652367
WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1;
23662368
WheelingWindowReleaseTimer = 0.0f;
23672369

2370+
DebugDrawIdConflicts = 0;
23682371
DebugHookIdInfo = 0;
23692372
HoveredId = HoveredIdPreviousFrame = 0;
2373+
HoveredIdPreviousFrameItemCount = 0;
23702374
HoveredIdAllowOverlap = false;
23712375
HoveredIdIsDisabled = false;
23722376
HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f;

imgui_widgets.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -3551,6 +3551,8 @@ int ImParseFormatPrecision(const char* fmt, int default_precision)
35513551

35523552
// Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
35533553
// FIXME: Facilitate using this in variety of other situations.
3554+
// FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but
3555+
// the expected relationship between TempInputXXX functions and LastItemData is a little fishy.
35543556
bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags)
35553557
{
35563558
// On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
@@ -3561,6 +3563,7 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char*
35613563
ClearActiveID();
35623564

35633565
g.CurrentWindow->DC.CursorPos = bb.Min;
3566+
g.LastItemData.InFlags |= ImGuiItemFlags_AllowDuplicateId;
35643567
bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem);
35653568
if (init)
35663569
{

0 commit comments

Comments
 (0)