From 8957b3df03b4cbe502688208af7d2fda52be985f Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 16 Mar 2026 11:41:01 +0100 Subject: [PATCH 01/26] InputScalar: minor rework to facilitate incoming change. Intended to have no side-effects. --- imgui_widgets.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 1bc875f63..6961d12b7 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3789,24 +3789,30 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; - bool value_changed = false; - if (p_step == NULL) + const bool has_step_buttons = (p_step != NULL); + const float button_size = has_step_buttons ? GetFrameHeight() : 0.0f; + bool ret; + if (has_step_buttons) { - if (InputText(label, buf, IM_COUNTOF(buf), flags)) - value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); - } - else - { - const float button_size = GetFrameHeight(); - + // With Step Buttons BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive() PushID(label); SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); - if (InputText("", buf, IM_COUNTOF(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view - value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); + ret = InputText("", buf, IM_COUNTOF(buf), flags); // PushID(label) + "" gives us the expected ID from outside point of view IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); + } + else + { + // Without Step Buttons + ret = InputText(label, buf, IM_COUNTOF(buf), flags); + } - // Step buttons + // Apply + bool value_changed = ret ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false; + + // Step buttons + if (has_step_buttons) + { const ImVec2 backup_frame_padding = style.FramePadding; style.FramePadding.x = style.FramePadding.y; if (flags & ImGuiInputTextFlags_ReadOnly) From 16772365e2664eeb8fbd0f99df1372f36f51ad51 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 16 Mar 2026 18:42:22 +0100 Subject: [PATCH 02/26] Moved ImGuiButtonFlags_AllowOverlap from imgui_internal.h to imgui.h + standardize comments. --- docs/CHANGELOG.txt | 3 +++ imgui.h | 7 ++++--- imgui_internal.h | 1 - 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 44c6175a1..457518dc3 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -115,6 +115,9 @@ Other Changes: - Implemented a custom tweak to extend hit-testing bounding box when window is sitting at the edge of a viewport (e.g. fullscreen or docked window), so that e.g. mouse the mouse at the extreme of the screen will reach the scrollbar. (#9276) +- Button: + - Moved ImGuiButtonFlags_AllowOverlap from imgui_internal.h to imgui.h, + as a convenience for when using e.g. InvisibleButton(). - Focus: fixed fallback "Debug" window temporarily taking focus and setting io.WantCaptureKeyboard for one frame on e.g. application boot if no other windows are submitted. (#9243) - Demo: fixed IMGUI_DEMO_MARKER locations for examples applets. (#9261, #3689) [@pthom] diff --git a/imgui.h b/imgui.h index b69a9b563..0086c60a3 100644 --- a/imgui.h +++ b/imgui.h @@ -1001,7 +1001,7 @@ namespace ImGui IMGUI_API void SetNavCursorVisible(bool visible); // alter visibility of keyboard/gamepad cursor. by default: show when using an arrow key, hide when clicking with mouse. // Overlapping mode - IMGUI_API void SetNextItemAllowOverlap(); // allow next item to be overlapped by a subsequent item. Useful with invisible buttons, selectable, treenode covering an area where subsequent items may need to be added. Note that both Selectable() and TreeNode() have dedicated flags doing this. + IMGUI_API void SetNextItemAllowOverlap(); // allow next item to be overlapped by a subsequent item. Typically useful with InvisibleButton(), Selectable(), TreeNode() covering an area where subsequent items may need to be added. Note that both Selectable() and TreeNode() have dedicated flags doing this. // Item/Widgets Utilities and Query Functions // - Most of the functions are referring to the previous Item that has been submitted. @@ -1303,7 +1303,7 @@ enum ImGuiTreeNodeFlags_ ImGuiTreeNodeFlags_None = 0, ImGuiTreeNodeFlags_Selected = 1 << 0, // Draw as selected ImGuiTreeNodeFlags_Framed = 1 << 1, // Draw frame with background (e.g. for CollapsingHeader) - ImGuiTreeNodeFlags_AllowOverlap = 1 << 2, // Hit testing to allow subsequent widgets to overlap this one + ImGuiTreeNodeFlags_AllowOverlap = 1 << 2, // Hit testing will allow subsequent widgets to overlap this one. Require previous frame HoveredId to match before being usable. Shortcut to calling SetNextItemAllowOverlap(). ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4, // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open @@ -1363,7 +1363,7 @@ enum ImGuiSelectableFlags_ ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Frame will span all columns of its container table (text will still fit in current column) ImGuiSelectableFlags_AllowDoubleClick = 1 << 2, // Generate press events on double clicks too ImGuiSelectableFlags_Disabled = 1 << 3, // Cannot be selected, display grayed out text - ImGuiSelectableFlags_AllowOverlap = 1 << 4, // (WIP) Hit testing to allow subsequent widgets to overlap this one + ImGuiSelectableFlags_AllowOverlap = 1 << 4, // Hit testing will allow subsequent widgets to overlap this one. Require previous frame HoveredId to match before being usable. Shortcut to calling SetNextItemAllowOverlap(). ImGuiSelectableFlags_Highlight = 1 << 5, // Make the item be displayed as if it is hovered ImGuiSelectableFlags_SelectOnNav = 1 << 6, // Auto-select when moved into, unless Ctrl is held. Automatic when in a BeginMultiSelect() block. @@ -1870,6 +1870,7 @@ enum ImGuiButtonFlags_ ImGuiButtonFlags_MouseButtonMiddle = 1 << 2, // React on center mouse button ImGuiButtonFlags_MouseButtonMask_ = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle, // [Internal] ImGuiButtonFlags_EnableNav = 1 << 3, // InvisibleButton(): do not disable navigation/tabbing. Otherwise disabled by default. + ImGuiButtonFlags_AllowOverlap = 1 << 12, // Hit testing will allow subsequent widgets to overlap this one. Require previous frame HoveredId to match before being usable. Shortcut to calling SetNextItemAllowOverlap(). }; // Flags for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() diff --git a/imgui_internal.h b/imgui_internal.h index cb5e4e1c0..7db4ca549 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1038,7 +1038,6 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_PressedOnDragDropHold = 1 << 9, // return true when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) //ImGuiButtonFlags_Repeat = 1 << 10, // hold to repeat -> use ImGuiItemFlags_ButtonRepeat instead. ImGuiButtonFlags_FlattenChildren = 1 << 11, // allow interactions even if a child window is overlapping - ImGuiButtonFlags_AllowOverlap = 1 << 12, // require previous frame HoveredId to either match id or be null before being usable. //ImGuiButtonFlags_DontClosePopups = 1 << 13, // disable automatically closing parent popup on press //ImGuiButtonFlags_Disabled = 1 << 14, // disable interactions -> use BeginDisabled() or ImGuiItemFlags_Disabled ImGuiButtonFlags_AlignTextBaseLine = 1 << 15, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine From 6464276b6258901a239100e08aebd8e056731bda Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 16 Mar 2026 12:19:04 +0100 Subject: [PATCH 03/26] InputText: cleanup/rework old comments + remove unnecessary indent in callback and main block setting apply_new_text. Amend 00f12b9a0, 3349296370 etc. --- imgui_internal.h | 1 + imgui_widgets.cpp | 187 ++++++++++++++++++++++------------------------ 2 files changed, 90 insertions(+), 98 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 7db4ca549..c7afcee78 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1258,6 +1258,7 @@ struct IMGUI_API ImGuiInputTextState void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation void OnCharPressed(unsigned int c); float GetPreferredOffsetX() const; + const char* GetText() { return TextA.Data ? TextA.Data : ""; } // Cursor & Selection void CursorAnimReset(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6961d12b7..24d93bae8 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5217,7 +5217,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); } - // Process callbacks and apply result back to user's buffer. + // Process revert and user callbacks const char* apply_new_text = NULL; int apply_new_text_length = 0; if (g.ActiveId == id) @@ -5247,110 +5247,99 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } } - // FIXME-OPT: We always reapply the live buffer back to the input buffer before clearing ActiveId, - // even though strictly speaking it wasn't modified on this frame. Should mark dirty state from the stb_textedit callbacks. - // If we do that, need to ensure that as special case, 'validated == true' also writes back. - // This also allows the user to use InputText() without maintaining any user-side storage. - // (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object - // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize). - const bool apply_edit_back_to_user_buffer = true;// !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); - if (apply_edit_back_to_user_buffer) + // User callback + if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0) { - // Apply current edited text immediately. - // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + IM_ASSERT(callback != NULL); - // User callback - if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0) + // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. + ImGuiInputTextFlags event_flag = 0; + ImGuiKey event_key = ImGuiKey_None; + if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id)) { - IM_ASSERT(callback != NULL); - - // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. - ImGuiInputTextFlags event_flag = 0; - ImGuiKey event_key = ImGuiKey_None; - if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id)) - { - event_flag = ImGuiInputTextFlags_CallbackCompletion; - event_key = ImGuiKey_Tab; - } - else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow)) - { - event_flag = ImGuiInputTextFlags_CallbackHistory; - event_key = ImGuiKey_UpArrow; - } - else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow)) - { - event_flag = ImGuiInputTextFlags_CallbackHistory; - event_key = ImGuiKey_DownArrow; - } - else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited) - { - event_flag = ImGuiInputTextFlags_CallbackEdit; - } - else if (flags & ImGuiInputTextFlags_CallbackAlways) - { - event_flag = ImGuiInputTextFlags_CallbackAlways; - } - - if (event_flag) - { - ImGuiInputTextCallbackData callback_data; - callback_data.Ctx = &g; - callback_data.ID = id; - callback_data.Flags = flags; - callback_data.EventFlag = event_flag; - callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated); - callback_data.UserData = callback_user_data; - - // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925 - char* callback_buf = is_readonly ? buf : state->TextA.Data; - IM_ASSERT(callback_buf == state->TextSrc); - state->CallbackTextBackup.resize(state->TextLen + 1); - memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1); - - callback_data.EventKey = event_key; - callback_data.Buf = callback_buf; - callback_data.BufTextLen = state->TextLen; - callback_data.BufSize = state->BufCapacity; - callback_data.BufDirty = false; - callback_data.CursorPos = state->Stb->cursor; - callback_data.SelectionStart = state->Stb->select_start; - callback_data.SelectionEnd = state->Stb->select_end; - - // Call user code - callback(&callback_data); - - // Read back what user may have modified - callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback - IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields - IM_ASSERT(callback_data.BufSize == state->BufCapacity); - IM_ASSERT(callback_data.Flags == flags); - if (callback_data.BufDirty || callback_data.CursorPos != state->Stb->cursor) - state->CursorFollow = true; - state->Stb->cursor = ImClamp(callback_data.CursorPos, 0, callback_data.BufTextLen); - state->Stb->select_start = ImClamp(callback_data.SelectionStart, 0, callback_data.BufTextLen); - state->Stb->select_end = ImClamp(callback_data.SelectionEnd, 0, callback_data.BufTextLen); - if (callback_data.BufDirty) - { - // Callback may update buffer and thus set buf_dirty even in read-only mode. - IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! - InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen); - state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() - state->CursorAnimReset(); - } - } + event_flag = ImGuiInputTextFlags_CallbackCompletion; + event_key = ImGuiKey_Tab; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_UpArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_DownArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited) + { + event_flag = ImGuiInputTextFlags_CallbackEdit; + } + else if (flags & ImGuiInputTextFlags_CallbackAlways) + { + event_flag = ImGuiInputTextFlags_CallbackAlways; } - // Will copy result string if modified - if (!is_readonly && strcmp(state->TextSrc, buf) != 0) + if (event_flag) { - apply_new_text = state->TextSrc; - apply_new_text_length = state->TextLen; - value_changed = true; + ImGuiInputTextCallbackData callback_data; + callback_data.Ctx = &g; + callback_data.ID = id; + callback_data.Flags = flags; + callback_data.EventFlag = event_flag; + callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated); + callback_data.UserData = callback_user_data; + + // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925 + char* callback_buf = is_readonly ? buf : state->TextA.Data; + IM_ASSERT(callback_buf == state->TextSrc); + state->CallbackTextBackup.resize(state->TextLen + 1); + memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1); + + callback_data.EventKey = event_key; + callback_data.Buf = callback_buf; + callback_data.BufTextLen = state->TextLen; + callback_data.BufSize = state->BufCapacity; + callback_data.BufDirty = false; + callback_data.CursorPos = state->Stb->cursor; + callback_data.SelectionStart = state->Stb->select_start; + callback_data.SelectionEnd = state->Stb->select_end; + + // Call user code + callback(&callback_data); + + // Read back what user may have modified + callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback + IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields + IM_ASSERT(callback_data.BufSize == state->BufCapacity); + IM_ASSERT(callback_data.Flags == flags); + if (callback_data.BufDirty || callback_data.CursorPos != state->Stb->cursor) + state->CursorFollow = true; + state->Stb->cursor = ImClamp(callback_data.CursorPos, 0, callback_data.BufTextLen); + state->Stb->select_start = ImClamp(callback_data.SelectionStart, 0, callback_data.BufTextLen); + state->Stb->select_end = ImClamp(callback_data.SelectionEnd, 0, callback_data.BufTextLen); + if (callback_data.BufDirty) + { + // Callback may update buffer and thus set buf_dirty even in read-only mode. + IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen); + state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + state->CursorAnimReset(); + } } } + + // Will copy result string if modified. + // FIXME-OPT: Could mark dirty state from the stb_textedit callbacks + if (!is_readonly && strcmp(state->TextSrc, buf) != 0) + { + apply_new_text = state->TextSrc; + apply_new_text_length = state->TextLen; + value_changed = true; + } } // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details) + // This is used when e.g. losing focus or tabbing out into another InputText() which may already be using the temp buffer. if (g.InputTextDeactivatedState.ID == id) { if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0) @@ -5363,12 +5352,14 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.InputTextDeactivatedState.ID = 0; } - // Copy result to user buffer. This can currently only happen when (g.ActiveId == id) + // Write back result to user buffer. This can currently only happen when (g.ActiveId == id) or when just deactivated. + // - As soon as the InputText() is active, our stored in-widget value gets priority over any underlying modification of the user buffer. + // - Make sure we always reapply the live buffer back to the input/user buffer before clearing ActiveId, even thought strictly speaking + // it was not modified on this frame. This allows the user to use InputText() without maintaining any user-side storage. + // (PS: if you use this property together with ImGuiInputTextFlags_CallbackResize, you are at the risk of recreating a temporary + // allocated/string object every frame. Which in the grand scheme of scheme is nothing, but isn't dear imgui vibe). if (apply_new_text != NULL) { - //// We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size - //// of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used - //// without any storage on user's side. IM_ASSERT(apply_new_text_length >= 0); if (is_resizable) { From 994ca12b29381d6e33b771cfad6bfefa5d0ced90 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 16 Mar 2026 18:51:38 +0100 Subject: [PATCH 04/26] Fixed warning. (Amend 1677236) --- imgui_widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 24d93bae8..22f1bae50 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -10546,7 +10546,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Click to Select a tab // Allow the close button to overlap - ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap); + ImGuiButtonFlags button_flags = ((ImGuiButtonFlags)(is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap); if (g.DragDropActive) button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; bool hovered, held, pressed; From 709be8c49540a6eb64bd068d3961ad095a42c26b Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 18 Mar 2026 15:34:12 +0100 Subject: [PATCH 05/26] Discard/GC of ImDrawList buffers for unused windows favor restoring them to ~Size*1.05 instead of Capacity when awakening again. (#9303) + made "GC now" button process even active windows. --- docs/CHANGELOG.txt | 4 ++++ imgui.cpp | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 457518dc3..1a4bafa40 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -120,6 +120,10 @@ Other Changes: as a convenience for when using e.g. InvisibleButton(). - Focus: fixed fallback "Debug" window temporarily taking focus and setting io.WantCaptureKeyboard for one frame on e.g. application boot if no other windows are submitted. (#9243) +- Memory: + - Discard/GC of ImDrawList buffers for unused windows favor restoring them to + ~Size*1.05 instead of Capacity when awakening again. Facilitate releasing ImDrawList + buffers after unusual usage spike. (#9303). - Demo: fixed IMGUI_DEMO_MARKER locations for examples applets. (#9261, #3689) [@pthom] - Backends: - SDLGPU3: removed unnecessary call to SDL_WaitForGPUIdle when releasing diff --git a/imgui.cpp b/imgui.cpp index b7c5d1b22..4793e7833 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4633,11 +4633,12 @@ void ImGui::GcCompactTransientMiscBuffers() // Not freed: // - ImGuiWindow, ImGuiWindowSettings, Name, StateStorage, ColumnsStorage (may hold useful data) // This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer growth/copy cost. +// FIXME: Consider exposing of elaborating GC policy, e.g. being able to trim excessive ImDrawList gaps. (#9303) void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window) { window->MemoryCompacted = true; - window->MemoryDrawListIdxCapacity = window->DrawList->IdxBuffer.Capacity; - window->MemoryDrawListVtxCapacity = window->DrawList->VtxBuffer.Capacity; + window->MemoryDrawListIdxCapacity = ImMin((int)(window->DrawList->IdxBuffer.Size * 1.05f), window->DrawList->IdxBuffer.Capacity); + window->MemoryDrawListVtxCapacity = ImMin((int)(window->DrawList->VtxBuffer.Size * 1.05f), window->DrawList->VtxBuffer.Capacity); window->IDStack.clear(); window->DrawList->_ClearFreeMemory(); window->DC.ChildWindows.clear(); @@ -5665,7 +5666,8 @@ void ImGui::NewFrame() // Mark all windows as not visible and compact unused memory. IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size); - const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer; + const bool gc_all = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f); + const float memory_compact_start_time = gc_all ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer; for (ImGuiWindow* window : g.Windows) { window->WasActive = window->Active; @@ -5675,7 +5677,7 @@ void ImGui::NewFrame() window->BeginCount = 0; // Garbage collect transient buffers of recently unused windows - if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time) + if ((!window->WasActive || gc_all) && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time) GcCompactTransientWindowBuffers(window); } From 27cacb0e307308743379deb516080eaded566fdb Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 18 Mar 2026 16:48:18 +0100 Subject: [PATCH 06/26] Fixed GetForegroundDrawList()/GetBackgroundDrawList() per-viewport buffers not being collected/ (#9303) --- docs/CHANGELOG.txt | 4 +++- imgui.cpp | 18 ++++++++++++++---- imgui_internal.h | 4 ++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 1a4bafa40..d8d0c3537 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -123,7 +123,9 @@ Other Changes: - Memory: - Discard/GC of ImDrawList buffers for unused windows favor restoring them to ~Size*1.05 instead of Capacity when awakening again. Facilitate releasing ImDrawList - buffers after unusual usage spike. (#9303). + buffers after unusual usage spike. (#9303) + - Fixed GetForegroundDrawList()/GetBackgroundDrawList() per-viewport buffers not being + collected when unused for io.ConfigMemoryCompactTimer amount of time. (#9303) - Demo: fixed IMGUI_DEMO_MARKER locations for examples applets. (#9261, #3689) [@pthom] - Backends: - SDLGPU3: removed unnecessary call to SDL_WaitForGPUIdle when releasing diff --git a/imgui.cpp b/imgui.cpp index 4793e7833..1478018a6 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5198,12 +5198,12 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw } // Our ImDrawList system requires that there is always a command - if (viewport->BgFgDrawListsLastFrame[drawlist_no] != g.FrameCount) + if (viewport->BgFgDrawListsLastTimeActive[drawlist_no] != (float)g.Time) { draw_list->_ResetForNewFrame(); draw_list->PushTexture(g.IO.Fonts->TexRef); draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); - viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount; + viewport->BgFgDrawListsLastTimeActive[drawlist_no] = (float)g.Time; } return draw_list; } @@ -6089,7 +6089,7 @@ void ImGui::Render() for (ImGuiViewportP* viewport : g.Viewports) { InitViewportDrawData(viewport); - if (viewport->BgFgDrawLists[0] != NULL) + if (viewport->BgFgDrawLists[0] != NULL && viewport->BgFgDrawListsLastTimeActive[0] == (float)g.Time) AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport)); } @@ -6121,7 +6121,7 @@ void ImGui::Render() FlattenDrawDataIntoSingleLayer(&viewport->DrawDataBuilder); // Add foreground ImDrawList (for each active viewport) - if (viewport->BgFgDrawLists[1] != NULL) + if (viewport->BgFgDrawLists[1] != NULL && viewport->BgFgDrawListsLastTimeActive[1] == (float)g.Time) AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetForegroundDrawList(viewport)); // We call _PopUnusedDrawCmd() last thing, as RenderDimmedBackgrounds() rely on a valid command being there (especially in docking branch). @@ -15821,6 +15821,7 @@ static void ImGui::UpdateViewportsNewFrame() main_viewport->FramebufferScale = g.IO.DisplayFramebufferScale; IM_ASSERT(main_viewport->FramebufferScale.x > 0.0f && main_viewport->FramebufferScale.y > 0.0f); + const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer; for (ImGuiViewportP* viewport : g.Viewports) { // Lock down space taken by menu bars and status bars @@ -15829,6 +15830,14 @@ static void ImGui::UpdateViewportsNewFrame() viewport->WorkInsetMax = viewport->BuildWorkInsetMax; viewport->BuildWorkInsetMin = viewport->BuildWorkInsetMax = ImVec2(0.0f, 0.0f); viewport->UpdateWorkRect(); + + // Garbage collect transient buffers of recently BG/FG drawlists + for (int n = 0; n < IM_COUNTOF(viewport->BgFgDrawLists); n++) + if (viewport->BgFgDrawListsLastTimeActive[n] < memory_compact_start_time && viewport->BgFgDrawLists[n] != NULL) + { + IM_DELETE(viewport->BgFgDrawLists[n]); + viewport->BgFgDrawLists[n] = NULL; + } } } @@ -16867,6 +16876,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) { ImGuiDebugAllocInfo* info = &g.DebugAllocInfo; Text("%d current allocations", info->TotalAllocCount - info->TotalFreeCount); + Text("Releasing selected unused buffers after: %.2f secs", g.IO.ConfigMemoryCompactTimer); if (SmallButton("GC now")) { g.GcCompactAll = true; } Text("Recent frames with allocations:"); int buf_size = IM_COUNTOF(info->LastEntriesBuf); diff --git a/imgui_internal.h b/imgui_internal.h index c7afcee78..3dbcfd966 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1958,7 +1958,7 @@ struct IMGUI_API ImGuiMultiSelectState // Every instance of ImGuiViewport is in fact a ImGuiViewportP. struct ImGuiViewportP : public ImGuiViewport { - int BgFgDrawListsLastFrame[2]; // Last frame number the background (0) and foreground (1) draw lists were used + float BgFgDrawListsLastTimeActive[2]; // Last frame number the background (0) and foreground (1) draw lists were used ImDrawList* BgFgDrawLists[2]; // Convenience background (0) and foreground (1) draw lists. We use them to draw software mouser cursor when io.MouseDrawCursor is set and to draw most debug overlays. ImDrawData DrawDataP; ImDrawDataBuilder DrawDataBuilder; // Temporary data while building final ImDrawData @@ -1972,7 +1972,7 @@ struct ImGuiViewportP : public ImGuiViewport ImVec2 BuildWorkInsetMin; // Work Area inset accumulator for current frame, to become next frame's WorkInset ImVec2 BuildWorkInsetMax; // " - ImGuiViewportP() { BgFgDrawListsLastFrame[0] = BgFgDrawListsLastFrame[1] = -1; BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; } + ImGuiViewportP() { BgFgDrawListsLastTimeActive[0] = BgFgDrawListsLastTimeActive[1] = -1.0f; BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; } ~ImGuiViewportP() { if (BgFgDrawLists[0]) IM_DELETE(BgFgDrawLists[0]); if (BgFgDrawLists[1]) IM_DELETE(BgFgDrawLists[1]); } // Calculate work rect pos/size given a set of offset (we have 1 pair of offset for rect locked from last frame data, and 1 pair for currently building rect) From f4c2f508960d3132e2a41514225107219e2e2d98 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 18 Mar 2026 18:37:04 +0100 Subject: [PATCH 07/26] InputText: fixed a crash when handling ImGuiInputTextFlags_CallbackResize. (#9174) Fix/amend cb3b7ff. --- 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 d8d0c3537..8828e28ec 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -87,6 +87,8 @@ Other Changes: signals to be emitted the same way regardless of that setting. (#9001, #9115) - Fixed a glitch when using ImGuiInputTextFlags_ElideLeft where the local x offset would be incorrect during the deactivation frame. (#9298) + - Fixed a crash introduced in 1.92.6 when handling ImGuiInputTextFlags_CallbackResize + in certain situations. (#9174) - Style: - Border sizes are now scaled (and rounded) by ScaleAllSizes(). - When using large values with ScallAllSizes(), the following items thickness diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 22f1bae50..cebe73b8b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4497,7 +4497,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* sta callback_data.Flags = flags; callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; callback_data.EventChar = (ImWchar)c; - callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated); + callback_data.EventActivated = (state && g.ActiveId == state->ID && g.ActiveIdIsJustActivated); callback_data.UserData = user_data; if (callback(&callback_data) != 0) return false; @@ -5286,7 +5286,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ callback_data.ID = id; callback_data.Flags = flags; callback_data.EventFlag = event_flag; - callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated); + callback_data.EventActivated = (state && g.ActiveId == state->ID && g.ActiveIdIsJustActivated); callback_data.UserData = callback_user_data; // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925 @@ -5368,7 +5368,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ callback_data.ID = id; callback_data.Flags = flags; callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; - callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated); + callback_data.EventActivated = (state != NULL && g.ActiveId == state->ID && g.ActiveIdIsJustActivated); callback_data.Buf = buf; callback_data.BufTextLen = apply_new_text_length; callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); From 6abe65aac686593d6fa25ba4734445f3f8fcd49f Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 18 Mar 2026 18:57:02 +0100 Subject: [PATCH 08/26] InputText: amend fix to avoid PVS-Studio sort of rightful false positive. Amend f4c2f50. (#9174) Checking for state != NULL in the two othr functions where state is already deferenced was misleading. imgui_widgets.cpp:4496:1: error: V595 The 'state' pointer was utilized before it was verified against nullptr. Check lines: 4496, 4500. imgui_widgets.cpp:5273:1: error: V595 The 'state' pointer was utilized before it was verified against nullptr. Check lines: 5273, 5289. --- imgui_widgets.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index cebe73b8b..8a2ab9f31 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4402,6 +4402,7 @@ void ImGui::PopPasswordFont() // Return false to discard a character. static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* state, unsigned int* p_char, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard) { + IM_ASSERT(state != NULL); unsigned int c = *p_char; ImGuiInputTextFlags flags = state->Flags; @@ -4497,7 +4498,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* sta callback_data.Flags = flags; callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; callback_data.EventChar = (ImWchar)c; - callback_data.EventActivated = (state && g.ActiveId == state->ID && g.ActiveIdIsJustActivated); + callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated); callback_data.UserData = user_data; if (callback(&callback_data) != 0) return false; @@ -5286,7 +5287,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ callback_data.ID = id; callback_data.Flags = flags; callback_data.EventFlag = event_flag; - callback_data.EventActivated = (state && g.ActiveId == state->ID && g.ActiveIdIsJustActivated); + callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated); callback_data.UserData = callback_user_data; // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925 From 4252275c64b575257a004d204ed80fb0b420959a Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 18 Mar 2026 20:10:14 +0100 Subject: [PATCH 09/26] InputTextMultiline: fixed an issue calculating lines count when inactive, no word-wrap, and ending with a \n. Amend 1e52e7b90c (#3237, #952, #1062, #7363) --- docs/CHANGELOG.txt | 3 +++ imgui.h | 2 +- imgui_widgets.cpp | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 8828e28ec..384863e51 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -89,6 +89,9 @@ Other Changes: would be incorrect during the deactivation frame. (#9298) - Fixed a crash introduced in 1.92.6 when handling ImGuiInputTextFlags_CallbackResize in certain situations. (#9174) + - InputTextMultiline: fixed an issue introduced in 1.92.3 where line count calculated + for vertical scrollbar range would be +1 when the widget is inactive, word-wrap is + disabled and the text buffer ends with '\n'. - Style: - Border sizes are now scaled (and rounded) by ScaleAllSizes(). - When using large values with ScallAllSizes(), the following items thickness diff --git a/imgui.h b/imgui.h index 0086c60a3..e4f4f5d8a 100644 --- a/imgui.h +++ b/imgui.h @@ -30,7 +30,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.7 WIP" -#define IMGUI_VERSION_NUM 19265 +#define IMGUI_VERSION_NUM 19266 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 8a2ab9f31..3c980b86c 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4588,6 +4588,7 @@ static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* li ImGuiContext& g = *GImGui; int size = 0; const char* s; + bool trailing_line_already_counted = false; if (flags & ImGuiInputTextFlags_WordWrap) { for (s = buf; s < buf_end; s = (*s == '\n') ? s + 1 : s) @@ -4608,6 +4609,7 @@ static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* li } else { + // Inactive path: we don't know buf_end ahead of time. const char* s_eol; for (s = buf; ; s = s_eol + 1) { @@ -4616,6 +4618,7 @@ static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* li if ((s_eol = strchr(s, '\n')) != NULL) continue; s += strlen(s); + trailing_line_already_counted = true; break; } } @@ -4626,7 +4629,7 @@ static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* li line_index->Offsets.push_back(0); size++; } - if (buf_end > buf && buf_end[-1] == '\n' && size <= max_output_buffer_size) + if (buf_end > buf && buf_end[-1] == '\n' && size <= max_output_buffer_size && !trailing_line_already_counted) { line_index->Offsets.push_back((int)(buf_end - buf)); size++; From b724f940d64430f26fbae67c675115dbb49ae4bc Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Mar 2026 11:20:00 +0100 Subject: [PATCH 10/26] InputText: fixed selection highlight Y1 offset being very slightly off (since 1.92.3). (#9311) Fixes 1e52e7b90c0 --- docs/CHANGELOG.txt | 1 + imgui_widgets.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 384863e51..1dcd0f8fd 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -89,6 +89,7 @@ Other Changes: would be incorrect during the deactivation frame. (#9298) - Fixed a crash introduced in 1.92.6 when handling ImGuiInputTextFlags_CallbackResize in certain situations. (#9174) + - Fixed selection highlight Y1 offset being very slightly off (since 1.92.3). (#9311) [@v-ein] - InputTextMultiline: fixed an issue introduced in 1.92.3 where line count calculated for vertical scrollbar range would be +1 when the widget is inactive, word-wrap is disabled and the text buffer ends with '\n'. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3c980b86c..f39469c45 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5532,7 +5532,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (render_selection) { const ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. - const float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + const float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME-DPI: those offsets should be part of the style? they don't play so well with multi-line selection. const float bg_offy_dn = is_multiline ? 0.0f : 2.0f; const float bg_eol_width = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines @@ -5561,7 +5561,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ rect.Min.y = draw_pos.y - draw_scroll.y + line_n * g.FontSize; rect.Max.x = rect.Min.x + rect_width; rect.Max.y = rect.Min.y + bg_offy_dn + g.FontSize; - rect.Min.y -= bg_offy_up; + rect.Min.y += bg_offy_up; rect.ClipWith(clip_rect); draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); } From 7fc3092870941483aa80e392822203543828a062 Mon Sep 17 00:00:00 2001 From: Starman <75682000+StarmanAkremis@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:06:13 +0000 Subject: [PATCH 11/26] Backends: SDLGPU3: Prevent DestroyTexture from deleting invalid textures if ImTextureID_Invalid != 0. (#9310, #9293) Amend 0db5919 --- backends/imgui_impl_sdlgpu3.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 783923052..75a2b7391 100644 --- a/backends/imgui_impl_sdlgpu3.cpp +++ b/backends/imgui_impl_sdlgpu3.cpp @@ -310,8 +310,9 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe static void ImGui_ImplSDLGPU3_DestroyTexture(ImTextureData* tex) { ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); - if (SDL_GPUTexture* raw_tex = (SDL_GPUTexture*)(intptr_t)tex->GetTexID()) - SDL_ReleaseGPUTexture(bd->InitInfo.Device, raw_tex); + if (tex->GetTexID() != ImTextureID_Invalid) + if (SDL_GPUTexture* raw_tex = (SDL_GPUTexture*)(intptr_t)tex->GetTexID()) + SDL_ReleaseGPUTexture(bd->InitInfo.Device, raw_tex); // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) tex->SetTexID(ImTextureID_Invalid); From 0500e546b57a15f232aca3795022059cbadeb5a4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Mar 2026 11:47:01 +0100 Subject: [PATCH 12/26] Backends: DX9, Metal, SDLRenderer2/3: fixed more assumptions that ImTextureID_Invald == 0 + Amend Changelogs. (#9310, #9293) --- backends/imgui_impl_dx9.cpp | 16 +++++++++------- backends/imgui_impl_metal.mm | 4 +++- backends/imgui_impl_opengl2.cpp | 1 + backends/imgui_impl_opengl3.cpp | 1 + backends/imgui_impl_sdlgpu3.cpp | 1 + backends/imgui_impl_sdlrenderer2.cpp | 6 ++++-- backends/imgui_impl_sdlrenderer3.cpp | 6 ++++-- docs/CHANGELOG.txt | 2 ++ 8 files changed, 25 insertions(+), 12 deletions(-) diff --git a/backends/imgui_impl_dx9.cpp b/backends/imgui_impl_dx9.cpp index 37b5815cc..81f142157 100644 --- a/backends/imgui_impl_dx9.cpp +++ b/backends/imgui_impl_dx9.cpp @@ -17,6 +17,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-03-19: Fixed issue in ImGui_ImplDX9_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. // 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap. @@ -431,14 +432,15 @@ void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy) { - if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID) - { - IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex); - backend_tex->Release(); + if (tex->ID != ImTextureID_Invalid) + if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID) + { + IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex); + backend_tex->Release(); - // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) - tex->SetTexID(ImTextureID_Invalid); - } + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + } tex->SetStatus(ImTextureStatus_Destroyed); } } diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 96695d9b7..0ab182a82 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -16,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-03-19: Fixed issue in ImGui_ImplMetal_RenderDrawData() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplMetal_CreateFontsTexture() and ImGui_ImplMetal_DestroyFontsTexture(). // 2025-02-03: Metal: Crash fix. (#8367) @@ -307,7 +308,8 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id [commandEncoder setScissorRect:scissorRect]; // Bind texture, Draw - if (ImTextureID tex_id = pcmd->GetTexID()) + ImTextureID tex_id = pcmd->GetTexID(); + if (tex_id != ImTextureID_Invalid) [commandEncoder setFragmentTexture:(__bridge id)(void*)(intptr_t)(tex_id) atIndex:0]; [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0]; diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 7b860098f..4fffc5774 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -25,6 +25,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-03-12: OpenGL: Fixed invalid assert in ImGui_ImplOpenGL3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures. (#8802) // 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL2_CreateFontsTexture() and ImGui_ImplOpenGL2_DestroyFontsTexture(). diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 893e8b852..d1894a135 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -23,6 +23,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-03-12: OpenGL: Fixed invalid assert in ImGui_ImplOpenGL3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-12-11: OpenGL: Fixed embedded loader multiple init/shutdown cycles broken on some platforms. (#8792, #9112) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-07-22: OpenGL: Add and call embedded loader shutdown during ImGui_ImplOpenGL3_Shutdown() to facilitate multiple init/shutdown cycles in same process. (#8792) diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 75a2b7391..54a527a3c 100644 --- a/backends/imgui_impl_sdlgpu3.cpp +++ b/backends/imgui_impl_sdlgpu3.cpp @@ -22,6 +22,7 @@ // Calling the function is MANDATORY, otherwise the ImGui will not upload neither the vertex nor the index buffer for the GPU. See imgui_impl_sdlgpu3.cpp for more info. // CHANGELOG +// 2026-03-19: Fixed issue in ImGui_ImplSDLGPU3_DestroyTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310) // 2026-02-25: Removed unnecessary call to SDL_WaitForGPUIdle when releasing vertex/index buffers. (#9262) // 2025-11-26: macOS version can use MSL shaders in order to support macOS 10.14+ (vs Metallib shaders requiring macOS 14+). Requires calling SDL_CreateGPUDevice() with SDL_GPU_SHADERFORMAT_MSL. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. diff --git a/backends/imgui_impl_sdlrenderer2.cpp b/backends/imgui_impl_sdlrenderer2.cpp index e03d13115..6d41a2b38 100644 --- a/backends/imgui_impl_sdlrenderer2.cpp +++ b/backends/imgui_impl_sdlrenderer2.cpp @@ -24,6 +24,7 @@ // - Introduction, links and more at the top of imgui.cpp // CHANGELOG +// 2026-03-12: Fixed invalid assert in ImGui_ImplSDLRenderer2_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer2_CreateFontsTexture() and ImGui_ImplSDLRenderer2_DestroyFontsTexture(). // 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color. @@ -267,8 +268,9 @@ void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy) { - if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID) - SDL_DestroyTexture(sdl_texture); + if (tex->ID != ImTextureID_Invalid) + if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID) + SDL_DestroyTexture(sdl_texture); // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) tex->SetTexID(ImTextureID_Invalid); diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index 72bc174ee..b9c1d3fad 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -24,6 +24,7 @@ // - Introduction, links and more at the top of imgui.cpp // CHANGELOG +// 2026-03-12: Fixed invalid assert in ImGui_ImplSDLRenderer3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer3_CreateFontsTexture() and ImGui_ImplSDLRenderer3_DestroyFontsTexture(). // 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color. @@ -283,8 +284,9 @@ void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy) { - if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID) - SDL_DestroyTexture(sdl_texture); + if (tex->ID != ImTextureID_Invalid) + if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID) + SDL_DestroyTexture(sdl_texture); // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) tex->SetTexID(ImTextureID_Invalid); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 1dcd0f8fd..fcdcd08ba 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -134,6 +134,8 @@ Other Changes: collected when unused for io.ConfigMemoryCompactTimer amount of time. (#9303) - Demo: fixed IMGUI_DEMO_MARKER locations for examples applets. (#9261, #3689) [@pthom] - Backends: + - DirectX9, OpenGL2, OpenGL3, Metal, SDLGPU3, SDLRenderer2, SDLRenderer3: fixed easy-to-fix + issues in code assuming ImTextureID_Invalid is always defined to 0. (#9295, #9310) - SDLGPU3: removed unnecessary call to SDL_WaitForGPUIdle when releasing vertex/index buffers. (#9262) [@jaenis] - WebGPU: fixed version check for Emscripten 5.0.0+. From 4d1ba782eef4ff774d671a0d3bb48d31aa5412c0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Mar 2026 11:54:42 +0100 Subject: [PATCH 13/26] Revert changing default value of ImTextureID_Invalid to -1. Back to 0. (#9295, #9310, #9293, #8745, #8465, #7090) Reverts 0db591935f08c73f1e0726869a92ca803e8660a9 --- docs/CHANGELOG.txt | 10 ---------- imgui.cpp | 4 ---- imgui.h | 10 +++++----- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index fcdcd08ba..9966d8519 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,16 +41,6 @@ HOW TO UPDATE? Breaking Changes: - - Changed default ImTextureID_Invalid value to -1 instead of 0 if not #define-d. - (#9293, #8745, #8465, #7090) - - It seems like a better default since it will work with backends storing - indices or memory offsets inside ImTextureID, where 0 might be a valid value. - - If this is causing problem with e.g your custom ImTextureID definition, you can - add '#define ImTextureID_Invalid 0' to your imconfig.h + PLEASE report this to GitHub. - - If you have hardcoded e.g. 'if (tex_id == 0)' checks they should be updated. - e.g. OpenGL2, OpenGL3 and SDLRenderer3 backends incorrectly had 'IM_ASSERT(tex->TexID == 0)' - lines which were replaced with 'IM_ASSERT(tex->TexID == ImTextureID_Invalid)'. - If you have copied or forked backends consider fixing locally. (#9295) - Separator(): fixed a legacy quirk where Separator() was submitting a zero-height item for layout purpose, even though it draws a 1-pixel separator. The fix could affect code e.g. computing height from multiple widgets in order to diff --git a/imgui.cpp b/imgui.cpp index 1478018a6..8de5a769d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -395,10 +395,6 @@ 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. - - 2026/03/12 (1.92.7) - Changed default ImTextureID_Invalid to -1 instead of 0 if not #define-d. (#9293, #8745, #8465, #7090) - It seems like a better default since it will work with backends storing indices or memory offsets inside ImTextureID, where 0 might be a valid value. - If this is causing problem with e.g. your custom ImTextureID definition, you can add '#define ImTextureID_Invalid 0' to your imconfig.h + PLEASE report this to GitHub. - If you have hard-coded e.g. 'if (tex_id == 0)' checks they should be updated. e.g. OpenGL2, OpenGL3 and SDLRenderer3 backends incorrectly had 'IM_ASSERT(tex->TexID == 0)' lines which were replaced with 'IM_ASSERT(tex->TexID == ImTextureID_Invalid)'. (#9295) - 2026/02/26 (1.92.7) - Separator: fixed a legacy quirk where Separator() was submitting a zero-height item for layout purpose, even though it draws a 1-pixel separator. The fix could affect code e.g. computing height from multiple widgets in order to allocate vertical space for a footer or multi-line status bar. (#2657, #9263) The "Console" example had such a bug: diff --git a/imgui.h b/imgui.h index e4f4f5d8a..f0f7c7c80 100644 --- a/imgui.h +++ b/imgui.h @@ -341,10 +341,10 @@ typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or #endif // Define this if you need to change the invalid value for your backend. -// - in v1.92.7 (2025/03/12): we changed default value from 0 to -1 as it is a better default, which supports storing offsets/indices. -// - If this is causing problem with your custom ImTextureID definition, you can add '#define ImTextureID_Invalid' to your imconfig + please report this to GitHub. +// - If your backend is using ImTextureID to store an index/offset and you need 0 to be valid, You can add '#define ImTextureID_Invalid ((ImTextureID)-1)' in your imconfig.h file. +// - From 2026/03/12 to 2026/03/19 we experimented with changing to default to -1, but I worried it would cause too many issues in third-party code so it was reverted. #ifndef ImTextureID_Invalid -#define ImTextureID_Invalid ((ImTextureID)-1) +#define ImTextureID_Invalid ((ImTextureID)0) #endif // ImTextureRef = higher-level identifier for a texture. Store a ImTextureID _or_ a ImTextureData*. @@ -3909,8 +3909,8 @@ inline ImTextureID ImTextureRef::GetTexID() const // Using an indirection to avoid patching ImDrawCmd after a SetTexID() call (but this could be an alternative solution too) inline ImTextureID ImDrawCmd::GetTexID() const { - // If you are getting this assert with ImTextureID_Invalid == 0 and your ImTextureID is used to store an index: - // - You can add '#define ImTextureID_Invalid ((ImTextureID)-1)' in your imconfig file. + // If you are getting this assert with ImTextureID_Invalid == 0 and your ImTextureID is used to store an index or an offset: + // - You can add '#define ImTextureID_Invalid ((ImTextureID)-1)' in your imconfig.h file. // If you are getting this assert with a renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92+): // - You must correctly iterate and handle ImTextureData requests stored in ImDrawData::Textures[]. See docs/BACKENDS.md. ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; // == TexRef.GetTexID() above. From 358d3912c98ae6226f0e4c4584d8c41c3b1a7ffe Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Mar 2026 12:34:11 +0100 Subject: [PATCH 14/26] Backends: SDLRenderer2/3: fixed build, typo in 0500e54. --- backends/imgui_impl_sdlrenderer2.cpp | 2 +- backends/imgui_impl_sdlrenderer3.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_sdlrenderer2.cpp b/backends/imgui_impl_sdlrenderer2.cpp index 6d41a2b38..dc86f6c26 100644 --- a/backends/imgui_impl_sdlrenderer2.cpp +++ b/backends/imgui_impl_sdlrenderer2.cpp @@ -268,7 +268,7 @@ void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy) { - if (tex->ID != ImTextureID_Invalid) + if (tex->TexID != ImTextureID_Invalid) if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID) SDL_DestroyTexture(sdl_texture); diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index b9c1d3fad..be6c97d8c 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -284,7 +284,7 @@ void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy) { - if (tex->ID != ImTextureID_Invalid) + if (tex->TexID != ImTextureID_Invalid) if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID) SDL_DestroyTexture(sdl_texture); From 20d8bcb600d6a0c54c911700a1a5c70765bdcc4c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Mar 2026 16:04:04 +0100 Subject: [PATCH 15/26] (Breaking) MultiSelect: renamed ImGuiMultiSelectFlags_SelectOnClick to ImGuiMultiSelectFlags_SelectOnAuto. (#1861, #6518) --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 1 + imgui.h | 10 ++++++++-- imgui_demo.cpp | 8 ++++---- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9966d8519..0c509600d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -53,6 +53,8 @@ Breaking Changes: BeginChild("ScrollingRegion", { 0, -footer_height }); When such idiom was used and assuming zero-height Separator, it is likely that in 1.92.7 the resulting window will have unexpected 1 pixel scrolling range. + - MultiSelect: renamed ImGuiMultiSelectFlags_SelectOnClick to ImGuiMultiSelectFlags_SelectOnAuto. + Kept inline redirection enum (will obsolete). - Combo(), ListBox(): commented out legacy signatures which were obsoleted in 1.90 (Nov 2023), when the getter callback type was changed from: getter type: bool (*getter)(void* user_data, int idx, const char** out_text) diff --git a/imgui.cpp b/imgui.cpp index 8de5a769d..1cada18fc 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -395,6 +395,7 @@ 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. + - 2026/03/19 (1.92.7) - MultiSelect: renamed ImGuiMultiSelectFlags_SelectOnClick to ImGuiMultiSelectFlags_SelectOnAuto. - 2026/02/26 (1.92.7) - Separator: fixed a legacy quirk where Separator() was submitting a zero-height item for layout purpose, even though it draws a 1-pixel separator. The fix could affect code e.g. computing height from multiple widgets in order to allocate vertical space for a footer or multi-line status bar. (#2657, #9263) The "Console" example had such a bug: diff --git a/imgui.h b/imgui.h index f0f7c7c80..f00ef968e 100644 --- a/imgui.h +++ b/imgui.h @@ -3025,16 +3025,22 @@ enum ImGuiMultiSelectFlags_ ImGuiMultiSelectFlags_NoAutoClearOnReselect = 1 << 5, // Disable clearing selection when clicking/selecting an already selected item. ImGuiMultiSelectFlags_BoxSelect1d = 1 << 6, // Enable box-selection with same width and same x pos items (e.g. full row Selectable()). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space. ImGuiMultiSelectFlags_BoxSelect2d = 1 << 7, // Enable box-selection with varying width or varying x pos items support (e.g. different width labels, or 2D layout/grid). This is slower: alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items. - ImGuiMultiSelectFlags_BoxSelectNoScroll = 1 << 8, // Disable scrolling when box-selecting near edges of scope. + ImGuiMultiSelectFlags_BoxSelectNoScroll = 1 << 8, // Disable scrolling when box-selecting and moving mouse near edges of scope. ImGuiMultiSelectFlags_ClearOnEscape = 1 << 9, // Clear selection when pressing Escape while scope is focused. ImGuiMultiSelectFlags_ClearOnClickVoid = 1 << 10, // Clear selection when clicking on empty location within scope. ImGuiMultiSelectFlags_ScopeWindow = 1 << 11, // Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window. ImGuiMultiSelectFlags_ScopeRect = 1 << 12, // Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window. - ImGuiMultiSelectFlags_SelectOnClick = 1 << 13, // Apply selection on mouse down when clicking on unselected item. (Default) + ImGuiMultiSelectFlags_SelectOnAuto = 1 << 13, // Apply selection on mouse down when clicking on unselected item, on mouse up when clicking on selected item. (Default) ImGuiMultiSelectFlags_SelectOnClickRelease = 1 << 14, // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection. //ImGuiMultiSelectFlags_RangeSelect2d = 1 << 15, // Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does. ImGuiMultiSelectFlags_NavWrapX = 1 << 16, // [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one. ImGuiMultiSelectFlags_NoSelectOnRightClick = 1 << 17, // Disable default right-click processing, which selects item on mouse down, and is designed for context-menus. + ImGuiMultiSelectFlags_SelectOnMask_ = ImGuiMultiSelectFlags_SelectOnAuto | ImGuiMultiSelectFlags_SelectOnClickRelease, + + // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiMultiSelectFlags_SelectOnClick = ImGuiMultiSelectFlags_SelectOnAuto, // RENAMED in 1.92.6 +#endif }; // Main IO structure returned by BeginMultiSelect()/EndMultiSelect(). diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 738096c57..366d9c7dd 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3241,10 +3241,10 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d flags &= ~ImGuiMultiSelectFlags_ScopeRect; if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeRect", &flags, ImGuiMultiSelectFlags_ScopeRect) && (flags & ImGuiMultiSelectFlags_ScopeRect)) flags &= ~ImGuiMultiSelectFlags_ScopeWindow; - if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClick", &flags, ImGuiMultiSelectFlags_SelectOnClick) && (flags & ImGuiMultiSelectFlags_SelectOnClick)) - flags &= ~ImGuiMultiSelectFlags_SelectOnClickRelease; - if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickRelease", &flags, ImGuiMultiSelectFlags_SelectOnClickRelease) && (flags & ImGuiMultiSelectFlags_SelectOnClickRelease)) - flags &= ~ImGuiMultiSelectFlags_SelectOnClick; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnAuto", &flags, ImGuiMultiSelectFlags_SelectOnAuto)) + flags &= ~(ImGuiMultiSelectFlags_SelectOnMask_ ^ ImGuiMultiSelectFlags_SelectOnAuto); + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickRelease", &flags, ImGuiMultiSelectFlags_SelectOnClickRelease)) + flags &= ~(ImGuiMultiSelectFlags_SelectOnMask_ ^ ImGuiMultiSelectFlags_SelectOnClickRelease); ImGui::SameLine(); HelpMarker("Allow dragging an unselected item without altering selection."); ImGui::TreePop(); } From 0b4967992a18e36c6da6fdbb8ce10fad7176ce2b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Mar 2026 16:29:03 +0100 Subject: [PATCH 16/26] MultiSelect: Box-Select: removed now seemingly unnecessary 'selected==false' check, which will also prevent implementation of ImGuiMultiSelectFlags_SelectOnClickAlways. (#9307) We enter into the block either though navigation, and then the Mouse check fails, either through mouse, and then Selected==false is tested above. Amend f904a6646. --- imgui_widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index f39469c45..e39d614ec 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -8293,7 +8293,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) // Box-select ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse; if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) - if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1) + if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1) BoxSelectPreStartDrag(ms->BoxSelectId, item_data); //---------------------------------------------------------------------------------------- From 9700846bb320d7f3670f3b2cc747058965d3ec1b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Mar 2026 16:38:13 +0100 Subject: [PATCH 17/26] MultiSelect: added ImGuiMultiSelectFlags_SelectOnClickAlways mode. Prevents Drag and Drop of multiple items but allows BoxSelect to always reselect even when clicking inside a selecttion. (#9307, #1861) --- docs/CHANGELOG.txt | 6 +++++- imgui.h | 5 +++-- imgui_demo.cpp | 22 ++++++++++++++++------ imgui_widgets.cpp | 9 ++++++--- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 0c509600d..8185b86c1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -53,7 +53,7 @@ Breaking Changes: BeginChild("ScrollingRegion", { 0, -footer_height }); When such idiom was used and assuming zero-height Separator, it is likely that in 1.92.7 the resulting window will have unexpected 1 pixel scrolling range. - - MultiSelect: renamed ImGuiMultiSelectFlags_SelectOnClick to ImGuiMultiSelectFlags_SelectOnAuto. + - Multi-Select: renamed ImGuiMultiSelectFlags_SelectOnClick to ImGuiMultiSelectFlags_SelectOnAuto. Kept inline redirection enum (will obsolete). - Combo(), ListBox(): commented out legacy signatures which were obsoleted in 1.90 (Nov 2023), when the getter callback type was changed from: @@ -105,6 +105,10 @@ Other Changes: BeginPopupContextItem(), BeginPopupContextWindow() or OpenPopupOnItemClick(). (#8803, #9270) [@exelix11, @ocornut] - Popups: pressing North button (PS4/PS5 triangle, SwitchX, Xbox Y) also open popups menus. +- Multi-Select: + - Added ImGuiMultiSelectFlags_SelectOnClickAlways mode (rarely used). + This prevents Drag and Drop of multiple items, but it allows to start a new Box-Selection + from inside an existing selection (Excel style). (#9307, #1861) - Clipper: - Clear `DisplayStart`/`DisplayEnd` fields when `Step()` returns false. - Added `UserIndex` helper storage. This is solely a convenience for cases where diff --git a/imgui.h b/imgui.h index f00ef968e..eef153c65 100644 --- a/imgui.h +++ b/imgui.h @@ -3031,11 +3031,12 @@ enum ImGuiMultiSelectFlags_ ImGuiMultiSelectFlags_ScopeWindow = 1 << 11, // Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window. ImGuiMultiSelectFlags_ScopeRect = 1 << 12, // Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window. ImGuiMultiSelectFlags_SelectOnAuto = 1 << 13, // Apply selection on mouse down when clicking on unselected item, on mouse up when clicking on selected item. (Default) - ImGuiMultiSelectFlags_SelectOnClickRelease = 1 << 14, // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection. + ImGuiMultiSelectFlags_SelectOnClickAlways = 1 << 14, // Apply selection on mouse down when clicking on any items. Prevents Drag and Drop from being used on multiple-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection. (Excel style behavior) + ImGuiMultiSelectFlags_SelectOnClickRelease = 1 << 15, // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection. //ImGuiMultiSelectFlags_RangeSelect2d = 1 << 15, // Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does. ImGuiMultiSelectFlags_NavWrapX = 1 << 16, // [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one. ImGuiMultiSelectFlags_NoSelectOnRightClick = 1 << 17, // Disable default right-click processing, which selects item on mouse down, and is designed for context-menus. - ImGuiMultiSelectFlags_SelectOnMask_ = ImGuiMultiSelectFlags_SelectOnAuto | ImGuiMultiSelectFlags_SelectOnClickRelease, + ImGuiMultiSelectFlags_SelectOnMask_ = ImGuiMultiSelectFlags_SelectOnAuto | ImGuiMultiSelectFlags_SelectOnClickAlways | ImGuiMultiSelectFlags_SelectOnClickRelease, // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 366d9c7dd..c95fa8bb4 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2632,7 +2632,7 @@ struct ExampleDualListBox } if (child_visible) { - ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None; + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_BoxSelect1d; ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); ApplySelectionRequests(ms_io, side); @@ -3243,6 +3243,10 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d flags &= ~ImGuiMultiSelectFlags_ScopeWindow; if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnAuto", &flags, ImGuiMultiSelectFlags_SelectOnAuto)) flags &= ~(ImGuiMultiSelectFlags_SelectOnMask_ ^ ImGuiMultiSelectFlags_SelectOnAuto); + ImGui::SameLine(); HelpMarker("Apply selection on mouse down when clicking on unselected item, on mouse up when clicking on selected item. (Default)"); + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickAlways", &flags, ImGuiMultiSelectFlags_SelectOnClickAlways)) + flags &= ~(ImGuiMultiSelectFlags_SelectOnMask_ ^ ImGuiMultiSelectFlags_SelectOnClickAlways); + ImGui::SameLine(); HelpMarker("Prevents Drag and Drop from being used on multi-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection. (Excel style behavior)"); if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickRelease", &flags, ImGuiMultiSelectFlags_SelectOnClickRelease)) flags &= ~(ImGuiMultiSelectFlags_SelectOnMask_ ^ ImGuiMultiSelectFlags_SelectOnClickRelease); ImGui::SameLine(); HelpMarker("Allow dragging an unselected item without altering selection."); @@ -10716,8 +10720,9 @@ struct ExampleAssetsBrowser // Options bool ShowTypeOverlay = true; bool AllowSorting = true; - bool AllowDragUnselected = false; - bool AllowBoxSelect = true; + bool AllowBoxSelect = true; // Will set ImGuiMultiSelectFlags_BoxSelect2d + bool AllowBoxSelectInsideSelection = false; // Will set ImGuiMultiSelectFlags_SelectOnClickAlways + bool AllowDragUnselected = false; // Will set ImGuiMultiSelectFlags_SelectOnClickRelease float IconSize = 32.0f; int IconSpacing = 10; int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer. @@ -10822,8 +10827,11 @@ struct ExampleAssetsBrowser ImGui::Checkbox("Allow Sorting", &AllowSorting); ImGui::SeparatorText("Selection Behavior"); - ImGui::Checkbox("Allow dragging unselected item", &AllowDragUnselected); ImGui::Checkbox("Allow box-selection", &AllowBoxSelect); + if (ImGui::Checkbox("Allow box-selection from selected items", &AllowBoxSelectInsideSelection) && AllowBoxSelectInsideSelection) + AllowDragUnselected = false; + if (ImGui::Checkbox("Allow dragging unselected item", &AllowDragUnselected) && AllowDragUnselected) + AllowBoxSelectInsideSelection = false; ImGui::SeparatorText("Layout"); ImGui::SliderFloat("Icon Size", &IconSize, 16.0f, 128.0f, "%.0f"); @@ -10879,9 +10887,11 @@ struct ExampleAssetsBrowser if (AllowBoxSelect) ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d; - // - This feature allows dragging an unselected item without selecting it (rarely used) + // - Selection mode if (AllowDragUnselected) - ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease; + ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease; // Rarely used: Allows dragging an unselected item without selecting it(rarely used) + else if (AllowBoxSelectInsideSelection) + ms_flags |= ImGuiMultiSelectFlags_SelectOnClickAlways; // Rarely used: Prevents Drag and Drop from being used on multiple-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection. // - Enable keyboard wrapping on X axis // (FIXME-MULTISELECT: We haven't designed/exposed a general nav wrapping api yet, so this flag is provided as a courtesy to avoid doing: diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index e39d614ec..936de9098 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -8170,10 +8170,13 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags { ImGuiButtonFlags button_flags = *p_button_flags; button_flags |= ImGuiButtonFlags_NoHoveredOnFocus; - if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease)) - button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease; - else + button_flags &= ~(ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickRelease); + if (ms->Flags & ImGuiMultiSelectFlags_SelectOnClickAlways) + button_flags |= ImGuiButtonFlags_PressedOnClick; + else if (ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease) button_flags |= ImGuiButtonFlags_PressedOnClickRelease; + else // ImGuiMultiSelectFlags_SelectOnAuto + button_flags |= (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnClickRelease; *p_button_flags = button_flags; } } From b2c3e37d55ee03e7f4cd6ad3f8d3cad333097cc0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Mar 2026 16:58:14 +0100 Subject: [PATCH 18/26] Multi-Select: fix/amend 9700846. . (#9307, #1861) --- imgui_widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 936de9098..7e51511dc 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -8170,7 +8170,7 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags { ImGuiButtonFlags button_flags = *p_button_flags; button_flags |= ImGuiButtonFlags_NoHoveredOnFocus; - button_flags &= ~(ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickRelease); + button_flags &= ~(ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease); if (ms->Flags & ImGuiMultiSelectFlags_SelectOnClickAlways) button_flags |= ImGuiButtonFlags_PressedOnClick; else if (ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease) From 386ce49c58393d469fc477340e7725cb4170f50c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 19 Mar 2026 18:17:35 +0100 Subject: [PATCH 19/26] Backends: DirectX9: fixed build typo in 0500e54. --- backends/imgui_impl_dx9.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/imgui_impl_dx9.cpp b/backends/imgui_impl_dx9.cpp index 81f142157..24911fadd 100644 --- a/backends/imgui_impl_dx9.cpp +++ b/backends/imgui_impl_dx9.cpp @@ -432,7 +432,7 @@ void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy) { - if (tex->ID != ImTextureID_Invalid) + if (tex->TexID != ImTextureID_Invalid) if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID) { IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex); From 3a26b640b2f2a4b9a9aa1e176cb967ca54c88cec Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 20 Mar 2026 11:50:05 +0100 Subject: [PATCH 20/26] Drag and Drop: make SetDragDropPayload() memcpy size match our buffer. --- imgui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 1cada18fc..26518852a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -14931,14 +14931,14 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s // Store in heap g.DragDropPayloadBufHeap.resize((int)data_size); payload.Data = g.DragDropPayloadBufHeap.Data; - memcpy(payload.Data, data, data_size); + memcpy(payload.Data, data, (size_t)(int)data_size); } else if (data_size > 0) { // Store locally memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); payload.Data = g.DragDropPayloadBufLocal; - memcpy(payload.Data, data, data_size); + memcpy(payload.Data, data, (size_t)(int)data_size); } else { From 763db046fa25517b8ab4627f9188a5cdbea5ea51 Mon Sep 17 00:00:00 2001 From: Pascal Thomet Date: Fri, 20 Mar 2026 12:40:32 +0100 Subject: [PATCH 21/26] Docs: fixed imgui_manual -> imgui_explorer link. (#9315) --- imgui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index eef153c65..32e4a814d 100644 --- a/imgui.h +++ b/imgui.h @@ -20,7 +20,7 @@ // - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui // - Issues & support ........... https://github.com/ocornut/imgui/issues // - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) -// - Web version of the Demo .... https://pthom.github.io/imgui_manual (w/ source code browser) +// - Web version of the Demo .... https://pthom.github.io/imgui_explorer (w/ source code browser) // For FIRST-TIME users having issues compiling/linking/running: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. From 325563a982c0c2c889f02e771c82a848ed78ab22 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 20 Mar 2026 15:17:07 +0100 Subject: [PATCH 22/26] InputTextMultiline: InputTextMultiline: fixed an issue calculating lines count when active. Amend 4252275 --- docs/CHANGELOG.txt | 3 ++- imgui.cpp | 2 +- imgui_widgets.cpp | 5 +---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 8185b86c1..71a1bb173 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -84,7 +84,8 @@ Other Changes: - Fixed selection highlight Y1 offset being very slightly off (since 1.92.3). (#9311) [@v-ein] - InputTextMultiline: fixed an issue introduced in 1.92.3 where line count calculated for vertical scrollbar range would be +1 when the widget is inactive, word-wrap is - disabled and the text buffer ends with '\n'. + disabled and the text buffer ends with '\n'. Fixed a similar issue related to clipping + large amount of text. - Style: - Border sizes are now scaled (and rounded) by ScaleAllSizes(). - When using large values with ScallAllSizes(), the following items thickness diff --git a/imgui.cpp b/imgui.cpp index 26518852a..8e760d05a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5565,7 +5565,7 @@ void ImGui::NewFrame() // As a result, custom widget using ButtonBehavior() _without_ ItemAdd() need to call KeepAliveID() themselves. if (g.ActiveId != 0 && g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId) { - IMGUI_DEBUG_LOG_ACTIVEID("NewFrame(): ClearActiveID() because it isn't marked alive anymore!\n"); + IMGUI_DEBUG_LOG_ACTIVEID("NewFrame(): ClearActiveID() 0x%08X because it isn't marked alive anymore!\n", g.ActiveId); ClearActiveID(); } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 7e51511dc..c7159c125 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4629,11 +4629,8 @@ static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* li line_index->Offsets.push_back(0); size++; } - if (buf_end > buf && buf_end[-1] == '\n' && size <= max_output_buffer_size && !trailing_line_already_counted) - { + if (buf_end > buf && buf_end[-1] == '\n' && !trailing_line_already_counted && size++ <= max_output_buffer_size) line_index->Offsets.push_back((int)(buf_end - buf)); - size++; - } return size; } From 2d957152e4347c214098b952080e0c52de4f50c3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 20 Mar 2026 15:37:56 +0100 Subject: [PATCH 23/26] InputTextMultiline: avoid going through reactivation path and InputTextDeactivateHook() when activating scrollbar. (#9308) --- docs/CHANGELOG.txt | 1 + imgui_widgets.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 71a1bb173..ae3eb97bc 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -86,6 +86,7 @@ Other Changes: for vertical scrollbar range would be +1 when the widget is inactive, word-wrap is disabled and the text buffer ends with '\n'. Fixed a similar issue related to clipping large amount of text. + - InputTextMultiline: avoid going through reactivation code when activating scrollbar. - Style: - Border sizes are now scaled (and rounded) by ScaleAllSizes(). - When using large values with ScallAllSizes(), the following items thickness diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c7159c125..096b3f94d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4770,8 +4770,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool input_requested_by_nav = (g.ActiveId != id) && (g.NavActivateId == id); const bool input_requested_by_reactivate = (g.InputTextReactivateId == id); // for io.ConfigInputTextEnterKeepActive const bool user_clicked = hovered && io.MouseClicked[0]; - const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); - const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); + const ImGuiID scrollbar_id = (is_multiline && state != NULL) ? GetWindowScrollbarID(draw_window, ImGuiAxis_Y) : 0; + const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == scrollbar_id; + const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == scrollbar_id; bool clear_active_id = false; bool select_all = false; @@ -4780,7 +4781,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf); const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state. const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav || input_requested_by_reactivate); - const bool init_state = (init_make_active || user_scroll_active); if (init_reload_from_user_buf) { int new_len = (int)ImStrlen(buf); @@ -4793,7 +4793,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->Stb->select_start = state->ReloadSelectionStart; state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; // will be clamped to bounds below } - else if ((init_state && g.ActiveId != id) || init_changed_specs) + else if ((init_make_active && g.ActiveId != id) || init_changed_specs) { // Access state even if we don't own it yet. state = &g.InputTextState; @@ -4903,7 +4903,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ClearActiveID(); // Release focus when we click outside - if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560 + if (g.ActiveId == id && io.MouseClicked[0] && !init_make_active) //-V560 clear_active_id = true; // Lock the decision of whether we are going to take the path displaying the cursor or selection From 04dfcd838b6c95381ce4cc4a3704bfc75cc81fd3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 20 Mar 2026 15:39:02 +0100 Subject: [PATCH 24/26] InputTextMultiline: fixed losing revert value when activating scrollbar. (toward #9308) --- docs/CHANGELOG.txt | 3 ++- imgui_widgets.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ae3eb97bc..8fabf7964 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -86,7 +86,8 @@ Other Changes: for vertical scrollbar range would be +1 when the widget is inactive, word-wrap is disabled and the text buffer ends with '\n'. Fixed a similar issue related to clipping large amount of text. - - InputTextMultiline: avoid going through reactivation code when activating scrollbar. + - InputTextMultiline: avoid going through reactivation code and fixed losing revert value + when activating scrollbar. - Style: - Border sizes are now scaled (and rounded) by ScaleAllSizes(). - When using large values with ScallAllSizes(), the following items thickness diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 096b3f94d..8898e69ae 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4806,8 +4806,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode) const int buf_len = (int)ImStrlen(buf); IM_ASSERT(((buf_len + 1 <= buf_size) || (buf_len == 0 && buf_size == 0)) && "Is your input buffer properly zero-terminated?"); - state->TextToRevertTo.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. - memcpy(state->TextToRevertTo.Data, buf, buf_len + 1); + if (!user_scroll_finish) + { + state->TextToRevertTo.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->TextToRevertTo.Data, buf, buf_len + 1); + } // Preserve cursor position and undo/redo stack if we come back to same widget // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate? From 2315b9f33dff22e3af7eee1192f529ad67d3a785 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 20 Mar 2026 15:47:26 +0100 Subject: [PATCH 25/26] InputTextMultiline: fixed an issue where edit buffer wouldn't be reapplied to back buffer on the IsItemDeactivatedAfterEdit() frame. (#9308, #8915, #8273) --- docs/CHANGELOG.txt | 4 ++++ imgui_widgets.cpp | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 8fabf7964..6606c10d8 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -88,6 +88,10 @@ Other Changes: large amount of text. - InputTextMultiline: avoid going through reactivation code and fixed losing revert value when activating scrollbar. + - InputTextMultiline: fixed an issue where edit buffer wouldn't be reapplied to back + buffer on the IsItemDeactivatedAfterEdit() frame. This could create issues when + using the idiom of not applying edits before IsItemDeactivatedAfterEdit(). + (#9308, #8915, #8273) - Style: - Border sizes are now scaled (and rounded) by ScaleAllSizes(). - When using large values with ScallAllSizes(), the following items thickness diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 8898e69ae..3022950e2 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4731,6 +4731,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it. draw_window->DC.CursorPos += style.FramePadding; inner_size.x -= draw_window->ScrollbarSizes.x; + + // FIXME: Could this be a ImGuiChildFlags to affect the SetLastItemDataForWindow() call? + g.LastItemData.ID = id; + g.LastItemData.ItemFlags = item_data_backup.ItemFlags; + g.LastItemData.StatusFlags = item_data_backup.StatusFlags; } else { From 4af77622d93dbd9d8752eef3dd347f598de04fc6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 20 Mar 2026 16:14:17 +0100 Subject: [PATCH 26/26] Scrollbar: Fixed an issue which could lead initial click to move the current scroll by a pixel. --- docs/CHANGELOG.txt | 1 + imgui_widgets.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 6606c10d8..ec8abd87b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -124,6 +124,7 @@ Other Changes: - Implemented a custom tweak to extend hit-testing bounding box when window is sitting at the edge of a viewport (e.g. fullscreen or docked window), so that e.g. mouse the mouse at the extreme of the screen will reach the scrollbar. (#9276) + - Fixed an issue which could lead initial click to move the current scroll by a pixel. - Button: - Moved ImGuiButtonFlags_AllowOverlap from imgui_internal.h to imgui.h, as a convenience for when using e.g. InvisibleButton(). diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3022950e2..117733349 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1042,7 +1042,7 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1); const float grab_h_minsize = ImMin(bb.GetSize()[axis], style.GrabMinSize); - const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v); + const float grab_h_pixels = (float)(int)ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v); const float grab_h_norm = grab_h_pixels / scrollbar_size_v; // As a special thing, we allow scrollbar near the edge of a screen/viewport to be reachable with mouse at the extreme edge (#9276)