Tab Bar: Various fixes. Tried to reduce code complexity. (#3291)

This commit is contained in:
Louis Schnellbach 2020-09-04 11:13:46 +02:00 committed by ocornut
parent 5e5f25e2dd
commit 3422cb1308
4 changed files with 64 additions and 88 deletions

View File

@ -65,9 +65,9 @@ Other Changes:
- InputText: Fixed minor scrolling glitch when erasing trailing lines in InputTextMultiline().
- InputText: Fixed cursor being partially covered after using Ctrl+End key.
- InputText: Fixed callback's helper DeleteChars() function when cursor is inside the deleted block. (#3454)
- InputText: Made pressing Down arrow on the last line when it doesn't have a carriage return not move to the end
of the line (so it is consistent with Up arrow, and behave same as Notepad and Visual Studio. Note that some
other text editors instead would move the crusor to the end of the line). [@Xipiryon]
- InputText: Made pressing Down arrow on the last line when it doesn't have a carriage return not move to
the end of the line (so it is consistent with Up arrow, and behave same as Notepad and Visual Studio.
Note that some other text editors instead would move the crusor to the end of the line). [@Xipiryon]
- DragFloat, DragScalar: Fixed ImGuiSliderFlags_ClampOnInput not being honored in the special case
where v_min == v_max. (#3361)
- SliderInt, SliderScalar: Fixed reaching of maximum value with inverted integer min/max ranges, both
@ -80,17 +80,17 @@ Other Changes:
rather than the Mouse Down+Up sequence, even if the _OpenOnArrow flag isn't set. This is standard behavior
and amends the change done in 1.76 which only affected cases were _OpenOnArrow flag was set.
(This is also necessary to support full multi/range-select/drag and drop operations.)
- Tab Bar: Added TabItemButton() to submit tab that behave like a button. (#3291) [@Xipiryon]
- Tab Bar: Added ImGuiTabItemFlags_Leading and ImGuiTabItemFlags_Trailing flags to position tabs or button
at either end of the tab bar. Those tabs won't be part of the scrolling region, and when reordering cannot
be moving outside of their section. Most often used with TabItemButton(). (#3291) [@Xipiryon]
- Tab Bar: Added ImGuiTabItemFlags_NoReorder flag to disable reordering a given tab.
- Tab Bar: Keep tab item close button visible while dragging a tab (independent of hovering state).
- Tab Bar: Fixed a small bug where closing a tab that is not selected would leave a tab hole for a frame.
- Tab Bar: Fixed a small bug where scrolling buttons (with ImGuiTabBarFlags_FittingPolicyScroll) would
generate an unnecessary extra draw call.
- Tab Bar: Fixed a small bug where toggling a tab bar from Reorderable to not Reorderable would leave
tabs reordered in the tab list popup. [@Xipiryon]
- Tab Bar: Added TabItemButton() to submit tab that behave like a button. (#3291) [@Xipiryon]
- Tab Bar: Added ImGuiTabItemFlags_Leading and ImGuiTabItemFlags_Trailing flags to position tabs or button at
either end of the tab bar. Those tabs won't be part of the scrolling region, won't shrink down, and when
reordering cannot be moving outside of their section. Most often used with TabItemButton(). (#3291) [@Xipiryon]
- Tab Bar: Added ImGuiTabItemFlags_NoReorder flag to disable reordering a given tab.
- Columns: Fix inverted ClipRect being passed to renderer when using certain primitives inside of
a fully clipped column. (#3475) [@szreder]
- Popups, Tooltips: Fix edge cases issues with positionning popups and tooltips when they are larger than

View File

@ -957,8 +957,8 @@ enum ImGuiTabItemFlags_
ImGuiTabItemFlags_NoPushId = 1 << 3, // Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem()
ImGuiTabItemFlags_NoTooltip = 1 << 4, // Disable tooltip for the given tab
ImGuiTabItemFlags_NoReorder = 1 << 5, // Disable reordering this tab or having another tab cross over this tab
ImGuiTabItemFlags_Leading = 1 << 6, // Enforce the tab position to the left of the tab bar (after the tab list popup button) and disable resizing down
ImGuiTabItemFlags_Trailing = 1 << 7 // Enforce the tab position to the right of the tab bar (before the scrolling buttons) and disable resizing down
ImGuiTabItemFlags_Leading = 1 << 6, // Enforce the tab position to the left of the tab bar (after the tab list popup button)
ImGuiTabItemFlags_Trailing = 1 << 7 // Enforce the tab position to the right of the tab bar (before the scrolling buttons)
};
// Flags for ImGui::IsWindowFocused()

View File

@ -1704,7 +1704,7 @@ enum ImGuiTabBarFlagsPrivate_
enum ImGuiTabItemFlagsPrivate_
{
ImGuiTabItemFlags_NoCloseButton = 1 << 20, // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout)
ImGuiTabItemFlags_Button = 1 << 21 // [Internal] Used by TabItemButton, change the tab item behavior to mimic a button
ImGuiTabItemFlags_Button = 1 << 21 // Used by TabItemButton, change the tab item behavior to mimic a button
};
// Storage for one active tab item (sizeof() 28~32 bytes)
@ -1732,6 +1732,7 @@ struct ImGuiTabBarSection
float Width;
float WidthIdeal;
float InnerSpacing; // Horizontal ItemInnerSpacing, used by Leading/Trailing section, to correctly offset from Central section
float WidthWithSpacing() const { return Width + InnerSpacing; }
ImGuiTabBarSection(){ memset(this, 0, sizeof(*this)); }
};
@ -1761,6 +1762,7 @@ struct ImGuiTabBar
bool WantLayout;
bool VisibleTabWasSubmitted;
short LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem()
bool TabsAddedNew; // Set to true when a new tab item or button has been added to the tab bar during last frame
ImVec2 FramePadding; // style.FramePadding locked at the time of BeginTabBar()
ImGuiTextBuffer TabsNames; // For non-docking tab bar we re-append names in a contiguous buffer.

View File

@ -6785,7 +6785,6 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected,
// - BeginTabBarEx() [Internal]
// - EndTabBar()
// - TabBarLayout() [Internal]
// - TabBarLayoutComputeTabsWidth() [Internal]
// - TabBarCalcTabID() [Internal]
// - TabBarCalcMaxTabWidth() [Internal]
// - TabBarFindTabById() [Internal]
@ -6801,7 +6800,6 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected,
namespace ImGui
{
static void TabBarLayout(ImGuiTabBar* tab_bar);
static void TabBarLayoutComputeTabsWidth(ImGuiTabBar* tab_bar, bool scrolling_buttons, float scrolling_buttons_width);
static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label);
static float TabBarCalcMaxTabWidth();
static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
@ -6824,6 +6822,7 @@ ImGuiTabBar::ImGuiTabBar()
TabsActiveCount = 0;
WantLayout = VisibleTabWasSubmitted = false;
LastTabItemIdx = -1;
TabsAddedNew = false;
}
static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
@ -6899,9 +6898,11 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG
return true;
}
// When toggling ImGuiTabBarFlags_Reorderable flag, ensure tabs are ordered based on their submission order.
if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) && tab_bar->Tabs.Size > 1)
// When toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable, ensure tabs are ordered based on their submission order.
if (((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)) || tab_bar->TabsAddedNew)
if (tab_bar->Tabs.Size > 1)
ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
tab_bar->TabsAddedNew = false;
// Flags
if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
@ -6992,6 +6993,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
int curr_tab_section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
int prev_tab_section_n = (prev_tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (prev_tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
// We will need sorting if either current tab is leading (section_n == 0), but not the previous one,
// or if the current is not trailing (section_n == 2), but the previous one is.
if (tab_dst_n > 0 && curr_tab_section_n == 0 && prev_tab_section_n != 0)
need_sort_trailing_or_leading = true;
if (tab_dst_n > 0 && prev_tab_section_n == 2 && curr_tab_section_n != 2)
@ -7011,6 +7014,10 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
tab_bar->Sections[1].TabStartIndex = tab_bar->Sections[0].TabStartIndex + tab_bar->Sections[0].TabCount;
tab_bar->Sections[2].TabStartIndex = tab_bar->Sections[1].TabStartIndex + tab_bar->Sections[1].TabCount;
tab_bar->Sections[0].InnerSpacing = tab_bar->Sections[0].TabCount > 0 && (tab_bar->Sections[1].TabCount + tab_bar->Sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
tab_bar->Sections[1].InnerSpacing = tab_bar->Sections[1].TabCount > 0 && tab_bar->Sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
tab_bar->Sections[2].InnerSpacing = 0.0f;
// Setup next selected tab
ImGuiID scroll_track_selected_tab_id = 0;
if (tab_bar->NextSelectedTabId)
@ -7040,6 +7047,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
// Compute ideal widths
tab_bar->Sections[0].Width = tab_bar->Sections[1].Width = tab_bar->Sections[2].Width = 0.0f;
tab_bar->Sections[0].WidthIdeal = tab_bar->Sections[1].WidthIdeal = tab_bar->Sections[2].WidthIdeal = 0.0f;
const float tab_max_width = TabBarCalcMaxTabWidth();
ImGuiTabItem* most_recently_selected_tab = NULL;
@ -7065,8 +7073,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
ImGuiTabBarSection* section = &tab_bar->Sections[section_n];
float width = ImMin(tab->ContentWidth, tab_max_width) + (tab_n > section->TabStartIndex) ? g.Style.ItemInnerSpacing.x : 0.0f;
section->Width = section->WidthIdeal = section->Width + width;
float width = ImMin(tab->ContentWidth, tab_max_width);
section->Width = section->WidthIdeal = section->Width + width + (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;
@ -7076,14 +7084,46 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
tab->Width = width;
}
tab_bar->WidthAllTabsIdeal = 0.0f;
for (int section_n = 0; section_n < 3; section_n++)
tab_bar->WidthAllTabsIdeal += tab_bar->Sections[section_n].WidthIdeal + tab_bar->Sections[section_n].InnerSpacing;
// We want to know here if we'll need the scrolling buttons, to adjust available width with resizable leading/trailing
bool scrolling_buttons = (tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll);
float scrolling_buttons_width = GetTabBarScrollingButtonSize().x * 2.0f;
TabBarLayoutComputeTabsWidth(tab_bar, scrolling_buttons, scrolling_buttons_width);
// Compute width
bool central_section_is_visible = tab_bar->Sections[0].WidthWithSpacing() + tab_bar->Sections[2].WidthWithSpacing() < tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f);
float width_excess = central_section_is_visible
? ImMax(tab_bar->Sections[1].WidthWithSpacing() - (tab_bar->BarRect.GetWidth() - tab_bar->Sections[0].WidthWithSpacing() - tab_bar->Sections[2].WidthWithSpacing()), 0.0f)
: (tab_bar->Sections[0].WidthWithSpacing() + tab_bar->Sections[2].WidthWithSpacing()) - (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f));
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
if (central_section_is_visible)
memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[1].TabCount); // Move central section tabs
else
memmove(g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount + tab_bar->Sections[1].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[2].TabCount); // Replace central section tabs with trailing
int tab_n_shrinkable = (central_section_is_visible ? tab_bar->Sections[1].TabCount : tab_bar->Sections[0].TabCount + tab_bar->Sections[2].TabCount);
ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess);
// Update each section width with shrink values
for (int tab_n = 0; tab_n < tab_n_shrinkable; 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;
tab_bar->Sections[section_n].Width -= (tab->Width - shrinked_width);
tab->Width = shrinked_width;
}
}
// Layout all active tabs
float next_tab_offset = 0.0f;
tab_bar->WidthAllTabs = tab_bar->WidthAllTabsIdeal = 0.0f;
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
@ -7097,8 +7137,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->Width + section->InnerSpacing, 0.0f);
tab_bar->WidthAllTabsIdeal += ImMax(section->WidthIdeal + section->InnerSpacing, 0.0f);
tab_bar->WidthAllTabs += ImMax(section->WidthWithSpacing(), 0.0f);
next_tab_offset += section->InnerSpacing;
}
@ -7150,70 +7189,6 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
ImGuiWindow* window = g.CurrentWindow;
window->DC.CursorPos = tab_bar->BarRect.Min;
ItemSize(ImVec2(tab_bar->WidthAllTabsIdeal, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
#ifdef IMGUI_ENABLE_TEST_ENGINE
if (g.IO.KeyAlt)
{
window->DrawList->AddRect(tab_bar->BarRect.Min, ImVec2(tab_bar->BarRect.Min.x + tab_bar->Sections[0].Width, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - tab_bar->Sections[2].Width, tab_bar->BarRect.Min.y), tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255));
}
#endif
}
static void ImGui::TabBarLayoutComputeTabsWidth(ImGuiTabBar* tab_bar, bool scrolling_buttons, float scrolling_buttons_width)
{
ImGuiContext& g = *GImGui;
// Compute Leading/Trailing relative additional horizontal inner spacing
float leading_trailing_common_inner_space = (tab_bar->Sections[0].TabCount > 0 && tab_bar->Sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f);
bool resizing_leading_trailing_only = (tab_bar->Sections[0].Width + tab_bar->Sections[2].Width + leading_trailing_common_inner_space) > (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f));
tab_bar->Sections[0].InnerSpacing = tab_bar->Sections[0].TabCount > 0 && (tab_bar->Sections[1].TabCount + tab_bar->Sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
tab_bar->Sections[1].InnerSpacing = tab_bar->Sections[1].TabCount > 0 && tab_bar->Sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
tab_bar->Sections[2].InnerSpacing = 0.0f;
// Compute width
float width_excess = resizing_leading_trailing_only
? (tab_bar->Sections[0].Width + tab_bar->Sections[2].Width + leading_trailing_common_inner_space) - (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f))
: ImMax(tab_bar->Sections[1].Width + tab_bar->Sections[1].InnerSpacing - (tab_bar->BarRect.GetWidth() - tab_bar->Sections[0].Width - tab_bar->Sections[0].InnerSpacing - tab_bar->Sections[2].Width - tab_bar->Sections[2].InnerSpacing), 0.0f);
if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || resizing_leading_trailing_only)
{
// All tabs are in the ShrinkWidthBuffer, but we will only resize leading/trailing or central tabs, so rearrange internal data
if (resizing_leading_trailing_only)
memmove(g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount + tab_bar->Sections[1].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[2].TabCount);
else
memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[1].TabCount);
int tab_n_shrinkable = (resizing_leading_trailing_only ? tab_bar->Sections[0].TabCount + tab_bar->Sections[2].TabCount : tab_bar->Sections[1].TabCount);
ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess);
// Total Leading and Trailing shrink values can be different, we need to keep track of how much each section was shrinked
float leading_excess = 0.0f;
float trailing_excess = 0.0f;
for (int tab_n = 0; tab_n < tab_n_shrinkable; tab_n++)
{
float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width);
ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];
if (tab->Flags & ImGuiTabItemFlags_Leading)
leading_excess += (tab->Width - shrinked_width);
else if (tab->Flags & ImGuiTabItemFlags_Trailing)
trailing_excess += (tab->Width - shrinked_width);
tab->Width = shrinked_width;
}
if (resizing_leading_trailing_only)
{
tab_bar->Sections[0].Width -= leading_excess;
tab_bar->Sections[2].Width -= trailing_excess;
}
else
{
tab_bar->Sections[1].Width -= width_excess;
}
}
}
// Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.
@ -7407,7 +7382,6 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
}
}
window->DC.CursorPos = backup_cursor_pos;
tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
return tab_to_scroll_to;
@ -7569,7 +7543,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
tab = &tab_bar->Tabs.back();
tab->ID = id;
tab->Width = size.x;
tab_is_new = true;
tab_bar->TabsAddedNew = tab_is_new = true;
}
tab_bar->LastTabItemIdx = (short)tab_bar->Tabs.index_from_ptr(tab);
tab->ContentWidth = size.x;