Merge branch 'master' into docking

# Conflicts:
#	imgui.cpp
#	imgui_internal.h
This commit is contained in:
ocornut
2024-09-27 19:03:25 +02:00
7 changed files with 435 additions and 206 deletions

476
imgui.cpp
View File

@@ -1440,6 +1440,11 @@ ImGuiIO::ImGuiIO()
ConfigDebugBeginReturnValueOnce = false;
ConfigDebugBeginReturnValueLoop = false;
ConfigErrorRecovery = true;
ConfigErrorRecoveryEnableAssert = true;
ConfigErrorRecoveryEnableDebugLog = true;
ConfigErrorRecoveryEnableTooltip = true;
// Inputs Behaviors
MouseDoubleClickTime = 0.30f;
MouseDoubleClickMaxDist = 6.0f;
@@ -3333,7 +3338,7 @@ void ImGui::PopStyleColor(int count)
ImGuiContext& g = *GImGui;
if (g.ColorStack.Size < count)
{
IM_ASSERT_USER_ERROR(g.ColorStack.Size > count, "Calling PopStyleColor() too many times!");
IM_ASSERT_USER_ERROR(0, "Calling PopStyleColor() too many times!");
count = g.ColorStack.Size;
}
while (count > 0)
@@ -3456,7 +3461,7 @@ void ImGui::PopStyleVar(int count)
ImGuiContext& g = *GImGui;
if (g.StyleVarStack.Size < count)
{
IM_ASSERT_USER_ERROR(g.StyleVarStack.Size > count, "Calling PopStyleVar() too many times!");
IM_ASSERT_USER_ERROR(0, "Calling PopStyleVar() too many times!");
count = g.StyleVarStack.Size;
}
while (count > 0)
@@ -4059,8 +4064,16 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
LogDepthRef = 0;
LogDepthToExpand = LogDepthToExpandDefault = 2;
DebugLogFlags = ImGuiDebugLogFlags_OutputToTTY;
ErrorCallback = NULL;
ErrorCallbackUserData = NULL;
ErrorFirst = true;
ErrorCountCurrentFrame = 0;
StackSizesInBeginForCurrentWindow = NULL;
DebugDrawIdConflictsCount = 0;
DebugLogFlags = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_OutputToTTY;
DebugLocateId = 0;
DebugLogSkippedErrors = 0;
DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None;
DebugLogAutoDisableFrames = 0;
DebugLocateFrames = 0;
@@ -4311,6 +4324,7 @@ static void SetCurrentWindow(ImGuiWindow* window)
{
ImGuiContext& g = *GImGui;
g.CurrentWindow = window;
g.StackSizesInBeginForCurrentWindow = g.CurrentWindow ? &g.CurrentWindowStack.back().StackSizesInBegin : NULL;
g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL;
if (window)
{
@@ -5236,7 +5250,8 @@ void ImGui::NewFrame()
KeepAliveID(g.DragDropPayload.SourceId);
// [DEBUG]
g.DebugDrawIdConflicts = 0;
if (!g.IO.ConfigDebugHighlightIdConflicts || !g.IO.KeyCtrl) // Count is locked while holding CTRL
g.DebugDrawIdConflicts = 0;
if (g.IO.ConfigDebugHighlightIdConflicts && g.HoveredIdPreviousFrameItemCount > 1)
g.DebugDrawIdConflicts = g.HoveredIdPreviousFrame;
@@ -5342,8 +5357,25 @@ void ImGui::NewFrame()
// (needs to be before UpdateMouseMovingWindowNewFrame so the window is already offset and following the mouse on the detaching frame)
DockContextNewFrameUpdateUndocking(&g);
// Mark all windows as not visible and compact unused memory.
IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size);
const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer;
for (ImGuiWindow* window : g.Windows)
{
window->WasActive = window->Active;
window->Active = false;
window->WriteAccessed = false;
window->BeginCountPreviousFrame = window->BeginCount;
window->BeginCount = 0;
// Garbage collect transient buffers of recently unused windows
if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time)
GcCompactTransientWindowBuffers(window);
}
// Find hovered window
// (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame)
// (currently needs to be done after the WasActive=Active loop and FindHoveredWindowEx uses ->Active)
UpdateHoveredWindowAndCaptureFlags();
// Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)
@@ -5365,22 +5397,6 @@ void ImGui::NewFrame()
// Mouse wheel scrolling, scale
UpdateMouseWheel();
// Mark all windows as not visible and compact unused memory.
IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size);
const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer;
for (ImGuiWindow* window : g.Windows)
{
window->WasActive = window->Active;
window->Active = false;
window->WriteAccessed = false;
window->BeginCountPreviousFrame = window->BeginCount;
window->BeginCount = 0;
// Garbage collect transient buffers of recently unused windows
if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time)
GcCompactTransientWindowBuffers(window);
}
// Garbage collect transient buffers of recently unused tables
for (int i = 0; i < g.TablesLastTimeActive.Size; i++)
if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time)
@@ -5434,6 +5450,10 @@ void ImGui::NewFrame()
Begin("Debug##Default");
IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true);
// Store stack sizes
g.ErrorCountCurrentFrame = 0;
ErrorRecoveryStoreState(&g.StackSizesInNewFrame);
// [DEBUG] When io.ConfigDebugBeginReturnValue is set, we make Begin()/BeginChild() return false at different level of the window-stack,
// allowing to validate correct Begin/End behavior in user code.
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
@@ -5700,32 +5720,13 @@ void ImGui::EndFrame()
return;
IM_ASSERT(g.WithinFrameScope && "Forgot to call ImGui::NewFrame()?");
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
if (g.DebugDrawIdConflicts != 0)
{
PushStyleColor(ImGuiCol_PopupBg, ImLerp(g.Style.Colors[ImGuiCol_PopupBg], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.10f));
if (g.DebugItemPickerActive == false && BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))
{
SeparatorText("MESSAGE FROM DEAR IMGUI");
Text("Programmer error: %d visible items with conflicting ID!", g.HoveredIdPreviousFrameItemCount);
BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!");
BulletText("Empty label e.g. Button(\"\") == same ID as parent widget/node. Use Button(\"##xx\") instead!");
BulletText("Press F1 to open \"FAQ -> About the ID Stack System\" and read details.");
BulletText("Press CTRL+P to activate Item Picker and debug-break in item call-stack.");
BulletText("Set io.ConfigDebugDetectIdConflicts=false to disable this warning in non-programmers builds.");
EndTooltip();
}
PopStyleColor();
if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_P, ImGuiInputFlags_RouteGlobal))
DebugStartItemPicker();
if (Shortcut(ImGuiKey_F1, ImGuiInputFlags_RouteGlobal) && g.PlatformIO.Platform_OpenInShellFn != NULL)
g.PlatformIO.Platform_OpenInShellFn(&g, "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage");
}
#endif
CallContextHooks(&g, ImGuiContextHookType_EndFramePre);
// [EXPERIMENTAL] Recover from errors
if (g.IO.ConfigErrorRecovery)
ErrorRecoveryTryToRecoverState(&g.StackSizesInNewFrame);
ErrorCheckEndFrameSanityChecks();
ErrorCheckEndFrameFinalizeErrorTooltip();
// Notify Platform/OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
ImGuiPlatformImeData* ime_data = &g.PlatformImeData;
@@ -5936,7 +5937,7 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi
{
ImGuiWindow* window = g.Windows[i];
IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.
if (!window->Active || window->Hidden)
if (!window->WasActive || window->Hidden)
continue;
if (window->Flags & ImGuiWindowFlags_NoMouseInputs)
continue;
@@ -6205,18 +6206,35 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I
window_flags |= ImGuiWindowFlags_NoMove;
}
// Forward child flags
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasChildFlags;
g.NextWindowData.ChildFlags = child_flags;
// Forward size
// Important: Begin() has special processing to switch condition to ImGuiCond_FirstUseEver for a given axis when ImGuiChildFlags_ResizeXXX is set.
// (the alternative would to store conditional flags per axis, which is possible but more code)
const ImVec2 size_avail = GetContentRegionAvail();
const ImVec2 size_default((child_flags & ImGuiChildFlags_AutoResizeX) ? 0.0f : size_avail.x, (child_flags & ImGuiChildFlags_AutoResizeY) ? 0.0f : size_avail.y);
const ImVec2 size = CalcItemSize(size_arg, size_default.x, size_default.y);
ImVec2 size = CalcItemSize(size_arg, size_default.x, size_default.y);
// A SetNextWindowSize() call always has priority (#8020)
// (since the code in Begin() never supported SizeVal==0.0f aka auto-resize via SetNextWindowSize() call, we don't support it here for now)
// FIXME: We only support ImGuiCond_Always in this path. Supporting other paths would requires to obtain window pointer.
if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) != 0 && (g.NextWindowData.SizeCond & ImGuiCond_Always) != 0)
{
if (g.NextWindowData.SizeVal.x > 0.0f)
{
size.x = g.NextWindowData.SizeVal.x;
child_flags &= ~ImGuiChildFlags_ResizeX;
}
if (g.NextWindowData.SizeVal.y > 0.0f)
{
size.y = g.NextWindowData.SizeVal.y;
child_flags &= ~ImGuiChildFlags_ResizeY;
}
}
SetNextWindowSize(size);
// Forward child flags
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasChildFlags;
g.NextWindowData.ChildFlags = child_flags;
// Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value.
// FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround.
// e.g. "ParentName###ParentIdentifier/ChildName###ChildIdentifier" would get hashed incorrectly by ImHashStr(), trailing _%08X somehow fixes it.
@@ -7372,12 +7390,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// Add to stack
g.CurrentWindow = window;
ImGuiWindowStackData window_stack_data;
g.CurrentWindowStack.resize(g.CurrentWindowStack.Size + 1);
ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back();
window_stack_data.Window = window;
window_stack_data.ParentLastItemDataBackup = g.LastItemData;
window_stack_data.StackSizesOnBegin.SetToContextState(&g);
window_stack_data.DisabledOverrideReenable = (flags & ImGuiWindowFlags_Tooltip) && (g.CurrentItemFlags & ImGuiItemFlags_Disabled);
g.CurrentWindowStack.push_back(window_stack_data);
ErrorRecoveryStoreState(&window_stack_data.StackSizesInBegin);
g.StackSizesInBeginForCurrentWindow = &window_stack_data.StackSizesInBegin;
if (flags & ImGuiWindowFlags_ChildMenu)
g.BeginMenuDepth++;
@@ -8251,7 +8270,11 @@ void ImGui::End()
g.BeginMenuDepth--;
if (window->Flags & ImGuiWindowFlags_Popup)
g.BeginPopupStack.pop_back();
window_stack_data.StackSizesOnBegin.CompareWithContextState(&g);
// Error handling, state recovery
if (g.IO.ConfigErrorRecovery)
ErrorRecoveryTryToRecoverWindowState(&window_stack_data.StackSizesInBegin);
g.CurrentWindowStack.pop_back();
SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window);
if (g.CurrentWindow)
@@ -8482,7 +8505,11 @@ void ImGui::PushFont(ImFont* font)
void ImGui::PopFont()
{
ImGuiContext& g = *GImGui;
IM_ASSERT(g.FontStack.Size > 0);
if (g.FontStack.Size <= 0)
{
IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!");
return;
}
g.FontStack.pop_back();
ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back();
SetCurrentFont(font);
@@ -8505,7 +8532,11 @@ void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled)
void ImGui::PopItemFlag()
{
ImGuiContext& g = *GImGui;
IM_ASSERT(g.ItemFlagsStack.Size > 1); // Too many calls to PopItemFlag() - we always leave a 0 at the bottom of the stack.
if (g.ItemFlagsStack.Size <= 1)
{
IM_ASSERT_USER_ERROR(0, "Calling PopItemFlag() too many times!");
return;
}
g.ItemFlagsStack.pop_back();
g.CurrentItemFlags = g.ItemFlagsStack.back();
}
@@ -8535,7 +8566,11 @@ void ImGui::BeginDisabled(bool disabled)
void ImGui::EndDisabled()
{
ImGuiContext& g = *GImGui;
IM_ASSERT(g.DisabledStackSize > 0);
if (g.DisabledStackSize <= 0)
{
IM_ASSERT_USER_ERROR(0, "Calling EndDisabled() too many times!");
return;
}
g.DisabledStackSize--;
bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
//PopItemFlag();
@@ -8570,14 +8605,21 @@ void ImGui::EndDisabledOverrideReenable()
void ImGui::PushTextWrapPos(float wrap_pos_x)
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos);
window->DC.TextWrapPos = wrap_pos_x;
}
void ImGui::PopTextWrapPos()
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
if (window->DC.TextWrapPosStack.Size <= 0)
{
IM_ASSERT_USER_ERROR(0, "Calling PopTextWrapPos() too many times!");
return;
}
window->DC.TextWrapPos = window->DC.TextWrapPosStack.back();
window->DC.TextWrapPosStack.pop_back();
}
@@ -9064,9 +9106,9 @@ void ImGui::PushFocusScope(ImGuiID id)
void ImGui::PopFocusScope()
{
ImGuiContext& g = *GImGui;
if (g.FocusScopeStack.Size == 0)
if (g.FocusScopeStack.Size <= g.StackSizesInBeginForCurrentWindow->SizeOfFocusScopeStack)
{
IM_ASSERT_USER_ERROR(g.FocusScopeStack.Size > 0, "Calling PopFocusScope() too many times!");
IM_ASSERT_USER_ERROR(0, "Calling PopFocusScope() too many times!");
return;
}
g.FocusScopeStack.pop_back();
@@ -9337,7 +9379,11 @@ ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed)
void ImGui::PopID()
{
ImGuiWindow* window = GImGui->CurrentWindow;
IM_ASSERT(window->IDStack.Size > 1); // Too many PopID(), or could be popping in a wrong/different window?
if (window->IDStack.Size <= 1)
{
IM_ASSERT_USER_ERROR(0, "Calling PopID() too many times!");
return;
}
window->IDStack.pop_back();
}
@@ -10919,9 +10965,10 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID own
// - ErrorCheckUsingSetCursorPosToExtendParentBoundaries()
// - ErrorCheckNewFrameSanityChecks()
// - ErrorCheckEndFrameSanityChecks()
// - ErrorCheckEndFrameRecover()
// - ErrorCheckEndWindowRecover()
// - ImGuiStackSizes
// - ErrorRecoveryStoreState()
// - ErrorRecoveryTryToRecoverState()
// - ErrorRecoveryTryToRecoverWindowState()
// - ErrorLog()
//-----------------------------------------------------------------------------
// Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues.
@@ -11020,6 +11067,10 @@ static void ImGui::ErrorCheckNewFrameSanityChecks()
IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation.");
#endif
// Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way.
if (g.IO.ConfigErrorRecovery)
IM_ASSERT(g.IO.ConfigErrorRecoveryEnableAssert || g.IO.ConfigErrorRecoveryEnableDebugLog || g.IO.ConfigErrorRecoveryEnableTooltip || g.ErrorCallback != NULL);
// Remap legacy clipboard handlers (OBSOLETED in 1.91.1, August 2024)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
if (g.IO.GetClipboardTextFn != NULL && (g.PlatformIO.Platform_GetClipboardTextFn == NULL || g.PlatformIO.Platform_GetClipboardTextFn == Platform_GetClipboardTextFn_DefaultImpl))
@@ -11070,126 +11121,113 @@ static void ImGui::ErrorCheckNewFrameSanityChecks()
static void ImGui::ErrorCheckEndFrameSanityChecks()
{
ImGuiContext& g = *GImGui;
// Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and EndFrame()
// One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame().
// It is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will
// send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs.
// We silently accommodate for this case by ignoring the case where all io.KeyXXX modifiers were released (aka key_mod_flags == 0),
// while still correctly asserting on mid-frame key press events.
ImGuiContext& g = *GImGui;
const ImGuiKeyChord key_mods = GetMergedModsFromKeys();
IM_ASSERT((key_mods == 0 || g.IO.KeyMods == key_mods) && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods");
IM_UNUSED(key_mods);
// [EXPERIMENTAL] Recover from errors: You may call this yourself before EndFrame().
//ErrorCheckEndFrameRecover();
// Report when there is a mismatch of Begin/BeginChild vs End/EndChild calls. Important: Remember that the Begin/BeginChild API requires you
// to always call End/EndChild even if Begin/BeginChild returns false! (this is unfortunately inconsistent with most other Begin* API).
if (g.CurrentWindowStack.Size != 1)
{
if (g.CurrentWindowStack.Size > 1)
{
ImGuiWindow* window = g.CurrentWindowStack.back().Window; // <-- This window was not Ended!
IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?");
IM_UNUSED(window);
while (g.CurrentWindowStack.Size > 1)
End();
}
else
{
IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?");
}
}
IM_ASSERT_USER_ERROR(g.GroupStack.Size == 0, "Missing EndGroup call!");
IM_ASSERT(g.CurrentWindowStack.Size == 1);
IM_ASSERT(g.CurrentWindowStack[0].Window->IsFallbackWindow);
}
// Default implementation of ImGuiErrorLogCallback that pipe errors to DebugLog: appears in tty + Tools->DebugLog
void ImGui::ErrorLogCallbackToDebugLog(void*, const char* fmt, ...)
// Save current stack sizes. Called e.g. by NewFrame() and by Begin() but may be called for manual recovery.
void ImGui::ErrorRecoveryStoreState(ImGuiErrorRecoveryState* state_out)
{
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
va_list args;
va_start(args, fmt);
DebugLogV(fmt, args);
va_end(args);
#else
IM_UNUSED(fmt);
#endif
ImGuiContext& g = *GImGui;
state_out->SizeOfWindowStack = (short)g.CurrentWindowStack.Size;
state_out->SizeOfIDStack = (short)g.CurrentWindow->IDStack.Size;
state_out->SizeOfTreeStack = (short)g.CurrentWindow->DC.TreeDepth; // NOT g.TreeNodeStack.Size which is a partial stack!
state_out->SizeOfColorStack = (short)g.ColorStack.Size;
state_out->SizeOfStyleVarStack = (short)g.StyleVarStack.Size;
state_out->SizeOfFontStack = (short)g.FontStack.Size;
state_out->SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size;
state_out->SizeOfGroupStack = (short)g.GroupStack.Size;
state_out->SizeOfItemFlagsStack = (short)g.ItemFlagsStack.Size;
state_out->SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size;
state_out->SizeOfDisabledStack = (short)g.DisabledStackSize;
}
// Experimental recovery from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls.
// Must be called during or before EndFrame().
// This is generally flawed as we are not necessarily End/Popping things in the right order.
// FIXME: Can't recover from inside BeginTabItem/EndTabItem yet.
void ImGui::ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data)
// Chosen name "Try to recover" over e.g. "Restore" to suggest this is not a 100% guaranteed recovery.
// Called by e.g. EndFrame() but may be called for manual recovery.
// Attempt to recover full window stack.
void ImGui::ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState* state_in)
{
// PVS-Studio V1044 is "Loop break conditions do not depend on the number of iterations"
ImGuiContext& g = *GImGui;
while (g.CurrentWindowStack.Size > 0) //-V1044
while (g.CurrentWindowStack.Size > state_in->SizeOfWindowStack) //-V1044
{
ErrorCheckEndWindowRecover(log_callback, user_data);
// Recap:
// - Begin()/BeginChild() return false to indicate the window is collapsed or fully clipped.
// - Always call a matching End() for each Begin() call, regardless of its return value!
// - Begin/End and BeginChild/EndChild logic is KNOWN TO BE INCONSISTENT WITH ALL OTHER BEGIN/END FUNCTIONS.
// - We will fix that in a future major update.
ImGuiWindow* window = g.CurrentWindow;
if (g.CurrentWindowStack.Size == 1)
{
IM_ASSERT(window->IsFallbackWindow);
break;
}
if (window->Flags & ImGuiWindowFlags_ChildWindow)
{
if (log_callback) log_callback(user_data, "Recovered from missing EndChild() for '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing EndChild()");
EndChild();
}
else
{
if (log_callback) log_callback(user_data, "Recovered from missing End() for '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing End()");
End();
}
}
if (g.CurrentWindowStack.Size == state_in->SizeOfWindowStack)
ErrorRecoveryTryToRecoverWindowState(state_in);
}
// Must be called before End()/EndChild()
void ImGui::ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, void* user_data)
// Called by e.g. End() but may be called for manual recovery.
// Read '// Error Handling [BETA]' block in imgui_internal.h for details.
// Attempt to recover from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls.
void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryState* state_in)
{
ImGuiContext& g = *GImGui;
while (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow)
{
if (log_callback) log_callback(user_data, "Recovered from missing EndTable() in '%s'\n", g.CurrentTable->OuterWindow->Name);
IM_ASSERT_USER_ERROR(0, "Missing EndTable()");
EndTable();
}
ImGuiWindow* window = g.CurrentWindow;
ImGuiStackSizes* stack_sizes = &g.CurrentWindowStack.back().StackSizesOnBegin;
IM_ASSERT(window != NULL);
// FIXME: Can't recover from inside BeginTabItem/EndTabItem yet.
while (g.CurrentTabBar != NULL && g.CurrentTabBar->Window == window) //-V1044
{
if (log_callback) log_callback(user_data, "Recovered from missing EndTabBar() in '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing EndTabBar()");
EndTabBar();
}
while (g.CurrentMultiSelect != NULL && g.CurrentMultiSelect->Storage->Window == window)
{
if (log_callback) log_callback(user_data, "Recovered from missing EndMultiSelect() in '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing EndMultiSelect()");
EndMultiSelect();
}
while (window->DC.TreeDepth > 0)
while (window->DC.TreeDepth > state_in->SizeOfTreeStack) //-V1044
{
if (log_callback) log_callback(user_data, "Recovered from missing TreePop() in '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing TreePop()");
TreePop();
}
while (g.GroupStack.Size > stack_sizes->SizeOfGroupStack) //-V1044
while (g.GroupStack.Size > state_in->SizeOfGroupStack) //-V1044
{
if (log_callback) log_callback(user_data, "Recovered from missing EndGroup() in '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing EndGroup()");
EndGroup();
}
while (window->IDStack.Size > 1)
IM_ASSERT(g.GroupStack.Size == state_in->SizeOfGroupStack);
while (window->IDStack.Size > state_in->SizeOfIDStack) //-V1044
{
if (log_callback) log_callback(user_data, "Recovered from missing PopID() in '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing PopID()");
PopID();
}
while (g.DisabledStackSize > stack_sizes->SizeOfDisabledStack) //-V1044
while (g.DisabledStackSize > state_in->SizeOfDisabledStack) //-V1044
{
if (log_callback) log_callback(user_data, "Recovered from missing EndDisabled() in '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing EndDisabled()");
if (g.CurrentItemFlags & ImGuiItemFlags_Disabled)
EndDisabled();
else
@@ -11198,70 +11236,150 @@ void ImGui::ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, vo
g.CurrentWindowStack.back().DisabledOverrideReenable = false;
}
}
while (g.ColorStack.Size > stack_sizes->SizeOfColorStack)
IM_ASSERT(g.DisabledStackSize == state_in->SizeOfDisabledStack);
while (g.ColorStack.Size > state_in->SizeOfColorStack)
{
if (log_callback) log_callback(user_data, "Recovered from missing PopStyleColor() in '%s' for ImGuiCol_%s\n", window->Name, GetStyleColorName(g.ColorStack.back().Col));
IM_ASSERT_USER_ERROR(0, "Missing PopStyleColor()");
PopStyleColor();
}
while (g.ItemFlagsStack.Size > stack_sizes->SizeOfItemFlagsStack) //-V1044
while (g.ItemFlagsStack.Size > state_in->SizeOfItemFlagsStack) //-V1044
{
if (log_callback) log_callback(user_data, "Recovered from missing PopItemFlag() in '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing PopItemFlag()");
PopItemFlag();
}
while (g.StyleVarStack.Size > stack_sizes->SizeOfStyleVarStack) //-V1044
while (g.StyleVarStack.Size > state_in->SizeOfStyleVarStack) //-V1044
{
if (log_callback) log_callback(user_data, "Recovered from missing PopStyleVar() in '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing PopStyleVar()");
PopStyleVar();
}
while (g.FontStack.Size > stack_sizes->SizeOfFontStack) //-V1044
while (g.FontStack.Size > state_in->SizeOfFontStack) //-V1044
{
if (log_callback) log_callback(user_data, "Recovered from missing PopFont() in '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing PopFont()");
PopFont();
}
while (g.FocusScopeStack.Size > stack_sizes->SizeOfFocusScopeStack + 1) //-V1044
while (g.FocusScopeStack.Size > state_in->SizeOfFocusScopeStack) //-V1044
{
if (log_callback) log_callback(user_data, "Recovered from missing PopFocusScope() in '%s'\n", window->Name);
IM_ASSERT_USER_ERROR(0, "Missing PopFocusScope()");
PopFocusScope();
}
//IM_ASSERT(g.FocusScopeStack.Size == state_in->SizeOfFocusScopeStack);
}
// Save current stack sizes for later compare
void ImGuiStackSizes::SetToContextState(ImGuiContext* ctx)
bool ImGui::ErrorLog(const char* msg)
{
ImGuiContext& g = *ctx;
ImGuiContext& g = *GImGui;
// Output to debug log
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
ImGuiWindow* window = g.CurrentWindow;
SizeOfIDStack = (short)window->IDStack.Size;
SizeOfColorStack = (short)g.ColorStack.Size;
SizeOfStyleVarStack = (short)g.StyleVarStack.Size;
SizeOfFontStack = (short)g.FontStack.Size;
SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size;
SizeOfGroupStack = (short)g.GroupStack.Size;
SizeOfItemFlagsStack = (short)g.ItemFlagsStack.Size;
SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size;
SizeOfDisabledStack = (short)g.DisabledStackSize;
if (g.IO.ConfigErrorRecoveryEnableDebugLog)
{
if (g.ErrorFirst)
IMGUI_DEBUG_LOG_ERROR("[imgui-error] (current settings: Assert=%d, Log=%d, Tooltip=%d)\n",
g.IO.ConfigErrorRecoveryEnableAssert, g.IO.ConfigErrorRecoveryEnableDebugLog, g.IO.ConfigErrorRecoveryEnableTooltip);
IMGUI_DEBUG_LOG_ERROR("[imgui-error] In window '%s': %s\n", window ? window->Name : "NULL", msg);
}
g.ErrorFirst = false;
// Output to tooltip
if (g.IO.ConfigErrorRecoveryEnableTooltip)
{
if (BeginErrorTooltip())
{
if (g.ErrorCountCurrentFrame < 20)
{
Text("In window '%s': %s", window ? window->Name : "NULL", msg);
if (window && (!window->IsFallbackWindow || window->WasActive))
GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 0, 0, 255));
}
if (g.ErrorCountCurrentFrame == 20)
Text("(and more errors)");
// EndFrame() will amend debug buttons to this window, after all errors have been submitted.
EndErrorTooltip();
}
g.ErrorCountCurrentFrame++;
}
#endif
// Output to callback
if (g.ErrorCallback != NULL)
g.ErrorCallback(&g, g.ErrorCallbackUserData, msg);
// Return whether we should assert
return g.IO.ConfigErrorRecoveryEnableAssert;
}
// Compare to detect usage errors
void ImGuiStackSizes::CompareWithContextState(ImGuiContext* ctx)
void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip()
{
ImGuiContext& g = *ctx;
ImGuiWindow* window = g.CurrentWindow;
IM_UNUSED(window);
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
ImGuiContext& g = *GImGui;
if (g.DebugDrawIdConflicts != 0 && g.IO.KeyCtrl == false)
g.DebugDrawIdConflictsCount = g.HoveredIdPreviousFrameItemCount;
if (g.DebugDrawIdConflicts != 0 && g.DebugItemPickerActive == false && BeginErrorTooltip())
{
Text("Programmer error: %d visible items with conflicting ID!", g.DebugDrawIdConflictsCount);
BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!");
BulletText("Empty label e.g. Button(\"\") == same ID as parent widget/node. Use Button(\"##xx\") instead!");
BulletText("Set io.ConfigDebugDetectIdConflicts=false to disable this warning in non-programmers builds.");
Separator();
Text("(Hold CTRL and: use");
SameLine();
if (SmallButton("Item Picker"))
DebugStartItemPicker();
SameLine();
Text("to break in item call-stack, or");
SameLine();
if (SmallButton("Open FAQ->About ID Stack System") && g.PlatformIO.Platform_OpenInShellFn != NULL)
g.PlatformIO.Platform_OpenInShellFn(&g, "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage");
EndErrorTooltip();
}
// Window stacks
// NOT checking: DC.ItemWidth, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin)
IM_ASSERT(SizeOfIDStack == window->IDStack.Size && "PushID/PopID or TreeNode/TreePop Mismatch!");
if (g.ErrorCountCurrentFrame > 0 && BeginErrorTooltip()) // Amend at end of frame
{
Separator();
Text("(Hold CTRL and:");
SameLine();
if (SmallButton("Enable Asserts"))
g.IO.ConfigErrorRecoveryEnableAssert = true;
//SameLine();
//if (SmallButton("Hide Error Tooltips"))
// g.IO.ConfigErrorRecoveryEnableTooltip = false; // Too dangerous
SameLine(0, 0);
Text(")");
EndErrorTooltip();
}
#endif
}
// Global stacks
// For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our checks a little to allow them.
IM_ASSERT(SizeOfGroupStack == g.GroupStack.Size && "BeginGroup/EndGroup Mismatch!");
IM_ASSERT(SizeOfBeginPopupStack == g.BeginPopupStack.Size && "BeginPopup/EndPopup or BeginMenu/EndMenu Mismatch!");
IM_ASSERT(SizeOfDisabledStack == g.DisabledStackSize && "BeginDisabled/EndDisabled Mismatch!");
IM_ASSERT(SizeOfItemFlagsStack >= g.ItemFlagsStack.Size && "PushItemFlag/PopItemFlag Mismatch!");
IM_ASSERT(SizeOfColorStack >= g.ColorStack.Size && "PushStyleColor/PopStyleColor Mismatch!");
IM_ASSERT(SizeOfStyleVarStack >= g.StyleVarStack.Size && "PushStyleVar/PopStyleVar Mismatch!");
IM_ASSERT(SizeOfFontStack >= g.FontStack.Size && "PushFont/PopFont Mismatch!");
IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!");
// Pseudo-tooltip. Follow mouse until CTRL is held. When CTRL is held we lock position, allowing to click it.
bool ImGui::BeginErrorTooltip()
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = FindWindowByName("##Tooltip_Error");
const bool use_locked_pos = (g.IO.KeyCtrl && window && window->WasActive);
PushStyleColor(ImGuiCol_PopupBg, ImLerp(g.Style.Colors[ImGuiCol_PopupBg], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.15f));
if (use_locked_pos)
SetNextWindowPos(g.ErrorTooltipLockedPos);
bool is_visible = Begin("##Tooltip_Error", NULL, ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize);
PopStyleColor();
if (is_visible && g.CurrentWindow->BeginCount == 1)
{
SeparatorText("MESSAGE FROM DEAR IMGUI");
BringWindowToDisplayFront(g.CurrentWindow);
BringWindowToFocusFront(g.CurrentWindow);
g.ErrorTooltipLockedPos = GetWindowPos();
}
else if (!is_visible)
{
End();
}
return is_visible;
}
void ImGui::EndErrorTooltip()
{
End();
}
//-----------------------------------------------------------------------------
@@ -11593,7 +11711,13 @@ void ImGui::PushMultiItemsWidths(int components, float w_full)
void ImGui::PopItemWidth()
{
ImGuiWindow* window = GetCurrentWindow();
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
if (window->DC.ItemWidthStack.Size <= 0)
{
IM_ASSERT_USER_ERROR(0, "Calling PopItemWidth() too many times!");
return;
}
window->DC.ItemWidth = window->DC.ItemWidthStack.back();
window->DC.ItemWidthStack.pop_back();
}
@@ -22092,12 +22216,24 @@ static void ShowDebugLogFlag(const char* name, ImGuiDebugLogFlags flags)
ImGuiContext& g = *GImGui;
ImVec2 size(ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x + ImGui::CalcTextSize(name).x, ImGui::GetFrameHeight());
SameLineOrWrap(size); // FIXME-LAYOUT: To be done automatically once we rework ItemSize/ItemAdd into ItemLayout.
bool highlight_errors = (flags == ImGuiDebugLogFlags_EventError && g.DebugLogSkippedErrors > 0);
if (highlight_errors)
ImGui::PushStyleColor(ImGuiCol_Text, ImLerp(g.Style.Colors[ImGuiCol_Text], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.30f));
if (ImGui::CheckboxFlags(name, &g.DebugLogFlags, flags) && g.IO.KeyShift && (g.DebugLogFlags & flags) != 0)
{
g.DebugLogAutoDisableFrames = 2;
g.DebugLogAutoDisableFlags |= flags;
}
ImGui::SetItemTooltip("Hold SHIFT when clicking to enable for 2 frames only (useful for spammy log entries)");
if (highlight_errors)
{
ImGui::PopStyleColor();
ImGui::SetItemTooltip("%d past errors skipped.", g.DebugLogSkippedErrors);
}
else
{
ImGui::SetItemTooltip("Hold SHIFT when clicking to enable for 2 frames only (useful for spammy log entries)");
}
}
void ImGui::ShowDebugLogWindow(bool* p_open)
@@ -22115,6 +22251,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open)
CheckboxFlags("All", &g.DebugLogFlags, all_enable_flags);
SetItemTooltip("(except InputRouting which is spammy)");
ShowDebugLogFlag("Errors", ImGuiDebugLogFlags_EventError);
ShowDebugLogFlag("ActiveId", ImGuiDebugLogFlags_EventActiveId);
ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper);
ShowDebugLogFlag("Docking", ImGuiDebugLogFlags_EventDocking);
@@ -22130,6 +22267,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open)
{
g.DebugLogBuf.clear();
g.DebugLogIndex.clear();
g.DebugLogSkippedErrors = 0;
}
SameLine();
if (SmallButton("Copy"))