From 6b76781c66403457e870e8de3943063acb2546ee Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Sep 2020 11:18:22 +0200 Subject: [PATCH] Tab Bar: Tidying up. Rework ShrinkWidths to allow marking tabs as not shrinkable (unused yet) + don't unnecessarily move data within ShrinkWidthBuffer. (#3291) --- imgui_internal.h | 3 +-- imgui_widgets.cpp | 68 +++++++++++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index b48e2cc4..7aa47618 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1729,11 +1729,10 @@ struct ImGuiTabBarSection { int TabStartIndex; // Index of first tab in this section. int TabCount; // Number of tabs in this section. - float Width; // Width of this section (after shrinking down) + float Width; // Sum of width of tabs in this section (after shrinking down) float Spacing; // Horizontal spacing at the end of the section. ImGuiTabBarSection(){ memset(this, 0, sizeof(*this)); } - float WidthWithSpacing() const { return Width + Spacing; } }; // Storage for a tab bar (sizeof() 92~96 bytes) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index d60223a2..c551c314 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1426,11 +1426,13 @@ static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) } // Shrink excess width from a set of item, by removing width from the larger items first. +// Set items Width to -1.0f to disable shrinking this item. void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess) { if (count == 1) { - items[0].Width = ImMax(items[0].Width - width_excess, 1.0f); + if (items[0].Width >= 0.0f) + items[0].Width = ImMax(items[0].Width - width_excess, 1.0f); return; } ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); @@ -1439,7 +1441,9 @@ void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_exc { while (count_same_width < count && items[0].Width <= items[count_same_width].Width) count_same_width++; - float max_width_to_remove_per_item = (count_same_width < count) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f); + float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f); + if (max_width_to_remove_per_item <= 0.0f) + break; float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item); for (int item_n = 0; item_n < count_same_width; item_n++) items[item_n].Width -= width_to_remove_per_item; @@ -7043,10 +7047,14 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x! scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; - // Compute ideal widths + // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central + // (whereas our tabs are stored as: leading, central, trailing) + int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount }; + g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); + + // Compute ideal tabs widths + store them into shrink buffer ImGuiTabItem* most_recently_selected_tab = NULL; bool found_selected_tab_id = false; - g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; @@ -7071,19 +7079,21 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) section->Width += tab->ContentWidth + (tab_n > section->TabStartIndex ? g.Style.ItemInnerSpacing.x : 0.0f); // Store data so we can build an array sorted by width if we need to shrink tabs down - g.ShrinkWidthBuffer[tab_n].Index = tab_n; - g.ShrinkWidthBuffer[tab_n].Width = tab->ContentWidth; + int shrink_buffer_index = shrink_buffer_indexes[section_n]++; + g.ShrinkWidthBuffer[shrink_buffer_index].Index = tab_n; + g.ShrinkWidthBuffer[shrink_buffer_index].Width = tab->ContentWidth; IM_ASSERT(tab->ContentWidth > 0.0f); tab->Width = tab->ContentWidth; } + // Compute total ideal width (used for e.g. auto-resizing a window) tab_bar->WidthAllTabsIdeal = 0.0f; for (int section_n = 0; section_n < 3; section_n++) tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing; // Horizontal scrolling buttons - // (Note that TabBarScrollButtons() will alter BarRect.Max.x) + // (note that TabBarScrollButtons() will alter BarRect.Max.x) if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) { @@ -7092,32 +7102,33 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->SelectedTabId = scroll_track_selected_tab_id; } - // Compute width - // FIXME: This is a mess - bool central_section_is_visible = (sections[0].WidthWithSpacing() + sections[2].WidthWithSpacing()) < tab_bar->BarRect.GetWidth(); - float width_excess = central_section_is_visible - ? ImMax(sections[1].WidthWithSpacing() - (tab_bar->BarRect.GetWidth() - sections[0].WidthWithSpacing() - sections[2].WidthWithSpacing()), 0.0f) - : (sections[0].WidthWithSpacing() + sections[2].WidthWithSpacing()) - tab_bar->BarRect.GetWidth(); + // Shrink widths if full tabs don't fit in their allocated space + float section_0_w = sections[0].Width + sections[0].Spacing; + float section_1_w = sections[1].Width + sections[1].Spacing; + float section_2_w = sections[2].Width + sections[2].Spacing; + bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth(); + float width_excess; + if (central_section_is_visible) + width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section + else + width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section - if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown || !central_section_is_visible)) + // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore + if (width_excess > 0.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible)) { - // All tabs are in the ShrinkWidthBuffer, but we will only resize leading/trailing or central tabs, so rearrange internal data - // FIXME: Why copying data, shouldn't we be able to call ShrinkWidths with the right offset and then use that in the loop below? - if (central_section_is_visible) - memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + sections[0].TabCount, sizeof(ImGuiShrinkWidthItem) * sections[1].TabCount); // Move central section tabs - else - memmove(g.ShrinkWidthBuffer.Data + sections[0].TabCount, g.ShrinkWidthBuffer.Data + sections[0].TabCount + sections[1].TabCount, sizeof(ImGuiShrinkWidthItem) * sections[2].TabCount); // Replace central section tabs with trailing - int tab_n_shrinkable = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); + int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); + int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0); + ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess); - ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess); - - // Update each section width with shrunk values - for (int tab_n = 0; tab_n < tab_n_shrinkable; tab_n++) + // Apply shrunk values into tabs and sections + for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++) { - float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index]; - int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); + if (shrinked_width < 0.0f) + continue; + int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; sections[section_n].Width -= (tab->Width - shrinked_width); tab->Width = shrinked_width; } @@ -7128,7 +7139,6 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->WidthAllTabs = 0.0f; for (int section_n = 0; section_n < 3; section_n++) { - // TabBarScrollingButtons() will alter BarRect.Max.x, so we need to anticipate BarRect width reduction // FIXME: The +1.0f is in TabBarScrollingButtons() ImGuiTabBarSection* section = §ions[section_n]; if (section_n == 2) @@ -7140,7 +7150,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab->Offset = next_tab_offset; next_tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); } - tab_bar->WidthAllTabs += ImMax(section->WidthWithSpacing(), 0.0f); + tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f); next_tab_offset += section->Spacing; }