diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d1f5fb2a..4be7d31d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -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 diff --git a/imgui.h b/imgui.h index 91a0b162..c7234346 100644 --- a/imgui.h +++ b/imgui.h @@ -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() diff --git a/imgui_internal.h b/imgui_internal.h index e61fbed7..796e03e5 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -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. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3628b161..2ddbbb88 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -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) - ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder); + // 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;