Error handling: Assert if user mistakenly calls End() instead of EndChild() on a child window. (#1651)

Internals: Moved some error handling code.
This commit is contained in:
omar
2019-11-13 21:35:42 +01:00
parent b138f8cbcd
commit 25eee91542
3 changed files with 88 additions and 54 deletions

118
imgui.cpp
View File

@ -54,6 +54,7 @@ CODE
// [SECTION] ImGuiListClipper
// [SECTION] RENDER HELPERS
// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
// [SECTION] ERROR CHECKING
// [SECTION] SCROLLING
// [SECTION] TOOLTIPS
// [SECTION] POPUPS
@ -838,7 +839,6 @@ static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 2.00f; // Lock
static void SetCurrentWindow(ImGuiWindow* window);
static void FindHoveredWindow();
static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags);
static void CheckStacksSize(ImGuiWindow* window, bool write);
static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges);
static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
@ -874,6 +874,10 @@ static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_win
static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window);
static int FindWindowFocusIndex(ImGuiWindow* window);
// Error Checking
static void ErrorCheckEndFrame();
static void ErrorCheckBeginEndCompareStacksSize(ImGuiWindow* window, bool write);
// Misc
static void UpdateMouseInputs();
static void UpdateMouseWheel();
@ -3539,7 +3543,7 @@ void ImGui::NewFrame()
}
g.Time += g.IO.DeltaTime;
g.FrameScopeActive = true;
g.WithinFrameScope = true;
g.FrameCount += 1;
g.TooltipOverrideCount = 0;
g.WindowsActiveCount = 0;
@ -3713,7 +3717,7 @@ void ImGui::NewFrame()
// This fallback is particularly important as it avoid ImGui:: calls from crashing.
SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver);
Begin("Debug##Default");
g.FrameScopePushedImplicitWindow = true;
g.WithinFrameScopeWithImplicitWindow = true;
#ifdef IMGUI_ENABLE_TEST_ENGINE
ImGuiTestEngineHook_PostNewFrame(&g);
@ -3982,7 +3986,7 @@ void ImGui::EndFrame()
IM_ASSERT(g.Initialized);
if (g.FrameCountEnded == g.FrameCount) // Don't process EndFrame() multiple times.
return;
IM_ASSERT(g.FrameScopeActive && "Forgot to call ImGui::NewFrame()?");
IM_ASSERT(g.WithinFrameScope && "Forgot to call ImGui::NewFrame()?");
// Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
if (g.IO.ImeSetInputScreenPosFn && (g.PlatformImeLastPos.x == FLT_MAX || ImLengthSqr(g.PlatformImeLastPos - g.PlatformImePos) > 0.0001f))
@ -3991,24 +3995,10 @@ void ImGui::EndFrame()
g.PlatformImeLastPos = g.PlatformImePos;
}
// 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)
{
IM_ASSERT(g.CurrentWindowStack.Size == 1 && "Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?");
while (g.CurrentWindowStack.Size > 1) // FIXME-ERRORHANDLING
End();
}
else
{
IM_ASSERT(g.CurrentWindowStack.Size == 1 && "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?");
}
}
ErrorCheckEndFrame();
// Hide implicit/fallback "Debug" window if it hasn't been used
g.FrameScopePushedImplicitWindow = false;
g.WithinFrameScopeWithImplicitWindow = false;
if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed)
g.CurrentWindow->Active = false;
End();
@ -4035,7 +4025,7 @@ void ImGui::EndFrame()
}
// End frame
g.FrameScopeActive = false;
g.WithinFrameScope = false;
g.FrameCountEnded = g.FrameCount;
// Initiate moving window + handle left-click and right-click focus
@ -4602,7 +4592,10 @@ void ImGui::EndChild()
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
IM_ASSERT(g.WithinEndChild == false);
IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss
g.WithinEndChild = true;
if (window->BeginCount > 1)
{
End();
@ -4634,6 +4627,7 @@ void ImGui::EndChild()
ItemAdd(bb, 0);
}
}
g.WithinEndChild = false;
}
// Helper to create a child window / scrolling region that looks like a normal widget frame.
@ -4656,22 +4650,6 @@ void ImGui::EndChildFrame()
EndChild();
}
// Save and compare stack sizes on Begin()/End() to detect usage errors
static void CheckStacksSize(ImGuiWindow* window, bool write)
{
// NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin)
ImGuiContext& g = *GImGui;
short* p_backup = &window->DC.StackSizesBackup[0];
{ int current = window->IDStack.Size; if (write) *p_backup = (short)current; else IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!"); p_backup++; } // Too few or too many PopID()/TreePop()
{ int current = window->DC.GroupStack.Size; if (write) *p_backup = (short)current; else IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!"); p_backup++; } // Too few or too many EndGroup()
{ int current = g.BeginPopupStack.Size; if (write) *p_backup = (short)current; else IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch"); p_backup++;}// Too few or too many EndMenu()/EndPopup()
// 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.
{ int current = g.ColorModifiers.Size; if (write) *p_backup = (short)current; else IM_ASSERT(*p_backup >= current && "PushStyleColor/PopStyleColor Mismatch!"); p_backup++; } // Too few or too many PopStyleColor()
{ int current = g.StyleModifiers.Size; if (write) *p_backup = (short)current; else IM_ASSERT(*p_backup >= current && "PushStyleVar/PopStyleVar Mismatch!"); p_backup++; } // Too few or too many PopStyleVar()
{ int current = g.FontStack.Size; if (write) *p_backup = (short)current; else IM_ASSERT(*p_backup >= current && "PushFont/PopFont Mismatch!"); p_backup++; } // Too few or too many PopFont()
IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup));
}
static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)
{
window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags);
@ -5238,7 +5216,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
IM_ASSERT(name != NULL && name[0] != '\0'); // Window name required
IM_ASSERT(g.FrameScopeActive); // Forgot to call ImGui::NewFrame()
IM_ASSERT(g.WithinFrameScope); // Forgot to call ImGui::NewFrame()
IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet
// Find or create
@ -5300,7 +5278,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow()
g.CurrentWindowStack.push_back(window);
g.CurrentWindow = NULL;
CheckStacksSize(window, true);
ErrorCheckBeginEndCompareStacksSize(window, true);
if (flags & ImGuiWindowFlags_Popup)
{
ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];
@ -5839,16 +5817,22 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
void ImGui::End()
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
if (g.CurrentWindowStack.Size <= 1 && g.FrameScopePushedImplicitWindow)
// Error checking: verify that user hasn't called End() too many times!
// FIXME-ERRORHANDLING
if (g.CurrentWindowStack.Size <= 1 && g.WithinFrameScopeWithImplicitWindow)
{
IM_ASSERT(g.CurrentWindowStack.Size > 1 && "Calling End() too many times!");
return; // FIXME-ERRORHANDLING
return;
}
IM_ASSERT(g.CurrentWindowStack.Size > 0);
ImGuiWindow* window = g.CurrentWindow;
// Error checking: verify that user doesn't directly call End() on a child window.
if (window->Flags & ImGuiWindowFlags_ChildWindow)
IM_ASSERT(g.WithinEndChild && "Must call EndChild() and not End()!");
// Close anything that is open
if (window->DC.CurrentColumns)
EndColumns();
PopClipRect(); // Inner window clip rectangle
@ -5861,7 +5845,7 @@ void ImGui::End()
g.CurrentWindowStack.pop_back();
if (window->Flags & ImGuiWindowFlags_Popup)
g.BeginPopupStack.pop_back();
CheckStacksSize(window, false);
ErrorCheckBeginEndCompareStacksSize(window, false);
SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back());
}
@ -7007,6 +6991,54 @@ void ImGui::Unindent(float indent_w)
}
//-----------------------------------------------------------------------------
// [SECTION] ERROR CHECKING
//-----------------------------------------------------------------------------
static void ImGui::ErrorCheckEndFrame()
{
// 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).
ImGuiContext& g = *GImGui;
if (g.CurrentWindowStack.Size != 1)
{
if (g.CurrentWindowStack.Size > 1)
{
IM_ASSERT(g.CurrentWindowStack.Size == 1 && "Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?");
while (g.CurrentWindowStack.Size > 1) // FIXME-ERRORHANDLING
End();
}
else
{
IM_ASSERT(g.CurrentWindowStack.Size == 1 && "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?");
}
}
}
// Save and compare stack sizes on Begin()/End() to detect usage errors
// Begin() calls this with write=true
// End() calls this with write=false
static void ImGui::ErrorCheckBeginEndCompareStacksSize(ImGuiWindow* window, bool write)
{
ImGuiContext& g = *GImGui;
short* p = &window->DC.StackSizesBackup[0];
// Window stacks
// NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin)
{ int n = window->IDStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p == n && "PushID/PopID or TreeNode/TreePop Mismatch!"); p++; } // Too few or too many PopID()/TreePop()
{ int n = window->DC.GroupStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p == n && "BeginGroup/EndGroup Mismatch!"); p++; } // Too few or too many EndGroup()
// 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.
{ int n = g.BeginPopupStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p == n && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch!"); p++; }// Too few or too many EndMenu()/EndPopup()
{ int n = g.ColorModifiers.Size; if (write) *p = (short)n; else IM_ASSERT(*p >= n && "PushStyleColor/PopStyleColor Mismatch!"); p++; } // Too few or too many PopStyleColor()
{ int n = g.StyleModifiers.Size; if (write) *p = (short)n; else IM_ASSERT(*p >= n && "PushStyleVar/PopStyleVar Mismatch!"); p++; } // Too few or too many PopStyleVar()
{ int n = g.FontStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p >= n && "PushFont/PopFont Mismatch!"); p++; } // Too few or too many PopFont()
IM_ASSERT(p == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup));
}
//-----------------------------------------------------------------------------
// [SECTION] SCROLLING
//-----------------------------------------------------------------------------