TabBar: Amend previous commit. Fix tab reordering when tab bar has scrolling.

Some tidying up with helpers + honor 16-bit offsets as with other tab bar features (unlikely single reorder can reach that but consistent)
This commit is contained in:
ocornut 2021-03-25 18:35:24 +01:00
parent 475c0fcc39
commit 688e06490e
3 changed files with 49 additions and 56 deletions

View File

@ -42,6 +42,8 @@ Other Changes:
- Scrolling: Fix scroll tracking with e.g. SetScrollHereX/Y() when WindowPadding < ItemSpacing. - Scrolling: Fix scroll tracking with e.g. SetScrollHereX/Y() when WindowPadding < ItemSpacing.
- Scrolling: Fix scroll snapping on edge of scroll region when both scrollbars are enabled. - Scrolling: Fix scroll snapping on edge of scroll region when both scrollbars are enabled.
- Tables: Expose TableSetColumnEnabled() in public api. (#3935) - Tables: Expose TableSetColumnEnabled() in public api. (#3935)
- TabBar: Fixed mouse reordering with very fast movements (e.g. crossing multiple tabs in a single
frame and then immediately standling still (would only affect automation/bots). [@rokups]
- Drags, Sliders, Inputs: Specifying a NULL format to Float functions default them to "%.3f" to be - Drags, Sliders, Inputs: Specifying a NULL format to Float functions default them to "%.3f" to be
consistent with the compile-time default. (#3922) consistent with the compile-time default. (#3922)
- DragScalar: Add default value for v_speed argument to match higher-level functions. (#3922) [@eliasdaler] - DragScalar: Add default value for v_speed argument to match higher-level functions. (#3922) [@eliasdaler]

View File

@ -1885,6 +1885,7 @@ enum ImGuiTabBarFlagsPrivate_
// Extend ImGuiTabItemFlags_ // Extend ImGuiTabItemFlags_
enum ImGuiTabItemFlagsPrivate_ enum ImGuiTabItemFlagsPrivate_
{ {
ImGuiTabItemFlags_SectionMask_ = ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing,
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_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 // 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
}; };
@ -1930,7 +1931,7 @@ struct ImGuiTabBar
float ScrollingRectMinX; float ScrollingRectMinX;
float ScrollingRectMaxX; float ScrollingRectMaxX;
ImGuiID ReorderRequestTabId; ImGuiID ReorderRequestTabId;
ImS8 ReorderRequestDir; ImS16 ReorderRequestOffset;
ImS8 BeginCount; ImS8 BeginCount;
bool WantLayout; bool WantLayout;
bool VisibleTabWasSubmitted; bool VisibleTabWasSubmitted;
@ -2422,7 +2423,7 @@ namespace ImGui
IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id); IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id);
IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id); IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id);
IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
IMGUI_API void TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir); IMGUI_API void TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset);
IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, ImVec2 mouse_pos); IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, ImVec2 mouse_pos);
IMGUI_API bool TabBarProcessReorder(ImGuiTabBar* tab_bar); IMGUI_API bool TabBarProcessReorder(ImGuiTabBar* tab_bar);
IMGUI_API bool TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags); IMGUI_API bool TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags);

View File

@ -6960,12 +6960,17 @@ ImGuiTabBar::ImGuiTabBar()
LastTabItemIdx = -1; LastTabItemIdx = -1;
} }
static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
{
return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
}
static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs) static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
{ {
const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
const int a_section = (a->Flags & ImGuiTabItemFlags_Leading) ? 0 : (a->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; const int a_section = TabItemGetSectionIdx(a);
const int b_section = (b->Flags & ImGuiTabItemFlags_Leading) ? 0 : (b->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; const int b_section = TabItemGetSectionIdx(b);
if (a_section != b_section) if (a_section != b_section)
return a_section - b_section; return a_section - b_section;
return (int)(a->IndexDuringLayout - b->IndexDuringLayout); return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
@ -7134,11 +7139,11 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
tab->IndexDuringLayout = (ImS16)tab_dst_n; tab->IndexDuringLayout = (ImS16)tab_dst_n;
// We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another) // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)
int curr_tab_section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; int curr_tab_section_n = TabItemGetSectionIdx(tab);
if (tab_dst_n > 0) if (tab_dst_n > 0)
{ {
ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1]; ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
int prev_tab_section_n = (prev_tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (prev_tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
if (curr_tab_section_n == 0 && prev_tab_section_n != 0) if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
need_sort_by_section = true; need_sort_by_section = true;
if (prev_tab_section_n == 2 && curr_tab_section_n != 2) if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
@ -7210,7 +7215,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true; const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true;
tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x; tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x;
int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; int section_n = TabItemGetSectionIdx(tab);
ImGuiTabBarSection* section = &sections[section_n]; ImGuiTabBarSection* section = &sections[section_n];
section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
curr_section_n = section_n; curr_section_n = section_n;
@ -7265,7 +7270,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
if (shrinked_width < 0.0f) if (shrinked_width < 0.0f)
continue; continue;
int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; int section_n = TabItemGetSectionIdx(tab);
sections[section_n].Width -= (tab->Width - shrinked_width); sections[section_n].Width -= (tab->Width - shrinked_width);
tab->Width = shrinked_width; tab->Width = shrinked_width;
} }
@ -7410,7 +7415,7 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui
ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id); ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
if (tab == NULL) if (tab == NULL)
return; return;
if (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
return; return;
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
@ -7439,56 +7444,48 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui
} }
} }
void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir) void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset)
{ {
IM_ASSERT(dir == -1 || dir == +1); IM_ASSERT(offset != 0);
IM_ASSERT(tab_bar->ReorderRequestTabId == 0); IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
tab_bar->ReorderRequestTabId = tab->ID; tab_bar->ReorderRequestTabId = tab->ID;
tab_bar->ReorderRequestDir = (ImS8)dir; tab_bar->ReorderRequestOffset = (ImS16)offset;
} }
void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, ImVec2 mouse_pos) void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* src_tab, ImVec2 mouse_pos)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
IM_ASSERT(tab_bar->ReorderRequestTabId == 0); IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0) if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
return; return;
int source_idx = tab_bar->Tabs.index_from_ptr(tab); const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
float bar_x = tab_bar->BarRect.Min.x; const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
int dir = bar_x + tab->Offset > mouse_pos.x ? -1 : +1;
int target_idx = source_idx;
for (int i = source_idx; 0 <= i && i < tab_bar->Tabs.Size; i += dir) // Count number of contiguous tabs we are crossing over
const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
int dst_idx = src_idx;
for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
{ {
const ImGuiTabItem* target_tab = &tab_bar->Tabs[i]; // Reordered tabs must share the same section
const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
// Reorder only within tab groups with _Leading, _Trailing flag or without either of them. if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
if ((target_tab->Flags & ImGuiTabItemFlags_Leading) != (tab->Flags & ImGuiTabItemFlags_Leading))
break; break;
if ((target_tab->Flags & ImGuiTabItemFlags_Trailing) != (tab->Flags & ImGuiTabItemFlags_Trailing)) if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
break; break;
dst_idx = i;
// Do not reorder past tabs with _NoReorder flag. // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
if (target_tab->Flags & ImGuiTabItemFlags_NoReorder) const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
break; const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
//GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
target_idx = i; // target_tab can be swapped with dragged tab. if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
// Current tab is destination tab under mouse position. Also include space after tab, so when mouse cursor is
// between tabs we would not continue checking further tabs that are not hovered.
if (dir > 0 && mouse_pos.x < bar_x + target_tab->Offset + target_tab->Width + g.Style.ItemInnerSpacing.x) // End of tab is past mouse_pos.
break;
if (dir < 0 && mouse_pos.x > bar_x + target_tab->Offset - g.Style.ItemInnerSpacing.x) // Mouse pos is past start of tab.
break; break;
} }
if (target_idx != source_idx) if (dst_idx != src_idx)
{ TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
tab_bar->ReorderRequestTabId = tab->ID;
tab_bar->ReorderRequestDir = (ImS8)(target_idx - source_idx);
}
} }
bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
@ -7498,30 +7495,23 @@ bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
return false; return false;
//IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir; int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestOffset;
if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size) if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
return false; return false;
// Reordered TabItem must share the same position flags than target // Reordered tabs must share the same section
// (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)
ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order]; ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
if (tab2->Flags & ImGuiTabItemFlags_NoReorder) if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
return false; return false;
if ((tab1->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (tab2->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing))) if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
return false; return false;
ImGuiTabItem item_tmp = *tab1; ImGuiTabItem item_tmp = *tab1;
ImGuiTabItem* src, *dst; ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
if (tab_bar->ReorderRequestDir > 0) ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
{ const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
dst = tab1; memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
src = tab1 + 1;
}
else
{
dst = tab2 + 1;
src = tab2;
}
memmove(dst, src, abs(tab_bar->ReorderRequestDir) * sizeof(ImGuiTabItem));
*tab2 = item_tmp; *tab2 = item_tmp;
if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings) if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
@ -7803,7 +7793,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
const ImVec2 backup_main_cursor_pos = window->DC.CursorPos; const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
// Layout // Layout
const bool is_central_section = (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) == 0; const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
size.x = tab->Width; size.x = tab->Width;
if (is_central_section) if (is_central_section)
window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f); window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f);