Browse Source

TabBar: Fixed a crash when using BeginTabBar() recursively (didn't affect docking). (#2371)
Added ImPool::Contains() helper.

omar 6 years ago
parent
commit
0f83145aa8
3 changed files with 48 additions and 17 deletions
  1. 1 0
      docs/CHANGELOG.txt
  2. 17 6
      imgui_internal.h
  3. 30 11
      imgui_widgets.cpp

+ 1 - 0
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)
   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
 - 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.
   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
 - 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)
   even if the OpenGL headers/loader happens to define the value. (#2366, #2186)
 
 

+ 17 - 6
imgui_internal.h

@@ -275,7 +275,8 @@ struct IMGUI_API ImPool
     T*          GetByIndex(ImPoolIdx n)             { return &Data[n]; }
     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); }
     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(); }
     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]; }
     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, 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); }
     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
 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
     unsigned char           DragDropPayloadBufLocal[8];         // Local buffer for small payloads
 
 
     // Tab bars
     // Tab bars
-    ImPool<ImGuiTabBar>     TabBars;
-    ImVector<ImGuiTabBar*>  CurrentTabBar;
+    ImPool<ImGuiTabBar>             TabBars;
+    ImGuiTabBar*                    CurrentTabBar;
+    ImVector<ImGuiTabBarRef>        CurrentTabBarStack;
     ImVector<ImGuiTabBarSortItem>   TabSortByWidthBuffer;
     ImVector<ImGuiTabBarSortItem>   TabSortByWidthBuffer;
 
 
     // Widget state
     // Widget state
@@ -1246,7 +1257,7 @@ struct ImGuiItemHoveredDataBackup
 
 
 enum ImGuiTabBarFlagsPrivate_
 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_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
     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
 };
 };

+ 30 - 11
imgui_widgets.cpp

@@ -5905,6 +5905,20 @@ static int IMGUI_CDECL TabBarSortItemComparer(const void* lhs, const void* rhs)
     return (b->Index - a->Index);
     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)
 bool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
 {
 {
     ImGuiContext& g = *GImGui;
     ImGuiContext& g = *GImGui;
@@ -5929,7 +5943,10 @@ bool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG
     if ((flags & ImGuiTabBarFlags_DockNode) == 0)
     if ((flags & ImGuiTabBarFlags_DockNode) == 0)
         window->IDStack.push_back(tab_bar->ID);
         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)
     if (tab_bar->CurrFrameVisible == g.FrameCount)
     {
     {
         //IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount);
         //IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount);
@@ -5975,8 +5992,8 @@ void    ImGui::EndTabBar()
     if (window->SkipItems)
     if (window->SkipItems)
         return;
         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)
     if (tab_bar->WantLayout)
         TabBarLayout(tab_bar);
         TabBarLayout(tab_bar);
 
 
@@ -5989,7 +6006,9 @@ void    ImGui::EndTabBar()
 
 
     if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
     if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
         PopID();
         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()
 // 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)
     if (g.CurrentWindow->SkipItems)
         return false;
         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);
     bool ret = TabItemEx(tab_bar, label, p_open, flags);
     if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
     if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
     {
     {
@@ -6373,9 +6392,9 @@ void    ImGui::EndTabItem()
     if (g.CurrentWindow->SkipItems)
     if (g.CurrentWindow->SkipItems)
         return;
         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];
     ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
     if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
     if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
         g.CurrentWindow->IDStack.pop_back();
         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)
 void    ImGui::SetTabItemClosed(const char* label)
 {
 {
     ImGuiContext& g = *GImGui;
     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)
     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()
         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);
         ImGuiID tab_id = TabBarCalcTabID(tab_bar, label);
         TabBarRemoveTab(tab_bar, tab_id);
         TabBarRemoveTab(tab_bar, tab_id);