From 9644c5118357cd0c0ba0ea957b3328ff49cf6260 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 25 Sep 2024 18:59:08 +0200 Subject: [PATCH] Error handling: rework error tooltip logic (will be reused by upcoming feature). (#7961, #7669, #1651) + Comments --- imgui.cpp | 85 ++++++++++++++++++++++++++++++++++------------- imgui_internal.h | 9 ++++- imgui_widgets.cpp | 8 +++++ 3 files changed, 77 insertions(+), 25 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index a0683e671..3833109bb 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3978,6 +3978,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) LogDepthRef = 0; LogDepthToExpand = LogDepthToExpandDefault = 2; + DebugDrawIdConflictsCount = 0; DebugLogFlags = ImGuiDebugLogFlags_OutputToTTY; DebugLocateId = 0; DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None; @@ -5054,7 +5055,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; @@ -5461,32 +5463,10 @@ 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); 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; @@ -10587,6 +10567,63 @@ void ImGuiStackSizes::CompareWithContextState(ImGuiContext* ctx) IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!"); } +void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() +{ +#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(); + } +#endif +} + +// 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(); +} + //----------------------------------------------------------------------------- // [SECTION] ITEM SUBMISSION //----------------------------------------------------------------------------- diff --git a/imgui_internal.h b/imgui_internal.h index 93decabaf..37c182b55 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2312,8 +2312,12 @@ struct ImGuiContext int LogDepthToExpand; int LogDepthToExpandDefault; // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call. + // Error handling + ImVec2 ErrorTooltipLockedPos; + // Debug Tools // (some of the highly frequently used data are interleaved in other structures above: DebugBreakXXX fields, DebugHookIdInfo, DebugLocateId etc.) + int DebugDrawIdConflictsCount; // Locked count (preserved when holding CTRL) ImGuiDebugLogFlags DebugLogFlags; ImGuiTextBuffer DebugLogBuf; ImGuiTextIndex DebugLogIndex; @@ -3412,11 +3416,14 @@ namespace ImGui IMGUI_API void GcCompactTransientWindowBuffers(ImGuiWindow* window); IMGUI_API void GcAwakeTransientWindowBuffers(ImGuiWindow* window); - // Error Checking, State Recovery + // Error handling, State Recovery IMGUI_API void ErrorLogCallbackToDebugLog(void* user_data, const char* fmt, ...); IMGUI_API void ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); IMGUI_API void ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); IMGUI_API void ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + IMGUI_API void ErrorCheckEndFrameFinalizeErrorTooltip(); + IMGUI_API bool BeginErrorTooltip(); + IMGUI_API void EndErrorTooltip(); // Debug Tools IMGUI_API void DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size); // size >= 0 : alloc, size = -1 : free diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 59a912dbd..5641134ff 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7391,6 +7391,7 @@ void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flag static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io) { ImGuiContext& g = *GImGui; + IM_UNUSED(function); for (const ImGuiSelectionRequest& req : io->Requests) { if (req.Type == ImGuiSelectionRequestType_SetAll) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", function, req.Selected, req.Selected ? "SelectAll" : "Clear"); @@ -8157,6 +8158,13 @@ void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) //------------------------------------------------------------------------- // This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. +// This handle some subtleties with capturing info from the label, but for 99% uses it could essentially be rewritten as: +// if (ImGui::BeginChild("...", ImVec2(ImGui::CalcItemWidth(), ImGui::GetTextLineHeight() * 7.5f), ImGuiChildFlags_FrameStyle)) +// { .... } +// ImGui::EndChild(); +// ImGui::SameLine(); +// ImGui::AlignTextToFramePadding(); +// ImGui::Text("Label"); // Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty" // Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height). bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)