diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 3634bf2c7..31305f181 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -40,6 +40,7 @@ Other Changes: meant to be square (to align with e.g. color button) we always use FramePadding.y. (#2367) - InputText: Fixed an edge case crash that would happen if another widget sharing the same ID is being swapped with an InputText that has yet to be activated. +- TabBar: Fixed a crash when using BeginTabBar() recursively (didn't affect docking). (#2371) - Examples: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if the OpenGL headers/loader happens to define the value. (#2366, #2186) diff --git a/imgui_internal.h b/imgui_internal.h index 5a80fc6e6..cc355e95b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -275,7 +275,8 @@ struct IMGUI_API ImPool T* GetByIndex(ImPoolIdx n) { return &Data[n]; } ImPoolIdx GetIndex(const T* p) const { IM_ASSERT(p >= Data.Data && p < Data.Data + Data.Size); return (ImPoolIdx)(p - Data.Data); } T* GetOrAddByKey(ImGuiID key) { int* p_idx = Map.GetIntRef(key, -1); if (*p_idx != -1) return &Data[*p_idx]; *p_idx = FreeIdx; return Add(); } - void Clear() { for (int n = 0; n < Map.Data.Size; n++) { int idx = Map.Data[n].val_i; if (idx != -1) Data[idx].~T(); } Map.Clear(); Data.clear(); FreeIdx = 0; } + bool Contains(const T* p) const { return (p >= Data.Data && p < Data.Data + Data.Size); } + void Clear() { for (int n = 0; n < Map.Data.Size; n++) { int idx = Map.Data[n].val_i; if (idx != -1) Data[idx].~T(); } Map.Clear(); Data.clear(); FreeIdx = 0; } T* Add() { int idx = FreeIdx; if (idx == Data.Size) { Data.resize(Data.Size + 1); FreeIdx++; } else { FreeIdx = *(int*)&Data[idx]; } IM_PLACEMENT_NEW(&Data[idx]) T(); return &Data[idx]; } void Remove(ImGuiID key, const T* p) { Remove(key, GetIndex(p)); } void Remove(ImGuiID key, ImPoolIdx idx) { Data[idx].~T(); *(int*)&Data[idx] = FreeIdx; FreeIdx = idx; Map.SetInt(key, -1); } @@ -749,8 +750,17 @@ struct ImGuiNextWindowData struct ImGuiTabBarSortItem { - int Index; - float Width; + int Index; + float Width; +}; + +struct ImGuiTabBarRef +{ + ImGuiTabBar* Ptr; // Either field can be set, not both. Dock node tab bars are loose while BeginTabBar() ones are in a pool. + int IndexInMainPool; + + ImGuiTabBarRef(ImGuiTabBar* ptr) { Ptr = ptr; IndexInMainPool = -1; } + ImGuiTabBarRef(int index_in_main_pool) { Ptr = NULL; IndexInMainPool = index_in_main_pool; } }; //----------------------------------------------------------------------------- @@ -883,8 +893,9 @@ struct ImGuiContext unsigned char DragDropPayloadBufLocal[8]; // Local buffer for small payloads // Tab bars - ImPool TabBars; - ImVector CurrentTabBar; + ImPool TabBars; + ImGuiTabBar* CurrentTabBar; + ImVector CurrentTabBarStack; ImVector TabSortByWidthBuffer; // Widget state @@ -1246,7 +1257,7 @@ struct ImGuiItemHoveredDataBackup enum ImGuiTabBarFlagsPrivate_ { - ImGuiTabBarFlags_DockNode = 1 << 20, // Part of a dock node + ImGuiTabBarFlags_DockNode = 1 << 20, // Part of a dock node [we don't use this in the master branch but it facilitate branch syncing to keep this around] ImGuiTabBarFlags_IsFocused = 1 << 21, ImGuiTabBarFlags_SaveSettings = 1 << 22 // FIXME: Settings are handled by the docking system, this only request the tab bar to mark settings dirty when reordering tabs }; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index cd059cc30..aaec4cb30 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5905,6 +5905,20 @@ static int IMGUI_CDECL TabBarSortItemComparer(const void* lhs, const void* rhs) return (b->Index - a->Index); } +static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiTabBarRef& ref) +{ + ImGuiContext& g = *GImGui; + return ref.Ptr ? ref.Ptr : g.TabBars.GetByIndex(ref.IndexInMainPool); +} + +static ImGuiTabBarRef GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + if (g.TabBars.Contains(tab_bar)) + return ImGuiTabBarRef(g.TabBars.GetIndex(tab_bar)); + return ImGuiTabBarRef(tab_bar); +} + bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) { ImGuiContext& g = *GImGui; @@ -5929,7 +5943,10 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG if ((flags & ImGuiTabBarFlags_DockNode) == 0) window->IDStack.push_back(tab_bar->ID); - g.CurrentTabBar.push_back(tab_bar); + // Add to stack + g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar)); + g.CurrentTabBar = tab_bar; + if (tab_bar->CurrFrameVisible == g.FrameCount) { //IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount); @@ -5975,8 +5992,8 @@ void ImGui::EndTabBar() if (window->SkipItems) return; - IM_ASSERT(!g.CurrentTabBar.empty()); // Mismatched BeginTabBar/EndTabBar - ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); + ImGuiTabBar* tab_bar = g.CurrentTabBar; + IM_ASSERT(tab_bar != NULL && "Mismatched BeginTabBar()/EndTabBar()!"); if (tab_bar->WantLayout) TabBarLayout(tab_bar); @@ -5989,7 +6006,9 @@ void ImGui::EndTabBar() if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) PopID(); - g.CurrentTabBar.pop_back(); + + g.CurrentTabBarStack.pop_back(); + g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back()); } // This is called only once a frame before by the first call to ItemTab() @@ -6356,8 +6375,8 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f if (g.CurrentWindow->SkipItems) return false; - IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!"); - ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); + ImGuiTabBar* tab_bar = g.CurrentTabBar; + IM_ASSERT(tab_bar && "Needs to be called between BeginTabBar() and EndTabBar()!"); bool ret = TabItemEx(tab_bar, label, p_open, flags); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) { @@ -6373,9 +6392,9 @@ void ImGui::EndTabItem() if (g.CurrentWindow->SkipItems) return; - IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!"); - ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); - IM_ASSERT(tab_bar->LastTabItemIdx >= 0 && "Needs to be called between BeginTabItem() and EndTabItem()"); + ImGuiTabBar* tab_bar = g.CurrentTabBar; + IM_ASSERT(tab_bar != NULL && "Needs to be called between BeginTabBar() and EndTabBar()!"); + IM_ASSERT(tab_bar->LastTabItemIdx >= 0); ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; if (!(tab->Flags & ImGuiTabItemFlags_NoPushId)) g.CurrentWindow->IDStack.pop_back(); @@ -6575,10 +6594,10 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, void ImGui::SetTabItemClosed(const char* label) { ImGuiContext& g = *GImGui; - bool is_within_manual_tab_bar = (g.CurrentTabBar.Size > 0) && !(g.CurrentTabBar.back()->Flags & ImGuiTabBarFlags_DockNode); + bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode); if (is_within_manual_tab_bar) { - ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); + ImGuiTabBar* tab_bar = g.CurrentTabBar; IM_ASSERT(tab_bar->WantLayout); // Needs to be called AFTER BeginTabBar() and BEFORE the first call to BeginTabItem() ImGuiID tab_id = TabBarCalcTabID(tab_bar, label); TabBarRemoveTab(tab_bar, tab_id);