Tabs: added new fitting policy ImGuiTabBarFlags_FittingPolicyMixed, new default. (#3421, #8800)

This commit is contained in:
ocornut
2025-07-31 23:20:47 +09:00
parent cc1fbcc9a9
commit 3ef6c8410a
6 changed files with 49 additions and 17 deletions

View File

@@ -54,11 +54,17 @@ Other Changes:
often more convenient to use index in caller-code, whereas TableGetRowIndex()
includes header rows).
- Tables: fixed imgui_internal.h's TableGetHoveredRow() the same way. (#7350, #6588, #6250)
- Tabs: fixed tab bar underline not drawing below scroll buttons, when
they are enabled (minor regression from 1.90). (#6820, #4859, #5022, #5239)
- Tabs: added new fitting policy ImGuiTabBarFlags_FittingPolicyMixed
and made it the default. This policy shrink tab width down to a given amount,
and then beyond that it enable scrolling buttons. (#3421, #8800)
- Tabs: added style.TabMinWidthShrink, ImGuiStyleVar_TabMinWidthShrink to
control the width to shrink to in ImGuiTabBarFlags_FittingPolicyMixed mode.
(#3421, #8800).
- Tabs: added style.TabMinWidthBase, ImGuiStyleVar_TabMinWidthBase to control
the base minimum width of a tab (default to 1.0f). This is the size before
any potential shrinking is applied.
- Tabs: fixed tab bar underline not drawing below scroll buttons, when
they are enabled (minor regression from 1.90). (#6820, #4859, #5022, #5239)
- Error Handling: minor improvements to error handling for TableGetSortSpecs()
and TableSetBgColor() calls. (#1651, #8499)
- Misc: fixed building with IMGUI_DISABLE_DEBUG_TOOLS only. (#8796)

View File

@@ -1419,6 +1419,7 @@ ImGuiStyle::ImGuiStyle()
TabRounding = 5.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
TabBorderSize = 0.0f; // Thickness of border around tabs.
TabMinWidthBase = 1.0f; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected.
TabMinWidthShrink = 80.0f; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy.
TabCloseButtonMinWidthSelected = -1.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width.
TabCloseButtonMinWidthUnselected = 0.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected.
TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus.
@@ -1486,6 +1487,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor)
ImageBorderSize = ImTrunc(ImageBorderSize * scale_factor);
TabRounding = ImTrunc(TabRounding * scale_factor);
TabMinWidthBase = ImTrunc(TabMinWidthBase * scale_factor);
TabMinWidthShrink = ImTrunc(TabMinWidthShrink * scale_factor);
TabCloseButtonMinWidthSelected = (TabCloseButtonMinWidthSelected > 0.0f && TabCloseButtonMinWidthSelected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthSelected * scale_factor) : TabCloseButtonMinWidthSelected;
TabCloseButtonMinWidthUnselected = (TabCloseButtonMinWidthUnselected > 0.0f && TabCloseButtonMinWidthUnselected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthUnselected * scale_factor) : TabCloseButtonMinWidthUnselected;
TabBarOverlineSize = ImTrunc(TabBarOverlineSize * scale_factor);
@@ -3506,6 +3508,7 @@ static const ImGuiStyleVarInfo GStyleVarsInfo[] =
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBorderSize) }, // ImGuiStyleVar_TabBorderSize
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabMinWidthBase) }, // ImGuiStyleVar_TabMinWidthBase
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabMinWidthShrink) }, // ImGuiStyleVar_TabMinWidthShrink
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize) }, // ImGuiStyleVar_TabBarOverlineSize
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle

11
imgui.h
View File

@@ -1372,10 +1372,11 @@ enum ImGuiTabBarFlags_
ImGuiTabBarFlags_DrawSelectedOverline = 1 << 6, // Draw selected overline markers over selected tab
// Fitting/Resize policy
ImGuiTabBarFlags_FittingPolicyShrink = 1 << 7, // Shrink down tabs when they don't fit
ImGuiTabBarFlags_FittingPolicyScroll = 1 << 8, // Add scroll buttons when tabs don't fit
ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyShrink | ImGuiTabBarFlags_FittingPolicyScroll,
ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyShrink,
ImGuiTabBarFlags_FittingPolicyMixed = 1 << 7, // Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling buttons.
ImGuiTabBarFlags_FittingPolicyShrink = 1 << 8, // Shrink down tabs when they don't fit
ImGuiTabBarFlags_FittingPolicyScroll = 1 << 9, // Enable scrolling buttons when tabs don't fit
ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyMixed | ImGuiTabBarFlags_FittingPolicyShrink | ImGuiTabBarFlags_FittingPolicyScroll,
ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyMixed,
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
ImGuiTabBarFlags_FittingPolicyResizeDown = ImGuiTabBarFlags_FittingPolicyShrink, // Renamed in 1.92.2
@@ -1810,6 +1811,7 @@ enum ImGuiStyleVar_
ImGuiStyleVar_TabRounding, // float TabRounding
ImGuiStyleVar_TabBorderSize, // float TabBorderSize
ImGuiStyleVar_TabMinWidthBase, // float TabMinWidthBase
ImGuiStyleVar_TabMinWidthShrink, // float TabMinWidthShrink
ImGuiStyleVar_TabBarBorderSize, // float TabBarBorderSize
ImGuiStyleVar_TabBarOverlineSize, // float TabBarOverlineSize
ImGuiStyleVar_TableAngledHeadersAngle, // float TableAngledHeadersAngle
@@ -2276,6 +2278,7 @@ struct ImGuiStyle
float TabRounding; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
float TabBorderSize; // Thickness of border around tabs.
float TabMinWidthBase; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected.
float TabMinWidthShrink; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy.
float TabCloseButtonMinWidthSelected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width.
float TabCloseButtonMinWidthUnselected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected.
float TabBarBorderSize; // Thickness of tab-bar separator, which takes on the tab active color to denote focus.

View File

@@ -3383,6 +3383,8 @@ static void EditTabBarFittingPolicyFlags(ImGuiTabBarFlags* p_flags)
{
if ((*p_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
*p_flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyMixed", p_flags, ImGuiTabBarFlags_FittingPolicyMixed))
*p_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyMixed);
if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyShrink", p_flags, ImGuiTabBarFlags_FittingPolicyShrink))
*p_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyShrink);
if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", p_flags, ImGuiTabBarFlags_FittingPolicyScroll))
@@ -8343,6 +8345,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, 3.0f, "%.0f");
SameLine(); HelpMarker("Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set.");
DragFloat("TabMinWidthBase", &style.TabMinWidthBase, 0.5f, 1.0f, 500.0f, "%.0f");
DragFloat("TabMinWidthShrink", &style.TabMinWidthShrink, 0.5f, 1.0f, 500.0f, "%0.f");
DragFloat("TabCloseButtonMinWidthSelected", &style.TabCloseButtonMinWidthSelected, 0.5f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthSelected < 0.0f) ? "%.0f (Always)" : "%.0f");
DragFloat("TabCloseButtonMinWidthUnselected", &style.TabCloseButtonMinWidthUnselected, 0.5f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthUnselected < 0.0f) ? "%.0f (Always)" : "%.0f");
SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f");

View File

@@ -2776,6 +2776,7 @@ struct IMGUI_API ImGuiTabBar
bool WantLayout;
bool VisibleTabWasSubmitted;
bool TabsAddedNew; // Set to true when a new tab item or button has been added to the tab bar during last frame
bool ScrollButtonEnabled;
ImS16 TabsActiveCount; // Number of tabs submitted this frame.
ImS16 LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem()
float ItemSpacingY;
@@ -3222,7 +3223,7 @@ namespace ImGui
IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_w, float default_h);
IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x);
IMGUI_API void PushMultiItemsWidths(int components, float width_full);
IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess);
IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min);
// Parameter stacks (shared)
IMGUI_API const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx);

View File

@@ -1830,27 +1830,31 @@ 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)
void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min)
{
if (count == 1)
{
if (items[0].Width >= 0.0f)
items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);
items[0].Width = ImMax(items[0].Width - width_excess, width_min);
return;
}
ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);
ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); // Sort largest first, smallest last.
int count_same_width = 1;
while (width_excess > 0.0f && count_same_width < count)
{
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[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f);
max_width_to_remove_per_item = ImMin(items[0].Width - width_min, max_width_to_remove_per_item);
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);
float base_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;
width_excess -= width_to_remove_per_item * count_same_width;
{
float width_to_remove_for_this_item = ImMin(base_width_to_remove_per_item, items[item_n].Width - width_min);
items[item_n].Width -= width_to_remove_for_this_item;
width_excess -= width_to_remove_for_this_item;
}
}
// Round width and redistribute remainder
@@ -9358,6 +9362,7 @@ struct ImGuiTabBarSection
{
int TabCount; // Number of tabs in this section.
float Width; // Sum of width of tabs in this section (after shrinking down)
float WidthAfterShrinkMinWidth;
float Spacing; // Horizontal spacing at the end of the section.
ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }
@@ -9626,6 +9631,9 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };
g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
// Minimum shrink width
const float shrink_min_width = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed) ? g.Style.TabMinWidthShrink : 1.0f;
// Compute ideal tabs widths + store them into shrink buffer
ImGuiTabItem* most_recently_selected_tab = NULL;
int curr_section_n = -1;
@@ -9654,6 +9662,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
int section_n = TabItemGetSectionIdx(tab);
ImGuiTabBarSection* section = &sections[section_n];
section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
section->WidthAfterShrinkMinWidth += ImMin(tab->ContentWidth, shrink_min_width) + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
curr_section_n = section_n;
// Store data so we can build an array sorted by width if we need to shrink tabs down
@@ -9665,13 +9674,19 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
}
// Compute total ideal width (used for e.g. auto-resizing a window)
float width_all_tabs_after_min_width_shrink = 0.0f;
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;
width_all_tabs_after_min_width_shrink += sections[section_n].WidthAfterShrinkMinWidth + sections[section_n].Spacing;
}
// Horizontal scrolling buttons
// Important: 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))
const bool can_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed);
tab_bar->ScrollButtonEnabled = ((width_all_tabs_after_min_width_shrink > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && can_scroll);
if (tab_bar->ScrollButtonEnabled)
if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))
{
scroll_to_tab_id = scroll_and_select_tab->ID;
@@ -9691,11 +9706,12 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section
// With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyShrink) || !central_section_is_visible))
const bool can_shrink = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyShrink) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed);
if (width_excess >= 1.0f && (can_shrink || !central_section_is_visible))
{
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 + shrink_data_offset, shrink_data_count, width_excess, shrink_min_width);
// Apply shrunk values into tabs and sections
for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++)
@@ -9750,7 +9766,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
// Apply request requests
if (scroll_to_tab_id != 0)
TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
else if ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow))
else if (tab_bar->ScrollButtonEnabled && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow))
{
const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH;
const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX;