From 2ab3946ecb12962eff96c9bc13ef83d403c84dd8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 30 Jul 2025 18:39:15 +0900 Subject: [PATCH 1/9] Windows: add StopMouseMovingWindow(), partial merge 80d78fa from docking. --- imgui.cpp | 36 ++++++++++++++++++++++++++---------- imgui_internal.h | 1 + 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 87e6cbd79..6c1e688e2 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4503,15 +4503,6 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) // Clear previous active id if (g.ActiveId != 0) { - // While most behaved code would make an effort to not steal active id during window move/drag operations, - // we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch - // may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that. - if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId) - { - IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow\n"); - g.MovingWindow = NULL; - } - // Store deactivate data ImGuiDeactivatedItemData* deactivated_data = &g.DeactivatedItemData; deactivated_data->ID = g.ActiveId; @@ -4524,6 +4515,15 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) // One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveID() if (g.InputTextState.ID == g.ActiveId) InputTextDeactivateHook(g.ActiveId); + + // While most behaved code would make an effort to not steal active id during window move/drag operations, + // we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch + // may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that. + if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId) + { + IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow\n"); + StopMouseMovingWindow(); + } } // Set active id @@ -5077,6 +5077,19 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window) g.MovingWindow = window; } +// This is not 100% symetric with StartMouseMovingWindow(). +// We do NOT clear ActiveID, because: +// - It would lead to rather confusing recursive code paths. Caller can call ClearActiveID() if desired. +// - Some code intentionally cancel moving but keep the ActiveID to lock inputs (e.g. code path taken when clicking a disabled item). +void ImGui::StopMouseMovingWindow() +{ + ImGuiContext& g = *GImGui; + + // [nb: docking branch has more stuff in this function] + + g.MovingWindow = NULL; +} + // Handle mouse moving window // Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing() // FIXME: We don't have strong guarantee that g.MovingWindow stay synced with g.ActiveId == g.MovingWindow->MoveId. @@ -5100,7 +5113,7 @@ void ImGui::UpdateMouseMovingWindowNewFrame() } else { - g.MovingWindow = NULL; + StopMouseMovingWindow(); ClearActiveID(); } } @@ -5142,6 +5155,9 @@ void ImGui::UpdateMouseMovingWindowEndFrame() { StartMouseMovingWindow(g.HoveredWindow); //-V595 + // FIXME: In principal we might be able to call StopMouseMovingWindow() below. + // Please note how StartMouseMovingWindow() and StopMouseMovingWindow() and not entirely symetrical, at the later doesn't clear ActiveId. + // Cancel moving if clicked outside of title bar if (g.IO.ConfigWindowsMoveFromTitleBarOnly) if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar)) diff --git a/imgui_internal.h b/imgui_internal.h index e92ab7b5c..35ffb0d5d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3150,6 +3150,7 @@ namespace ImGui IMGUI_API void UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos); IMGUI_API void FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window); IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); + IMGUI_API void StopMouseMovingWindow(); IMGUI_API void UpdateMouseMovingWindowNewFrame(); IMGUI_API void UpdateMouseMovingWindowEndFrame(); From a0cdac48e00159d5494183d2fd0177889a35d40c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 31 Jul 2025 13:16:51 +0900 Subject: [PATCH 2/9] Tables: fixed TableGetHoveredRow() (#7350, #6588, #6250) + TableGetRowIndex() which never correctly worked when using a clipper. Amend e09454aec. Can't understand the comment nor why code was commented. Code looks alright. Compared TableEndRow() between 1.80 and current as well. --- docs/CHANGELOG.txt | 5 +++++ imgui.cpp | 2 +- imgui.h | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 8d4dc5cf7..9381e0185 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -46,6 +46,11 @@ Other Changes: - Windows: fixed an issue where resizable child windows would emit border logic when hidden/non-visible (e.g. when in a docked window that is not selected), impacting code not checking for BeginChild() return value. (#8815) +- Tables: fixed TableGetRowIndex() which never correctly worked when using + a clipper (it exists for consistency but is almost never used, as it is + 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) - Error Handling: minor improvements to error handling for TableGetSortSpecs() and TableSetBgColor() calls. (#1651, #8499) - Misc: fixed building with IMGUI_DISABLE_DEBUG_TOOLS only. (#8796) diff --git a/imgui.cpp b/imgui.cpp index 6c1e688e2..792fb7d99 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3125,7 +3125,7 @@ static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_ ImGui::TableEndRow(table); table->RowPosY2 = window->DC.CursorPos.y; const int row_increase = (int)((off_y / line_height) + 0.5f); - //table->CurrentRow += row_increase; // Can't do without fixing TableEndRow() + table->CurrentRow += row_increase; table->RowBgColorCounter += row_increase; } } diff --git a/imgui.h b/imgui.h index 62a3a309b..5c45b6486 100644 --- a/imgui.h +++ b/imgui.h @@ -916,7 +916,7 @@ namespace ImGui IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable(). IMGUI_API int TableGetColumnCount(); // return number of columns (value passed to BeginTable) IMGUI_API int TableGetColumnIndex(); // return current column index. - IMGUI_API int TableGetRowIndex(); // return current row index. + IMGUI_API int TableGetRowIndex(); // return current row index (header rows are accounted for) IMGUI_API const char* TableGetColumnName(int column_n = -1); // return "" if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. IMGUI_API ImGuiTableColumnFlags TableGetColumnFlags(int column_n = -1); // return column flags so you can query their Enabled/Visible/Sorted/Hovered status flags. Pass -1 to use current column. IMGUI_API void TableSetColumnEnabled(int column_n, bool v);// change user accessible enabled/disabled state of a column. Set to false to hide the column. User can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) From 046a8eae0ff0854864760b99e0776c904d80b74d Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 31 Jul 2025 22:04:01 +0900 Subject: [PATCH 3/9] Tabs: fixed tab bar underline not drawing below scroll buttons. (#6820, #4859, #5022, #5239) Fix ef8ff1b5d8 which accidentally meant we are using BarRect after it may have been modified by TabBarScrollingButtons(). --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9381e0185..d3c5d6609 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -51,6 +51,8 @@ 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) - Error Handling: minor improvements to error handling for TableGetSortSpecs() and TableSetBgColor() calls. (#1651, #8499) - Misc: fixed building with IMGUI_DISABLE_DEBUG_TOOLS only. (#8796) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 63dd9dc21..b4d5bc1d7 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9429,8 +9429,8 @@ bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id); ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2); tab_bar->ID = id; - tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f); - tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f); + tab_bar->SeparatorMinX = tab_bar_bb.x - IM_TRUNC(window->WindowPadding.x * 0.5f); + tab_bar->SeparatorMaxX = tab_bar_bb.x + IM_TRUNC(window->WindowPadding.x * 0.5f); //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false)) flags |= ImGuiTabBarFlags_IsFocused; return BeginTabBarEx(tab_bar, tab_bar_bb, flags); @@ -9668,7 +9668,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing; // Horizontal scrolling buttons - // (note that TabBarScrollButtons() will alter BarRect.Max.x) + // 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)) if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar)) { From 7bb9db501cfc4a181cc1f54824517ad28b0941b0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 31 Jul 2025 22:05:19 +0900 Subject: [PATCH 4/9] Tabs: fixed 046a8ea (commited a modified file). --- imgui_widgets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index b4d5bc1d7..13aad7459 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9429,8 +9429,8 @@ bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id); ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2); tab_bar->ID = id; - tab_bar->SeparatorMinX = tab_bar_bb.x - IM_TRUNC(window->WindowPadding.x * 0.5f); - tab_bar->SeparatorMaxX = tab_bar_bb.x + IM_TRUNC(window->WindowPadding.x * 0.5f); + tab_bar->SeparatorMinX = tab_bar_bb.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f); + tab_bar->SeparatorMaxX = tab_bar_bb.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f); //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false)) flags |= ImGuiTabBarFlags_IsFocused; return BeginTabBarEx(tab_bar, tab_bar_bb, flags); From 7278cda039b67602905683f2d6add8e0d5abd395 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 31 Jul 2025 22:27:24 +0900 Subject: [PATCH 5/9] Tabs: added TabMinWidthBase, ImGuiStyleVar_TabMinWidthBase. --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 3 +++ imgui.h | 2 ++ imgui_demo.cpp | 5 +++-- imgui_internal.h | 2 +- imgui_widgets.cpp | 2 ++ 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d3c5d6609..a70d9fe56 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -53,6 +53,9 @@ Other Changes: - 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 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. - Error Handling: minor improvements to error handling for TableGetSortSpecs() and TableSetBgColor() calls. (#1651, #8499) - Misc: fixed building with IMGUI_DISABLE_DEBUG_TOOLS only. (#8796) diff --git a/imgui.cpp b/imgui.cpp index 792fb7d99..bfbf6fe1e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1417,6 +1417,7 @@ ImGuiStyle::ImGuiStyle() ImageBorderSize = 0.0f; // Thickness of border around tabs. 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. 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. @@ -1483,6 +1484,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor); ImageBorderSize = ImTrunc(ImageBorderSize * scale_factor); TabRounding = ImTrunc(TabRounding * scale_factor); + TabMinWidthBase = ImTrunc(TabMinWidthBase * 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); @@ -3502,6 +3504,7 @@ static const ImGuiStyleVarInfo GStyleVarsInfo[] = { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ImageBorderSize) }, // ImGuiStyleVar_ImageBorderSize { 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, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize) }, // ImGuiStyleVar_TabBarOverlineSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle diff --git a/imgui.h b/imgui.h index 5c45b6486..ddce1e2a6 100644 --- a/imgui.h +++ b/imgui.h @@ -1803,6 +1803,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_ImageBorderSize, // float ImageBorderSize ImGuiStyleVar_TabRounding, // float TabRounding ImGuiStyleVar_TabBorderSize, // float TabBorderSize + ImGuiStyleVar_TabMinWidthBase, // float TabMinWidthBase ImGuiStyleVar_TabBarBorderSize, // float TabBarBorderSize ImGuiStyleVar_TabBarOverlineSize, // float TabBarOverlineSize ImGuiStyleVar_TableAngledHeadersAngle, // float TableAngledHeadersAngle @@ -2268,6 +2269,7 @@ struct ImGuiStyle float ImageBorderSize; // Thickness of border around Image() calls. 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 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. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 6063a3c6c..c50d3400f 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -8341,8 +8341,9 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, 2.0f, "%.0f"); 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("TabCloseButtonMinWidthSelected", &style.TabCloseButtonMinWidthSelected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthSelected < 0.0f) ? "%.0f (Always)" : "%.0f"); - DragFloat("TabCloseButtonMinWidthUnselected", &style.TabCloseButtonMinWidthUnselected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthUnselected < 0.0f) ? "%.0f (Always)" : "%.0f"); + DragFloat("TabMinWidthBase", &style.TabMinWidthBase, 0.5f, 1.0f, 500.0f, "%.0f"); + 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"); SeparatorText("Tables"); diff --git a/imgui_internal.h b/imgui_internal.h index 35ffb0d5d..ea6bd0c1d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2735,7 +2735,7 @@ struct ImGuiTabItem int LastFrameSelected; // This allows us to infer an ordered list of the last activated tabs with little maintenance float Offset; // Position relative to beginning of tab float Width; // Width currently displayed - float ContentWidth; // Width of label, stored during BeginTabItem() call + float ContentWidth; // Width of label + padding, stored during BeginTabItem() call (misnamed as "Content" would normally imply width of label only) float RequestedWidth; // Width optionally requested by caller, -1.0f is unused ImS32 NameOffset; // When Window==NULL, offset to name within parent ImGuiTabBar::TabsNames ImS16 BeginOrder; // BeginTabItem() order, used to re-order tabs after toggling ImGuiTabBarFlags_Reorderable diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 13aad7459..75b4aa633 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9648,6 +9648,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) const char* tab_name = TabBarGetTabName(tab_bar, tab); const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument); tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x; + if ((tab->Flags & ImGuiTabItemFlags_Button) == 0) + tab->ContentWidth = ImMax(tab->ContentWidth, g.Style.TabMinWidthBase); int section_n = TabItemGetSectionIdx(tab); ImGuiTabBarSection* section = §ions[section_n]; From 78d11cd78117e71bd4f2aec9d2dccd612f48a3f1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 31 Jul 2025 22:41:00 +0900 Subject: [PATCH 6/9] Tabs: (Breaking) renamed ImGuiTabBarFlags_FittingPolicyResizeDown to ImGuiTabBarFlags_FittingPolicyShrink. (#261, #351) Amend 54a60aaa40 --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 3 ++- imgui.h | 14 ++++++++++---- imgui_demo.cpp | 25 +++++++++++++------------ imgui_widgets.cpp | 2 +- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a70d9fe56..57ae8d067 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,6 +41,9 @@ HOW TO UPDATE? Breaking Changes: +- Tabs: Renamed ImGuiTabBarFlags_FittingPolicyResizeDown to ImGuiTabBarFlags_FittingPolicyShrink. + Kept inline redirection enum (will obsolete). (#261, #351) + Other Changes: - Windows: fixed an issue where resizable child windows would emit border diff --git a/imgui.cpp b/imgui.cpp index bfbf6fe1e..e4e893041 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -392,7 +392,8 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. - - 2025/06/25 (1.92.0) - layout: commented out legacy ErrorCheckUsingSetCursorPosToExtendParentBoundaries() fallback obsoleted in 1.89 (August 2022) which allowed a SetCursorPos()/SetCursorScreenPos() call WITHOUT AN ITEM + - 2025/07/31 (1.92.2) - Tabs: Renamed ImGuiTabBarFlags_FittingPolicyResizeDown to ImGuiTabBarFlags_FittingPolicyShrink. Kept inline redirection enum (will obsolete). + - 2025/06/25 (1.92.0) - Layout: commented out legacy ErrorCheckUsingSetCursorPosToExtendParentBoundaries() fallback obsoleted in 1.89 (August 2022) which allowed a SetCursorPos()/SetCursorScreenPos() call WITHOUT AN ITEM to extend parent window/cell boundaries. Replaced with assert/tooltip that would already happens if previously using IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#5548, #4510, #3355, #1760, #1490, #4152, #150) - Incorrect way to make a window content size 200x200: Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); diff --git a/imgui.h b/imgui.h index ddce1e2a6..ffef8e90a 100644 --- a/imgui.h +++ b/imgui.h @@ -29,7 +29,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.92.2 WIP" -#define IMGUI_VERSION_NUM 19212 +#define IMGUI_VERSION_NUM 19213 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -1370,10 +1370,16 @@ enum ImGuiTabBarFlags_ ImGuiTabBarFlags_NoTabListScrollingButtons = 1 << 4, // Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll) ImGuiTabBarFlags_NoTooltip = 1 << 5, // Disable tooltips when hovering a tab ImGuiTabBarFlags_DrawSelectedOverline = 1 << 6, // Draw selected overline markers over selected tab - ImGuiTabBarFlags_FittingPolicyResizeDown = 1 << 7, // Resize tabs when they don't fit + + // 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_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyScroll, - ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyResizeDown, + ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyShrink | ImGuiTabBarFlags_FittingPolicyScroll, + ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyShrink, + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiTabBarFlags_FittingPolicyResizeDown = ImGuiTabBarFlags_FittingPolicyShrink, // Renamed in 1.92.2 +#endif }; // Flags for ImGui::BeginTabItem() diff --git a/imgui_demo.cpp b/imgui_demo.cpp index c50d3400f..e0a9ffc61 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3379,6 +3379,16 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d // [SECTION] DemoWindowWidgetsTabs() //----------------------------------------------------------------------------- +static void EditTabBarFittingPolicyFlags(ImGuiTabBarFlags* p_flags) +{ + if ((*p_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) + *p_flags |= ImGuiTabBarFlags_FittingPolicyDefault_; + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyShrink", p_flags, ImGuiTabBarFlags_FittingPolicyShrink)) + *p_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyShrink); + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", p_flags, ImGuiTabBarFlags_FittingPolicyScroll)) + *p_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); +} + static void DemoWindowWidgetsTabs() { IMGUI_DEMO_MARKER("Widgets/Tabs"); @@ -3421,12 +3431,7 @@ static void DemoWindowWidgetsTabs() ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); ImGui::CheckboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", &tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton); ImGui::CheckboxFlags("ImGuiTabBarFlags_DrawSelectedOverline", &tab_bar_flags, ImGuiTabBarFlags_DrawSelectedOverline); - if ((tab_bar_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) - tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyDefault_; - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); + EditTabBarFittingPolicyFlags(&tab_bar_flags); // Tab Bar ImGui::AlignTextToFramePadding(); @@ -3475,12 +3480,8 @@ static void DemoWindowWidgetsTabs() ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs - static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown; - ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); + static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyShrink; + EditTabBarFittingPolicyFlags(&tab_bar_flags); if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) { diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 75b4aa633..1821b5f3b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9691,7 +9691,7 @@ 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_FittingPolicyResizeDown) || !central_section_is_visible)) + if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyShrink) || !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); From cc1fbcc9a95c13e4d9622486da9451da797c193c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 31 Jul 2025 23:04:35 +0900 Subject: [PATCH 7/9] Fonts: undo change done in b884533 since 19d1ad0 made them unnecessary. (#8794, #8850) --- imstb_truetype.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/imstb_truetype.h b/imstb_truetype.h index 1a2778773..cf33289f6 100644 --- a/imstb_truetype.h +++ b/imstb_truetype.h @@ -4017,8 +4017,7 @@ STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int s #define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) -/*static*/ -void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) { unsigned char buffer[STBTT_MAX_OVERSAMPLE]; int safe_w = w - kernel_width; @@ -4080,8 +4079,7 @@ void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes } } -/*static*/ -void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) { unsigned char buffer[STBTT_MAX_OVERSAMPLE]; int safe_h = h - kernel_width; @@ -4143,8 +4141,7 @@ void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes } } -/*static*/ -float stbtt__oversample_shift(int oversample) +static float stbtt__oversample_shift(int oversample) { if (!oversample) return 0.0f; From 3ef6c8410a763b8137928d191aed72188ce8770b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 31 Jul 2025 23:20:47 +0900 Subject: [PATCH 8/9] Tabs: added new fitting policy ImGuiTabBarFlags_FittingPolicyMixed, new default. (#3421, #8800) --- docs/CHANGELOG.txt | 10 ++++++++-- imgui.cpp | 3 +++ imgui.h | 11 +++++++---- imgui_demo.cpp | 3 +++ imgui_internal.h | 3 ++- imgui_widgets.cpp | 36 ++++++++++++++++++++++++++---------- 6 files changed, 49 insertions(+), 17 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 57ae8d067..808007d6f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -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) diff --git a/imgui.cpp b/imgui.cpp index e4e893041..1cc310c57 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -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 diff --git a/imgui.h b/imgui.h index ffef8e90a..1624384c2 100644 --- a/imgui.h +++ b/imgui.h @@ -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. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index e0a9ffc61..eb15dcd1e 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -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"); diff --git a/imgui_internal.h b/imgui_internal.h index ea6bd0c1d..553c314d3 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -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); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 1821b5f3b..f96a61a0b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -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 = §ions[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; From 320c94bfaa475fae8e2c3ec2c52cbd48fcbd567a Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 31 Jul 2025 23:39:44 +0900 Subject: [PATCH 9/9] Tabs: when scrolling is enabled, track selected tabs when resizing down parent container. (#3421, #8800) --- docs/CHANGELOG.txt | 3 +++ imgui_internal.h | 1 + imgui_widgets.cpp | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 808007d6f..89e8b34eb 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -60,6 +60,9 @@ Other Changes: - Tabs: added style.TabMinWidthShrink, ImGuiStyleVar_TabMinWidthShrink to control the width to shrink to in ImGuiTabBarFlags_FittingPolicyMixed mode. (#3421, #8800). +- Tabs: when scrolling is enabled, track selected tabs when resizing down + parent container. This does not prevent to horizontally scroll it out of + view during normal operations. (#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. diff --git a/imgui_internal.h b/imgui_internal.h index 553c314d3..87472b1fb 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2758,6 +2758,7 @@ struct IMGUI_API ImGuiTabBar int CurrFrameVisible; int PrevFrameVisible; ImRect BarRect; + float BarRectPrevWidth; // Backup of previous width. When width change we enforce keep horizontal scroll on focused tab. float CurrTabsContentsHeight; float PrevTabsContentsHeight; // Record the height of contents submitted below the tab bar float WidthAllTabs; // Actual width of all tabs (locked during layout) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index f96a61a0b..3210140c9 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9555,6 +9555,10 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImGuiContext& g = *GImGui; tab_bar->WantLayout = false; + // Track selected tab when resizing our parent down + const bool scroll_to_selected_tab = (tab_bar->BarRectPrevWidth > tab_bar->BarRect.GetWidth()); + tab_bar->BarRectPrevWidth = tab_bar->BarRect.GetWidth(); + // Garbage collect by compacting list // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section) int tab_dst_n = 0; @@ -9693,6 +9697,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0) tab_bar->SelectedTabId = scroll_to_tab_id; } + if (scroll_to_tab_id == 0 && scroll_to_selected_tab) + scroll_to_tab_id = tab_bar->SelectedTabId; // Shrink widths if full tabs don't fit in their allocated space float section_0_w = sections[0].Width + sections[0].Spacing;