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

Added ImPool::Contains() helper.
This commit is contained in:
omar 2019-02-22 12:24:27 +01:00
parent f988618ebe
commit 0f83145aa8
3 changed files with 48 additions and 17 deletions

View File

@ -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)

View File

@ -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; int Index;
float Width; 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; ImPool<ImGuiTabBar> TabBars;
ImVector<ImGuiTabBar*> CurrentTabBar; 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
}; };

View File

@ -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;
ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); 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;
ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); 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;
ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); IM_ASSERT(tab_bar != NULL && "Needs to be called between BeginTabBar() and EndTabBar()!");
IM_ASSERT(tab_bar->LastTabItemIdx >= 0 && "Needs to be called between BeginTabItem() and EndTabItem()"); 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);