|
@@ -22,6 +22,8 @@ Index of this file:
|
|
// [SECTION] Widgets: PlotLines, PlotHistogram
|
|
// [SECTION] Widgets: PlotLines, PlotHistogram
|
|
// [SECTION] Widgets: Value helpers
|
|
// [SECTION] Widgets: Value helpers
|
|
// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
|
|
// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
|
|
|
|
+// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
|
|
|
|
+// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
|
|
|
|
|
|
*/
|
|
*/
|
|
|
|
|
|
@@ -5748,3 +5750,791 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected,
|
|
}
|
|
}
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+//-------------------------------------------------------------------------
|
|
|
|
+// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
|
|
|
|
+//-------------------------------------------------------------------------
|
|
|
|
+// [BETA API] API may evolve! This code has been extracted out of the Docking branch,
|
|
|
|
+// and some of the construct which are not used in Master may be left here to facilitate merging.
|
|
|
|
+//-------------------------------------------------------------------------
|
|
|
|
+// - BeginTabBar()
|
|
|
|
+// - BeginTabBarEx() [Internal]
|
|
|
|
+// - EndTabBar()
|
|
|
|
+// - TabBarLayout() [Internal]
|
|
|
|
+// - TabBarCalcTabID() [Internal]
|
|
|
|
+// - TabBarCalcMaxTabWidth() [Internal]
|
|
|
|
+// - TabBarFindTabById() [Internal]
|
|
|
|
+// - TabBarRemoveTab() [Internal]
|
|
|
|
+// - TabBarCloseTab() [Internal]
|
|
|
|
+// - TabBarScrollClamp()v
|
|
|
|
+// - TabBarScrollToTab() [Internal]
|
|
|
|
+// - TabBarQueueChangeTabOrder() [Internal]
|
|
|
|
+// - TabBarScrollingButtons() [Internal]
|
|
|
|
+//-------------------------------------------------------------------------
|
|
|
|
+
|
|
|
|
+namespace ImGui
|
|
|
|
+{
|
|
|
|
+ static void TabBarLayout(ImGuiTabBar* tab_bar);
|
|
|
|
+ static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label);
|
|
|
|
+ static float TabBarCalcMaxTabWidth();
|
|
|
|
+ static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
|
|
|
|
+ static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
|
|
|
|
+ static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+ImGuiTabBar::ImGuiTabBar()
|
|
|
|
+{
|
|
|
|
+ ID = 0;
|
|
|
|
+ SelectedTabId = NextSelectedTabId = VisibleTabId = 0;
|
|
|
|
+ CurrFrameVisible = PrevFrameVisible = -1;
|
|
|
|
+ OffsetMax = OffsetNextTab = 0.0f;
|
|
|
|
+ ScrollingAnim = ScrollingTarget = 0.0f;
|
|
|
|
+ Flags = ImGuiTabBarFlags_None;
|
|
|
|
+ ReorderRequestTabId = 0;
|
|
|
|
+ ReorderRequestDir = 0;
|
|
|
|
+ WantLayout = VisibleTabWasSubmitted = false;
|
|
|
|
+ LastTabItemIdx = -1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int IMGUI_CDECL TabItemComparerByVisibleOffset(const void* lhs, const void* rhs)
|
|
|
|
+{
|
|
|
|
+ const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
|
|
|
|
+ const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
|
|
|
|
+ return (int)(a->Offset - b->Offset);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int IMGUI_CDECL TabBarSortItemComparer(const void* lhs, const void* rhs)
|
|
|
|
+{
|
|
|
|
+ const ImGuiTabBarSortItem* a = (const ImGuiTabBarSortItem*)lhs;
|
|
|
|
+ const ImGuiTabBarSortItem* b = (const ImGuiTabBarSortItem*)rhs;
|
|
|
|
+ if (int d = (int)(b->Width - a->Width))
|
|
|
|
+ return d;
|
|
|
|
+ return (b->Index - a->Index);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
+ if (window->SkipItems)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ ImGuiID id = window->GetID(str_id);
|
|
|
|
+ ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
|
|
|
|
+ ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->InnerClipRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);
|
|
|
|
+ tab_bar->ID = id;
|
|
|
|
+ return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
+ if (window->SkipItems)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ if ((flags & ImGuiTabBarFlags_DockNode) == 0)
|
|
|
|
+ window->IDStack.push_back(tab_bar->ID);
|
|
|
|
+
|
|
|
|
+ g.CurrentTabBar.push_back(tab_bar);
|
|
|
|
+ if (tab_bar->CurrFrameVisible == g.FrameCount)
|
|
|
|
+ {
|
|
|
|
+ //printf("[%05d] BeginTabBarEx already called this frame\n", g.FrameCount);
|
|
|
|
+ IM_ASSERT(0);
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // When toggling back from ordered to manually-reorderable, shuffle tabs to enforce the last visible order.
|
|
|
|
+ // Otherwise, the most recently inserted tabs would move at the end of visible list which can be a little too confusing or magic for the user.
|
|
|
|
+ if ((flags & ImGuiTabBarFlags_Reorderable) && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable) && tab_bar->Tabs.Size > 1 && tab_bar->PrevFrameVisible != -1)
|
|
|
|
+ ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByVisibleOffset);
|
|
|
|
+
|
|
|
|
+ // Flags
|
|
|
|
+ if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
|
|
|
|
+ flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
|
|
|
|
+
|
|
|
|
+ tab_bar->Flags = flags;
|
|
|
|
+ tab_bar->BarRect = tab_bar_bb;
|
|
|
|
+ tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
|
|
|
|
+ tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
|
|
|
|
+ tab_bar->CurrFrameVisible = g.FrameCount;
|
|
|
|
+
|
|
|
|
+ // Layout
|
|
|
|
+ ItemSize(ImVec2(tab_bar->OffsetMax, tab_bar->BarRect.GetHeight()));
|
|
|
|
+ window->DC.CursorPos.x = tab_bar->BarRect.Min.x;
|
|
|
|
+
|
|
|
|
+ // Draw separator
|
|
|
|
+ const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_Tab);
|
|
|
|
+ const float y = tab_bar->BarRect.Max.y - 1.0f;
|
|
|
|
+ {
|
|
|
|
+ const float separator_min_x = tab_bar->BarRect.Min.x - ((flags & ImGuiTabBarFlags_DockNodeIsDockSpace) ? 0.0f : window->WindowPadding.x);
|
|
|
|
+ const float separator_max_x = tab_bar->BarRect.Max.x + ((flags & ImGuiTabBarFlags_DockNodeIsDockSpace) ? 0.0f : window->WindowPadding.x);
|
|
|
|
+ window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void ImGui::EndTabBar()
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
+ if (window->SkipItems)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ IM_ASSERT(!g.CurrentTabBar.empty()); // Mismatched BeginTabBar/EndTabBar
|
|
|
|
+ ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
|
|
|
|
+ if (tab_bar->WantLayout)
|
|
|
|
+ TabBarLayout(tab_bar);
|
|
|
|
+
|
|
|
|
+ // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
|
|
|
|
+ const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
|
|
|
|
+ if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
|
|
|
|
+ tab_bar->ContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, 0.0f);
|
|
|
|
+ else
|
|
|
|
+ window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->ContentsHeight;
|
|
|
|
+
|
|
|
|
+ if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
|
|
|
|
+ PopID();
|
|
|
|
+ g.CurrentTabBar.pop_back();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// This is called only once a frame before by the first call to ItemTab()
|
|
|
|
+// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
|
|
|
|
+static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ tab_bar->WantLayout = false;
|
|
|
|
+
|
|
|
|
+ // Garbage collect
|
|
|
|
+ int tab_dst_n = 0;
|
|
|
|
+ for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
|
|
|
|
+ {
|
|
|
|
+ ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
|
|
|
|
+ if (tab->LastFrameVisible < tab_bar->PrevFrameVisible)
|
|
|
|
+ {
|
|
|
|
+ if (tab->ID == tab_bar->SelectedTabId)
|
|
|
|
+ tab_bar->SelectedTabId = 0;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ if (tab_dst_n != tab_src_n)
|
|
|
|
+ tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
|
|
|
|
+ tab_dst_n++;
|
|
|
|
+ }
|
|
|
|
+ if (tab_bar->Tabs.Size != tab_dst_n)
|
|
|
|
+ tab_bar->Tabs.resize(tab_dst_n);
|
|
|
|
+
|
|
|
|
+ // Setup next selected tab
|
|
|
|
+ ImGuiID scroll_track_selected_tab_id = 0;
|
|
|
|
+ if (tab_bar->NextSelectedTabId)
|
|
|
|
+ {
|
|
|
|
+ tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
|
|
|
|
+ tab_bar->NextSelectedTabId = 0;
|
|
|
|
+ scroll_track_selected_tab_id = tab_bar->SelectedTabId;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
|
|
|
|
+ if (tab_bar->ReorderRequestTabId != 0)
|
|
|
|
+ {
|
|
|
|
+ if (ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId))
|
|
|
|
+ {
|
|
|
|
+ //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
|
|
|
|
+ int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir;
|
|
|
|
+ if (tab2_order >= 0 && tab2_order < tab_bar->Tabs.Size)
|
|
|
|
+ {
|
|
|
|
+ ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
|
|
|
|
+ ImGuiTabItem item_tmp = *tab1;
|
|
|
|
+ *tab1 = *tab2;
|
|
|
|
+ *tab2 = item_tmp;
|
|
|
|
+ if (tab2->ID == tab_bar->SelectedTabId)
|
|
|
|
+ scroll_track_selected_tab_id = tab2->ID;
|
|
|
|
+ tab1 = tab2 = NULL;
|
|
|
|
+ }
|
|
|
|
+ if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
|
|
|
|
+ MarkIniSettingsDirty();
|
|
|
|
+ }
|
|
|
|
+ tab_bar->ReorderRequestTabId = 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ImVector<ImGuiTabBarSortItem>& width_sort_buffer = g.TabSortByWidthBuffer;
|
|
|
|
+ width_sort_buffer.resize(tab_bar->Tabs.Size);
|
|
|
|
+
|
|
|
|
+ // Compute ideal widths
|
|
|
|
+ float width_total_contents = 0.0f;
|
|
|
|
+ ImGuiTabItem* most_recently_selected_tab = NULL;
|
|
|
|
+ bool found_selected_tab_id = false;
|
|
|
|
+ for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
|
|
|
|
+ {
|
|
|
|
+ ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
|
|
|
|
+ IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
|
|
|
|
+
|
|
|
|
+ if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
|
|
|
|
+ most_recently_selected_tab = tab;
|
|
|
|
+ if (tab->ID == tab_bar->SelectedTabId)
|
|
|
|
+ found_selected_tab_id = true;
|
|
|
|
+
|
|
|
|
+ // Refresh tab width immediately if we can (for manual tab bar, WidthContent will lag by one frame which is mostly noticeable when changing style.FramePadding.x)
|
|
|
|
+ // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
|
|
|
|
+ // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
|
|
|
|
+ width_total_contents += (tab_n > 0 ? g.Style.ItemInnerSpacing.x : 0.0f) + tab->WidthContents;
|
|
|
|
+
|
|
|
|
+ // Store data so we can build an array sorted by width if we need to shrink tabs down
|
|
|
|
+ width_sort_buffer[tab_n].Index = tab_n;
|
|
|
|
+ width_sort_buffer[tab_n].Width = tab->WidthContents;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Compute width
|
|
|
|
+ const float width_avail = tab_bar->BarRect.GetWidth();
|
|
|
|
+ float width_excess = (width_avail < width_total_contents) ? (width_total_contents - width_avail) : 0.0f;
|
|
|
|
+ if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown))
|
|
|
|
+ {
|
|
|
|
+ // If we don't have enough room, resize down the largest tabs first
|
|
|
|
+ if (tab_bar->Tabs.Size > 1)
|
|
|
|
+ ImQsort(width_sort_buffer.Data, (size_t)width_sort_buffer.Size, sizeof(ImGuiTabBarSortItem), TabBarSortItemComparer);
|
|
|
|
+ int tab_count_same_width = 1;
|
|
|
|
+ while (width_excess > 0.0f && tab_count_same_width < tab_bar->Tabs.Size)
|
|
|
|
+ {
|
|
|
|
+ while (tab_count_same_width < tab_bar->Tabs.Size && width_sort_buffer[0].Width == width_sort_buffer[tab_count_same_width].Width)
|
|
|
|
+ tab_count_same_width++;
|
|
|
|
+ float width_to_remove_per_tab_max = (tab_count_same_width < tab_bar->Tabs.Size) ? (width_sort_buffer[0].Width - width_sort_buffer[tab_count_same_width].Width) : (width_sort_buffer[0].Width - 1.0f);
|
|
|
|
+ float width_to_remove_per_tab = ImMin(width_excess / tab_count_same_width, width_to_remove_per_tab_max);
|
|
|
|
+ for (int tab_n = 0; tab_n < tab_count_same_width; tab_n++)
|
|
|
|
+ width_sort_buffer[tab_n].Width -= width_to_remove_per_tab;
|
|
|
|
+ width_excess -= width_to_remove_per_tab * tab_count_same_width;
|
|
|
|
+ }
|
|
|
|
+ for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
|
|
|
|
+ tab_bar->Tabs[width_sort_buffer[tab_n].Index].Width = (float)(int)width_sort_buffer[tab_n].Width;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ const float tab_max_width = TabBarCalcMaxTabWidth();
|
|
|
|
+ for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
|
|
|
|
+ {
|
|
|
|
+ ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
|
|
|
|
+ tab->Width = ImMin(tab->WidthContents, tab_max_width);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Layout all active tabs
|
|
|
|
+ float offset_x = 0.0f;
|
|
|
|
+ for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
|
|
|
|
+ {
|
|
|
|
+ ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
|
|
|
|
+ tab->Offset = offset_x;
|
|
|
|
+ if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID)
|
|
|
|
+ scroll_track_selected_tab_id = tab->ID;
|
|
|
|
+ offset_x += tab->Width + g.Style.ItemInnerSpacing.x;
|
|
|
|
+ }
|
|
|
|
+ tab_bar->OffsetMax = ImMax(offset_x - g.Style.ItemInnerSpacing.x, 0.0f);
|
|
|
|
+ tab_bar->OffsetNextTab = 0.0f;
|
|
|
|
+
|
|
|
|
+ // Horizontal scrolling buttons
|
|
|
|
+ const bool scrolling_buttons = (tab_bar->OffsetMax > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll);
|
|
|
|
+ if (scrolling_buttons)
|
|
|
|
+ if (ImGuiTabItem* tab_to_select = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x!
|
|
|
|
+ scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
|
|
|
|
+
|
|
|
|
+ // If we have lost the selected tab, select the next most recently active one
|
|
|
|
+ if (found_selected_tab_id == false)
|
|
|
|
+ tab_bar->SelectedTabId = 0;
|
|
|
|
+ if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
|
|
|
|
+ scroll_track_selected_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
|
|
|
|
+
|
|
|
|
+ // Lock in visible tab
|
|
|
|
+ tab_bar->VisibleTabId = tab_bar->SelectedTabId;
|
|
|
|
+ tab_bar->VisibleTabWasSubmitted = false;
|
|
|
|
+
|
|
|
|
+ // Update scrolling
|
|
|
|
+ if (scroll_track_selected_tab_id)
|
|
|
|
+ if (ImGuiTabItem* scroll_track_selected_tab = TabBarFindTabByID(tab_bar, scroll_track_selected_tab_id))
|
|
|
|
+ TabBarScrollToTab(tab_bar, scroll_track_selected_tab);
|
|
|
|
+ tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
|
|
|
|
+ tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
|
|
|
|
+ const float scrolling_speed = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) ? FLT_MAX : (g.IO.DeltaTime * g.FontSize * 70.0f);
|
|
|
|
+ if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
|
|
|
|
+ tab_bar->ScrollingAnim = ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, scrolling_speed);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.
|
|
|
|
+static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label)
|
|
|
|
+{
|
|
|
|
+ if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)
|
|
|
|
+ {
|
|
|
|
+ ImGuiID id = ImHash(label, 0);
|
|
|
|
+ KeepAliveID(id);
|
|
|
|
+ return id;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ ImGuiWindow* window = GImGui->CurrentWindow;
|
|
|
|
+ return window->GetID(label);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static float ImGui::TabBarCalcMaxTabWidth()
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ return g.FontSize * 20.0f;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
|
|
|
|
+{
|
|
|
|
+ if (tab_id != 0)
|
|
|
|
+ for (int n = 0; n < tab_bar->Tabs.Size; n++)
|
|
|
|
+ if (tab_bar->Tabs[n].ID == tab_id)
|
|
|
|
+ return &tab_bar->Tabs[n];
|
|
|
|
+ return NULL;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
|
|
|
|
+void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
|
|
|
|
+{
|
|
|
|
+ if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
|
|
|
|
+ tab_bar->Tabs.erase(tab);
|
|
|
|
+ if (tab_bar->VisibleTabId == tab_id) { tab_bar->VisibleTabId = 0; }
|
|
|
|
+ if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; }
|
|
|
|
+ if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Called on manual closure attempt
|
|
|
|
+void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
|
|
|
|
+{
|
|
|
|
+ if ((tab_bar->VisibleTabId == tab->ID) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
|
|
|
|
+ {
|
|
|
|
+ // This will remove a frame of lag for selecting another tab on closure.
|
|
|
|
+ // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure
|
|
|
|
+ tab->LastFrameVisible = -1;
|
|
|
|
+ tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
|
|
|
|
+ }
|
|
|
|
+ else if ((tab_bar->VisibleTabId != tab->ID) && (tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
|
|
|
|
+ {
|
|
|
|
+ // Actually select before expecting closure
|
|
|
|
+ tab_bar->NextSelectedTabId = tab->ID;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
|
|
|
|
+{
|
|
|
|
+ scrolling = ImMin(scrolling, tab_bar->OffsetMax - tab_bar->BarRect.GetWidth());
|
|
|
|
+ return ImMax(scrolling, 0.0f);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar)
|
|
|
|
+ int order = tab_bar->GetTabOrder(tab);
|
|
|
|
+ float tab_x1 = tab->Offset + (order > 0 ? -margin : 0.0f);
|
|
|
|
+ float tab_x2 = tab->Offset + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f);
|
|
|
|
+ if (tab_bar->ScrollingTarget > tab_x1)
|
|
|
|
+ tab_bar->ScrollingTarget = tab_x1;
|
|
|
|
+ if (tab_bar->ScrollingTarget + tab_bar->BarRect.GetWidth() < tab_x2)
|
|
|
|
+ tab_bar->ScrollingTarget = tab_x2 - tab_bar->BarRect.GetWidth();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void ImGui::TabBarQueueChangeTabOrder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir)
|
|
|
|
+{
|
|
|
|
+ IM_ASSERT(dir == -1 || dir == +1);
|
|
|
|
+ IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
|
|
|
|
+ tab_bar->ReorderRequestTabId = tab->ID;
|
|
|
|
+ tab_bar->ReorderRequestDir = dir;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
+
|
|
|
|
+ const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
|
|
|
|
+ const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
|
|
|
|
+
|
|
|
|
+ const ImVec2 backup_cursor_pos = window->DC.CursorPos;
|
|
|
|
+ //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255));
|
|
|
|
+
|
|
|
|
+ const ImRect avail_bar_rect = tab_bar->BarRect;
|
|
|
|
+ bool want_clip_rect = !avail_bar_rect.Contains(ImRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(scrolling_buttons_width, 0.0f)));
|
|
|
|
+ if (want_clip_rect)
|
|
|
|
+ PushClipRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max + ImVec2(g.Style.ItemInnerSpacing.x, 0.0f), true);
|
|
|
|
+
|
|
|
|
+ ImGuiTabItem* tab_to_select = NULL;
|
|
|
|
+
|
|
|
|
+ int select_dir = 0;
|
|
|
|
+ ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
|
|
|
|
+ arrow_col.w *= 0.5f;
|
|
|
|
+
|
|
|
|
+ PushStyleColor(ImGuiCol_Text, arrow_col);
|
|
|
|
+ PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
|
|
|
+ const float backup_repeat_delay = g.IO.KeyRepeatDelay;
|
|
|
|
+ const float backup_repeat_rate = g.IO.KeyRepeatRate;
|
|
|
|
+ g.IO.KeyRepeatDelay = 0.250f;
|
|
|
|
+ g.IO.KeyRepeatRate = 0.200f;
|
|
|
|
+ window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y);
|
|
|
|
+ if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
|
|
|
|
+ select_dir = -1;
|
|
|
|
+ window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width + arrow_button_size.x, tab_bar->BarRect.Min.y);
|
|
|
|
+ if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
|
|
|
|
+ select_dir = +1;
|
|
|
|
+ PopStyleColor(2);
|
|
|
|
+ g.IO.KeyRepeatRate = backup_repeat_rate;
|
|
|
|
+ g.IO.KeyRepeatDelay = backup_repeat_delay;
|
|
|
|
+
|
|
|
|
+ if (want_clip_rect)
|
|
|
|
+ PopClipRect();
|
|
|
|
+
|
|
|
|
+ if (select_dir != 0)
|
|
|
|
+ if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
|
|
|
|
+ {
|
|
|
|
+ int selected_order = tab_bar->GetTabOrder(tab_item);
|
|
|
|
+ int target_order = selected_order + select_dir;
|
|
|
|
+ tab_to_select = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; // If we are at the end of the list, still scroll to make our tab visible
|
|
|
|
+ }
|
|
|
|
+ window->DC.CursorPos = backup_cursor_pos;
|
|
|
|
+ tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
|
|
|
|
+
|
|
|
|
+ return tab_to_select;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//-------------------------------------------------------------------------
|
|
|
|
+// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
|
|
|
|
+//-------------------------------------------------------------------------
|
|
|
|
+// [BETA API] API may evolve! This code has been extracted out of the Docking branch,
|
|
|
|
+// and some of the construct which are not used in Master may be left here to facilitate merging.
|
|
|
|
+//-------------------------------------------------------------------------
|
|
|
|
+// - BeginTabItem()
|
|
|
|
+// - EndTabItem()
|
|
|
|
+// - TabItemEx() [Internal]
|
|
|
|
+// - SetTabItemClosed()
|
|
|
|
+// - TabItemCalcSize() [Internal]
|
|
|
|
+// - TabItemRenderBackground() [Internal]
|
|
|
|
+// - TabItemLabelAndCloseButton() [Internal]
|
|
|
|
+//-------------------------------------------------------------------------
|
|
|
|
+
|
|
|
|
+bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ 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();
|
|
|
|
+ bool ret = TabItemEx(tab_bar, label, p_open, flags);
|
|
|
|
+ if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
|
|
|
|
+ PushID(tab_bar->Tabs[tab_bar->LastTabItemIdx].ID);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void ImGui::EndTabItem()
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ 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();
|
|
|
|
+ ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
|
|
|
|
+ if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
|
|
|
|
+ PopID();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags)
|
|
|
|
+{
|
|
|
|
+ // Layout whole tab bar if not already done
|
|
|
|
+ if (tab_bar->WantLayout)
|
|
|
|
+ TabBarLayout(tab_bar);
|
|
|
|
+
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
+ if (window->SkipItems)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ const ImGuiStyle& style = g.Style;
|
|
|
|
+ const ImGuiID id = TabBarCalcTabID(tab_bar, label);
|
|
|
|
+
|
|
|
|
+ // If the user called us with *p_open == false, we early out and don't render. We make a dummy call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.
|
|
|
|
+ if (p_open && !*p_open)
|
|
|
|
+ {
|
|
|
|
+ PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
|
|
|
|
+ ItemAdd(ImRect(), id);
|
|
|
|
+ PopItemFlag();
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Calculate tab contents size
|
|
|
|
+ ImVec2 size = TabItemCalcSize(label, p_open != NULL);
|
|
|
|
+
|
|
|
|
+ // Acquire tab data
|
|
|
|
+ ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
|
|
|
|
+ bool tab_is_new = false;
|
|
|
|
+ if (tab == NULL)
|
|
|
|
+ {
|
|
|
|
+ tab_bar->Tabs.push_back(ImGuiTabItem());
|
|
|
|
+ tab = &tab_bar->Tabs.back();
|
|
|
|
+ tab->ID = id;
|
|
|
|
+ tab->Width = size.x;
|
|
|
|
+ tab_is_new = true;
|
|
|
|
+ }
|
|
|
|
+ tab_bar->LastTabItemIdx = (short)tab_bar->Tabs.index_from_pointer(tab);
|
|
|
|
+ tab->WidthContents = size.x;
|
|
|
|
+
|
|
|
|
+ const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
|
|
|
|
+ const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
|
|
|
|
+ const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
|
|
|
|
+ tab->LastFrameVisible = g.FrameCount;
|
|
|
|
+ tab->Flags = flags;
|
|
|
|
+
|
|
|
|
+ // If we are not reorderable, always reset offset based on submission order.
|
|
|
|
+ // (We already handled layout and sizing using the previous known order, but sizing is not affected by order!)
|
|
|
|
+ if (!tab_appearing && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
|
|
|
|
+ {
|
|
|
|
+ tab->Offset = tab_bar->OffsetNextTab;
|
|
|
|
+ tab_bar->OffsetNextTab += tab->Width + g.Style.ItemInnerSpacing.x;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Update selected tab
|
|
|
|
+ if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
|
|
|
|
+ if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
|
|
|
|
+ tab_bar->NextSelectedTabId = id; // New tabs gets activated
|
|
|
|
+
|
|
|
|
+ // Lock visibility
|
|
|
|
+ bool tab_contents_visible = (tab_bar->VisibleTabId == id);
|
|
|
|
+ if (tab_contents_visible)
|
|
|
|
+ tab_bar->VisibleTabWasSubmitted = true;
|
|
|
|
+
|
|
|
|
+ // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
|
|
|
|
+ if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)
|
|
|
|
+ if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
|
|
|
|
+ tab_contents_visible = true;
|
|
|
|
+
|
|
|
|
+ if (tab_appearing && !(tab_bar_appearing && !tab_is_new))
|
|
|
|
+ {
|
|
|
|
+ PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
|
|
|
|
+ ItemAdd(ImRect(), id);
|
|
|
|
+ PopItemFlag();
|
|
|
|
+ return tab_contents_visible;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (tab_bar->SelectedTabId == id)
|
|
|
|
+ tab->LastFrameSelected = g.FrameCount;
|
|
|
|
+
|
|
|
|
+ // Backup current layout position
|
|
|
|
+ const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
|
|
|
|
+
|
|
|
|
+ // Layout
|
|
|
|
+ size.x = tab->Width;
|
|
|
|
+ window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2((float)(int)tab->Offset - tab_bar->ScrollingAnim, 0.0f);
|
|
|
|
+ ImVec2 pos = window->DC.CursorPos;
|
|
|
|
+ ImRect bb(pos, pos + size);
|
|
|
|
+
|
|
|
|
+ // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation)
|
|
|
|
+ bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x) || (bb.Max.x >= tab_bar->BarRect.Max.x);
|
|
|
|
+ if (want_clip_rect)
|
|
|
|
+ PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x, bb.Max.y), true);
|
|
|
|
+
|
|
|
|
+ ItemSize(bb, style.FramePadding.y);
|
|
|
|
+ if (!ItemAdd(bb, id))
|
|
|
|
+ {
|
|
|
|
+ if (want_clip_rect)
|
|
|
|
+ PopClipRect();
|
|
|
|
+ window->DC.CursorPos = backup_main_cursor_pos;
|
|
|
|
+ return tab_contents_visible;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Click to Select a tab
|
|
|
|
+ ImGuiButtonFlags button_flags = (ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_AllowItemOverlap);
|
|
|
|
+ bool hovered, held;
|
|
|
|
+ bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
|
|
|
|
+ hovered |= (g.HoveredId == id);
|
|
|
|
+ if (pressed || ((flags & ImGuiTabItemFlags_SetSelected) && !tab_contents_visible)) // SetSelected can only be passed on explicit tab bar
|
|
|
|
+ tab_bar->NextSelectedTabId = id;
|
|
|
|
+
|
|
|
|
+ // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)
|
|
|
|
+ if (!held)
|
|
|
|
+ SetItemAllowOverlap();
|
|
|
|
+
|
|
|
|
+ // Drag and drop: re-order tabs
|
|
|
|
+ if (held && !tab_appearing && IsMouseDragging(0))
|
|
|
|
+ {
|
|
|
|
+ if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
|
|
|
|
+ {
|
|
|
|
+ // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
|
|
|
|
+ if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
|
|
|
|
+ {
|
|
|
|
+ if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
|
|
|
|
+ TabBarQueueChangeTabOrder(tab_bar, tab, -1);
|
|
|
|
+ }
|
|
|
|
+ else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
|
|
|
|
+ {
|
|
|
|
+ if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
|
|
|
|
+ TabBarQueueChangeTabOrder(tab_bar, tab, +1);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+#if 0
|
|
|
|
+ if (hovered && g.HoveredIdNotActiveTimer > 0.50f && bb.GetWidth() < tab->WidthContents)
|
|
|
|
+ {
|
|
|
|
+ // Enlarge tab display when hovering
|
|
|
|
+ bb.Max.x = bb.Min.x + (float)(int)ImLerp(bb.GetWidth(), tab->WidthContents, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f));
|
|
|
|
+ display_draw_list = GetOverlayDrawList(window);
|
|
|
|
+ TabItemRenderBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
|
|
|
|
+ }
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+ // Render tab shape
|
|
|
|
+ ImDrawList* display_draw_list = window->DrawList;
|
|
|
|
+ const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabUnfocused));
|
|
|
|
+ TabItemBackground(display_draw_list, bb, flags, tab_col);
|
|
|
|
+ RenderNavHighlight(bb, id);
|
|
|
|
+
|
|
|
|
+ // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
|
|
|
|
+ const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
|
|
|
|
+ if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)))
|
|
|
|
+ tab_bar->NextSelectedTabId = id;
|
|
|
|
+
|
|
|
|
+ if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
|
|
|
|
+ flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
|
|
|
|
+
|
|
|
|
+ // Render tab label, process close button
|
|
|
|
+ const ImGuiID close_button_id = p_open ? window->GetID((void*)(intptr_t)(id + 1)) : 0;
|
|
|
|
+ bool just_closed = TabItemLabelAndCloseButton(display_draw_list, bb, flags, label, id, close_button_id);
|
|
|
|
+ if (just_closed)
|
|
|
|
+ {
|
|
|
|
+ *p_open = false;
|
|
|
|
+ TabBarCloseTab(tab_bar, tab);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Restore main window position so user can draw there
|
|
|
|
+ if (want_clip_rect)
|
|
|
|
+ PopClipRect();
|
|
|
|
+ window->DC.CursorPos = backup_main_cursor_pos;
|
|
|
|
+
|
|
|
|
+ // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer)
|
|
|
|
+ if (g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > 0.50f)
|
|
|
|
+ SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
|
|
|
|
+
|
|
|
|
+ return tab_contents_visible;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
|
|
|
|
+// To use it to need to call the function SetTabItemClosed() after BeginTabBar() and before any call to BeginTabItem()
|
|
|
|
+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);
|
|
|
|
+ if (is_within_manual_tab_bar)
|
|
|
|
+ {
|
|
|
|
+ ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
|
|
|
|
+ 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);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ ImVec2 label_size = CalcTextSize(label, NULL, true);
|
|
|
|
+ ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
|
|
|
|
+ if (has_close_button)
|
|
|
|
+ size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
|
|
|
|
+ else
|
|
|
|
+ size.x += g.Style.FramePadding.x + 1.0f;
|
|
|
|
+ return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
|
|
|
|
+{
|
|
|
|
+ // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it.
|
|
|
|
+ (void)flags;
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ const float width = bb.GetWidth();
|
|
|
|
+ IM_ASSERT(width > 0.0f);
|
|
|
|
+ const float rounding = ImMax(0.0f, ImMin(g.Style.TabRounding, width * 0.5f - 1.0f));
|
|
|
|
+ float y1 = bb.Min.y + 1.0f;
|
|
|
|
+ float y2 = bb.Max.y - 1.0f;
|
|
|
|
+ draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
|
|
|
|
+ draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
|
|
|
|
+ draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
|
|
|
|
+ draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
|
|
|
|
+ draw_list->AddConvexPolyFilled(draw_list->_Path.Data, draw_list->_Path.Size, col);
|
|
|
|
+ if (g.Style.TabBorderSize > 0.0f)
|
|
|
|
+ draw_list->AddPolyline(draw_list->_Path.Data, draw_list->_Path.Size, GetColorU32(ImGuiCol_Border), false, g.Style.TabBorderSize);
|
|
|
|
+ draw_list->PathClear();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
|
|
|
|
+bool ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, const char* label, ImGuiID tab_id, ImGuiID close_button_id)
|
|
|
|
+{
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ ImGuiStyle& style = g.Style;
|
|
|
|
+ ImVec2 label_size = CalcTextSize(label, NULL, true);
|
|
|
|
+ if (bb.GetWidth() <= 1.0f)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ // Render text label (with clipping + alpha gradient) + unsaved marker
|
|
|
|
+ const char* TAB_UNSAVED_MARKER = "*";
|
|
|
|
+ ImRect text_pixel_clip_bb(bb.Min.x + style.FramePadding.x, bb.Min.y + style.FramePadding.y, bb.Max.x - style.FramePadding.x, bb.Max.y);
|
|
|
|
+ if (flags & ImGuiTabItemFlags_UnsavedDocument)
|
|
|
|
+ {
|
|
|
|
+ text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x;
|
|
|
|
+ ImVec2 unsaved_marker_pos(ImMin(bb.Min.x + style.FramePadding.x + label_size.x + 2, text_pixel_clip_bb.Max.x), bb.Min.y + style.FramePadding.y + (float)(int)(-g.FontSize * 0.25f));
|
|
|
|
+ RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - style.FramePadding, TAB_UNSAVED_MARKER, NULL, NULL);
|
|
|
|
+ }
|
|
|
|
+ ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
|
|
|
|
+
|
|
|
|
+ // Close Button
|
|
|
|
+ // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
|
|
|
|
+ // 'hovered' will be true when hovering the Tab but NOT when hovering the close button
|
|
|
|
+ // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
|
|
|
|
+ // 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
|
|
|
|
+ bool close_button_pressed = false;
|
|
|
|
+ bool close_button_visible = false;
|
|
|
|
+ if (close_button_id != 0)
|
|
|
|
+ if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == close_button_id)
|
|
|
|
+ close_button_visible = true;
|
|
|
|
+ if (close_button_visible)
|
|
|
|
+ {
|
|
|
|
+ ImGuiItemHoveredDataBackup last_item_backup;
|
|
|
|
+ const float close_button_sz = g.FontSize * 0.5f;
|
|
|
|
+ if (CloseButton(close_button_id, ImVec2(bb.Max.x - style.FramePadding.x - close_button_sz, bb.Min.y + style.FramePadding.y + close_button_sz), close_button_sz))
|
|
|
|
+ close_button_pressed = true;
|
|
|
|
+ last_item_backup.Restore();
|
|
|
|
+
|
|
|
|
+ // Close with middle mouse button
|
|
|
|
+ if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
|
|
|
|
+ close_button_pressed = true;
|
|
|
|
+
|
|
|
|
+ text_pixel_clip_bb.Max.x -= close_button_sz * 2.0f;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Label with ellipsis
|
|
|
|
+ // FIXME: This should be extracted into a helper but the use of text_pixel_clip_bb and !close_button_visible makes it tricky to abstract at the moment
|
|
|
|
+ const char* label_display_end = FindRenderedTextEnd(label);
|
|
|
|
+ if (label_size.x > text_ellipsis_clip_bb.GetWidth())
|
|
|
|
+ {
|
|
|
|
+ const int ellipsis_dot_count = 3;
|
|
|
|
+ const float ellipsis_width = (1.0f + 1.0f) * ellipsis_dot_count - 1.0f;
|
|
|
|
+ const char* label_end = NULL;
|
|
|
|
+ float label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, text_ellipsis_clip_bb.GetWidth() - ellipsis_width + 1.0f, 0.0f, label, label_display_end, &label_end).x;
|
|
|
|
+ if (label_end == label && label_end < label_display_end) // Always display at least 1 character if there's no room for character + ellipsis
|
|
|
|
+ {
|
|
|
|
+ label_end = label + ImTextCountUtf8BytesFromChar(label, label_display_end);
|
|
|
|
+ label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label, label_end).x;
|
|
|
|
+ }
|
|
|
|
+ while (label_end > label && ImCharIsBlankA(label_end[-1])) // Trim trailing space
|
|
|
|
+ {
|
|
|
|
+ label_end--;
|
|
|
|
+ label_size_clipped_x -= g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label_end, label_end + 1).x; // Ascii blanks are always 1 byte
|
|
|
|
+ }
|
|
|
|
+ RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_end, &label_size, ImVec2(0.0f, 0.0f));
|
|
|
|
+
|
|
|
|
+ const float ellipsis_x = text_pixel_clip_bb.Min.x + label_size_clipped_x + 1.0f;
|
|
|
|
+ if (!close_button_visible && ellipsis_x + ellipsis_width <= bb.Max.x)
|
|
|
|
+ RenderPixelEllipsis(draw_list, g.Font, ImVec2(ellipsis_x, text_pixel_clip_bb.Min.y), ellipsis_dot_count, GetColorU32(ImGuiCol_Text));
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_display_end, &label_size, ImVec2(0.0f, 0.0f));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return close_button_pressed;
|
|
|
|
+}
|